1219b88be1e6499fafbe9b4c0cc86ee59727a8f7
[geeqie.git] / src / filelist.c
1 /*
2  * GQview
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
13 #include "gqview.h"
14 #include "filelist.h"
15
16 #include "cache.h"
17 #include "rcfile.h"
18 #include "ui_fileops.h"
19
20
21 /*
22  *-----------------------------------------------------------------------------
23  * file filtering
24  *-----------------------------------------------------------------------------
25  */
26
27 static GList *filter_list = NULL;
28 static GList *extension_list = NULL;
29
30 gint ishidden(const gchar *name)
31 {
32         if (name[0] != '.') return FALSE;
33         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
34         return TRUE;
35 }
36
37 static FilterEntry *filter_entry_new(const gchar *key, const gchar *description,
38                                      const gchar *extensions, gint enabled)
39 {
40         FilterEntry *fe;
41
42         fe = g_new0(FilterEntry, 1);
43         fe->key = g_strdup(key);
44         fe->description = g_strdup(description);
45         fe->extensions = g_strdup(extensions);
46         fe->enabled = enabled;
47         
48         return fe;
49 }
50
51 static void filter_entry_free(FilterEntry *fe)
52 {
53         if (!fe) return;
54
55         g_free(fe->key);
56         g_free(fe->description);
57         g_free(fe->extensions);
58         g_free(fe);
59 }
60
61 GList *filter_get_list(void)
62 {
63         return filter_list;
64 }
65
66 void filter_remove_entry(FilterEntry *fe)
67 {
68         if (!g_list_find(filter_list, fe)) return;
69
70         filter_list = g_list_remove(filter_list, fe);
71         filter_entry_free(fe);
72 }
73
74 static gint filter_key_exists(const gchar *key)
75 {
76         GList *work;
77
78         if (!key) return FALSE;
79
80         work = filter_list;
81         while (work)
82                 {
83                 FilterEntry *fe = work->data;
84                 work = work->next;
85
86                 if (strcmp(fe->key, key) == 0) return TRUE;
87                 }
88
89         return FALSE;
90 }
91
92 void filter_add(const gchar *key, const gchar *description, const gchar *extensions, gint enabled)
93 {
94         filter_list = g_list_append(filter_list, filter_entry_new(key, description, extensions, enabled));
95 }
96
97 void filter_add_unique(const gchar *description, const gchar *extensions, gint enabled)
98 {
99         gchar *key;
100         gint n;
101
102         key = g_strdup("user0");
103         n = 1;
104         while (filter_key_exists(key))
105                 {
106                 g_free(key);
107                 if (n > 999) return;
108                 key = g_strdup_printf("user%d", n);
109                 n++;
110                 }
111
112         filter_add(key, description, extensions, enabled);
113         g_free(key);
114 }
115
116 static void filter_add_if_missing(const gchar *key, const gchar *description, const gchar *extensions, gint enabled)
117 {
118         GList *work;
119
120         if (!key) return;
121
122         work = filter_list;
123         while (work)
124                 {
125                 FilterEntry *fe = work->data;
126                 work = work->next;
127                 if (fe->key && strcmp(fe->key, key) == 0) return;
128                 }
129
130         filter_add(key, description, extensions, enabled);
131 }
132
133 void filter_reset(void)
134 {
135         GList *work;
136
137         work = filter_list;
138         while (work)
139                 {
140                 FilterEntry *fe = work->data;
141                 work = work->next;
142                 filter_entry_free(fe);
143                 }
144
145         g_list_free(filter_list);
146         filter_list = NULL;
147 }
148
149 void filter_add_defaults(void)
150 {
151         GSList *list, *work;
152
153         list = gdk_pixbuf_get_formats();
154         work = list;
155         while (work)
156                 {
157                 GdkPixbufFormat *format;
158                 gchar *name;
159                 gchar *desc;
160                 gchar **extensions;
161                 GString *filter = NULL;
162                 gint i;
163                 
164                 format = work->data;
165                 work = work->next;
166
167                 name = gdk_pixbuf_format_get_name(format);
168                 desc = gdk_pixbuf_format_get_description(format);
169                 extensions = gdk_pixbuf_format_get_extensions(format);
170
171                 i = 0;
172                 while (extensions[i])
173                         {
174                         if (!filter)
175                                 {
176                                 filter = g_string_new(".");
177                                 filter = g_string_append(filter, extensions[i]);
178                                 }
179                         else
180                                 {
181                                 filter = g_string_append(filter, ";.");
182                                 filter = g_string_append(filter, extensions[i]);
183                                 }
184                         i++;
185                         }
186
187                 if (debug) printf("loader reported [%s] [%s] [%s]\n", name, desc, filter->str);
188
189                 filter_add_if_missing(name, desc, filter->str, TRUE);
190
191                 g_free(name);
192                 g_free(desc);
193                 g_strfreev(extensions);
194                 g_string_free(filter, TRUE);
195                 }
196         g_slist_free(list);
197
198         /* add defaults even if gdk-pixbuf does not have them, but disabled */
199         filter_add_if_missing("jpeg", "JPEG group", ".jpg;.jpeg;.jpe", FALSE);
200         filter_add_if_missing("png", "Portable Network Graphic", ".png", FALSE);
201         filter_add_if_missing("tiff", "Tiff", ".tif;.tiff", FALSE);
202         filter_add_if_missing("pnm", "Packed Pixel formats", ".pbm;.pgm;.pnm;.ppm", FALSE);
203         filter_add_if_missing("gif", "Graphics Interchange Format", ".gif", FALSE);
204         filter_add_if_missing("xbm", "X bitmap", ".xbm", FALSE);
205         filter_add_if_missing("xpm", "X pixmap", ".xpm", FALSE);
206         filter_add_if_missing("bmp", "Bitmap", ".bmp", FALSE);
207         filter_add_if_missing("ico", "Icon file", ".ico;.cur", FALSE);
208         filter_add_if_missing("ras", "Raster", ".ras", FALSE);
209         filter_add_if_missing("svg", "Scalable Vector Graphics", ".svg", FALSE);
210
211         /* These are the raw camera formats with embedded jpeg/exif.
212          * (see format_raw.c)
213          */
214         filter_add_if_missing("raf", "Fujifilm raw format", ".raf", TRUE);
215         filter_add_if_missing("crw", "Canon raw format", ".crw;.cr2", TRUE);
216 }
217
218 static GList *filter_to_list(const gchar *extensions)
219 {
220         GList *list = NULL;
221         const gchar *p;
222
223         if (!extensions) return NULL;
224
225         p = extensions;
226         while (*p != '\0')
227                 {
228                 const gchar *b;
229                 gint l = 0;
230
231                 b = p;
232                 while (*p != '\0' && *p != ';')
233                         {
234                         p++;
235                         l++;
236                         }
237                 list = g_list_append(list, g_strndup(b, l));
238                 if (*p == ';') p++;
239                 }
240
241         return list;
242 }
243
244 void filter_rebuild(void)
245 {
246         GList *work;
247
248         path_list_free(extension_list);
249         extension_list = NULL;
250
251         work = filter_list;
252         while (work)
253                 {
254                 FilterEntry *fe;
255
256                 fe = work->data;
257                 work = work->next;
258
259                 if (fe->enabled)
260                         {
261                         GList *ext;
262
263                         ext = filter_to_list(fe->extensions);
264                         if (ext) extension_list = g_list_concat(extension_list, ext);
265                         }
266                 }
267 }
268
269 gint filter_name_exists(const gchar *name)
270 {
271         GList *work;
272         if (!extension_list || file_filter_disable) return TRUE;
273
274         work = extension_list;
275         while (work)
276                 {
277                 gchar *filter = work->data;
278                 gint lf = strlen(filter);
279                 gint ln = strlen(name);
280                 if (ln >= lf)
281                         {
282                         if (strncasecmp(name + ln - lf, filter, lf) == 0) return TRUE;
283                         }
284                 work = work->next;
285                 }
286
287         return FALSE;
288 }
289
290 void filter_write_list(FILE *f)
291 {
292         GList *work;
293
294         work = filter_list;
295         while (work)
296                 {
297                 FilterEntry *fe = work->data;
298                 work = work->next;
299
300                 fprintf(f, "filter_ext: \"%s%s\" \"%s\" \"%s\"\n", (fe->enabled) ? "" : "#",
301                         fe->key, fe->extensions,
302                         (fe->description) ? fe->description : "");
303                 }
304 }
305
306 void filter_parse(const gchar *text)
307 {
308         const gchar *p;
309         gchar *key;
310         gchar *ext;
311         gchar *desc;
312         gint enabled = TRUE;
313
314         if (!text || text[0] != '"') return;
315
316         key = quoted_value(text);
317         if (!key) return;
318
319         p = text;
320         p++;
321         while (*p != '"' && *p != '\0') p++;
322         if (*p != '"')
323                 {
324                 g_free(key);
325                 return;
326                 }
327         p++;
328         while (*p != '"' && *p != '\0') p++;
329         if (*p != '"')
330                 {
331                 g_free(key);
332                 return;
333                 }
334
335         ext = quoted_value(p);
336
337         p++;
338         while (*p != '"' && *p != '\0') p++;
339         if (*p == '"') p++;
340         while (*p != '"' && *p != '\0') p++;
341
342         if (*p == '"')
343                 {
344                 desc = quoted_value(p);
345                 }
346         else
347                 {
348                 desc = NULL;
349                 }
350
351         if (key && key[0] == '#')
352                 {
353                 gchar *tmp;
354                 tmp = g_strdup(key + 1);
355                 g_free(key);
356                 key = tmp;
357
358                 enabled = FALSE;
359                 }
360
361         if (key && strlen(key) > 0 && ext) filter_add(key, desc, ext, enabled);
362
363         g_free(key);
364         g_free(ext);
365         g_free(desc);
366 }
367
368 GList *path_list_filter(GList *list, gint is_dir_list)
369 {
370         GList *work;
371
372         if (!is_dir_list && file_filter_disable && show_dot_files) return list;
373
374         work = list;
375         while (work)
376                 {
377                 gchar *name = work->data;
378                 const gchar *base;
379
380                 base = filename_from_path(name);
381
382                 if ((!show_dot_files && ishidden(base)) ||
383                     (!is_dir_list && !filter_name_exists(base)) ||
384                     (is_dir_list && base[0] == '.' && (strcmp(base, GQVIEW_CACHE_LOCAL_THUMB) == 0 ||
385                                                        strcmp(base, GQVIEW_CACHE_LOCAL_METADATA) == 0)) )
386                         {
387                         GList *link = work;
388                         work = work->next;
389                         list = g_list_remove_link(list, link);
390                         g_free(name);
391                         g_list_free(link);
392                         }
393                 else
394                         {
395                         work = work->next;
396                         }
397                 }
398
399         return list;
400 }
401
402 /*
403  *-----------------------------------------------------------------------------
404  * path list recursive
405  *-----------------------------------------------------------------------------
406  */
407
408 static gint path_list_sort_cb(gconstpointer a, gconstpointer b)
409 {
410         return CASE_SORT((gchar *)a, (gchar *)b);
411 }
412
413 GList *path_list_sort(GList *list)
414 {
415         return g_list_sort(list, path_list_sort_cb);
416 }
417
418 static void path_list_recursive_append(GList **list, GList *dirs)
419 {
420         GList *work;
421
422         work = dirs;
423         while (work)
424                 {
425                 const gchar *path = work->data;
426                 GList *f = NULL;
427                 GList *d = NULL;
428
429                 if (path_list(path, &f, &d))
430                         {
431                         f = path_list_filter(f, FALSE);
432                         f = path_list_sort(f);
433                         *list = g_list_concat(*list, f);
434
435                         d = path_list_filter(d, TRUE);
436                         d = path_list_sort(d);
437                         path_list_recursive_append(list, d);
438                         path_list_free(d);
439                         }
440
441                 work = work->next;
442                 }
443 }
444
445 GList *path_list_recursive(const gchar *path)
446 {
447         GList *list = NULL;
448         GList *d = NULL;
449
450         if (!path_list(path, &list, &d)) return NULL;
451         list = path_list_filter(list, FALSE);
452         list = path_list_sort(list);
453
454         d = path_list_filter(d, TRUE);
455         d = path_list_sort(d);
456         path_list_recursive_append(&list, d);
457         path_list_free(d);
458
459         return list;
460 }
461
462 /*
463  *-----------------------------------------------------------------------------
464  * text conversion utils
465  *-----------------------------------------------------------------------------
466  */
467
468 gchar *text_from_size(gint64 size)
469 {
470         gchar *a, *b;
471         gchar *s, *d;
472         gint l, n, i;
473
474         /* what I would like to use is printf("%'d", size)
475          * BUT: not supported on every libc :(
476          */
477         if (size > G_MAXUINT)
478                 {
479                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
480                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
481                 }
482         else
483                 {
484                 a = g_strdup_printf("%d", (guint)size);
485                 }
486         l = strlen(a);
487         n = (l - 1)/ 3;
488         if (n < 1) return a;
489
490         b = g_new(gchar, l + n + 1);
491
492         s = a;
493         d = b;
494         i = l - n * 3;
495         while (*s != '\0')
496                 {
497                 if (i < 1)
498                         {
499                         i = 3;
500                         *d = ',';
501                         d++;
502                         }
503
504                 *d = *s;
505                 s++;
506                 d++;
507                 i--;
508                 }
509         *d = '\0';
510
511         g_free(a);
512         return b;
513 }
514
515 gchar *text_from_size_abrev(gint64 size)
516 {
517         if (size < (gint64)1024)
518                 {
519                 return g_strdup_printf(_("%d bytes"), (gint)size);
520                 }
521         if (size < (gint64)1048576)
522                 {
523                 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
524                 }
525         if (size < (gint64)1073741824)
526                 {
527                 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
528                 }
529
530         /* to avoid overflowing the double, do division in two steps */
531         size /= 1048576;
532         return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
533 }
534
535 /* note: returned string is valid until next call to text_from_time() */
536 const gchar *text_from_time(time_t t)
537 {
538         static gchar *ret = NULL;
539         gchar buf[128];
540         gint buflen;
541         struct tm *btime;
542         GError *error = NULL;
543
544         btime = localtime(&t);
545
546         /* the %x warning about 2 digit years is not an error */
547         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
548         if (buflen < 1) return "";
549
550         g_free(ret);
551         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
552         if (error)
553                 {
554                 printf("Error converting locale strftime to UTF-8: %s\n", error->message);
555                 g_error_free(error);
556                 return "";
557                 }
558
559         return ret;
560 }
561
562 /*
563  *-----------------------------------------------------------------------------
564  * file info struct
565  *-----------------------------------------------------------------------------
566  */
567
568 FileData *file_data_new(const gchar *path, struct stat *st)
569 {
570         FileData *fd;
571
572         fd = g_new0(FileData, 1);
573         fd->path = path_to_utf8(path);
574         fd->name = filename_from_path(fd->path);
575         fd->size = st->st_size;
576         fd->date = st->st_mtime;
577         fd->pixbuf = NULL;
578
579         return fd;
580 }
581
582 FileData *file_data_new_simple(const gchar *path)
583 {
584         FileData *fd;
585         struct stat st;
586
587         fd = g_new0(FileData, 1);
588         fd->path = g_strdup(path);
589         fd->name = filename_from_path(fd->path);
590
591         if (stat_utf8(fd->path, &st))
592                 {
593                 fd->size = st.st_size;
594                 fd->date = st.st_mtime;
595                 }
596
597         fd->pixbuf = NULL;
598
599         return fd;
600 }
601
602 void file_data_free(FileData *fd)
603 {
604         g_free(fd->path);
605         if (fd->pixbuf) g_object_unref(fd->pixbuf);
606         g_free(fd);
607 }
608
609 /*
610  *-----------------------------------------------------------------------------
611  * load file list
612  *-----------------------------------------------------------------------------
613  */
614
615 static SortType filelist_sort_method = SORT_NONE;
616 static gint filelist_sort_ascend = TRUE;
617
618 static gint sort_file_cb(void *a, void *b)
619 {
620         FileData *fa = a;
621         FileData *fb = b;
622
623         if (!filelist_sort_ascend)
624                 {
625                 fa = b;
626                 fb = a;
627                 }
628
629         switch (filelist_sort_method)
630                 {
631                 case SORT_SIZE:
632                         if (fa->size < fb->size) return -1;
633                         if (fa->size > fb->size) return 1;
634                         return 0;
635                         break;
636                 case SORT_TIME:
637                         if (fa->date < fb->date) return -1;
638                         if (fa->date > fb->date) return 1;
639                         return 0;
640                         break;
641 #ifdef HAVE_STRVERSCMP
642                 case SORT_NUMBER:
643                         return strverscmp(fa->name, fb->name);
644                         break;
645 #endif
646                 case SORT_NAME:
647                 default:
648                         return CASE_SORT(fa->name, fb->name);
649                         break;
650                 }
651 }
652
653 GList *filelist_sort(GList *list, SortType method, gint ascend)
654 {
655         filelist_sort_method = method;
656         filelist_sort_ascend = ascend;
657         return g_list_sort(list, (GCompareFunc) sort_file_cb);
658 }
659
660 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
661 {
662         filelist_sort_method = method;
663         filelist_sort_ascend = ascend;
664         return g_list_insert_sorted(list, fd, (GCompareFunc) sort_file_cb);
665 }
666
667 gint filelist_read(const gchar *path, GList **files, GList **dirs)
668 {
669         DIR *dp;
670         struct dirent *dir;
671         struct stat ent_sbuf;
672         gchar *pathl;
673         GList *dlist;
674         GList *flist;
675
676         dlist = NULL;
677         flist = NULL;
678
679         pathl = path_from_utf8(path);
680         if (!pathl || (dp = opendir(pathl)) == NULL)
681                 {
682                 g_free(pathl);
683                 if (files) *files = NULL;
684                 if (dirs) *dirs = NULL;
685                 return FALSE;
686                 }
687
688         /* root dir fix */
689         if (pathl[0] == '/' && pathl[1] == '\0')
690                 {
691                 g_free(pathl);
692                 pathl = g_strdup("");
693                 }
694
695         while ((dir = readdir(dp)) != NULL)
696                 {
697                 gchar *name = dir->d_name;
698                 if (show_dot_files || !ishidden(name))
699                         {
700                         gchar *filepath = g_strconcat(pathl, "/", name, NULL);
701                         if (stat(filepath, &ent_sbuf) >= 0)
702                                 {
703                                 if (S_ISDIR(ent_sbuf.st_mode))
704                                         {
705                                         /* we ignore the .thumbnails dir for cleanliness */
706                                         if ((dirs) &&
707                                             !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
708                                             strcmp(name, GQVIEW_CACHE_LOCAL_THUMB) != 0 &&
709                                             strcmp(name, GQVIEW_CACHE_LOCAL_METADATA) != 0)
710                                                 {
711                                                 dlist = g_list_prepend(dlist, file_data_new(filepath, &ent_sbuf));
712                                                 }
713                                         }
714                                 else
715                                         {
716                                         if ((files) && filter_name_exists(name))
717                                                 {
718                                                 flist = g_list_prepend(flist, file_data_new(filepath, &ent_sbuf));
719                                                 }
720                                         }
721                                 }
722                         g_free(filepath);
723                         }
724                 }
725
726         closedir(dp);
727
728         g_free(pathl);
729
730         if (dirs) *dirs = dlist;
731         if (files) *files = flist;
732
733         return TRUE;
734 }
735
736 void filelist_free(GList *list)
737 {
738         GList *work;
739
740         work = list;
741         while (work)
742                 {
743                 file_data_free((FileData *)work->data);
744                 work = work->next;
745                 }
746
747         g_list_free(list);
748 }
749
750