use fputs instead of printf - patch by Uwe Ohse
[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\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                 log_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                 log_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         size_t 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 void string_list_free(GList *list)
508 {
509         g_list_foreach(list, (GFunc)g_free, NULL);
510         g_list_free(list);
511 }
512
513 GList *string_list_copy(GList *list)
514 {
515         GList *new_list = NULL;
516         GList *work;
517
518         work = list;
519         while (work)
520                 {
521                 gchar *path;
522
523                 path = work->data;
524                 work = work->next;
525
526                 new_list = g_list_prepend(new_list, g_strdup(path));
527                 }
528
529         return g_list_reverse(new_list);
530 }
531
532 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gint pad)
533 {
534         gchar *unique;
535         gint n = 1;
536
537         if (!ext) ext = "";
538         if (!divider) divider = "";
539
540         unique = g_strconcat(path, ext, NULL);
541         while (isname(unique))
542                 {
543                 g_free(unique);
544                 if (pad)
545                         {
546                         unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
547                         }
548                 else
549                         {
550                         unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
551                         }
552                 n++;
553                 if (n > 999)
554                         {
555                         /* well, we tried */
556                         g_free(unique);
557                         return NULL;
558                         }
559                 }
560
561         return unique;
562 }
563
564 gchar *unique_filename_simple(const gchar *path)
565 {
566         gchar *unique;
567         const gchar *name;
568         const gchar *ext;
569
570         if (!path) return NULL;
571
572         name = filename_from_path(path);
573         if (!name) return NULL;
574
575         ext = extension_from_path(name);
576
577         if (!ext)
578                 {
579                 unique = unique_filename(path, NULL, "_", TRUE);
580                 }
581         else
582                 {
583                 gchar *base;
584
585                 base = remove_extension_from_path(path);
586                 unique = unique_filename(base, ext, "_", TRUE);
587                 g_free(base);
588                 }
589
590         return unique;
591 }
592
593 const gchar *filename_from_path(const gchar *path)
594 {
595         const gchar *base;
596
597         if (!path) return NULL;
598
599         base = strrchr(path, G_DIR_SEPARATOR);
600         if (base) return base + 1;
601
602         return path;
603 }
604
605 gchar *remove_level_from_path(const gchar *path)
606 {
607         gint p = 0, n = -1;
608
609         if (!path) return NULL;
610
611         while (path[p])
612                 {
613                 if (path[p] == G_DIR_SEPARATOR) n = p;
614                 p++;
615                 }
616         if (n <= 0) n++;
617
618         return g_strndup(path, (gsize) n);
619 }
620
621 const gchar *extension_from_path(const gchar *path)
622 {
623         if (!path) return NULL;
624         return strrchr(path, '.');
625 }
626
627 gint file_extension_match(const gchar *path, const gchar *ext)
628 {
629         gint p;
630         gint e;
631
632         if (!path) return FALSE;
633         if (!ext) return TRUE;
634
635         p = strlen(path);
636         e = strlen(ext);
637
638         /* FIXME: utf8 */
639         return (p > e && strncasecmp(path + p - e, ext, e) == 0);
640 }
641
642 gchar *remove_extension_from_path(const gchar *path)
643 {
644         gint p = 0, n = -1;
645
646         if (!path) return NULL;
647
648         while (path[p])
649                 {
650                 if (path[p] == '.') n = p;
651                 p++;
652                 }
653         if (n < 0) n = p;
654
655         return g_strndup(path, (gsize) n);
656 }
657
658 void parse_out_relatives(gchar *path)
659 {
660         gint s, t;
661
662         if (!path) return;
663
664         s = t = 0;
665
666         while (path[s] != '\0')
667                 {
668                 if (path[s] == G_DIR_SEPARATOR && path[s+1] == '.' && (path[s+2] == G_DIR_SEPARATOR || path[s+2] == '\0') )
669                         {
670                         s += 2;
671                         }
672                 else if (path[s] == G_DIR_SEPARATOR && path[s+1] == '.' && path[s+2] == '.' && (path[s+3] == G_DIR_SEPARATOR || path[s+3] == '\0') )
673                         {
674                         s += 3;
675                         if (t > 0) t--;
676                         while (path[t] != G_DIR_SEPARATOR && t > 0) t--;
677                         }
678                 else
679                         {
680                         if (s != t) path[t] = path[s];
681                         t++;
682                         s++;
683                         }
684                 }
685         if (t == 0 && path[t] == G_DIR_SEPARATOR) t++;
686         if (t > 1 && path[t-1] == G_DIR_SEPARATOR) t--;
687         path[t] = '\0';
688 }
689
690 gint file_in_path(const gchar *name)
691 {
692         gchar *path;
693         gchar *namel;
694         gint p, l;
695         gint ret = FALSE;
696
697         if (!name) return FALSE;
698         path = g_strdup(getenv("PATH"));
699         if (!path) return FALSE;
700         namel = path_from_utf8(name);
701
702         p = 0;
703         l = strlen(path);
704         while (p < l && !ret)
705                 {
706                 gchar *f;
707                 gint e = p;
708                 while (path[e] != ':' && path[e] != '\0') e++;
709                 path[e] = '\0';
710                 e++;
711                 f = g_build_filename(path + p, namel, NULL);
712                 if (isfile(f)) ret = TRUE;
713                 g_free(f);
714                 p = e;
715                 }
716         g_free(namel);
717         g_free(path);
718
719         return ret;
720 }