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