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