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