Fix untranslated messages. French translation and POTFILES.in were updated.
[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         fputs((text_l) ? text_l : text_utf8, stdout);
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"), PACKAGE);
96         g_string_append(string, "\n");
97         g_string_append(string, _("If your filenames are not encoded in utf-8, try setting the environment variable G_BROKEN_FILENAMES=1\n"));
98         if (bf)
99                 g_string_append_printf(string, _("It appears G_BROKEN_FILENAMES is set to %s\n"), bf);
100         else
101                 g_string_append(string, _("It appears G_BROKEN_FILENAMES is not set\n"));
102         g_string_append(string, "\n");
103         g_string_append_printf(string, _("The locale appears to be set to \"%s\"\n(set by the LANG environment variable)\n"), (lc) ? lc : "undefined");
104         if (lc && (strstr(lc, "UTF-8") || strstr(lc, "utf-8")))
105                 {
106                 gchar *name;
107                 name = g_convert(path, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
108                 string = g_string_append(string, _("\nPreferred encoding appears to be UTF-8, however the file:\n"));
109                 g_string_append_printf(string, "\"%s\"\n", (name) ? name : _("[name not displayable]"));
110                 
111                 if (g_utf8_validate(path, -1, NULL))
112                         g_string_append_printf(string, _("\"%s\" is encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
113                 else
114                         g_string_append_printf(string, _("\"%s\" is not encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
115                 g_string_append(string, "\n");
116                 g_free(name);
117                 }
118
119         gd = generic_dialog_new(_("Filename encoding locale mismatch"),
120                                 GQ_WMCLASS, "locale warning", NULL, TRUE, NULL, NULL);
121         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE);
122
123         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING,
124                                    _("Filename encoding locale mismatch"), string->str);
125
126         gtk_widget_show(gd->dialog);
127
128         g_string_free(string, TRUE);
129 }
130
131 gchar *path_to_utf8(const gchar *path)
132 {
133         gchar *utf8;
134         GError *error = NULL;
135
136         if (!path) return NULL;
137
138         utf8 = g_filename_to_utf8(path, -1, NULL, NULL, &error);
139         if (error)
140                 {
141                 log_printf("Unable to convert filename to UTF-8:\n%s\n%s\n", path, error->message);
142                 g_error_free(error);
143                 encoding_dialog(path);
144                 }
145         if (!utf8)
146                 {
147                 /* just let it through, but bad things may happen */
148                 utf8 = g_strdup(path);
149                 }
150
151         return utf8;
152 }
153
154 gchar *path_from_utf8(const gchar *utf8)
155 {
156         gchar *path;
157         GError *error = NULL;
158
159         if (!utf8) return NULL;
160
161         path = g_filename_from_utf8(utf8, -1, NULL, NULL, &error);
162         if (error)
163                 {
164                 log_printf("Unable to convert filename to locale from UTF-8:\n%s\n%s\n", utf8, error->message);
165                 g_error_free(error);
166                 }
167         if (!path)
168                 {
169                 /* if invalid UTF-8, text probaby still in original form, so just copy it */
170                 path = g_strdup(utf8);
171                 }
172
173         return path;
174 }
175
176 /* first we try the HOME environment var, if that doesn't work, we try getpwuid(). */
177 const gchar *homedir(void)
178 {
179         static gchar *home = NULL;
180
181         if (!home)
182                 {
183                 home = path_to_utf8(getenv("HOME"));
184                 }
185         if (!home)
186                 {
187                 struct passwd *pw = getpwuid(getuid());
188                 if (pw) home = path_to_utf8(pw->pw_dir);
189                 }
190
191         return home;
192 }
193
194 gint stat_utf8(const gchar *s, struct stat *st)
195 {
196         gchar *sl;
197         gint ret;
198
199         if (!s) return FALSE;
200         sl = path_from_utf8(s);
201         ret = (stat(sl, st) == 0);
202         g_free(sl);
203
204         return ret;
205 }
206
207 gint lstat_utf8(const gchar *s, struct stat *st)
208 {
209         gchar *sl;
210         gint ret;
211
212         if (!s) return FALSE;
213         sl = path_from_utf8(s);
214         ret = (lstat(sl, st) == 0);
215         g_free(sl);
216
217         return ret;
218 }
219
220 gint isname(const gchar *s)
221 {
222         struct stat st;
223
224         return stat_utf8(s, &st);
225 }
226
227 gint isfile(const gchar *s)
228 {
229         struct stat st;
230
231         return (stat_utf8(s, &st) && S_ISREG(st.st_mode));
232 }
233
234 gint isdir(const gchar *s)
235 {
236         struct stat st;
237
238         return (stat_utf8(s ,&st) && S_ISDIR(st.st_mode));
239 }
240
241 gint islink(const gchar *s)
242 {
243         struct stat st;
244
245         return (lstat_utf8(s ,&st) && S_ISLNK(st.st_mode));
246 }
247
248 gint64 filesize(const gchar *s)
249 {
250         struct stat st;
251
252         if (!stat_utf8(s, &st)) return 0;
253         return st.st_size;
254 }
255
256 time_t filetime(const gchar *s)
257 {
258         struct stat st;
259
260         if (!stat_utf8(s, &st)) return 0;
261         return st.st_mtime;
262 }
263
264 gint filetime_set(const gchar *s, time_t tval)
265 {
266         gint ret = FALSE;
267
268         if (tval > 0)
269                 {
270                 struct utimbuf ut;
271                 gchar *sl;
272
273                 ut.actime = ut.modtime = tval;
274
275                 sl = path_from_utf8(s);
276                 ret = (utime(sl, &ut) == 0);
277                 g_free(sl);
278                 }
279
280         return ret;
281 }
282
283 gint access_file(const gchar *s, int mode)
284 {
285         gchar *sl;
286         gint ret;
287
288         if (!s) return FALSE;
289
290         sl = path_from_utf8(s);
291         ret = (access(sl, mode) == 0);
292         g_free(sl);
293
294         return ret;
295 }
296
297 gint unlink_file(const gchar *s)
298 {
299         gchar *sl;
300         gint ret;
301
302         if (!s) return FALSE;
303
304         sl = path_from_utf8(s);
305         ret = (unlink(sl) == 0);
306         g_free(sl);
307
308         return ret;
309 }
310
311 gint symlink_utf8(const gchar *source, const gchar *target)
312 {
313         gchar *sl;
314         gchar *tl;
315         gint ret;
316
317         if (!source || !target) return FALSE;
318
319         sl = path_from_utf8(source);
320         tl = path_from_utf8(target);
321
322         ret = (symlink(sl, tl) == 0);
323
324         g_free(sl);
325         g_free(tl);
326
327         return ret;
328 }
329
330 gint mkdir_utf8(const gchar *s, int mode)
331 {
332         gchar *sl;
333         gint ret;
334
335         if (!s) return FALSE;
336
337         sl = path_from_utf8(s);
338         ret = (mkdir(sl, mode) == 0);
339         g_free(sl);
340         return ret;
341 }
342
343 gint rmdir_utf8(const gchar *s)
344 {
345         gchar *sl;
346         gint ret;
347
348         if (!s) return FALSE;
349
350         sl = path_from_utf8(s);
351         ret = (rmdir(sl) == 0);
352         g_free(sl);
353
354         return ret;
355 }
356
357 gint copy_file_attributes(const gchar *s, const gchar *t, gint perms, gint mtime)
358 {
359         struct stat st;
360         gchar *sl, *tl;
361         gint ret = FALSE;
362
363         if (!s || !t) return FALSE;
364
365         sl = path_from_utf8(s);
366         tl = path_from_utf8(t);
367
368         if (stat(sl, &st) == 0)
369                 {
370                 struct utimbuf tb;
371
372                 ret = TRUE;
373
374                 /* set the dest file attributes to that of source (ignoring errors) */
375
376                 if (perms && chown(tl, st.st_uid, st.st_gid) < 0) ret = FALSE;
377                 if (perms && chmod(tl, st.st_mode) < 0) ret = FALSE;
378
379                 tb.actime = st.st_atime;
380                 tb.modtime = st.st_mtime;
381                 if (mtime && utime(tl, &tb) < 0) ret = FALSE;
382                 }
383
384         g_free(sl);
385         g_free(tl);
386
387         return ret;
388 }
389
390 /* paths are in filesystem encoding */
391 static gint hard_linked(const gchar *a, const gchar *b)
392 {
393         struct stat sta;
394         struct stat stb;
395
396         if (stat(a, &sta) !=  0 || stat(b, &stb) != 0) return FALSE;
397
398         return (sta.st_dev == stb.st_dev &&
399                 sta.st_ino == stb.st_ino);
400 }
401
402 gint copy_file(const gchar *s, const gchar *t)
403 {
404         FILE *fi = NULL;
405         FILE *fo = NULL;
406         gchar *sl, *tl;
407         gchar buf[4096];
408         size_t b;
409
410         sl = path_from_utf8(s);
411         tl = path_from_utf8(t);
412
413         if (hard_linked(sl, tl))
414                 {
415                 g_free(sl);
416                 g_free(tl);
417                 return TRUE;
418                 }
419
420         fi = fopen(sl, "rb");
421         if (fi)
422                 {
423                 fo = fopen(tl, "wb");
424                 if (!fo)
425                         {
426                         fclose(fi);
427                         fi = NULL;
428                         }
429                 }
430
431         g_free(sl);
432         g_free(tl);
433
434         if (!fi || !fo) return FALSE;
435
436         while ((b = fread(buf, sizeof(char), sizeof(buf), fi)) && b != 0)
437                 {
438                 if (fwrite(buf, sizeof(char), b, fo) != b)
439                         {
440                         fclose(fi);
441                         fclose(fo);
442                         return FALSE;
443                         }
444                 }
445
446         fclose(fi);
447         fclose(fo);
448
449         copy_file_attributes(s, t, TRUE, TRUE);
450
451         return TRUE;
452 }
453
454 gint move_file(const gchar *s, const gchar *t)
455 {
456         gchar *sl, *tl;
457         gint ret = TRUE;
458
459         if (!s || !t) return FALSE;
460
461         sl = path_from_utf8(s);
462         tl = path_from_utf8(t);
463         if (rename(sl, tl) < 0)
464                 {
465                 /* this may have failed because moving a file across filesystems
466                 was attempted, so try copy and delete instead */
467                 if (copy_file(s, t))
468                         {
469                         if (unlink(sl) < 0)
470                                 {
471                                 /* err, now we can't delete the source file so return FALSE */
472                                 ret = FALSE;
473                                 }
474                         }
475                 else
476                         {
477                         ret = FALSE;
478                         }
479                 }
480         g_free(sl);
481         g_free(tl);
482
483         return ret;
484 }
485
486 gint rename_file(const gchar *s, const gchar *t)
487 {
488         gchar *sl, *tl;
489         gint ret;
490
491         if (!s || !t) return FALSE;
492
493         sl = path_from_utf8(s);
494         tl = path_from_utf8(t);
495         ret = (rename(sl, tl) == 0);
496         g_free(sl);
497         g_free(tl);
498
499         return ret;
500 }
501
502 gchar *get_current_dir(void)
503 {
504         gchar *pathl;
505         gchar *path8;
506
507         pathl = g_get_current_dir();
508         path8 = path_to_utf8(pathl);
509         g_free(pathl);
510
511         return path8;
512 }
513
514 void string_list_free(GList *list)
515 {
516         g_list_foreach(list, (GFunc)g_free, NULL);
517         g_list_free(list);
518 }
519
520 GList *string_list_copy(GList *list)
521 {
522         GList *new_list = NULL;
523         GList *work;
524
525         work = list;
526         while (work)
527                 {
528                 gchar *path;
529
530                 path = work->data;
531                 work = work->next;
532
533                 new_list = g_list_prepend(new_list, g_strdup(path));
534                 }
535
536         return g_list_reverse(new_list);
537 }
538
539 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gint pad)
540 {
541         gchar *unique;
542         gint n = 1;
543
544         if (!ext) ext = "";
545         if (!divider) divider = "";
546
547         unique = g_strconcat(path, ext, NULL);
548         while (isname(unique))
549                 {
550                 g_free(unique);
551                 if (pad)
552                         {
553                         unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
554                         }
555                 else
556                         {
557                         unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
558                         }
559                 n++;
560                 if (n > 999)
561                         {
562                         /* well, we tried */
563                         g_free(unique);
564                         return NULL;
565                         }
566                 }
567
568         return unique;
569 }
570
571 gchar *unique_filename_simple(const gchar *path)
572 {
573         gchar *unique;
574         const gchar *name;
575         const gchar *ext;
576
577         if (!path) return NULL;
578
579         name = filename_from_path(path);
580         if (!name) return NULL;
581
582         ext = extension_from_path(name);
583
584         if (!ext)
585                 {
586                 unique = unique_filename(path, NULL, "_", TRUE);
587                 }
588         else
589                 {
590                 gchar *base;
591
592                 base = remove_extension_from_path(path);
593                 unique = unique_filename(base, ext, "_", TRUE);
594                 g_free(base);
595                 }
596
597         return unique;
598 }
599
600 const gchar *filename_from_path(const gchar *path)
601 {
602         const gchar *base;
603
604         if (!path) return NULL;
605
606         base = strrchr(path, G_DIR_SEPARATOR);
607         if (base) return base + 1;
608
609         return path;
610 }
611
612 gchar *remove_level_from_path(const gchar *path)
613 {
614         gint p = 0, n = -1;
615
616         if (!path) return NULL;
617
618         while (path[p])
619                 {
620                 if (path[p] == G_DIR_SEPARATOR) n = p;
621                 p++;
622                 }
623         if (n <= 0) n++;
624
625         return g_strndup(path, (gsize) n);
626 }
627
628 const gchar *extension_from_path(const gchar *path)
629 {
630         if (!path) return NULL;
631         return strrchr(path, '.');
632 }
633
634 gint file_extension_match(const gchar *path, const gchar *ext)
635 {
636         gint p;
637         gint e;
638
639         if (!path) return FALSE;
640         if (!ext) return TRUE;
641
642         p = strlen(path);
643         e = strlen(ext);
644
645         /* FIXME: utf8 */
646         return (p > e && strncasecmp(path + p - e, ext, e) == 0);
647 }
648
649 gchar *remove_extension_from_path(const gchar *path)
650 {
651         gint p = 0, n = -1;
652
653         if (!path) return NULL;
654
655         while (path[p])
656                 {
657                 if (path[p] == '.') n = p;
658                 p++;
659                 }
660         if (n < 0) n = p;
661
662         return g_strndup(path, (gsize) n);
663 }
664
665 void parse_out_relatives(gchar *path)
666 {
667         gint s, t;
668
669         if (!path) return;
670
671         s = t = 0;
672
673         while (path[s] != '\0')
674                 {
675                 if (path[s] == G_DIR_SEPARATOR && path[s+1] == '.')
676                         {
677                         /* /. occurence, let's see more */
678                         gint p = s + 2;
679
680                         if (path[p] == G_DIR_SEPARATOR || path[p] == '\0')
681                                 {
682                                 /* /./ or /., just skip this part */
683                                 s = p;
684                                 continue;
685                                 }
686                         else if (path[p] == '.' && (path[p+1] == G_DIR_SEPARATOR || path[p+1] == '\0'))
687                                 {
688                                 /* /../ or /.., remove previous part, ie. /a/b/../ becomes /a/ */
689                                 s = p + 1;
690                                 if (t > 0) t--;
691                                 while (path[t] != G_DIR_SEPARATOR && t > 0) t--;
692                                 continue;
693                                 }
694                         }
695         
696                 if (s != t) path[t] = path[s];
697                 t++;
698                 s++;
699                 }
700
701         if (t == 0 && path[t] == G_DIR_SEPARATOR) t++;
702         if (t > 1 && path[t-1] == G_DIR_SEPARATOR) t--;
703         path[t] = '\0';
704 }
705
706 gint file_in_path(const gchar *name)
707 {
708         gchar *path;
709         gchar *namel;
710         gint p, l;
711         gint ret = FALSE;
712
713         if (!name) return FALSE;
714         path = g_strdup(getenv("PATH"));
715         if (!path) return FALSE;
716         namel = path_from_utf8(name);
717
718         p = 0;
719         l = strlen(path);
720         while (p < l && !ret)
721                 {
722                 gchar *f;
723                 gint e = p;
724                 while (path[e] != ':' && path[e] != '\0') e++;
725                 path[e] = '\0';
726                 e++;
727                 f = g_build_filename(path + p, namel, NULL);
728                 if (isfile(f)) ret = TRUE;
729                 g_free(f);
730                 p = e;
731                 }
732         g_free(namel);
733         g_free(path);
734
735         return ret;
736 }