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