Fri Oct 13 10:27:22 2006 John Ellis <johne@verizon.net>
[geeqie.git] / src / ui_fileops.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2006 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #ifdef HAVE_CONFIG_H
13 #  include "config.h"
14 #endif
15
16 #include <pwd.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 #include <sys/param.h>
22 #include <dirent.h>
23 #include <utime.h>
24
25 #include <glib.h>
26 #include <gtk/gtk.h>    /* for locale warning dialog */
27
28 #include "ui_fileops.h"
29
30 #include "ui_utildlg.h" /* for locale warning dialog */
31
32 /*
33  *-----------------------------------------------------------------------------
34  * generic file information and manipulation routines (public)
35  *-----------------------------------------------------------------------------
36  */ 
37
38 /* file sorting method (case) */
39 gint file_sort_case_sensitive = FALSE;
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                                 PACKAGE, "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), 4096, 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 gint path_list(const gchar *path, GList **files, GList **dirs)
508 {
509         DIR *dp;
510         struct dirent *dir;
511         struct stat ent_sbuf;
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                 gchar *name = dir->d_name;
537                 gchar *filepath = g_strconcat(pathl, "/", name, NULL);
538
539                 if (stat(filepath, &ent_sbuf) >= 0)
540                         {
541                         gchar *path8;
542                         gchar *name8;
543
544                         name8 = path_to_utf8(name);
545                         path8 = g_strconcat(path, "/", name8, NULL);
546                         g_free(name8);
547
548                         if (dirs && S_ISDIR(ent_sbuf.st_mode) &&
549                             !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) )
550                                 {
551                                 d_list = g_list_prepend(d_list, path8);
552                                 path8 = NULL;
553                                 }
554                         else if (files && S_ISREG(ent_sbuf.st_mode))
555                                 {
556                                 f_list = g_list_prepend(f_list, path8);
557                                 path8 = NULL;
558                                 }
559                         g_free(path8);
560                         }
561
562                 g_free(filepath);
563                 }
564
565         closedir(dp);
566
567         g_free(pathl);
568
569         if (dirs) *dirs = g_list_reverse(d_list);
570         if (files) *files = g_list_reverse(f_list);
571
572         return TRUE;
573 }
574
575 void path_list_free(GList *list)
576 {
577         g_list_foreach(list, (GFunc)g_free, NULL);
578         g_list_free(list);
579 }
580
581 GList *path_list_copy(GList *list)
582 {
583         GList *new_list = NULL;
584         GList *work;
585
586         work = list;
587         while (work)
588                 {
589                 gchar *path;
590  
591                 path = work->data;
592                 work = work->next;
593  
594                 new_list = g_list_prepend(new_list, g_strdup(path));
595                 }
596  
597         return g_list_reverse(new_list);
598 }
599
600 long checksum_simple(const gchar *path)
601 {
602         gchar *path8;
603         FILE *f;
604         long sum = 0;
605         gint c;
606
607         path8 = path_from_utf8(path);
608         f = fopen(path8, "r");
609         g_free(path8);
610         if (!f) return -1;
611
612         while((c = fgetc(f)) != EOF)
613                 {
614                 sum += c;
615                 }
616
617         fclose(f);
618
619         return sum;
620 }
621
622 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gint pad)
623 {
624         gchar *unique;
625         gint n = 1;
626
627         if (!ext) ext = "";
628         if (!divider) divider = "";
629
630         unique = g_strconcat(path, ext, NULL);
631         while (isname(unique))
632                 {
633                 g_free(unique);
634                 if (pad)
635                         {
636                         unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
637                         }
638                 else
639                         {
640                         unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
641                         }
642                 n++;
643                 if (n > 999)
644                         {
645                         /* well, we tried */
646                         g_free(unique);
647                         return NULL;
648                         }
649                 }
650
651         return unique;
652 }
653
654 gchar *unique_filename_simple(const gchar *path)
655 {
656         gchar *unique;
657         const gchar *name;
658         const gchar *ext;
659
660         if (!path) return NULL;
661
662         name = filename_from_path(path);
663         if (!name) return NULL;
664
665         ext = extension_from_path(name);
666
667         if (!ext)
668                 {
669                 unique = unique_filename(path, NULL, "_", TRUE);
670                 }
671         else
672                 {
673                 gchar *base;
674
675                 base = remove_extension_from_path(path);
676                 unique = unique_filename(base, ext, "_", TRUE);
677                 g_free(base);
678                 }
679
680         return unique;
681 }
682
683 const gchar *filename_from_path(const gchar *path)
684 {
685         const gchar *base;
686
687         if (!path) return NULL;
688
689         base = strrchr(path, '/');
690         if (base) return base + 1;
691
692         return path;
693 }
694
695 gchar *remove_level_from_path(const gchar *path)
696 {
697         gchar *new_path;
698         const gchar *ptr = path;
699         gint p;
700
701         if (!path) return NULL;
702
703         p = strlen(path) - 1;
704         if (p < 0) return NULL;
705         while(ptr[p] != '/' && p > 0) p--;
706         if (p == 0 && ptr[p] == '/') p++;
707         new_path = g_strndup(path, (guint)p);
708         return new_path;
709 }
710
711 gchar *concat_dir_and_file(const gchar *base, const gchar *name)
712 {
713         if (!base || !name) return NULL;
714
715         if (strcmp(base, "/") == 0) return g_strconcat(base, name, NULL);
716
717         return g_strconcat(base, "/", name, NULL);
718 }
719
720 const gchar *extension_from_path(const gchar *path)
721 {
722         if (!path) return NULL;
723         return strrchr(path, '.');
724 }
725
726 gint file_extension_match(const gchar *path, const gchar *ext)
727 {
728         gint p;
729         gint e;
730
731         if (!path) return FALSE;
732         if (!ext) return TRUE;
733
734         p = strlen(path);
735         e = strlen(ext);
736
737         return (p > e && strncasecmp(path + p - e, ext, e) == 0);
738 }
739
740 gchar *remove_extension_from_path(const gchar *path)
741 {
742         gchar *new_path;
743         const gchar *ptr = path;
744         gint p;
745
746         if (!path) return NULL;
747         if (strlen(path) < 2) return g_strdup(path);
748
749         p = strlen(path) - 1;
750         while(ptr[p] != '.' && p > 0) p--;
751         if (p == 0) p = strlen(path) - 1;
752         new_path = g_strndup(path, (guint)p);
753         return new_path;
754 }
755
756 void parse_out_relatives(gchar *path)
757 {
758         gint s, t;
759
760         if (!path) return;
761
762         s = t = 0;
763
764         while (path[s] != '\0')
765                 {
766                 if (path[s] == '/' && path[s+1] == '.' && (path[s+2] == '/' || path[s+2] == '\0') )
767                         {
768                         s += 2;
769                         }
770                 else if (path[s] == '/' && path[s+1] == '.' && path[s+2] == '.' && (path[s+3] == '/' || path[s+3] == '\0') )
771                         {
772                         s += 3;
773                         if (t > 0) t--;
774                         while (path[t] != '/' && t > 0) t--;
775                         }
776                 else
777                         {
778                         if (s != t) path[t] = path[s];
779                         t++;
780                         s++;
781                         }
782                 }
783         if (t == 0 && path[t] == '/') t++;
784         if (t > 1 && path[t-1] == '/') t--;
785         path[t] = '\0';
786 }
787
788 gint file_in_path(const gchar *name)
789 {
790         gchar *path;
791         gchar *namel;
792         gint p, l;
793         gint ret = FALSE;
794
795         if (!name) return FALSE;
796         path = g_strdup(getenv("PATH"));
797         if (!path) return FALSE;
798         namel = path_from_utf8(name);
799
800         p = 0;
801         l = strlen(path);
802         while (p < l && !ret)
803                 {
804                 gchar *f;
805                 gint e = p;
806                 while (path[e] != ':' && path[e] != '\0') e++;
807                 path[e] = '\0';
808                 e++;
809                 f = g_strconcat(path + p, "/", namel, NULL);
810                 if (isfile(f)) ret = TRUE;
811                 g_free(f);
812                 p = e;
813                 }
814         g_free(namel);
815         g_free(path);
816
817         return ret;
818 }
819