0d8de3be2a6de2138456043541dc5582bba989ef
[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 #include "filefilter.h"
45 #include "layout.h"
46 #include "utilops.h"
47 #include "secure_save.h"
48
49 /*
50  *-----------------------------------------------------------------------------
51  * generic file information and manipulation routines (public)
52  *-----------------------------------------------------------------------------
53  */
54
55
56
57 void print_term(gboolean err, const gchar *text_utf8)
58 {
59         gchar *text_l;
60
61         text_l = g_locale_from_utf8(text_utf8, -1, NULL, NULL, NULL);
62         if (err)
63                 {
64                 fputs((text_l) ? text_l : text_utf8, stderr);
65                 }
66         else
67                 {
68                 fputs((text_l) ? text_l : text_utf8, stdout);
69                 }
70         if(command_line && command_line->ssi)
71                 secure_fputs(command_line->ssi, (text_l) ? text_l : text_utf8);
72         g_free(text_l);
73 }
74
75 static void encoding_dialog(const gchar *path)
76 {
77         static gboolean warned_user = FALSE;
78         GenericDialog *gd;
79         GString *string;
80         const gchar *lc;
81         const gchar *bf;
82
83         if (warned_user) return;
84         warned_user = TRUE;
85
86         lc = getenv("LANG");
87         bf = getenv("G_BROKEN_FILENAMES");
88
89         string = g_string_new("");
90         g_string_append(string, _("One or more filenames are not encoded with the preferred locale character set.\n"));
91         g_string_append_printf(string, _("Operations on, and display of these files with %s may not succeed.\n"), PACKAGE);
92         g_string_append(string, "\n");
93         g_string_append(string, _("If your filenames are not encoded in utf-8, try setting the environment variable G_BROKEN_FILENAMES=1\n"));
94         if (bf)
95                 g_string_append_printf(string, _("It appears G_BROKEN_FILENAMES is set to %s\n"), bf);
96         else
97                 g_string_append(string, _("It appears G_BROKEN_FILENAMES is not set\n"));
98         g_string_append(string, "\n");
99         g_string_append_printf(string, _("The locale appears to be set to \"%s\"\n(set by the LANG environment variable)\n"), (lc) ? lc : "undefined");
100         if (lc && (strstr(lc, "UTF-8") || strstr(lc, "utf-8")))
101                 {
102                 gchar *name;
103                 name = g_convert(path, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
104                 string = g_string_append(string, _("\nPreferred encoding appears to be UTF-8, however the file:\n"));
105                 g_string_append_printf(string, "\"%s\"\n", (name) ? name : _("[name not displayable]"));
106
107                 if (g_utf8_validate(path, -1, NULL))
108                         g_string_append_printf(string, _("\"%s\" is encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
109                 else
110                         g_string_append_printf(string, _("\"%s\" is not encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
111                 g_string_append(string, "\n");
112                 g_free(name);
113                 }
114
115         gd = generic_dialog_new(_("Filename encoding locale mismatch"),
116                                 "locale warning", NULL, TRUE, NULL, NULL);
117         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE);
118
119         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING,
120                                    _("Filename encoding locale mismatch"), string->str, TRUE);
121
122         gtk_widget_show(gd->dialog);
123
124         g_string_free(string, TRUE);
125 }
126
127 #if GQ_DEBUG_PATH_UTF8
128 gchar *path_to_utf8_debug(const gchar *path, const gchar *file, gint line)
129 #else
130 gchar *path_to_utf8(const gchar *path)
131 #endif
132 {
133         gchar *utf8;
134         GError *error = NULL;
135
136         if (!path) return NULL;
137
138         utf8 = g_filename_to_utf8(path, -1, NULL, NULL, &error);
139         if (error)
140                 {
141 #if GQ_DEBUG_PATH_UTF8
142                 log_printf("%s:%d: Unable to convert filename to UTF-8:\n%s\n%s\n", file, line, path, error->message);
143 #else
144                 log_printf("Unable to convert filename to UTF-8:\n%s\n%s\n", path, error->message);
145 #endif
146                 g_error_free(error);
147                 encoding_dialog(path);
148                 }
149         if (!utf8)
150                 {
151                 /* just let it through, but bad things may happen */
152                 utf8 = g_strdup(path);
153                 }
154
155         return utf8;
156 }
157
158 #if GQ_DEBUG_PATH_UTF8
159 gchar *path_from_utf8_debug(const gchar *utf8, const gchar *file, gint line)
160 #else
161 gchar *path_from_utf8(const gchar *utf8)
162 #endif
163 {
164         gchar *path;
165         GError *error = NULL;
166
167         if (!utf8) return NULL;
168
169         path = g_filename_from_utf8(utf8, -1, NULL, NULL, &error);
170         if (error)
171                 {
172 #if GQ_DEBUG_PATH_UTF8
173                 log_printf("%s:%d: Unable to convert filename to locale from UTF-8:\n%s\n%s\n", file, line, utf8, error->message);
174 #else
175                 log_printf("Unable to convert filename to locale from UTF-8:\n%s\n%s\n", utf8, error->message);
176 #endif
177                 g_error_free(error);
178                 }
179         if (!path)
180                 {
181                 /* if invalid UTF-8, text probaby still in original form, so just copy it */
182                 path = g_strdup(utf8);
183                 }
184
185         return path;
186 }
187
188 /* first we try the HOME environment var, if that doesn't work, we try g_get_homedir(). */
189 const gchar *homedir(void)
190 {
191         static gchar *home = NULL;
192
193         if (!home)
194                 home = path_to_utf8(getenv("HOME"));
195
196         if (!home)
197                 home = path_to_utf8(g_get_home_dir());
198
199         DEBUG_1("Home directory: %s", home);
200
201         return home;
202 }
203
204 static gchar *xdg_dir_get(const gchar *key, const gchar *fallback)
205 {
206         gchar *dir = getenv(key);
207
208         if (!dir || dir[0] == '\0')
209                 {
210                 return g_build_filename(homedir(), fallback, NULL);
211                 }
212
213         DEBUG_1("Got xdg %s: %s", key, dir);
214
215         return path_to_utf8(dir);
216 }
217
218 const gchar *xdg_data_home_get(void)
219 {
220         static const gchar *xdg_data_home = NULL;
221
222         if (xdg_data_home) return xdg_data_home;
223
224         xdg_data_home = xdg_dir_get("XDG_DATA_HOME", ".local/share");
225
226         return xdg_data_home;
227 }
228
229 const gchar *xdg_config_home_get(void)
230 {
231         static const gchar *xdg_config_home = NULL;
232
233         if (xdg_config_home) return xdg_config_home;
234
235         xdg_config_home = xdg_dir_get("XDG_CONFIG_HOME", ".config");
236
237         return xdg_config_home;
238 }
239
240 const gchar *xdg_cache_home_get(void)
241 {
242         static const gchar *xdg_cache_home = NULL;
243
244         if (xdg_cache_home) return xdg_cache_home;
245
246         xdg_cache_home = xdg_dir_get("XDG_CACHE_HOME", ".cache");
247
248         return xdg_cache_home;
249 }
250
251 const gchar *get_rc_dir(void)
252 {
253         static gchar *rc_dir = NULL;
254
255         if (rc_dir) return rc_dir;
256
257         if (USE_XDG)
258                 {
259                 rc_dir = g_build_filename(xdg_config_home_get(), GQ_APPNAME_LC, NULL);
260                 }
261         else
262                 {
263                 rc_dir = g_build_filename(homedir(), GQ_RC_DIR, NULL);
264                 }
265
266         return rc_dir;
267 }
268
269 const gchar *get_collections_dir(void)
270 {
271         static gchar *collections_dir = NULL;
272
273         if (collections_dir) return collections_dir;
274
275         if (USE_XDG)
276                 {
277                 collections_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_COLLECTIONS_DIR, NULL);
278                 }
279         else
280                 {
281                 collections_dir = g_build_filename(get_rc_dir(), GQ_COLLECTIONS_DIR, NULL);
282                 }
283
284         return collections_dir;
285 }
286
287 const gchar *get_trash_dir(void)
288 {
289         static gchar *trash_dir = NULL;
290
291         if (trash_dir) return trash_dir;
292
293         if (USE_XDG)
294                 {
295                 trash_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_TRASH_DIR, NULL);
296                 }
297         else
298                 {
299                 trash_dir = g_build_filename(get_rc_dir(), GQ_TRASH_DIR, NULL);
300         }
301
302         return trash_dir;
303 }
304
305 gboolean stat_utf8(const gchar *s, struct stat *st)
306 {
307         gchar *sl;
308         gboolean ret;
309
310         if (!s) return FALSE;
311         sl = path_from_utf8(s);
312         ret = (stat(sl, st) == 0);
313         g_free(sl);
314
315         return ret;
316 }
317
318 gboolean lstat_utf8(const gchar *s, struct stat *st)
319 {
320         gchar *sl;
321         gboolean ret;
322
323         if (!s) return FALSE;
324         sl = path_from_utf8(s);
325         ret = (lstat(sl, st) == 0);
326         g_free(sl);
327
328         return ret;
329 }
330
331 gboolean isname(const gchar *s)
332 {
333         struct stat st;
334
335         return stat_utf8(s, &st);
336 }
337
338 gboolean isfile(const gchar *s)
339 {
340         struct stat st;
341
342         return (stat_utf8(s, &st) && S_ISREG(st.st_mode));
343 }
344
345 gboolean isdir(const gchar *s)
346 {
347         struct stat st;
348
349         return (stat_utf8(s, &st) && S_ISDIR(st.st_mode));
350 }
351
352 gboolean islink(const gchar *s)
353 {
354         struct stat st;
355
356         return (lstat_utf8(s, &st) && S_ISLNK(st.st_mode));
357 }
358
359 gint64 filesize(const gchar *s)
360 {
361         struct stat st;
362
363         if (!stat_utf8(s, &st)) return 0;
364         return st.st_size;
365 }
366
367 time_t filetime(const gchar *s)
368 {
369         struct stat st;
370
371         if (!stat_utf8(s, &st)) return 0;
372         return st.st_mtime;
373 }
374
375 gboolean filetime_set(const gchar *s, time_t tval)
376 {
377         gboolean ret = FALSE;
378
379         if (tval > 0)
380                 {
381                 struct utimbuf ut;
382                 gchar *sl;
383
384                 ut.actime = ut.modtime = tval;
385
386                 sl = path_from_utf8(s);
387                 ret = (utime(sl, &ut) == 0);
388                 g_free(sl);
389                 }
390
391         return ret;
392 }
393
394 gboolean is_readable_file(const gchar *s)
395 {
396         if (!s || !s[0] || !isfile(s)) return FALSE;
397         return access_file(s, R_OK);
398 }
399
400 gboolean access_file(const gchar *s, gint mode)
401 {
402         gchar *sl;
403         gint ret;
404
405         if (!s || !s[0]) return FALSE;
406
407         sl = path_from_utf8(s);
408         ret = (access(sl, mode) == 0);
409         g_free(sl);
410
411         return ret;
412 }
413
414 gboolean unlink_file(const gchar *s)
415 {
416         gchar *sl;
417         gboolean ret;
418
419         if (!s) return FALSE;
420
421         sl = path_from_utf8(s);
422         ret = (unlink(sl) == 0);
423         g_free(sl);
424
425         return ret;
426 }
427
428 gboolean symlink_utf8(const gchar *source, const gchar *target)
429 {
430         gchar *sl;
431         gchar *tl;
432         gboolean ret;
433
434         if (!source || !target) return FALSE;
435
436         sl = path_from_utf8(source);
437         tl = path_from_utf8(target);
438
439         ret = (symlink(sl, tl) == 0);
440
441         g_free(sl);
442         g_free(tl);
443
444         return ret;
445 }
446
447 gboolean mkdir_utf8(const gchar *s, gint mode)
448 {
449         gchar *sl;
450         gboolean ret;
451
452         if (!s) return FALSE;
453
454         sl = path_from_utf8(s);
455         ret = (mkdir(sl, mode) == 0);
456         g_free(sl);
457         return ret;
458 }
459
460 gboolean rmdir_utf8(const gchar *s)
461 {
462         gchar *sl;
463         gboolean ret;
464
465         if (!s) return FALSE;
466
467         sl = path_from_utf8(s);
468         ret = (rmdir(sl) == 0);
469         g_free(sl);
470
471         return ret;
472 }
473
474 gboolean copy_file_attributes(const gchar *s, const gchar *t, gint perms, gint mtime)
475 {
476         struct stat st;
477         gchar *sl, *tl;
478         gboolean ret = FALSE;
479
480         if (!s || !t) return FALSE;
481
482         sl = path_from_utf8(s);
483         tl = path_from_utf8(t);
484
485         if (stat(sl, &st) == 0)
486                 {
487                 struct utimbuf tb;
488
489                 ret = TRUE;
490
491                 /* set the dest file attributes to that of source (ignoring errors) */
492
493                 if (perms)
494                         {
495                         ret = chown(tl, st.st_uid, st.st_gid);
496                         /* Ignores chown errors, while still doing chown
497                            (so root still can copy files preserving ownership) */
498                         ret = TRUE;
499                         if (chmod(tl, st.st_mode) < 0) {
500                             struct stat st2;
501                             if (stat(tl, &st2) != 0 || st2.st_mode != st.st_mode) {
502                                 ret = FALSE;
503                             }
504                         }
505                         }
506
507                 tb.actime = st.st_atime;
508                 tb.modtime = st.st_mtime;
509                 if (mtime && utime(tl, &tb) < 0) ret = FALSE;
510                 }
511
512         g_free(sl);
513         g_free(tl);
514
515         return ret;
516 }
517
518 /* paths are in filesystem encoding */
519 static gboolean hard_linked(const gchar *a, const gchar *b)
520 {
521         struct stat sta;
522         struct stat stb;
523
524         if (stat(a, &sta) !=  0 || stat(b, &stb) != 0) return FALSE;
525
526         return (sta.st_dev == stb.st_dev &&
527                 sta.st_ino == stb.st_ino);
528 }
529
530 gboolean copy_file(const gchar *s, const gchar *t)
531 {
532         FILE *fi = NULL;
533         FILE *fo = NULL;
534         gchar *sl = NULL;
535         gchar *tl = NULL;
536         gchar *randname = NULL;
537         gchar buf[16384];
538         size_t b;
539         gint ret = FALSE;
540         gint fd = -1;
541
542         sl = path_from_utf8(s);
543         tl = path_from_utf8(t);
544
545         if (hard_linked(sl, tl))
546                 {
547                 ret = TRUE;
548                 goto end;
549                 }
550
551         /* Do not dereference absolute symlinks, but copy them "as is".
552         * For a relative symlink, we don't know how to properly change it when
553         * copied/moved to another dir to keep pointing it to same target as
554         * a relative symlink, so we turn it into absolute symlink using
555         * realpath() instead. */
556         struct stat st;
557         if (lstat_utf8(sl, &st) && S_ISLNK(st.st_mode))
558                 {
559                 gchar *link_target;
560                 ssize_t i;
561
562                 link_target = g_malloc(st.st_size + 1);
563                 i = readlink(sl, link_target, st.st_size);
564                 if (i<0)
565                         {
566                         g_free(link_target);
567                         goto orig_copy;  // try a "normal" copy
568                         }
569                 link_target[st.st_size] = '\0';
570
571                 if (link_target[0] != G_DIR_SEPARATOR) // if it is a relative symlink
572                         {
573                         gchar *absolute;
574
575                         gchar *lastslash = strrchr(sl, G_DIR_SEPARATOR);
576                         gint len = lastslash - sl + 1;
577
578                         absolute = g_malloc(len + st.st_size + 1);
579                         strncpy(absolute, sl, len);
580                         strcpy(absolute + len, link_target);
581                         g_free(link_target);
582                         link_target = absolute;
583
584                         gchar *realPath;
585                         realPath = realpath(link_target, NULL);
586
587                         if (realPath != NULL) // successfully resolved into an absolute path
588                                 {
589                                 g_free(link_target);
590                                 link_target = g_strdup(realPath);
591                                 g_free(realPath);
592                                 }
593                         else                 // could not get absolute path, got some error instead
594                                 {
595                                 g_free(link_target);
596                                 goto orig_copy;  // so try a "normal" copy
597                                 }
598                         }
599
600                 if (stat_utf8(tl, &st)) unlink(tl); // first try to remove directory entry in destination directory if such entry exists
601
602                 gint success = (symlink(link_target, tl) == 0);
603                 g_free(link_target);
604
605                 if (success)
606                         {
607                         ret = TRUE;
608                         goto end;
609                         }
610                 } // if symlink did not succeed, continue on to try a copy procedure
611         orig_copy:
612
613         fi = fopen(sl, "rb");
614         if (!fi) goto end;
615
616         /* First we write to a temporary file, then we rename it on success,
617            and attributes from original file are copied */
618         randname = g_strconcat(tl, ".tmp_XXXXXX", NULL);
619         if (!randname) goto end;
620
621         fd = g_mkstemp(randname);
622         if (fd == -1) goto end;
623
624         fo = fdopen(fd, "wb");
625         if (!fo) {
626                 close(fd);
627                 goto end;
628         }
629
630         while ((b = fread(buf, sizeof(gchar), sizeof(buf), fi)) && b != 0)
631                 {
632                 if (fwrite(buf, sizeof(gchar), b, fo) != b)
633                         {
634                         unlink(randname);
635                         goto end;
636                         }
637                 }
638
639         fclose(fi); fi = NULL;
640         fclose(fo); fo = NULL;
641
642         if (rename(randname, tl) < 0) {
643                 unlink(randname);
644                 goto end;
645         }
646
647         ret = copy_file_attributes(s, t, TRUE, TRUE);
648
649 end:
650         if (fi) fclose(fi);
651         if (fo) fclose(fo);
652         if (sl) g_free(sl);
653         if (tl) g_free(tl);
654         if (randname) g_free(randname);
655         return ret;
656 }
657
658 gboolean move_file(const gchar *s, const gchar *t)
659 {
660         gchar *sl, *tl;
661         gboolean ret = TRUE;
662
663         if (!s || !t) return FALSE;
664
665         sl = path_from_utf8(s);
666         tl = path_from_utf8(t);
667         if (rename(sl, tl) < 0)
668                 {
669                 /* this may have failed because moving a file across filesystems
670                 was attempted, so try copy and delete instead */
671                 if (copy_file(s, t))
672                         {
673                         if (unlink(sl) < 0)
674                                 {
675                                 /* err, now we can't delete the source file so return FALSE */
676                                 ret = FALSE;
677                                 }
678                         }
679                 else
680                         {
681                         ret = FALSE;
682                         }
683                 }
684         g_free(sl);
685         g_free(tl);
686
687         return ret;
688 }
689
690 gboolean rename_file(const gchar *s, const gchar *t)
691 {
692         gchar *sl, *tl;
693         gboolean ret;
694
695         if (!s || !t) return FALSE;
696
697         sl = path_from_utf8(s);
698         tl = path_from_utf8(t);
699         ret = (rename(sl, tl) == 0);
700         g_free(sl);
701         g_free(tl);
702
703         return ret;
704 }
705
706 gchar *get_current_dir(void)
707 {
708         gchar *pathl;
709         gchar *path8;
710
711         pathl = g_get_current_dir();
712         path8 = path_to_utf8(pathl);
713         g_free(pathl);
714
715         return path8;
716 }
717
718 void string_list_free(GList *list)
719 {
720         g_list_foreach(list, (GFunc)g_free, NULL);
721         g_list_free(list);
722 }
723
724 GList *string_list_copy(const GList *list)
725 {
726         GList *new_list = NULL;
727         GList *work = (GList *) list;
728
729         while (work)
730                 {
731                 gchar *path;
732
733                 path = work->data;
734                 work = work->next;
735
736                 new_list = g_list_prepend(new_list, g_strdup(path));
737                 }
738
739         return g_list_reverse(new_list);
740 }
741
742 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gboolean pad)
743 {
744         gchar *unique;
745         gint n = 1;
746
747         if (!ext) ext = "";
748         if (!divider) divider = "";
749
750         unique = g_strconcat(path, ext, NULL);
751         while (isname(unique))
752                 {
753                 g_free(unique);
754                 if (pad)
755                         {
756                         unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
757                         }
758                 else
759                         {
760                         unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
761                         }
762                 n++;
763                 if (n > 999)
764                         {
765                         /* well, we tried */
766                         g_free(unique);
767                         return NULL;
768                         }
769                 }
770
771         return unique;
772 }
773
774 gchar *unique_filename_simple(const gchar *path)
775 {
776         gchar *unique;
777         const gchar *name;
778         const gchar *ext;
779
780         if (!path) return NULL;
781
782         name = filename_from_path(path);
783         if (!name) return NULL;
784
785         ext = registered_extension_from_path(name);
786
787         if (!ext)
788                 {
789                 unique = unique_filename(path, NULL, "_", TRUE);
790                 }
791         else
792                 {
793                 gchar *base;
794
795                 base = remove_extension_from_path(path);
796                 unique = unique_filename(base, ext, "_", TRUE);
797                 g_free(base);
798                 }
799
800         return unique;
801 }
802
803 const gchar *filename_from_path(const gchar *path)
804 {
805         const gchar *base;
806
807         if (!path) return NULL;
808
809         base = strrchr(path, G_DIR_SEPARATOR);
810         if (base) return base + 1;
811
812         return path;
813 }
814
815 gchar *remove_level_from_path(const gchar *path)
816 {
817         const gchar *base;
818
819         if (!path) return g_strdup("");
820
821         base = strrchr(path, G_DIR_SEPARATOR);
822         /* Take account of a file being in the root ( / ) folder - ensure the returned value
823          * is at least one character long */
824         if (base) return g_strndup(path, (strlen(path)-strlen(base)) == 0 ? 1 : (strlen(path)-strlen(base)));
825
826         return g_strdup("");
827 }
828
829 gboolean file_extension_match(const gchar *path, const gchar *ext)
830 {
831         gint p;
832         gint e;
833
834         if (!path) return FALSE;
835         if (!ext) return TRUE;
836
837         p = strlen(path);
838         e = strlen(ext);
839
840         /* FIXME: utf8 */
841         return (p > e && g_ascii_strncasecmp(path + p - e, ext, e) == 0);
842 }
843
844 gchar *remove_extension_from_path(const gchar *path)
845 {
846         const gchar *reg_ext;
847
848         if (!path) return NULL;
849
850         reg_ext = registered_extension_from_path(path);
851
852         return g_strndup(path, strlen(path) - (reg_ext == NULL ? 0 : strlen(reg_ext)));
853 }
854
855 void parse_out_relatives(gchar *path)
856 {
857         gint s, t;
858
859         if (!path) return;
860
861         s = t = 0;
862
863         while (path[s] != '\0')
864                 {
865                 if (path[s] == G_DIR_SEPARATOR && path[s+1] == '.')
866                         {
867                         /* /. occurence, let's see more */
868                         gint p = s + 2;
869
870                         if (path[p] == G_DIR_SEPARATOR || path[p] == '\0')
871                                 {
872                                 /* /./ or /., just skip this part */
873                                 s = p;
874                                 continue;
875                                 }
876                         else if (path[p] == '.' && (path[p+1] == G_DIR_SEPARATOR || path[p+1] == '\0'))
877                                 {
878                                 /* /../ or /.., remove previous part, ie. /a/b/../ becomes /a/ */
879                                 s = p + 1;
880                                 if (t > 0) t--;
881                                 while (path[t] != G_DIR_SEPARATOR && t > 0) t--;
882                                 continue;
883                                 }
884                         }
885
886                 if (s != t) path[t] = path[s];
887                 t++;
888                 s++;
889                 }
890
891         if (t == 0 && path[t] == G_DIR_SEPARATOR) t++;
892         if (t > 1 && path[t-1] == G_DIR_SEPARATOR) t--;
893         path[t] = '\0';
894 }
895
896 gboolean file_in_path(const gchar *name)
897 {
898         gchar *path;
899         gchar *namel;
900         gint p, l;
901         gboolean ret = FALSE;
902
903         if (!name) return FALSE;
904         path = g_strdup(getenv("PATH"));
905         if (!path) return FALSE;
906         namel = path_from_utf8(name);
907
908         p = 0;
909         l = strlen(path);
910         while (p < l && !ret)
911                 {
912                 gchar *f;
913                 gint e = p;
914                 while (path[e] != ':' && path[e] != '\0') e++;
915                 path[e] = '\0';
916                 e++;
917                 f = g_build_filename(path + p, namel, NULL);
918                 if (isfile(f)) ret = TRUE;
919                 g_free(f);
920                 p = e;
921                 }
922         g_free(namel);
923         g_free(path);
924
925         return ret;
926 }
927
928 gboolean recursive_mkdir_if_not_exists(const gchar *path, mode_t mode)
929 {
930         if (!path) return FALSE;
931
932         if (!isdir(path))
933                 {
934                 gchar *npath = g_strdup(path);
935                 gchar *p = npath;
936
937                 while (p[0] != '\0')
938                         {
939                         p++;
940                         if (p[0] == G_DIR_SEPARATOR || p[0] == '\0')
941                                 {
942                                 gboolean end = TRUE;
943
944                                 if (p[0] != '\0')
945                                         {
946                                         p[0] = '\0';
947                                         end = FALSE;
948                                         }
949
950                                 if (!isdir(npath))
951                                         {
952                                         DEBUG_1("creating sub dir:%s", npath);
953                                         if (!mkdir_utf8(npath, mode))
954                                                 {
955                                                 log_printf("create dir failed: %s\n", npath);
956                                                 g_free(npath);
957                                                 return FALSE;
958                                                 }
959                                         }
960
961                                 if (!end) p[0] = G_DIR_SEPARATOR;
962                                 }
963                         }
964                 g_free(npath);
965                 }
966
967         return TRUE;
968 }
969
970 /* does filename utf8 to filesystem encoding first */
971 gboolean md5_get_digest_from_file_utf8(const gchar *path, guchar digest[16])
972 {
973         gboolean success;
974         gchar *pathl;
975
976         pathl = path_from_utf8(path);
977         success = md5_get_digest_from_file(pathl, digest);
978         g_free(pathl);
979
980         return success;
981 }
982
983
984 gchar *md5_text_from_file_utf8(const gchar *path, const gchar *error_text)
985 {
986         guchar digest[16];
987
988         if (!md5_get_digest_from_file_utf8(path, digest)) return g_strdup(error_text);
989
990         return md5_digest_to_text(digest);
991 }
992
993 /* Download web file
994  */
995 typedef struct _WebData WebData;
996 struct _WebData
997 {
998         GenericDialog *gd;
999         GCancellable *cancellable;
1000         LayoutWindow *lw;
1001
1002         GtkWidget *progress;
1003         GFile *tmp_g_file;
1004         GFile *web_file;
1005 };
1006
1007 static void web_file_async_ready_cb(GObject *source_object, GAsyncResult *res, gpointer data)
1008 {
1009         GError *error = NULL;
1010         WebData* web = data;
1011         gchar *tmp_filename;
1012
1013         if (!g_cancellable_is_cancelled(web->cancellable))
1014                 {
1015                 generic_dialog_close(web->gd);
1016                 }
1017
1018         if (g_file_copy_finish(G_FILE(source_object), res, &error))
1019                 {
1020                 tmp_filename = g_file_get_parse_name(web->tmp_g_file);
1021                 g_free(tmp_filename);
1022                 layout_set_path(web->lw, g_file_get_path(web->tmp_g_file));
1023                 }
1024         else
1025                 {
1026                 file_util_warning_dialog(_("Web file download failed"), error->message, GTK_STOCK_DIALOG_ERROR, NULL);
1027                 }
1028
1029         g_object_unref(web->tmp_g_file);
1030         web->tmp_g_file = NULL;
1031         g_object_unref(web->cancellable);
1032         g_object_unref(web->web_file);
1033 }
1034
1035 static void web_file_progress_cb(goffset current_num_bytes, goffset total_num_bytes, gpointer data)
1036 {
1037         WebData* web = data;
1038
1039         if (!g_cancellable_is_cancelled(web->cancellable))
1040                 {
1041                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(web->progress), (gdouble)current_num_bytes / total_num_bytes);
1042                 }
1043 }
1044
1045 static void download_web_file_cancel_button_cb(GenericDialog *gd, gpointer data)
1046 {
1047         WebData* web = data;
1048         GError *error = NULL;
1049
1050         g_cancellable_cancel(web->cancellable);
1051 }
1052
1053 gboolean download_web_file(const gchar *text, gboolean minimized, gpointer data)
1054 {
1055         gchar *scheme;
1056         LayoutWindow *lw = data;
1057         gchar *tmp_dir;
1058         GError *error = NULL;
1059         WebData *web;
1060         gchar *base;
1061         gboolean ret;
1062         gchar *message;
1063         FileFormatClass format_class;
1064
1065         scheme = g_uri_parse_scheme(text);
1066         if (g_strcmp0("http", scheme) == 0 || g_strcmp0("https", scheme) == 0)
1067                 {
1068                 format_class = filter_file_get_class(text);
1069
1070                 if (format_class == FORMAT_CLASS_IMAGE || format_class == FORMAT_CLASS_RAWIMAGE || format_class == FORMAT_CLASS_VIDEO || format_class == FORMAT_CLASS_DOCUMENT)
1071                         {
1072                         tmp_dir = g_dir_make_tmp("geeqie_XXXXXX", &error);
1073                         if (error)
1074                                 {
1075                                 log_printf("Error: could not create temporary file n%s\n", error->message);
1076                                 g_error_free(error);
1077                                 error = NULL;
1078                                 ret = TRUE;
1079                                 }
1080                         else
1081                                 {
1082                                 web = g_new0(WebData, 1);
1083                                 web->lw = lw;
1084
1085                                 web->web_file = g_file_new_for_uri(text);
1086
1087                                 base = g_strdup(g_file_get_basename(web->web_file));
1088                                 web->tmp_g_file = g_file_new_for_path(g_build_filename(tmp_dir, base, NULL));
1089
1090                                 web->gd = generic_dialog_new(_("Download web file"), "download_web_file", NULL, TRUE, download_web_file_cancel_button_cb, web);
1091
1092                                 message = g_strconcat(_("Downloading "), base, NULL);
1093                                 generic_dialog_add_message(web->gd, GTK_STOCK_DIALOG_INFO, message, NULL, FALSE);
1094
1095                                 web->progress = gtk_progress_bar_new();
1096                                 gtk_box_pack_start(GTK_BOX(web->gd->vbox), web->progress, FALSE, FALSE, 0);
1097                                 gtk_widget_show(web->progress);
1098                                 if (minimized)
1099                                         {
1100                                         gtk_window_iconify(GTK_WINDOW(web->gd->dialog));
1101                                         }
1102
1103                                 gtk_widget_show(web->gd->dialog);
1104                                 web->cancellable = g_cancellable_new();
1105                                 g_file_copy_async(web->web_file, web->tmp_g_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_LOW, web->cancellable, web_file_progress_cb, web, web_file_async_ready_cb, web);
1106
1107                                 g_free(base);
1108                                 g_free(message);
1109                                 ret = TRUE;
1110                                 }
1111                         }
1112                 }
1113         else
1114                 {
1115                 ret = FALSE;
1116                 }
1117
1118         g_free(scheme);
1119         return ret;
1120
1121 }
1122 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */