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