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