Tue Jun 7 03:47:03 2005 John Ellis <johne@verizon.net>
[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("crw", "Canon raw format", ".crw;.cr2", TRUE);
215         filter_add_if_missing("raf", "Fujifilm raw format", ".raf", TRUE);
216         filter_add_if_missing("nef", "Nikon raw format", ".nef", TRUE);
217 }
218
219 static GList *filter_to_list(const gchar *extensions)
220 {
221         GList *list = NULL;
222         const gchar *p;
223
224         if (!extensions) return NULL;
225
226         p = extensions;
227         while (*p != '\0')
228                 {
229                 const gchar *b;
230                 gint l = 0;
231
232                 b = p;
233                 while (*p != '\0' && *p != ';')
234                         {
235                         p++;
236                         l++;
237                         }
238                 list = g_list_append(list, g_strndup(b, l));
239                 if (*p == ';') p++;
240                 }
241
242         return list;
243 }
244
245 void filter_rebuild(void)
246 {
247         GList *work;
248
249         path_list_free(extension_list);
250         extension_list = NULL;
251
252         work = filter_list;
253         while (work)
254                 {
255                 FilterEntry *fe;
256
257                 fe = work->data;
258                 work = work->next;
259
260                 if (fe->enabled)
261                         {
262                         GList *ext;
263
264                         ext = filter_to_list(fe->extensions);
265                         if (ext) extension_list = g_list_concat(extension_list, ext);
266                         }
267                 }
268 }
269
270 gint filter_name_exists(const gchar *name)
271 {
272         GList *work;
273         if (!extension_list || file_filter_disable) return TRUE;
274
275         work = extension_list;
276         while (work)
277                 {
278                 gchar *filter = work->data;
279                 gint lf = strlen(filter);
280                 gint ln = strlen(name);
281                 if (ln >= lf)
282                         {
283                         if (strncasecmp(name + ln - lf, filter, lf) == 0) return TRUE;
284                         }
285                 work = work->next;
286                 }
287
288         return FALSE;
289 }
290
291 void filter_write_list(FILE *f)
292 {
293         GList *work;
294
295         work = filter_list;
296         while (work)
297                 {
298                 FilterEntry *fe = work->data;
299                 work = work->next;
300
301                 fprintf(f, "filter_ext: \"%s%s\" \"%s\" \"%s\"\n", (fe->enabled) ? "" : "#",
302                         fe->key, fe->extensions,
303                         (fe->description) ? fe->description : "");
304                 }
305 }
306
307 void filter_parse(const gchar *text)
308 {
309         const gchar *p;
310         gchar *key;
311         gchar *ext;
312         gchar *desc;
313         gint enabled = TRUE;
314
315         if (!text || text[0] != '"') return;
316
317         key = quoted_value(text);
318         if (!key) return;
319
320         p = text;
321         p++;
322         while (*p != '"' && *p != '\0') p++;
323         if (*p != '"')
324                 {
325                 g_free(key);
326                 return;
327                 }
328         p++;
329         while (*p != '"' && *p != '\0') p++;
330         if (*p != '"')
331                 {
332                 g_free(key);
333                 return;
334                 }
335
336         ext = quoted_value(p);
337
338         p++;
339         while (*p != '"' && *p != '\0') p++;
340         if (*p == '"') p++;
341         while (*p != '"' && *p != '\0') p++;
342
343         if (*p == '"')
344                 {
345                 desc = quoted_value(p);
346                 }
347         else
348                 {
349                 desc = NULL;
350                 }
351
352         if (key && key[0] == '#')
353                 {
354                 gchar *tmp;
355                 tmp = g_strdup(key + 1);
356                 g_free(key);
357                 key = tmp;
358
359                 enabled = FALSE;
360                 }
361
362         if (key && strlen(key) > 0 && ext) filter_add(key, desc, ext, enabled);
363
364         g_free(key);
365         g_free(ext);
366         g_free(desc);
367 }
368
369 GList *path_list_filter(GList *list, gint is_dir_list)
370 {
371         GList *work;
372
373         if (!is_dir_list && file_filter_disable && show_dot_files) return list;
374
375         work = list;
376         while (work)
377                 {
378                 gchar *name = work->data;
379                 const gchar *base;
380
381                 base = filename_from_path(name);
382
383                 if ((!show_dot_files && ishidden(base)) ||
384                     (!is_dir_list && !filter_name_exists(base)) ||
385                     (is_dir_list && base[0] == '.' && (strcmp(base, GQVIEW_CACHE_LOCAL_THUMB) == 0 ||
386                                                        strcmp(base, GQVIEW_CACHE_LOCAL_METADATA) == 0)) )
387                         {
388                         GList *link = work;
389                         work = work->next;
390                         list = g_list_remove_link(list, link);
391                         g_free(name);
392                         g_list_free(link);
393                         }
394                 else
395                         {
396                         work = work->next;
397                         }
398                 }
399
400         return list;
401 }
402
403 /*
404  *-----------------------------------------------------------------------------
405  * path list recursive
406  *-----------------------------------------------------------------------------
407  */
408
409 static gint path_list_sort_cb(gconstpointer a, gconstpointer b)
410 {
411         return CASE_SORT((gchar *)a, (gchar *)b);
412 }
413
414 GList *path_list_sort(GList *list)
415 {
416         return g_list_sort(list, path_list_sort_cb);
417 }
418
419 static void path_list_recursive_append(GList **list, GList *dirs)
420 {
421         GList *work;
422
423         work = dirs;
424         while (work)
425                 {
426                 const gchar *path = work->data;
427                 GList *f = NULL;
428                 GList *d = NULL;
429
430                 if (path_list(path, &f, &d))
431                         {
432                         f = path_list_filter(f, FALSE);
433                         f = path_list_sort(f);
434                         *list = g_list_concat(*list, f);
435
436                         d = path_list_filter(d, TRUE);
437                         d = path_list_sort(d);
438                         path_list_recursive_append(list, d);
439                         path_list_free(d);
440                         }
441
442                 work = work->next;
443                 }
444 }
445
446 GList *path_list_recursive(const gchar *path)
447 {
448         GList *list = NULL;
449         GList *d = NULL;
450
451         if (!path_list(path, &list, &d)) return NULL;
452         list = path_list_filter(list, FALSE);
453         list = path_list_sort(list);
454
455         d = path_list_filter(d, TRUE);
456         d = path_list_sort(d);
457         path_list_recursive_append(&list, d);
458         path_list_free(d);
459
460         return list;
461 }
462
463 /*
464  *-----------------------------------------------------------------------------
465  * text conversion utils
466  *-----------------------------------------------------------------------------
467  */
468
469 gchar *text_from_size(gint64 size)
470 {
471         gchar *a, *b;
472         gchar *s, *d;
473         gint l, n, i;
474
475         /* what I would like to use is printf("%'d", size)
476          * BUT: not supported on every libc :(
477          */
478         if (size > G_MAXUINT)
479                 {
480                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
481                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
482                 }
483         else
484                 {
485                 a = g_strdup_printf("%d", (guint)size);
486                 }
487         l = strlen(a);
488         n = (l - 1)/ 3;
489         if (n < 1) return a;
490
491         b = g_new(gchar, l + n + 1);
492
493         s = a;
494         d = b;
495         i = l - n * 3;
496         while (*s != '\0')
497                 {
498                 if (i < 1)
499                         {
500                         i = 3;
501                         *d = ',';
502                         d++;
503                         }
504
505                 *d = *s;
506                 s++;
507                 d++;
508                 i--;
509                 }
510         *d = '\0';
511
512         g_free(a);
513         return b;
514 }
515
516 gchar *text_from_size_abrev(gint64 size)
517 {
518         if (size < (gint64)1024)
519                 {
520                 return g_strdup_printf(_("%d bytes"), (gint)size);
521                 }
522         if (size < (gint64)1048576)
523                 {
524                 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
525                 }
526         if (size < (gint64)1073741824)
527                 {
528                 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
529                 }
530
531         /* to avoid overflowing the double, do division in two steps */
532         size /= 1048576;
533         return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
534 }
535
536 /* note: returned string is valid until next call to text_from_time() */
537 const gchar *text_from_time(time_t t)
538 {
539         static gchar *ret = NULL;
540         gchar buf[128];
541         gint buflen;
542         struct tm *btime;
543         GError *error = NULL;
544
545         btime = localtime(&t);
546
547         /* the %x warning about 2 digit years is not an error */
548         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
549         if (buflen < 1) return "";
550
551         g_free(ret);
552         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
553         if (error)
554                 {
555                 printf("Error converting locale strftime to UTF-8: %s\n", error->message);
556                 g_error_free(error);
557                 return "";
558                 }
559
560         return ret;
561 }
562
563 /*
564  *-----------------------------------------------------------------------------
565  * file info struct
566  *-----------------------------------------------------------------------------
567  */
568
569 FileData *file_data_new(const gchar *path, struct stat *st)
570 {
571         FileData *fd;
572
573         fd = g_new0(FileData, 1);
574         fd->path = path_to_utf8(path);
575         fd->name = filename_from_path(fd->path);
576         fd->size = st->st_size;
577         fd->date = st->st_mtime;
578         fd->pixbuf = NULL;
579
580         return fd;
581 }
582
583 FileData *file_data_new_simple(const gchar *path)
584 {
585         FileData *fd;
586         struct stat st;
587
588         fd = g_new0(FileData, 1);
589         fd->path = g_strdup(path);
590         fd->name = filename_from_path(fd->path);
591
592         if (stat_utf8(fd->path, &st))
593                 {
594                 fd->size = st.st_size;
595                 fd->date = st.st_mtime;
596                 }
597
598         fd->pixbuf = NULL;
599
600         return fd;
601 }
602
603 void file_data_free(FileData *fd)
604 {
605         g_free(fd->path);
606         if (fd->pixbuf) g_object_unref(fd->pixbuf);
607         g_free(fd);
608 }
609
610 /*
611  *-----------------------------------------------------------------------------
612  * load file list
613  *-----------------------------------------------------------------------------
614  */
615
616 static SortType filelist_sort_method = SORT_NONE;
617 static gint filelist_sort_ascend = TRUE;
618
619 static gint sort_file_cb(void *a, void *b)
620 {
621         FileData *fa = a;
622         FileData *fb = b;
623
624         if (!filelist_sort_ascend)
625                 {
626                 fa = b;
627                 fb = a;
628                 }
629
630         switch (filelist_sort_method)
631                 {
632                 case SORT_SIZE:
633                         if (fa->size < fb->size) return -1;
634                         if (fa->size > fb->size) return 1;
635                         return 0;
636                         break;
637                 case SORT_TIME:
638                         if (fa->date < fb->date) return -1;
639                         if (fa->date > fb->date) return 1;
640                         return 0;
641                         break;
642 #ifdef HAVE_STRVERSCMP
643                 case SORT_NUMBER:
644                         return strverscmp(fa->name, fb->name);
645                         break;
646 #endif
647                 case SORT_NAME:
648                 default:
649                         return CASE_SORT(fa->name, fb->name);
650                         break;
651                 }
652 }
653
654 GList *filelist_sort(GList *list, SortType method, gint ascend)
655 {
656         filelist_sort_method = method;
657         filelist_sort_ascend = ascend;
658         return g_list_sort(list, (GCompareFunc) sort_file_cb);
659 }
660
661 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
662 {
663         filelist_sort_method = method;
664         filelist_sort_ascend = ascend;
665         return g_list_insert_sorted(list, fd, (GCompareFunc) sort_file_cb);
666 }
667
668 gint filelist_read(const gchar *path, GList **files, GList **dirs)
669 {
670         DIR *dp;
671         struct dirent *dir;
672         struct stat ent_sbuf;
673         gchar *pathl;
674         GList *dlist;
675         GList *flist;
676
677         dlist = NULL;
678         flist = NULL;
679
680         pathl = path_from_utf8(path);
681         if (!pathl || (dp = opendir(pathl)) == NULL)
682                 {
683                 g_free(pathl);
684                 if (files) *files = NULL;
685                 if (dirs) *dirs = NULL;
686                 return FALSE;
687                 }
688
689         /* root dir fix */
690         if (pathl[0] == '/' && pathl[1] == '\0')
691                 {
692                 g_free(pathl);
693                 pathl = g_strdup("");
694                 }
695
696         while ((dir = readdir(dp)) != NULL)
697                 {
698                 gchar *name = dir->d_name;
699                 if (show_dot_files || !ishidden(name))
700                         {
701                         gchar *filepath = g_strconcat(pathl, "/", name, NULL);
702                         if (stat(filepath, &ent_sbuf) >= 0)
703                                 {
704                                 if (S_ISDIR(ent_sbuf.st_mode))
705                                         {
706                                         /* we ignore the .thumbnails dir for cleanliness */
707                                         if ((dirs) &&
708                                             !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
709                                             strcmp(name, GQVIEW_CACHE_LOCAL_THUMB) != 0 &&
710                                             strcmp(name, GQVIEW_CACHE_LOCAL_METADATA) != 0)
711                                                 {
712                                                 dlist = g_list_prepend(dlist, file_data_new(filepath, &ent_sbuf));
713                                                 }
714                                         }
715                                 else
716                                         {
717                                         if ((files) && filter_name_exists(name))
718                                                 {
719                                                 flist = g_list_prepend(flist, file_data_new(filepath, &ent_sbuf));
720                                                 }
721                                         }
722                                 }
723                         g_free(filepath);
724                         }
725                 }
726
727         closedir(dp);
728
729         g_free(pathl);
730
731         if (dirs) *dirs = dlist;
732         if (files) *files = flist;
733
734         return TRUE;
735 }
736
737 void filelist_free(GList *list)
738 {
739         GList *work;
740
741         work = list;
742         while (work)
743                 {
744                 file_data_free((FileData *)work->data);
745                 work = work->next;
746                 }
747
748         g_list_free(list);
749 }
750
751