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