Sync to GQview 1.5.9 release.
[geeqie.git] / src / ui_fileops.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2004 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 isname(const gchar *s)
201 {
202         struct stat st;
203
204         return stat_utf8(s, &st);
205 }
206
207 gint isfile(const gchar *s)
208 {
209         struct stat st;
210
211         return (stat_utf8(s, &st) && S_ISREG(st.st_mode));
212 }
213
214 gint isdir(const gchar *s)
215 {
216         struct stat st;
217    
218         return (stat_utf8(s ,&st) && S_ISDIR(st.st_mode));
219 }
220
221 gint64 filesize(const gchar *s)
222 {
223         struct stat st;
224    
225         if (!stat_utf8(s, &st)) return 0;
226         return (gint)st.st_size;
227 }
228
229 time_t filetime(const gchar *s)
230 {
231         struct stat st;
232
233         if (!stat_utf8(s, &st)) return 0;
234         return st.st_mtime;
235 }
236
237 gint filetime_set(const gchar *s, time_t tval)
238 {
239         gint ret = FALSE;
240
241         if (tval > 0)
242                 {
243                 struct utimbuf ut;
244                 gchar *sl;
245
246                 ut.actime = ut.modtime = tval;
247
248                 sl = path_from_utf8(s);
249                 ret = (utime(sl, &ut) == 0);
250                 g_free(sl);
251                 }
252
253         return ret;
254 }
255
256 gint access_file(const gchar *s, int mode)
257 {
258         gchar *sl;
259         gint ret;
260
261         if (!s) return FALSE;
262
263         sl = path_from_utf8(s);
264         ret = (access(sl, mode) == 0);
265         g_free(sl);
266
267         return ret;
268 }
269
270 gint unlink_file(const gchar *s)
271 {
272         gchar *sl;
273         gint ret;
274
275         if (!s) return FALSE;
276
277         sl = path_from_utf8(s);
278         ret = (unlink(sl) == 0);
279         g_free(sl);
280
281         return ret;
282 }
283
284 gint symlink_utf8(const gchar *source, const gchar *target)
285 {
286         gchar *sl;
287         gchar *tl;
288         gint ret;
289
290         if (!source || !target) return FALSE;
291
292         sl = path_from_utf8(source);
293         tl = path_from_utf8(target);
294
295         ret = (symlink(sl, tl) == 0);
296
297         g_free(sl);
298         g_free(tl);
299
300         return ret;
301 }
302
303 gint mkdir_utf8(const gchar *s, int mode)
304 {
305         gchar *sl;
306         gint ret;
307
308         if (!s) return FALSE;
309
310         sl = path_from_utf8(s);
311         ret = (mkdir(sl, mode) == 0);
312         g_free(sl);
313         return ret;
314 }
315
316 gint rmdir_utf8(const gchar *s)
317 {
318         gchar *sl;
319         gint ret;
320
321         if (!s) return FALSE;
322
323         sl = path_from_utf8(s);
324         ret = (rmdir(sl) == 0);
325         g_free(sl);
326
327         return ret;
328 }
329
330 gint copy_file_attributes(const gchar *s, const gchar *t, gint perms, gint mtime)
331 {
332         struct stat st;
333         gchar *sl, *tl;
334         gint ret = FALSE;
335
336         if (!s || !t) return FALSE;
337
338         sl = path_from_utf8(s);
339         tl = path_from_utf8(t);
340
341         if (stat(sl, &st) == 0)
342                 {
343                 struct utimbuf tb;
344
345                 ret = TRUE;
346
347                 /* set the dest file attributes to that of source (ignoring errors) */
348
349                 if (perms && chown(tl, st.st_uid, st.st_gid) < 0) ret = FALSE;
350                 if (perms && chmod(tl, st.st_mode) < 0) ret = FALSE;
351
352                 tb.actime = st.st_atime;
353                 tb.modtime = st.st_mtime;
354                 if (mtime && utime(tl, &tb) < 0) ret = FALSE;
355                 }
356
357         g_free(sl);
358         g_free(tl);
359
360         return ret;
361 }
362
363 /* paths are in filesystem encoding */
364 static gint hard_linked(const gchar *a, const gchar *b)
365 {
366         struct stat sta;
367         struct stat stb;
368
369         if (stat(a, &sta) !=  0 || stat(b, &stb) != 0) return FALSE;
370
371         return (sta.st_dev == stb.st_dev &&
372                 sta.st_ino == stb.st_ino);
373 }
374
375 gint copy_file(const gchar *s, const gchar *t)
376 {
377         FILE *fi = NULL;
378         FILE *fo = NULL;
379         gchar *sl, *tl;
380         gchar buf[4096];
381         gint b;
382
383         sl = path_from_utf8(s);
384         tl = path_from_utf8(t);
385
386         if (hard_linked(sl, tl))
387                 {
388                 g_free(sl);
389                 g_free(tl);
390                 return TRUE;
391                 }
392
393         fi = fopen(sl, "rb");
394         if (fi)
395                 {
396                 fo = fopen(tl, "wb");
397                 if (!fo)
398                         {
399                         fclose(fi);
400                         fi = NULL;
401                         }
402                 }
403
404         g_free(sl);
405         g_free(tl);
406
407         if (!fi || !fo) return FALSE;
408
409         while((b = fread(buf, sizeof(char), 4096, fi)) && b != 0)
410                 {
411                 if (fwrite(buf, sizeof(char), b, fo) != b)
412                         {
413                         fclose(fi);
414                         fclose(fo);
415                         return FALSE;
416                         }
417                 }
418
419         fclose(fi);
420         fclose(fo);
421
422         copy_file_attributes(s, t, TRUE, TRUE);
423
424         return TRUE;
425 }
426
427 gint move_file(const gchar *s, const gchar *t)
428 {
429         gchar *sl, *tl;
430         gint ret = TRUE;
431
432         if (!s || !t) return FALSE;
433
434         sl = path_from_utf8(s);
435         tl = path_from_utf8(t);
436         if (rename(sl, tl) < 0)
437                 {
438                 /* this may have failed because moving a file across filesystems
439                 was attempted, so try copy and delete instead */
440                 if (copy_file(s, t))
441                         {
442                         if (unlink(sl) < 0)
443                                 {
444                                 /* err, now we can't delete the source file so return FALSE */
445                                 ret = FALSE;
446                                 }
447                         }
448                 else
449                         {
450                         ret = FALSE;
451                         }
452                 }
453         g_free(sl);
454         g_free(tl);
455
456         return ret;
457 }
458
459 gint rename_file(const gchar *s, const gchar *t)
460 {
461         gchar *sl, *tl;
462         gint ret;
463
464         if (!s || !t) return FALSE;
465
466         sl = path_from_utf8(s);
467         tl = path_from_utf8(t);
468         ret = (rename(sl, tl) == 0);
469         g_free(sl);
470         g_free(tl);
471
472         return ret;
473 }
474
475 gchar *get_current_dir(void)
476 {
477         gchar *pathl;
478         gchar *path8;
479
480         pathl = g_get_current_dir();
481         path8 = path_to_utf8(pathl);
482         g_free(pathl);
483
484         return path8;
485 }
486
487 gint path_list(const gchar *path, GList **files, GList **dirs)
488 {
489         DIR *dp;
490         struct dirent *dir;
491         struct stat ent_sbuf;
492         GList *f_list = NULL;
493         GList *d_list = NULL;
494         gchar *pathl;
495
496         if (!path) return FALSE;
497
498         pathl = path_from_utf8(path);
499         dp = opendir(pathl);
500         if (!dp)
501                 {
502                 /* dir not found */
503                 g_free(pathl);
504                 return FALSE;
505                 }
506
507         /* root dir fix */
508         if (pathl[0] == '/' && pathl[1] == '\0')
509                 {
510                 g_free(pathl);
511                 pathl = g_strdup("");
512                 }
513
514         while ((dir = readdir(dp)) != NULL)
515                 {
516                 /* skip removed files */
517                 if (dir->d_ino > 0)
518                         {
519                         gchar *name = dir->d_name;
520                         gchar *filepath = g_strconcat(pathl, "/", name, NULL);
521                         if (stat(filepath, &ent_sbuf) >= 0)
522                                 {
523                                 gchar *path8;
524                                 gchar *name8;
525
526                                 name8 = path_to_utf8(name);
527                                 path8 = g_strconcat(path, "/", name8, NULL);
528                                 g_free(name8);
529
530                                 if (dirs && S_ISDIR(ent_sbuf.st_mode) &&
531                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) )
532                                         {
533                                         d_list = g_list_prepend(d_list, path8);
534                                         path8 = NULL;
535                                         }
536                                 else if (files && S_ISREG(ent_sbuf.st_mode))
537                                         {
538                                         f_list = g_list_prepend(f_list, path8);
539                                         path8 = NULL;
540                                         }
541                                 g_free(path8);
542                                 }
543                         g_free(filepath);
544                         }
545                 }
546         closedir(dp);
547
548         g_free(pathl);
549
550         if (dirs) *dirs = g_list_reverse(d_list);
551         if (files) *files = g_list_reverse(f_list);
552
553         return TRUE;
554 }
555
556 void path_list_free(GList *list)
557 {
558         g_list_foreach(list, (GFunc)g_free, NULL);
559         g_list_free(list);
560 }
561
562 GList *path_list_copy(GList *list)
563 {
564         GList *new_list = NULL;
565         GList *work;
566
567         work = list;
568         while (work)
569                 {
570                 gchar *path;
571  
572                 path = work->data;
573                 work = work->next;
574  
575                 new_list = g_list_prepend(new_list, g_strdup(path));
576                 }
577  
578         return g_list_reverse(new_list);
579 }
580
581 long checksum_simple(const gchar *path)
582 {
583         gchar *path8;
584         FILE *f;
585         long sum = 0;
586         gint c;
587
588         path8 = path_from_utf8(path);
589         f = fopen(path8, "r");
590         g_free(path8);
591         if (!f) return -1;
592
593         while((c = fgetc(f)) != EOF)
594                 {
595                 sum += c;
596                 }
597
598         fclose(f);
599
600         return sum;
601 }
602
603 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gint pad)
604 {
605         gchar *unique;
606         gint n = 1;
607
608         if (!ext) ext = "";
609         if (!divider) divider = "";
610
611         unique = g_strconcat(path, ext, NULL);
612         while (isname(unique))
613                 {
614                 g_free(unique);
615                 if (pad)
616                         {
617                         unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
618                         }
619                 else
620                         {
621                         unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
622                         }
623                 n++;
624                 if (n > 999)
625                         {
626                         /* well, we tried */
627                         g_free(unique);
628                         return NULL;
629                         }
630                 }
631
632         return unique;
633 }
634
635 gchar *unique_filename_simple(const gchar *path)
636 {
637         gchar *unique;
638         const gchar *name;
639         const gchar *ext;
640
641         if (!path) return NULL;
642
643         name = filename_from_path(path);
644         if (!name) return NULL;
645
646         ext = extension_from_path(name);
647
648         if (!ext)
649                 {
650                 unique = unique_filename(path, NULL, "_", TRUE);
651                 }
652         else
653                 {
654                 gchar *base;
655
656                 base = remove_extension_from_path(path);
657                 unique = unique_filename(base, ext, "_", TRUE);
658                 g_free(base);
659                 }
660
661         return unique;
662 }
663
664 const gchar *filename_from_path(const gchar *path)
665 {
666         const gchar *base;
667
668         if (!path) return NULL;
669
670         base = strrchr(path, '/');
671         if (base) return base + 1;
672
673         return path;
674 }
675
676 gchar *remove_level_from_path(const gchar *path)
677 {
678         gchar *new_path;
679         const gchar *ptr = path;
680         gint p;
681
682         if (!path) return NULL;
683
684         p = strlen(path) - 1;
685         if (p < 0) return NULL;
686         while(ptr[p] != '/' && p > 0) p--;
687         if (p == 0 && ptr[p] == '/') p++;
688         new_path = g_strndup(path, (guint)p);
689         return new_path;
690 }
691
692 gchar *concat_dir_and_file(const gchar *base, const gchar *name)
693 {
694         if (!base || !name) return NULL;
695
696         if (strcmp(base, "/") == 0) return g_strconcat(base, name, NULL);
697
698         return g_strconcat(base, "/", name, NULL);
699 }
700
701 const gchar *extension_from_path(const gchar *path)
702 {
703         if (!path) return NULL;
704         return strrchr(path, '.');
705 }
706
707 gint file_extension_match(const gchar *path, const gchar *ext)
708 {
709         gint p;
710         gint e;
711
712         if (!path) return FALSE;
713         if (!ext) return TRUE;
714
715         p = strlen(path);
716         e = strlen(ext);
717
718         return (p > e && strncasecmp(path + p - e, ext, e) == 0);
719 }
720
721 gchar *remove_extension_from_path(const gchar *path)
722 {
723         gchar *new_path;
724         const gchar *ptr = path;
725         gint p;
726
727         if (!path) return NULL;
728         if (strlen(path) < 2) return g_strdup(path);
729
730         p = strlen(path) - 1;
731         while(ptr[p] != '.' && p > 0) p--;
732         if (p == 0) p = strlen(path) - 1;
733         new_path = g_strndup(path, (guint)p);
734         return new_path;
735 }
736
737 void parse_out_relatives(gchar *path)
738 {
739         gint s, t;
740
741         if (!path) return;
742
743         s = t = 0;
744
745         while (path[s] != '\0')
746                 {
747                 if (path[s] == '/' && path[s+1] == '.' && (path[s+2] == '/' || path[s+2] == '\0') )
748                         {
749                         s += 2;
750                         }
751                 else if (path[s] == '/' && path[s+1] == '.' && path[s+2] == '.' && (path[s+3] == '/' || path[s+3] == '\0') )
752                         {
753                         s += 3;
754                         if (t > 0) t--;
755                         while (path[t] != '/' && t > 0) t--;
756                         }
757                 else
758                         {
759                         if (s != t) path[t] = path[s];
760                         t++;
761                         s++;
762                         }
763                 }
764         if (t == 0 && path[t] == '/') t++;
765         if (t > 1 && path[t-1] == '/') t--;
766         path[t] = '\0';
767 }
768
769 gint file_in_path(const gchar *name)
770 {
771         gchar *path;
772         gchar *namel;
773         gint p, l;
774         gint ret = FALSE;
775
776         if (!name) return FALSE;
777         path = g_strdup(getenv("PATH"));
778         if (!path) return FALSE;
779         namel = path_from_utf8(name);
780
781         p = 0;
782         l = strlen(path);
783         while (p < l && !ret)
784                 {
785                 gchar *f;
786                 gint e = p;
787                 while (path[e] != ':' && path[e] != '\0') e++;
788                 path[e] = '\0';
789                 e++;
790                 f = g_strconcat(path + p, "/", namel, NULL);
791                 if (isfile(f)) ret = TRUE;
792                 g_free(f);
793                 p = e;
794                 }
795         g_free(namel);
796         g_free(path);
797
798         return ret;
799 }
800