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