f09219c640ced545528bc58fa460a41242655ba4
[geeqie.git] / src / ui_fileops.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2012 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #  include "config.h"
15 #endif
16
17 #include <pwd.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <sys/param.h>
23 #include <dirent.h>
24 #include <utime.h>
25
26 #include <glib.h>
27 #include <gtk/gtk.h>    /* for locale warning dialog */
28
29 #include "main.h"
30 #include "ui_fileops.h"
31
32 #include "ui_utildlg.h" /* for locale warning dialog */
33 #include "md5-util.h"
34
35 /*
36  *-----------------------------------------------------------------------------
37  * generic file information and manipulation routines (public)
38  *-----------------------------------------------------------------------------
39  */
40
41
42
43 void print_term(const gchar *text_utf8)
44 {
45         gchar *text_l;
46
47         text_l = g_locale_from_utf8(text_utf8, -1, NULL, NULL, NULL);
48         fputs((text_l) ? text_l : text_utf8, stderr);
49         g_free(text_l);
50 }
51
52 static void encoding_dialog(const gchar *path)
53 {
54         static gboolean warned_user = FALSE;
55         GenericDialog *gd;
56         GString *string;
57         const gchar *lc;
58         const gchar *bf;
59
60         if (warned_user) return;
61         warned_user = TRUE;
62
63         lc = getenv("LANG");
64         bf = getenv("G_BROKEN_FILENAMES");
65
66         string = g_string_new("");
67         g_string_append(string, _("One or more filenames are not encoded with the preferred locale character set.\n"));
68         g_string_append_printf(string, _("Operations on, and display of these files with %s may not succeed.\n"), PACKAGE);
69         g_string_append(string, "\n");
70         g_string_append(string, _("If your filenames are not encoded in utf-8, try setting the environment variable G_BROKEN_FILENAMES=1\n"));
71         if (bf)
72                 g_string_append_printf(string, _("It appears G_BROKEN_FILENAMES is set to %s\n"), bf);
73         else
74                 g_string_append(string, _("It appears G_BROKEN_FILENAMES is not set\n"));
75         g_string_append(string, "\n");
76         g_string_append_printf(string, _("The locale appears to be set to \"%s\"\n(set by the LANG environment variable)\n"), (lc) ? lc : "undefined");
77         if (lc && (strstr(lc, "UTF-8") || strstr(lc, "utf-8")))
78                 {
79                 gchar *name;
80                 name = g_convert(path, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
81                 string = g_string_append(string, _("\nPreferred encoding appears to be UTF-8, however the file:\n"));
82                 g_string_append_printf(string, "\"%s\"\n", (name) ? name : _("[name not displayable]"));
83                 
84                 if (g_utf8_validate(path, -1, NULL))
85                         g_string_append_printf(string, _("\"%s\" is encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
86                 else
87                         g_string_append_printf(string, _("\"%s\" is not encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
88                 g_string_append(string, "\n");
89                 g_free(name);
90                 }
91
92         gd = generic_dialog_new(_("Filename encoding locale mismatch"),
93                                 "locale warning", NULL, TRUE, NULL, NULL);
94         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE);
95
96         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING,
97                                    _("Filename encoding locale mismatch"), string->str);
98
99         gtk_widget_show(gd->dialog);
100
101         g_string_free(string, TRUE);
102 }
103
104 #if GQ_DEBUG_PATH_UTF8
105 gchar *path_to_utf8_debug(const gchar *path, const gchar *file, gint line)
106 #else
107 gchar *path_to_utf8(const gchar *path)
108 #endif
109 {
110         gchar *utf8;
111         GError *error = NULL;
112
113         if (!path) return NULL;
114
115         utf8 = g_filename_to_utf8(path, -1, NULL, NULL, &error);
116         if (error)
117                 {
118 #if GQ_DEBUG_PATH_UTF8
119                 log_printf("%s:%d: Unable to convert filename to UTF-8:\n%s\n%s\n", file, line, path, error->message);
120 #else
121                 log_printf("Unable to convert filename to UTF-8:\n%s\n%s\n", path, error->message);
122 #endif
123                 g_error_free(error);
124                 encoding_dialog(path);
125                 }
126         if (!utf8)
127                 {
128                 /* just let it through, but bad things may happen */
129                 utf8 = g_strdup(path);
130                 }
131
132         return utf8;
133 }
134
135 #if GQ_DEBUG_PATH_UTF8
136 gchar *path_from_utf8_debug(const gchar *utf8, const gchar *file, gint line)
137 #else
138 gchar *path_from_utf8(const gchar *utf8)
139 #endif
140 {
141         gchar *path;
142         GError *error = NULL;
143
144         if (!utf8) return NULL;
145
146         path = g_filename_from_utf8(utf8, -1, NULL, NULL, &error);
147         if (error)
148                 {
149 #if GQ_DEBUG_PATH_UTF8
150                 log_printf("%s:%d: Unable to convert filename to locale from UTF-8:\n%s\n%s\n", file, line, utf8, error->message);
151 #else
152                 log_printf("Unable to convert filename to locale from UTF-8:\n%s\n%s\n", utf8, error->message);
153 #endif
154                 g_error_free(error);
155                 }
156         if (!path)
157                 {
158                 /* if invalid UTF-8, text probaby still in original form, so just copy it */
159                 path = g_strdup(utf8);
160                 }
161
162         return path;
163 }
164
165 /* first we try the HOME environment var, if that doesn't work, we try g_get_homedir(). */
166 const gchar *homedir(void)
167 {
168         static gchar *home = NULL;
169
170         if (!home)
171                 home = path_to_utf8(getenv("HOME"));
172         
173         if (!home)
174                 home = path_to_utf8(g_get_home_dir());
175
176         return home;
177 }
178
179 static gchar *xdg_dir_get(const gchar *key, const gchar *fallback)
180 {
181         gchar *dir = getenv(key);
182
183         if (!dir || dir[0] == '\0')
184                 {
185                 return g_build_filename(homedir(), fallback, NULL);
186                 }
187         
188         return path_to_utf8(dir);
189 }
190
191 const gchar *xdg_data_home_get(void)
192 {
193         static const gchar *xdg_data_home = NULL;
194
195         if (xdg_data_home) return xdg_data_home;
196         
197         xdg_data_home = xdg_dir_get("XDG_DATA_HOME", ".local/share");
198
199         return xdg_data_home;
200 }
201
202 const gchar *xdg_config_home_get(void)
203 {
204         static const gchar *xdg_config_home = NULL;
205
206         if (xdg_config_home) return xdg_config_home;
207         
208         xdg_config_home = xdg_dir_get("XDG_CONFIG_HOME", ".config");
209
210         return xdg_config_home;
211 }
212
213 const gchar *xdg_cache_home_get(void)
214 {
215         static const gchar *xdg_cache_home = NULL;
216
217         if (xdg_cache_home) return xdg_cache_home;
218         
219         xdg_cache_home = xdg_dir_get("XDG_CACHE_HOME", ".cache");
220
221         return xdg_cache_home;
222 }
223
224 const gchar *get_rc_dir(void)
225 {
226         static gchar *rc_dir = NULL;
227         
228         if (rc_dir) return rc_dir;
229
230         if (USE_XDG)
231                 {
232                 rc_dir = g_build_filename(xdg_config_home_get(), GQ_APPNAME_LC, NULL);
233                 }
234         else
235                 {
236                 rc_dir = g_build_filename(homedir(), GQ_RC_DIR, NULL);
237                 }
238
239         return rc_dir;
240 }
241
242 const gchar *get_collections_dir(void)
243 {
244         static gchar *collections_dir = NULL;
245
246         if (collections_dir) return collections_dir;
247
248         if (USE_XDG)
249                 {
250                 collections_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_COLLECTIONS_DIR, NULL);
251                 }
252         else
253                 {
254                 collections_dir = g_build_filename(get_rc_dir(), GQ_COLLECTIONS_DIR, NULL);
255                 }
256
257         return collections_dir;
258 }
259
260 const gchar *get_trash_dir(void)
261 {
262         static gchar *trash_dir = NULL;
263
264         if (trash_dir) return trash_dir;
265         
266         if (USE_XDG)
267                 {
268                 trash_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_TRASH_DIR, NULL);
269                 }
270         else
271                 {
272                 trash_dir = g_build_filename(get_rc_dir(), GQ_TRASH_DIR, NULL);
273         }
274
275         return trash_dir;
276 }
277
278 gboolean stat_utf8(const gchar *s, struct stat *st)
279 {
280         gchar *sl;
281         gboolean ret;
282
283         if (!s) return FALSE;
284         sl = path_from_utf8(s);
285         ret = (stat(sl, st) == 0);
286         g_free(sl);
287
288         return ret;
289 }
290
291 gboolean lstat_utf8(const gchar *s, struct stat *st)
292 {
293         gchar *sl;
294         gboolean ret;
295
296         if (!s) return FALSE;
297         sl = path_from_utf8(s);
298         ret = (lstat(sl, st) == 0);
299         g_free(sl);
300
301         return ret;
302 }
303
304 gboolean isname(const gchar *s)
305 {
306         struct stat st;
307
308         return stat_utf8(s, &st);
309 }
310
311 gboolean isfile(const gchar *s)
312 {
313         struct stat st;
314
315         return (stat_utf8(s, &st) && S_ISREG(st.st_mode));
316 }
317
318 gboolean isdir(const gchar *s)
319 {
320         struct stat st;
321
322         return (stat_utf8(s, &st) && S_ISDIR(st.st_mode));
323 }
324
325 gboolean islink(const gchar *s)
326 {
327         struct stat st;
328
329         return (lstat_utf8(s, &st) && S_ISLNK(st.st_mode));
330 }
331
332 gint64 filesize(const gchar *s)
333 {
334         struct stat st;
335
336         if (!stat_utf8(s, &st)) return 0;
337         return st.st_size;
338 }
339
340 time_t filetime(const gchar *s)
341 {
342         struct stat st;
343
344         if (!stat_utf8(s, &st)) return 0;
345         return st.st_mtime;
346 }
347
348 gboolean filetime_set(const gchar *s, time_t tval)
349 {
350         gboolean ret = FALSE;
351
352         if (tval > 0)
353                 {
354                 struct utimbuf ut;
355                 gchar *sl;
356
357                 ut.actime = ut.modtime = tval;
358
359                 sl = path_from_utf8(s);
360                 ret = (utime(sl, &ut) == 0);
361                 g_free(sl);
362                 }
363
364         return ret;
365 }
366
367 gboolean is_readable_file(const gchar *s)
368 {
369         if (!s || !s[0] || !isfile(s)) return FALSE;
370         return access_file(s, R_OK);
371 }
372
373 gboolean access_file(const gchar *s, gint mode)
374 {
375         gchar *sl;
376         gint ret;
377
378         if (!s || !s[0]) return FALSE;
379
380         sl = path_from_utf8(s);
381         ret = (access(sl, mode) == 0);
382         g_free(sl);
383
384         return ret;
385 }
386
387 gboolean unlink_file(const gchar *s)
388 {
389         gchar *sl;
390         gboolean ret;
391
392         if (!s) return FALSE;
393
394         sl = path_from_utf8(s);
395         ret = (unlink(sl) == 0);
396         g_free(sl);
397
398         return ret;
399 }
400
401 gboolean symlink_utf8(const gchar *source, const gchar *target)
402 {
403         gchar *sl;
404         gchar *tl;
405         gboolean ret;
406
407         if (!source || !target) return FALSE;
408
409         sl = path_from_utf8(source);
410         tl = path_from_utf8(target);
411
412         ret = (symlink(sl, tl) == 0);
413
414         g_free(sl);
415         g_free(tl);
416
417         return ret;
418 }
419
420 gboolean mkdir_utf8(const gchar *s, gint mode)
421 {
422         gchar *sl;
423         gboolean ret;
424
425         if (!s) return FALSE;
426
427         sl = path_from_utf8(s);
428         ret = (mkdir(sl, mode) == 0);
429         g_free(sl);
430         return ret;
431 }
432
433 gboolean rmdir_utf8(const gchar *s)
434 {
435         gchar *sl;
436         gboolean ret;
437
438         if (!s) return FALSE;
439
440         sl = path_from_utf8(s);
441         ret = (rmdir(sl) == 0);
442         g_free(sl);
443
444         return ret;
445 }
446
447 gboolean copy_file_attributes(const gchar *s, const gchar *t, gint perms, gint mtime)
448 {
449         struct stat st;
450         gchar *sl, *tl;
451         gboolean ret = FALSE;
452
453         if (!s || !t) return FALSE;
454
455         sl = path_from_utf8(s);
456         tl = path_from_utf8(t);
457
458         if (stat(sl, &st) == 0)
459                 {
460                 struct utimbuf tb;
461
462                 ret = TRUE;
463
464                 /* set the dest file attributes to that of source (ignoring errors) */
465
466                 if (perms)
467                         {
468                         ret = chown(tl, st.st_uid, st.st_gid);
469                         /* Ignores chown errors, while still doing chown
470                            (so root still can copy files preserving ownership) */
471                         ret = TRUE;
472                         if (chmod(tl, st.st_mode) < 0) ret = FALSE;
473                         }
474
475                 tb.actime = st.st_atime;
476                 tb.modtime = st.st_mtime;
477                 if (mtime && utime(tl, &tb) < 0) ret = FALSE;
478                 }
479
480         g_free(sl);
481         g_free(tl);
482
483         return ret;
484 }
485
486 /* paths are in filesystem encoding */
487 static gboolean hard_linked(const gchar *a, const gchar *b)
488 {
489         struct stat sta;
490         struct stat stb;
491
492         if (stat(a, &sta) !=  0 || stat(b, &stb) != 0) return FALSE;
493
494         return (sta.st_dev == stb.st_dev &&
495                 sta.st_ino == stb.st_ino);
496 }
497
498 gboolean copy_file(const gchar *s, const gchar *t)
499 {
500         FILE *fi = NULL;
501         FILE *fo = NULL;
502         gchar *sl = NULL;
503         gchar *tl = NULL;
504         gchar *randname = NULL;
505         gchar buf[16384];
506         size_t b;
507         gint ret = FALSE;
508         gint fd = -1;
509
510         sl = path_from_utf8(s);
511         tl = path_from_utf8(t);
512
513         if (hard_linked(sl, tl))
514                 {
515                 ret = TRUE;
516                 goto end;
517                 }
518
519         fi = fopen(sl, "rb");
520         if (!fi) goto end;
521         
522         /* First we write to a temporary file, then we rename it on success,
523            and attributes from original file are copied */
524         randname = g_strconcat(tl, ".tmp_XXXXXX", NULL);
525         if (!randname) goto end;
526         
527         fd = g_mkstemp(randname);
528         if (fd == -1) goto end;
529         
530         fo = fdopen(fd, "wb");
531         if (!fo) {
532                 close(fd);
533                 goto end;
534         }
535
536         while ((b = fread(buf, sizeof(gchar), sizeof(buf), fi)) && b != 0)
537                 {
538                 if (fwrite(buf, sizeof(gchar), b, fo) != b)
539                         {
540                         unlink(randname);
541                         goto end;
542                         }
543                 }
544
545         fclose(fi); fi = NULL;
546         fclose(fo); fo = NULL;
547
548         if (rename(randname, tl) < 0) {
549                 unlink(randname);
550                 goto end;       
551         }
552
553         ret = copy_file_attributes(s, t, TRUE, TRUE);
554
555 end:
556         if (fi) fclose(fi);
557         if (fo) fclose(fo);
558         if (sl) g_free(sl);
559         if (tl) g_free(tl);
560         if (randname) g_free(randname);
561         return ret;
562 }
563
564 gboolean move_file(const gchar *s, const gchar *t)
565 {
566         gchar *sl, *tl;
567         gboolean ret = TRUE;
568
569         if (!s || !t) return FALSE;
570
571         sl = path_from_utf8(s);
572         tl = path_from_utf8(t);
573         if (rename(sl, tl) < 0)
574                 {
575                 /* this may have failed because moving a file across filesystems
576                 was attempted, so try copy and delete instead */
577                 if (copy_file(s, t))
578                         {
579                         if (unlink(sl) < 0)
580                                 {
581                                 /* err, now we can't delete the source file so return FALSE */
582                                 ret = FALSE;
583                                 }
584                         }
585                 else
586                         {
587                         ret = FALSE;
588                         }
589                 }
590         g_free(sl);
591         g_free(tl);
592
593         return ret;
594 }
595
596 gboolean rename_file(const gchar *s, const gchar *t)
597 {
598         gchar *sl, *tl;
599         gboolean ret;
600
601         if (!s || !t) return FALSE;
602
603         sl = path_from_utf8(s);
604         tl = path_from_utf8(t);
605         ret = (rename(sl, tl) == 0);
606         g_free(sl);
607         g_free(tl);
608
609         return ret;
610 }
611
612 gchar *get_current_dir(void)
613 {
614         gchar *pathl;
615         gchar *path8;
616
617         pathl = g_get_current_dir();
618         path8 = path_to_utf8(pathl);
619         g_free(pathl);
620
621         return path8;
622 }
623
624 void string_list_free(GList *list)
625 {
626         g_list_foreach(list, (GFunc)g_free, NULL);
627         g_list_free(list);
628 }
629
630 GList *string_list_copy(const GList *list)
631 {
632         GList *new_list = NULL;
633         GList *work = (GList *) list;
634
635         while (work)
636                 {
637                 gchar *path;
638
639                 path = work->data;
640                 work = work->next;
641
642                 new_list = g_list_prepend(new_list, g_strdup(path));
643                 }
644
645         return g_list_reverse(new_list);
646 }
647
648 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gboolean pad)
649 {
650         gchar *unique;
651         gint n = 1;
652
653         if (!ext) ext = "";
654         if (!divider) divider = "";
655
656         unique = g_strconcat(path, ext, NULL);
657         while (isname(unique))
658                 {
659                 g_free(unique);
660                 if (pad)
661                         {
662                         unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
663                         }
664                 else
665                         {
666                         unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
667                         }
668                 n++;
669                 if (n > 999)
670                         {
671                         /* well, we tried */
672                         g_free(unique);
673                         return NULL;
674                         }
675                 }
676
677         return unique;
678 }
679
680 gchar *unique_filename_simple(const gchar *path)
681 {
682         gchar *unique;
683         const gchar *name;
684         const gchar *ext;
685
686         if (!path) return NULL;
687
688         name = filename_from_path(path);
689         if (!name) return NULL;
690
691         ext = extension_from_path(name);
692
693         if (!ext)
694                 {
695                 unique = unique_filename(path, NULL, "_", TRUE);
696                 }
697         else
698                 {
699                 gchar *base;
700
701                 base = remove_extension_from_path(path);
702                 unique = unique_filename(base, ext, "_", TRUE);
703                 g_free(base);
704                 }
705
706         return unique;
707 }
708
709 const gchar *filename_from_path(const gchar *path)
710 {
711         const gchar *base;
712
713         if (!path) return NULL;
714
715         base = strrchr(path, G_DIR_SEPARATOR);
716         if (base) return base + 1;
717
718         return path;
719 }
720
721 gchar *remove_level_from_path(const gchar *path)
722 {
723         gint p = 0, n = -1;
724
725         if (!path) return NULL;
726
727         while (path[p])
728                 {
729                 if (path[p] == G_DIR_SEPARATOR) n = p;
730                 p++;
731                 }
732         if (n <= 0) n++;
733
734         return g_strndup(path, (gsize) n);
735 }
736
737 const gchar *extension_from_path(const gchar *path)
738 {
739         if (!path) return NULL;
740         return strrchr(path, '.');
741 }
742
743 gboolean file_extension_match(const gchar *path, const gchar *ext)
744 {
745         gint p;
746         gint e;
747
748         if (!path) return FALSE;
749         if (!ext) return TRUE;
750
751         p = strlen(path);
752         e = strlen(ext);
753
754         /* FIXME: utf8 */
755         return (p > e && g_ascii_strncasecmp(path + p - e, ext, e) == 0);
756 }
757
758 gchar *remove_extension_from_path(const gchar *path)
759 {
760         gint p = 0, n = -1;
761
762         if (!path) return NULL;
763
764         while (path[p])
765                 {
766                 if (path[p] == '.') n = p;
767                 p++;
768                 }
769         if (n < 0) n = p;
770
771         return g_strndup(path, (gsize) n);
772 }
773
774 void parse_out_relatives(gchar *path)
775 {
776         gint s, t;
777
778         if (!path) return;
779
780         s = t = 0;
781
782         while (path[s] != '\0')
783                 {
784                 if (path[s] == G_DIR_SEPARATOR && path[s+1] == '.')
785                         {
786                         /* /. occurence, let's see more */
787                         gint p = s + 2;
788
789                         if (path[p] == G_DIR_SEPARATOR || path[p] == '\0')
790                                 {
791                                 /* /./ or /., just skip this part */
792                                 s = p;
793                                 continue;
794                                 }
795                         else if (path[p] == '.' && (path[p+1] == G_DIR_SEPARATOR || path[p+1] == '\0'))
796                                 {
797                                 /* /../ or /.., remove previous part, ie. /a/b/../ becomes /a/ */
798                                 s = p + 1;
799                                 if (t > 0) t--;
800                                 while (path[t] != G_DIR_SEPARATOR && t > 0) t--;
801                                 continue;
802                                 }
803                         }
804         
805                 if (s != t) path[t] = path[s];
806                 t++;
807                 s++;
808                 }
809
810         if (t == 0 && path[t] == G_DIR_SEPARATOR) t++;
811         if (t > 1 && path[t-1] == G_DIR_SEPARATOR) t--;
812         path[t] = '\0';
813 }
814
815 gboolean file_in_path(const gchar *name)
816 {
817         gchar *path;
818         gchar *namel;
819         gint p, l;
820         gboolean ret = FALSE;
821
822         if (!name) return FALSE;
823         path = g_strdup(getenv("PATH"));
824         if (!path) return FALSE;
825         namel = path_from_utf8(name);
826
827         p = 0;
828         l = strlen(path);
829         while (p < l && !ret)
830                 {
831                 gchar *f;
832                 gint e = p;
833                 while (path[e] != ':' && path[e] != '\0') e++;
834                 path[e] = '\0';
835                 e++;
836                 f = g_build_filename(path + p, namel, NULL);
837                 if (isfile(f)) ret = TRUE;
838                 g_free(f);
839                 p = e;
840                 }
841         g_free(namel);
842         g_free(path);
843
844         return ret;
845 }
846
847 gboolean recursive_mkdir_if_not_exists(const gchar *path, mode_t mode)
848 {
849         if (!path) return FALSE;
850
851         if (!isdir(path))
852                 {
853                 gchar *npath = g_strdup(path);
854                 gchar *p = npath;
855
856                 while (p[0] != '\0')
857                         {
858                         p++;
859                         if (p[0] == G_DIR_SEPARATOR || p[0] == '\0')
860                                 {
861                                 gboolean end = TRUE;
862
863                                 if (p[0] != '\0')
864                                         {
865                                         p[0] = '\0';
866                                         end = FALSE;
867                                         }
868                                 
869                                 if (!isdir(npath))
870                                         {
871                                         DEBUG_1("creating sub dir:%s", npath);
872                                         if (!mkdir_utf8(npath, mode))
873                                                 {
874                                                 log_printf("create dir failed: %s\n", npath);
875                                                 g_free(npath);
876                                                 return FALSE;
877                                                 }
878                                         }
879                                 
880                                 if (!end) p[0] = G_DIR_SEPARATOR;
881                                 }
882                         }
883                 g_free(npath);
884                 }
885
886         return TRUE;
887 }
888
889 /* does filename utf8 to filesystem encoding first */
890 gboolean md5_get_digest_from_file_utf8(const gchar *path, guchar digest[16])
891 {
892         gboolean success;
893         gchar *pathl;
894
895         pathl = path_from_utf8(path);
896         success = md5_get_digest_from_file(pathl, digest);
897         g_free(pathl);
898
899         return success;
900 }
901
902
903 gchar *md5_text_from_file_utf8(const gchar *path, const gchar *error_text)
904 {
905         guchar digest[16];
906
907         if (!md5_get_digest_from_file_utf8(path, digest)) return g_strdup(error_text);
908
909         return md5_digest_to_text(digest);
910 }
911
912
913 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */