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