reload changed images
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "filedata.h"
16
17 #include "filefilter.h"
18 #include "cache.h"
19 #include "rcfile.h"
20 #include "secure_save.h"
21 #include "thumb_standard.h"
22 #include "ui_fileops.h"
23
24
25 static GHashTable *file_data_pool = NULL;
26
27 static gint sidecar_file_priority(const gchar *path);
28
29
30 /*
31  *-----------------------------------------------------------------------------
32  * text conversion utils
33  *-----------------------------------------------------------------------------
34  */
35
36 gchar *text_from_size(gint64 size)
37 {
38         gchar *a, *b;
39         gchar *s, *d;
40         gint l, n, i;
41
42         /* what I would like to use is printf("%'d", size)
43          * BUT: not supported on every libc :(
44          */
45         if (size > G_MAXUINT)
46                 {
47                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
48                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
49                 }
50         else
51                 {
52                 a = g_strdup_printf("%d", (guint)size);
53                 }
54         l = strlen(a);
55         n = (l - 1)/ 3;
56         if (n < 1) return a;
57
58         b = g_new(gchar, l + n + 1);
59
60         s = a;
61         d = b;
62         i = l - n * 3;
63         while (*s != '\0')
64                 {
65                 if (i < 1)
66                         {
67                         i = 3;
68                         *d = ',';
69                         d++;
70                         }
71
72                 *d = *s;
73                 s++;
74                 d++;
75                 i--;
76                 }
77         *d = '\0';
78
79         g_free(a);
80         return b;
81 }
82
83 gchar *text_from_size_abrev(gint64 size)
84 {
85         if (size < (gint64)1024)
86                 {
87                 return g_strdup_printf(_("%d bytes"), (gint)size);
88                 }
89         if (size < (gint64)1048576)
90                 {
91                 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
92                 }
93         if (size < (gint64)1073741824)
94                 {
95                 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
96                 }
97
98         /* to avoid overflowing the double, do division in two steps */
99         size /= 1048576;
100         return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
101 }
102
103 /* note: returned string is valid until next call to text_from_time() */
104 const gchar *text_from_time(time_t t)
105 {
106         static gchar *ret = NULL;
107         gchar buf[128];
108         gint buflen;
109         struct tm *btime;
110         GError *error = NULL;
111
112         btime = localtime(&t);
113
114         /* the %x warning about 2 digit years is not an error */
115         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
116         if (buflen < 1) return "";
117
118         g_free(ret);
119         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
120         if (error)
121                 {
122                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
123                 g_error_free(error);
124                 return "";
125                 }
126
127         return ret;
128 }
129
130 /*
131  *-----------------------------------------------------------------------------
132  * file info struct
133  *-----------------------------------------------------------------------------
134  */
135
136 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
137 static void file_data_check_sidecars(FileData *fd);
138 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
139
140
141 void file_data_increment_version(FileData *fd)
142 {
143         fd->version++;
144         if (fd->parent) fd->parent->version++;
145 }
146
147 static void file_data_set_collate_keys(FileData *fd)
148 {
149         gchar *caseless_name;
150
151         g_assert(g_utf8_validate(fd->name, -1, NULL));
152
153         caseless_name = g_utf8_casefold(fd->name, -1);
154
155         g_free(fd->collate_key_name);
156         g_free(fd->collate_key_name_nocase);
157
158 #if GLIB_CHECK_VERSION(2, 8, 0)
159         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
160         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
161 #else
162         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
163         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
164 #endif
165         g_free(caseless_name);
166 }
167
168 static void file_data_set_path(FileData *fd, const gchar *path)
169 {
170         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
171         g_assert(file_data_pool);
172
173         g_free(fd->path);
174
175         if (fd->original_path)
176                 {
177                 g_hash_table_remove(file_data_pool, fd->original_path);
178                 g_free(fd->original_path);
179                 }
180         fd->original_path = g_strdup(path);
181         g_hash_table_insert(file_data_pool, fd->original_path, fd);
182
183         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
184                 {
185                 fd->path = g_strdup(path);
186                 fd->name = fd->path;
187                 fd->extension = fd->name + 1;
188                 file_data_set_collate_keys(fd);
189                 return;
190                 }
191
192         fd->path = g_strdup(path);
193         fd->name = filename_from_path(fd->path);
194
195         if (strcmp(fd->name, "..") == 0)
196                 {
197                 gchar *dir = remove_level_from_path(path);
198                 g_free(fd->path);
199                 fd->path = remove_level_from_path(dir);
200                 g_free(dir);
201                 fd->name = "..";
202                 fd->extension = fd->name + 2;
203                 file_data_set_collate_keys(fd);
204                 return;
205                 }
206         else if (strcmp(fd->name, ".") == 0)
207                 {
208                 g_free(fd->path);
209                 fd->path = remove_level_from_path(path);
210                 fd->name = ".";
211                 fd->extension = fd->name + 1;
212                 file_data_set_collate_keys(fd);
213                 return;
214                 }
215
216         fd->extension = extension_from_path(fd->path);
217         if (fd->extension == NULL)
218                 fd->extension = fd->name + strlen(fd->name);
219
220         file_data_set_collate_keys(fd);
221 }
222
223 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
224 {
225         gboolean ret = FALSE;
226         GList *work;
227         
228         if (fd->size != st->st_size ||
229             fd->date != st->st_mtime)
230                 {
231                 fd->size = st->st_size;
232                 fd->date = st->st_mtime;
233                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
234                 fd->thumb_pixbuf = NULL;
235                 file_data_increment_version(fd);
236                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
237                 ret = TRUE;
238                 }
239
240         work = fd->sidecar_files;
241         while (work)
242                 {
243                 FileData *sfd = work->data;
244                 struct stat st;
245                 work = work->next;
246
247                 if (!stat_utf8(sfd->path, &st))
248                         {
249                         fd->size = 0;
250                         fd->date = 0;
251                         file_data_disconnect_sidecar_file(fd, sfd);
252                         ret = TRUE;
253                         continue;
254                         }
255
256                 ret |= file_data_check_changed_files_recursive(sfd, &st);
257                 }
258         return ret;
259 }
260
261
262 gboolean file_data_check_changed_files(FileData *fd)
263 {
264         gboolean ret = FALSE;
265         struct stat st;
266         
267         if (fd->parent) fd = fd->parent;
268
269         if (!stat_utf8(fd->path, &st))
270                 {
271                 GList *work;
272                 FileData *sfd = NULL;
273
274                 /* parent is missing, we have to rebuild whole group */
275                 ret = TRUE;
276                 fd->size = 0;
277                 fd->date = 0;
278                 
279                 work = fd->sidecar_files;
280                 while (work)
281                         {
282                         sfd = work->data;
283                         work = work->next;
284                 
285                         file_data_disconnect_sidecar_file(fd, sfd);
286                         }
287                 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
288                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
289                 }
290         else
291                 {
292                 ret |= file_data_check_changed_files_recursive(fd, &st);
293                 }
294
295         return ret;
296 }
297
298 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
299 {
300         FileData *fd;
301
302         DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
303
304         if (!file_data_pool)
305                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
306
307         fd = g_hash_table_lookup(file_data_pool, path_utf8);
308         if (fd)
309                 {
310                 gboolean changed;
311
312                 if (fd->parent)
313                         changed = file_data_check_changed_files(fd);
314                 else
315                         changed = file_data_check_changed_files_recursive(fd, st);
316                 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
317                         file_data_check_sidecars(fd);
318                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
319                 
320                 return file_data_ref(fd);
321                 }
322
323         fd = g_new0(FileData, 1);
324         
325         fd->path = NULL;
326         fd->name = NULL;
327         fd->collate_key_name = NULL;
328         fd->collate_key_name_nocase = NULL;
329         fd->original_path = NULL;
330
331         fd->size = st->st_size;
332         fd->date = st->st_mtime;
333         fd->thumb_pixbuf = NULL;
334         fd->sidecar_files = NULL;
335         fd->ref = 1;
336         fd->magick = 0x12345678;
337
338         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
339
340         if (check_sidecars)
341                 file_data_check_sidecars(fd);
342
343         return fd;
344 }
345
346 static void file_data_check_sidecars(FileData *fd)
347 {
348         int base_len;
349         GString *fname;
350         FileData *parent_fd = NULL;
351         GList *work;
352
353         if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
354                 return;
355
356         base_len = fd->extension - fd->path;
357         fname = g_string_new_len(fd->path, base_len);
358         work = sidecar_ext_get_list();
359
360         while (work)
361                 {
362                 /* check for possible sidecar files;
363                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
364                    they have fd->ref set to 0 and file_data unref must chack and free them all together
365                    (using fd->ref would cause loops and leaks)
366                 */
367
368                 FileData *new_fd;
369                 gchar *ext = work->data;
370
371                 work = work->next;
372
373                 if (strcmp(ext, fd->extension) == 0)
374                         {
375                         new_fd = fd; /* processing the original file */
376                         }
377                 else
378                         {
379                         struct stat nst;
380                         g_string_truncate(fname, base_len);
381                         g_string_append(fname, ext);
382
383                         if (!stat_utf8(fname->str, &nst))
384                                 continue;
385
386                         new_fd = file_data_new(fname->str, &nst, FALSE);
387                         
388                         if (new_fd->disable_grouping)
389                                 {
390                                 file_data_unref(new_fd);
391                                 continue;
392                                 }
393                         
394                         new_fd->ref--; /* do not use ref here */
395                         }
396
397                 if (!parent_fd)
398                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
399                 else
400                         file_data_merge_sidecar_files(parent_fd, new_fd);
401                 }
402         g_string_free(fname, TRUE);
403 }
404
405
406 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
407 {
408         gchar *path_utf8 = path_to_utf8(path);
409         FileData *ret = file_data_new(path_utf8, st, check_sidecars);
410
411         g_free(path_utf8);
412         return ret;
413 }
414
415 FileData *file_data_new_simple(const gchar *path_utf8)
416 {
417         struct stat st;
418
419         if (!stat_utf8(path_utf8, &st))
420                 {
421                 st.st_size = 0;
422                 st.st_mtime = 0;
423                 }
424
425         return file_data_new(path_utf8, &st, TRUE);
426 }
427
428 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
429 {
430         sfd->parent = target;
431         if (!g_list_find(target->sidecar_files, sfd))
432                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
433         file_data_increment_version(sfd); /* increments both sfd and target */
434         return target;
435 }
436
437
438 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
439 {
440         GList *work;
441         
442         file_data_add_sidecar_file(target, source);
443
444         work = source->sidecar_files;
445         while (work)
446                 {
447                 FileData *sfd = work->data;
448                 file_data_add_sidecar_file(target, sfd);
449                 work = work->next;
450                 }
451
452         g_list_free(source->sidecar_files);
453         source->sidecar_files = NULL;
454
455         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
456         
457         return target;
458 }
459
460 #ifdef DEBUG_FILEDATA
461 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
462 #else
463 FileData *file_data_ref(FileData *fd)
464 #endif
465 {
466         if (fd == NULL) return NULL;
467 #ifdef DEBUG_FILEDATA
468         if (fd->magick != 0x12345678)
469                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
470 #endif
471         g_assert(fd->magick == 0x12345678);
472         fd->ref++;
473
474 #ifdef DEBUG_FILEDATA
475         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
476 #else
477         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
478 #endif
479         return fd;
480 }
481
482 static void file_data_free(FileData *fd)
483 {
484         g_assert(fd->magick == 0x12345678);
485         g_assert(fd->ref == 0);
486
487         g_hash_table_remove(file_data_pool, fd->original_path);
488
489         g_free(fd->path);
490         g_free(fd->original_path);
491         g_free(fd->collate_key_name);
492         g_free(fd->collate_key_name_nocase);
493         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
494
495         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
496
497         file_data_change_info_free(NULL, fd);
498         g_free(fd);
499 }
500
501 #ifdef DEBUG_FILEDATA
502 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
503 #else
504 void file_data_unref(FileData *fd)
505 #endif
506 {
507         if (fd == NULL) return;
508 #ifdef DEBUG_FILEDATA
509         if (fd->magick != 0x12345678)
510                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
511 #endif
512         g_assert(fd->magick == 0x12345678);
513         
514         fd->ref--;
515 #ifdef DEBUG_FILEDATA
516         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
517
518 #else
519         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
520 #endif
521         if (fd->ref == 0)
522                 {
523                 GList *work;
524                 FileData *parent = fd->parent ? fd->parent : fd;
525                 
526                 if (parent->ref > 0)
527                         return;
528
529                 work = parent->sidecar_files;
530                 while (work)
531                         {
532                         FileData *sfd = work->data;
533                         if (sfd->ref > 0)
534                                 return;
535                         work = work->next;
536                         }
537
538                 /* none of parent/children is referenced, we can free everything */
539
540                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
541
542                 work = parent->sidecar_files;
543                 while (work)
544                         {
545                         FileData *sfd = work->data;
546                         file_data_free(sfd);
547                         work = work->next;
548                         }
549
550                 g_list_free(parent->sidecar_files);
551                 parent->sidecar_files = NULL;
552
553                 file_data_free(parent);
554                 }
555 }
556
557 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
558 {
559         sfd->parent = target;
560         g_assert(g_list_find(target->sidecar_files, sfd));
561         
562         file_data_increment_version(sfd); /* increments both sfd and target */
563
564         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
565         sfd->parent = NULL;
566
567         if (sfd->ref == 0)
568                 {
569                 file_data_free(sfd);
570                 return NULL;
571                 }
572
573         return sfd;
574 }
575
576 /* disables / enables grouping for particular file, sends UPDATE notification */
577 void file_data_disable_grouping(FileData *fd, gboolean disable)
578 {
579         if (!fd->disable_grouping == !disable) return;
580         fd->disable_grouping = !!disable;
581         
582         if (disable)
583                 {
584                 if (fd->parent)
585                         {
586                         FileData *parent = file_data_ref(fd->parent);
587                         file_data_disconnect_sidecar_file(parent, fd);
588                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
589                         file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
590                         file_data_unref(parent);
591                         }
592                 else if (fd->sidecar_files)
593                         {
594                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
595                         GList *work = sidecar_files;
596                         while (work)
597                                 {
598                                 FileData *sfd = work->data;
599                                 work = work->next;
600                                 file_data_disconnect_sidecar_file(fd, sfd);
601                                 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
602                                 }
603                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
604                         file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
605                         filelist_free(sidecar_files);
606                         }
607                 }
608         else
609                 {
610                 file_data_check_sidecars(fd);
611                 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
612                 }
613 }
614
615 /* compare name without extension */
616 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
617 {
618         size_t len1 = fd1->extension - fd1->name;
619         size_t len2 = fd2->extension - fd2->name;
620
621         if (len1 < len2) return -1;
622         if (len1 > len2) return 1;
623
624         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
625 }
626
627 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
628 {
629         FileDataChangeInfo *fdci;
630
631         if (fd->change) return FALSE;
632
633         fdci = g_new0(FileDataChangeInfo, 1);
634         fdci->type = type;
635
636         if (src)
637                 fdci->source = g_strdup(src);
638         else
639                 fdci->source = g_strdup(fd->path);
640
641         if (dest)
642                 fdci->dest = g_strdup(dest);
643
644         fd->change = fdci;
645
646         return TRUE;
647 }
648
649 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
650 {
651         if (!fdci && fd)
652                 fdci = fd->change;
653
654         if (!fdci)
655                 return;
656
657         g_free(fdci->source);
658         g_free(fdci->dest);
659
660         g_free(fdci);
661
662         if (fd)
663                 fd->change = NULL;
664 }
665
666
667
668
669 /*
670  *-----------------------------------------------------------------------------
671  * sidecar file info struct
672  *-----------------------------------------------------------------------------
673  */
674
675
676
677 static gint sidecar_file_priority(const gchar *path)
678 {
679         const char *extension = extension_from_path(path);
680         int i = 1;
681         GList *work;
682
683         if (extension == NULL)
684                 return 0;
685
686         work = sidecar_ext_get_list();
687
688         while (work) {
689                 gchar *ext = work->data;
690                 
691                 work = work->next;
692                 if (strcmp(extension, ext) == 0) return i;
693                 i++;
694         }
695         return 0;
696 }
697
698
699 /*
700  *-----------------------------------------------------------------------------
701  * load file list
702  *-----------------------------------------------------------------------------
703  */
704
705 static SortType filelist_sort_method = SORT_NONE;
706 static gint filelist_sort_ascend = TRUE;
707
708
709 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
710 {
711         if (!filelist_sort_ascend)
712                 {
713                 FileData *tmp = fa;
714                 fa = fb;
715                 fb = tmp;
716                 }
717
718         switch (filelist_sort_method)
719                 {
720                 case SORT_NAME:
721                         break;
722                 case SORT_SIZE:
723                         if (fa->size < fb->size) return -1;
724                         if (fa->size > fb->size) return 1;
725                         /* fall back to name */
726                         break;
727                 case SORT_TIME:
728                         if (fa->date < fb->date) return -1;
729                         if (fa->date > fb->date) return 1;
730                         /* fall back to name */
731                         break;
732 #ifdef HAVE_STRVERSCMP
733                 case SORT_NUMBER:
734                         return strverscmp(fa->name, fb->name);
735                         break;
736 #endif
737                 default:
738                         break;
739                 }
740
741         if (options->file_sort.case_sensitive)
742                 return strcmp(fa->collate_key_name, fb->collate_key_name);
743         else
744                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
745 }
746
747 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
748 {
749         filelist_sort_method = method;
750         filelist_sort_ascend = ascend;
751         return filelist_sort_compare_filedata(fa, fb);
752 }
753
754 static gint filelist_sort_file_cb(void *a, void *b)
755 {
756         return filelist_sort_compare_filedata(a, b);
757 }
758
759 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
760 {
761         filelist_sort_method = method;
762         filelist_sort_ascend = ascend;
763         return g_list_sort(list, cb);
764 }
765
766 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
767 {
768         filelist_sort_method = method;
769         filelist_sort_ascend = ascend;
770         return g_list_insert_sorted(list, data, cb);
771 }
772
773 GList *filelist_sort(GList *list, SortType method, gint ascend)
774 {
775         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
776 }
777
778 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
779 {
780         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
781 }
782
783
784 static GList *filelist_filter_out_sidecars(GList *flist)
785 {
786         GList *work = flist;
787         GList *flist_filtered = NULL;
788
789         while (work)
790                 {
791                 FileData *fd = work->data;
792         
793                 work = work->next;
794                 if (fd->parent) /* remove fd's that are children */
795                         file_data_unref(fd);
796                 else
797                         flist_filtered = g_list_prepend(flist_filtered, fd);
798                 }
799         g_list_free(flist);
800
801         return flist_filtered;
802 }
803
804 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
805 {
806         DIR *dp;
807         struct dirent *dir;
808         gchar *pathl;
809         GList *dlist = NULL;
810         GList *flist = NULL;
811         int (*stat_func)(const char *path, struct stat *buf);
812
813         g_assert(files || dirs);
814
815         if (files) *files = NULL;
816         if (dirs) *dirs = NULL;
817
818         pathl = path_from_utf8(dir_fd->path);
819         if (!pathl) return FALSE;
820
821         dp = opendir(pathl);
822         if (dp == NULL)
823                 {
824                 g_free(pathl);
825                 return FALSE;
826                 }
827
828         if (follow_symlinks)
829                 stat_func = stat;
830         else
831                 stat_func = lstat;
832
833         while ((dir = readdir(dp)) != NULL)
834                 {
835                 struct stat ent_sbuf;
836                 const gchar *name = dir->d_name;
837                 gchar *filepath;
838
839                 if (!options->file_filter.show_hidden_files && ishidden(name))
840                         continue;
841
842                 filepath = g_build_filename(pathl, name, NULL);
843                 if (stat_func(filepath, &ent_sbuf) >= 0)
844                         {
845                         if (S_ISDIR(ent_sbuf.st_mode))
846                                 {
847                                 /* we ignore the .thumbnails dir for cleanliness */
848                                 if (dirs &&
849                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
850                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
851                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
852                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
853                                         {
854                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
855                                         }
856                                 }
857                         else
858                                 {
859                                 if (files && filter_name_exists(name))
860                                         {
861                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
862                                         }
863                                 }
864                         }
865                 g_free(filepath);
866                 }
867
868         closedir(dp);
869         
870         g_free(pathl);
871
872         if (dirs) *dirs = dlist;
873         if (files) *files = filelist_filter_out_sidecars(flist);
874
875         return TRUE;
876 }
877
878 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
879 {
880         return filelist_read_real(dir_fd, files, dirs, TRUE);
881 }
882
883 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
884 {
885         return filelist_read_real(dir_fd, files, dirs, FALSE);
886 }
887
888 void filelist_free(GList *list)
889 {
890         GList *work;
891
892         work = list;
893         while (work)
894                 {
895                 file_data_unref((FileData *)work->data);
896                 work = work->next;
897                 }
898
899         g_list_free(list);
900 }
901
902
903 GList *filelist_copy(GList *list)
904 {
905         GList *new_list = NULL;
906         GList *work;
907
908         work = list;
909         while (work)
910                 {
911                 FileData *fd;
912
913                 fd = work->data;
914                 work = work->next;
915
916                 new_list = g_list_prepend(new_list, file_data_ref(fd));
917                 }
918
919         return g_list_reverse(new_list);
920 }
921
922 GList *filelist_from_path_list(GList *list)
923 {
924         GList *new_list = NULL;
925         GList *work;
926
927         work = list;
928         while (work)
929                 {
930                 gchar *path;
931
932                 path = work->data;
933                 work = work->next;
934
935                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
936                 }
937
938         return g_list_reverse(new_list);
939 }
940
941 GList *filelist_to_path_list(GList *list)
942 {
943         GList *new_list = NULL;
944         GList *work;
945
946         work = list;
947         while (work)
948                 {
949                 FileData *fd;
950
951                 fd = work->data;
952                 work = work->next;
953
954                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
955                 }
956
957         return g_list_reverse(new_list);
958 }
959
960 GList *filelist_filter(GList *list, gint is_dir_list)
961 {
962         GList *work;
963
964         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
965
966         work = list;
967         while (work)
968                 {
969                 FileData *fd = (FileData *)(work->data);
970                 const gchar *name = fd->name;
971
972                 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
973                     (!is_dir_list && !filter_name_exists(name)) ||
974                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
975                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
976                         {
977                         GList *link = work;
978                         
979                         list = g_list_remove_link(list, link);
980                         file_data_unref(fd);
981                         g_list_free(link);
982                         }
983         
984                 work = work->next;
985                 }
986
987         return list;
988 }
989
990 /*
991  *-----------------------------------------------------------------------------
992  * filelist recursive
993  *-----------------------------------------------------------------------------
994  */
995
996 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
997 {
998         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
999 }
1000
1001 GList *filelist_sort_path(GList *list)
1002 {
1003         return g_list_sort(list, filelist_sort_path_cb);
1004 }
1005
1006 static void filelist_recursive_append(GList **list, GList *dirs)
1007 {
1008         GList *work;
1009
1010         work = dirs;
1011         while (work)
1012                 {
1013                 FileData *fd = (FileData *)(work->data);
1014                 GList *f;
1015                 GList *d;
1016
1017                 if (filelist_read(fd, &f, &d))
1018                         {
1019                         f = filelist_filter(f, FALSE);
1020                         f = filelist_sort_path(f);
1021                         *list = g_list_concat(*list, f);
1022
1023                         d = filelist_filter(d, TRUE);
1024                         d = filelist_sort_path(d);
1025                         filelist_recursive_append(list, d);
1026                         filelist_free(d);
1027                         }
1028
1029                 work = work->next;
1030                 }
1031 }
1032
1033 GList *filelist_recursive(FileData *dir_fd)
1034 {
1035         GList *list;
1036         GList *d;
1037
1038         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1039         list = filelist_filter(list, FALSE);
1040         list = filelist_sort_path(list);
1041
1042         d = filelist_filter(d, TRUE);
1043         d = filelist_sort_path(d);
1044         filelist_recursive_append(&list, d);
1045         filelist_free(d);
1046
1047         return list;
1048 }
1049
1050
1051 /*
1052  * marks and orientation
1053  */
1054  
1055  
1056 gboolean file_data_get_mark(FileData *fd, gint n)
1057 {
1058         return !!(fd->marks & (1 << n));
1059 }
1060
1061 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1062 {
1063         if (!value == !(fd->marks & (1 << n))) return;
1064
1065         fd->marks = fd->marks ^ (1 << n);
1066         file_data_increment_version(fd);
1067         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1068 }
1069
1070 gint file_data_get_user_orientation(FileData *fd)
1071 {
1072         return fd->user_orientation;
1073 }
1074
1075 void file_data_set_user_orientation(FileData *fd, gint value)
1076 {
1077         if (fd->user_orientation == value) return;
1078
1079         fd->user_orientation = value;
1080         file_data_increment_version(fd);
1081         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1082 }
1083
1084
1085
1086 /*
1087  * file_data    - operates on the given fd
1088  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1089  */
1090
1091
1092 /* return list of sidecar file extensions in a string */
1093 gchar *file_data_sc_list_to_string(FileData *fd)
1094 {
1095         GList *work;
1096         GString *result = g_string_new("");
1097
1098         work = fd->sidecar_files;
1099         while (work)
1100                 {
1101                 FileData *sfd = work->data;
1102
1103                 result = g_string_append(result, "+ ");
1104                 result = g_string_append(result, sfd->extension);
1105                 work = work->next;
1106                 if (work) result = g_string_append_c(result, ' ');
1107                 }
1108
1109         return g_string_free(result, FALSE);
1110 }
1111
1112
1113                                 
1114 /* 
1115  * add FileDataChangeInfo (see typedefs.h) for the given operation 
1116  * uses file_data_add_change_info
1117  *
1118  * fails if the fd->change already exists - change operations can't run in parallel
1119  * fd->change_info works as a lock
1120  *
1121  * dest can be NULL - in this case the current name is used for now, it will
1122  * be changed later 
1123  */
1124
1125 /*
1126    FileDataChangeInfo types:
1127    COPY
1128    MOVE - patch is changed, name may be changed too
1129    RENAME - path remains unchanged, name is changed
1130             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1131             sidecar names are changed too, extensions are not changed
1132    DELETE
1133    UPDATE - file size, date or grouping has been changed 
1134 */
1135
1136 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1137 {
1138         FileDataChangeInfo *fdci;
1139
1140         if (fd->change) return FALSE;
1141
1142         fdci = g_new0(FileDataChangeInfo, 1);
1143
1144         fdci->type = type;
1145
1146         if (src)
1147                 fdci->source = g_strdup(src);
1148         else
1149                 fdci->source = g_strdup(fd->path);
1150
1151         if (dest)
1152                 fdci->dest = g_strdup(dest);
1153
1154         fd->change = fdci;
1155         
1156         return TRUE;
1157 }
1158
1159 void file_data_free_ci(FileData *fd)
1160 {
1161         FileDataChangeInfo *fdci = fd->change;
1162
1163         if (!fdci)
1164                 return;
1165
1166         g_free(fdci->source);
1167         g_free(fdci->dest);
1168
1169         g_free(fdci);
1170
1171         fd->change = NULL;
1172 }
1173
1174  
1175 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1176 {
1177         GList *work;
1178
1179         if (fd->parent) fd = fd->parent;
1180         
1181         if (fd->change) return FALSE;
1182         
1183         work = fd->sidecar_files;
1184         while (work)
1185                 {
1186                 FileData *sfd = work->data;
1187                 
1188                 if (sfd->change) return FALSE;
1189                 work = work->next;
1190                 }
1191
1192         file_data_add_ci(fd, type, NULL, NULL);
1193         
1194         work = fd->sidecar_files;
1195         while (work)
1196                 {
1197                 FileData *sfd = work->data;
1198                 
1199                 file_data_add_ci(sfd, type, NULL, NULL);
1200                 work = work->next;
1201                 }
1202                 
1203         return TRUE;    
1204 }
1205
1206 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1207 {
1208         GList *work;
1209         
1210         if (fd->parent) fd = fd->parent;
1211         
1212         if (!fd->change || fd->change->type != type) return FALSE;
1213         
1214         work = fd->sidecar_files;
1215         while (work)
1216                 {
1217                 FileData *sfd = work->data;
1218
1219                 if (!sfd->change || sfd->change->type != type) return FALSE;
1220                 work = work->next;
1221                 }
1222
1223         return TRUE;
1224 }
1225
1226
1227 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1228 {
1229         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1230         file_data_sc_update_ci_copy(fd, dest_path);
1231         return TRUE;
1232 }
1233
1234 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1235 {
1236         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1237         file_data_sc_update_ci_move(fd, dest_path);
1238         return TRUE;
1239 }
1240
1241 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1242 {
1243         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1244         file_data_sc_update_ci_rename(fd, dest_path);
1245         return TRUE;
1246 }
1247
1248 gboolean file_data_sc_add_ci_delete(FileData *fd)
1249 {
1250         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1251 }
1252
1253 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1254 {
1255         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1256         file_data_sc_update_ci_unspecified(fd, dest_path);
1257         return TRUE;
1258 }
1259
1260 void file_data_sc_free_ci(FileData *fd)
1261 {
1262         GList *work;
1263
1264         if (fd->parent) fd = fd->parent;
1265         
1266         file_data_free_ci(fd);
1267         
1268         work = fd->sidecar_files;
1269         while (work)
1270                 {
1271                 FileData *sfd = work->data;
1272         
1273                 file_data_free_ci(sfd);
1274                 work = work->next;
1275                 }
1276 }
1277
1278 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1279 {
1280         GList *work;
1281         gboolean ret = TRUE;
1282
1283         work = fd_list;
1284         while (work)
1285                 {
1286                 FileData *fd = work->data;
1287         
1288                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1289                 work = work->next;
1290                 }
1291
1292         return ret;
1293 }
1294
1295 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1296 {
1297         GList *work;
1298         gboolean ret = TRUE;
1299         
1300         work = fd_list;
1301         while (work)
1302                 {
1303                 FileData *fd = work->data;
1304                 
1305                 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1306                 work = work->next;
1307                 }
1308         
1309         return ret;
1310 }
1311
1312 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1313 {
1314         GList *work;
1315         gboolean ret = TRUE;
1316         
1317         work = fd_list;
1318         while (work)
1319                 {
1320                 FileData *fd = work->data;
1321                 
1322                 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1323                 work = work->next;
1324                 }
1325         
1326         return ret;
1327 }
1328
1329 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1330 {
1331         GList *work;
1332         gboolean ret = TRUE;
1333         
1334         work = fd_list;
1335         while (work)
1336                 {
1337                 FileData *fd = work->data;
1338                 
1339                 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1340                 work = work->next;
1341                 }
1342         
1343         return ret;
1344 }
1345
1346 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1347 {
1348         GList *work;
1349         gboolean ret = TRUE;
1350         
1351         work = fd_list;
1352         while (work)
1353                 {
1354                 FileData *fd = work->data;
1355                 
1356                 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1357                 work = work->next;
1358                 }
1359         
1360         return ret;
1361 }
1362
1363 void file_data_sc_free_ci_list(GList *fd_list)
1364 {
1365         GList *work;
1366         
1367         work = fd_list;
1368         while (work)
1369                 {
1370                 FileData *fd = work->data;
1371                 
1372                 file_data_sc_free_ci(fd);
1373                 work = work->next;
1374                 }
1375 }
1376
1377 /* 
1378  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1379  * fails if fd->change does not exist or the change type does not match
1380  */
1381
1382 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1383 {
1384         g_free(fd->change->dest);
1385         fd->change->dest = g_strdup(dest_path);
1386 }
1387
1388 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1389 {
1390         const char *extension = extension_from_path(fd->change->source);
1391         gchar *base = remove_extension_from_path(dest_path);
1392         
1393         g_free(fd->change->dest);
1394         fd->change->dest = g_strdup_printf("%s%s", base, extension);
1395         
1396         g_free(base);
1397 }
1398
1399 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1400 {
1401         GList *work;
1402         gchar *dest_path_full = NULL;
1403         
1404         if (fd->parent) fd = fd->parent;
1405         
1406         if (!dest_path) dest_path = fd->path;
1407         
1408         if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1409                 {
1410                 gchar *dir = remove_level_from_path(fd->path);
1411                 
1412                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1413                 g_free(dir);
1414                 dest_path = dest_path_full;
1415                 }
1416         
1417         if (isdir(dest_path))
1418                 {
1419                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1420                 dest_path = dest_path_full;
1421                 }
1422                 
1423         file_data_update_ci_dest(fd, dest_path);
1424         
1425         work = fd->sidecar_files;
1426         while (work)
1427                 {
1428                 FileData *sfd = work->data;
1429                 
1430                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1431                 work = work->next;
1432                 }
1433         
1434         g_free(dest_path_full);
1435 }
1436
1437 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1438 {
1439         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1440         file_data_sc_update_ci(fd, dest_path);
1441         return TRUE;
1442 }
1443         
1444 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1445 {
1446         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1447         file_data_sc_update_ci(fd, dest_path);
1448         return TRUE;
1449 }
1450
1451 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1452 {
1453         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1454         file_data_sc_update_ci(fd, dest_path);
1455         return TRUE;
1456 }
1457
1458 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1459 {
1460         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1461         file_data_sc_update_ci(fd, dest_path);
1462         return TRUE;
1463 }
1464
1465
1466 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1467 {
1468         GList *work;
1469         gboolean ret = TRUE;
1470         
1471         work = fd_list;
1472         while (work)
1473                 {
1474                 FileData *fd = work->data;
1475                 
1476                 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1477                 work = work->next;
1478                 }
1479         
1480         return ret;
1481 }
1482
1483 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1484 {
1485         GList *work;
1486         gboolean ret = TRUE;
1487         
1488         work = fd_list;
1489         while (work)
1490                 {
1491                 FileData *fd = work->data;
1492                 
1493                 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1494                 work = work->next;
1495                 }
1496         
1497         return ret;
1498 }
1499
1500 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1501 {
1502         GList *work;
1503         gboolean ret = TRUE;
1504         
1505         work = fd_list;
1506         while (work)
1507                 {
1508                 FileData *fd = work->data;
1509                 
1510                 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1511                 work = work->next;
1512                 }
1513         
1514         return ret;
1515 }
1516
1517
1518 /*
1519  * check dest paths - dest image exists, etc.
1520  * returns FIXME
1521  * it should detect all possible problems with the planned operation
1522  */
1523  
1524 gint file_data_sc_check_ci_dest(FileData *fd)
1525 {
1526 }
1527
1528
1529
1530
1531 /*
1532  * perform the change described by FileFataChangeInfo
1533  * it is used for internal operations, 
1534  * this function actually operates with files on the filesystem
1535  * it should implement safe delete
1536  */
1537  
1538 static gboolean file_data_perform_move(FileData *fd)
1539 {
1540         g_assert(!strcmp(fd->change->source, fd->path));
1541         return move_file(fd->change->source, fd->change->dest);
1542 }
1543
1544 static gboolean file_data_perform_copy(FileData *fd)
1545 {
1546         g_assert(!strcmp(fd->change->source, fd->path));
1547         return copy_file(fd->change->source, fd->change->dest);
1548 }
1549
1550 static gboolean file_data_perform_delete(FileData *fd)
1551 {
1552         return unlink_file(fd->path);
1553 }
1554
1555 static gboolean file_data_perform_ci(FileData *fd)
1556 {
1557         FileDataChangeType type = fd->change->type;
1558         switch (type)
1559                 {
1560                 case FILEDATA_CHANGE_MOVE:
1561                         return file_data_perform_move(fd);
1562                 case FILEDATA_CHANGE_COPY:
1563                         return file_data_perform_copy(fd);
1564                 case FILEDATA_CHANGE_RENAME:
1565                         return file_data_perform_move(fd); /* the same as move */
1566                 case FILEDATA_CHANGE_DELETE:
1567                         return file_data_perform_delete(fd);
1568                 case FILEDATA_CHANGE_UNSPECIFIED:
1569                         /* nothing to do here */
1570                         break;
1571                 }
1572         return TRUE;
1573 }
1574
1575
1576
1577 gboolean file_data_sc_perform_ci(FileData *fd)
1578 {
1579         GList *work;
1580         gboolean ret = TRUE;
1581         FileDataChangeType type = fd->change->type;
1582         
1583         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1584
1585         work = fd->sidecar_files;
1586         while (work)
1587                 {
1588                 FileData *sfd = work->data;
1589                 
1590                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1591                 work = work->next;
1592                 }
1593         
1594         if (!file_data_perform_ci(fd)) ret = FALSE;
1595         
1596         return ret;
1597 }
1598
1599 /*
1600  * updates FileData structure according to FileDataChangeInfo
1601  */
1602  
1603 static void file_data_apply_ci(FileData *fd)
1604 {
1605         FileDataChangeType type = fd->change->type;
1606         
1607         /* FIXME delete ?*/
1608         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1609                 {
1610                 file_data_set_path(fd, fd->change->dest);
1611                 }
1612         file_data_increment_version(fd);
1613         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1614 }
1615
1616 gint file_data_sc_apply_ci(FileData *fd)
1617 {
1618         GList *work;
1619         FileDataChangeType type = fd->change->type;
1620         
1621         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1622
1623         work = fd->sidecar_files;
1624         while (work)
1625                 {
1626                 FileData *sfd = work->data;
1627                 
1628                 file_data_apply_ci(sfd);
1629                 work = work->next;
1630                 }
1631         
1632         file_data_apply_ci(fd);
1633         
1634         return TRUE;
1635 }
1636
1637
1638 /*
1639  * notify other modules about the change described by FileFataChangeInfo
1640  */
1641  
1642 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1643    FIXME do we need the ignore_list? It looks like a workaround for ineffective
1644    implementation in view_file_list.c */
1645
1646
1647
1648
1649 typedef struct _NotifyData NotifyData;
1650
1651 struct _NotifyData {
1652         FileDataNotifyFunc func;
1653         gpointer data;
1654         NotifyPriority priority;
1655         };
1656
1657 static GList *notify_func_list = NULL;
1658
1659 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1660 {
1661         NotifyData *nda = (NotifyData *)a;
1662         NotifyData *ndb = (NotifyData *)b;
1663
1664         if (nda->priority < ndb->priority) return -1;
1665         if (nda->priority > ndb->priority) return 1;
1666         return 0;
1667 }
1668
1669 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1670 {
1671         NotifyData *nd;
1672         
1673         nd = g_new(NotifyData, 1);
1674         nd->func = func;
1675         nd->data = data;
1676         nd->priority = priority;
1677
1678         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1679         DEBUG_1("Notify func registered: %p", nd);
1680         
1681         return TRUE;
1682 }
1683
1684 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1685 {
1686         GList *work = notify_func_list;
1687         
1688         while (work)
1689                 {
1690                 NotifyData *nd = (NotifyData *)work->data;
1691         
1692                 if (nd->func == func && nd->data == data)
1693                         {
1694                         notify_func_list = g_list_delete_link(notify_func_list, work);
1695                         g_free(nd);
1696                         DEBUG_1("Notify func unregistered: %p", nd);
1697                         return TRUE;
1698                         }
1699                 work = work->next;
1700                 }
1701
1702         return FALSE;
1703 }
1704
1705
1706 void file_data_send_notification(FileData *fd, NotifyType type)
1707 {
1708         GList *work = notify_func_list;
1709
1710         while (work)
1711                 {
1712                 NotifyData *nd = (NotifyData *)work->data;
1713                 
1714                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1715                 nd->func(fd, type, nd->data);
1716                 work = work->next;
1717                 }
1718 }
1719
1720 static GHashTable *file_data_monitor_pool = NULL;
1721 static gint realtime_monitor_id = -1;
1722
1723 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1724 {
1725         FileData *fd = key;
1726
1727         file_data_check_changed_files(fd);
1728         
1729         DEBUG_1("monitor %s", fd->path);
1730 }
1731
1732 static gboolean realtime_monitor_cb(gpointer data)
1733 {
1734         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1735         return TRUE;
1736 }
1737
1738 gint file_data_register_real_time_monitor(FileData *fd)
1739 {
1740         gint count = 0;
1741         
1742         file_data_ref(fd);
1743         
1744         if (!file_data_monitor_pool)
1745                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1746         
1747         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1748
1749         DEBUG_1("Register realtime %d %s", count, fd->path);
1750         
1751         count++;
1752         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1753         
1754         if (realtime_monitor_id == -1)
1755                 {
1756                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1757                 }
1758         
1759         return TRUE;
1760 }
1761
1762 gint file_data_unregister_real_time_monitor(FileData *fd)
1763 {
1764         gint count;
1765
1766         g_assert(file_data_monitor_pool);
1767         
1768         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1769         
1770         DEBUG_1("Unregister realtime %d %s", count, fd->path);
1771         
1772         g_assert(count > 0);
1773         
1774         count--;
1775         
1776         if (count == 0)
1777                 g_hash_table_remove(file_data_monitor_pool, fd);
1778         else
1779                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1780
1781         file_data_unref(fd);
1782         
1783         if (g_hash_table_size(file_data_monitor_pool) == 0)
1784                 {
1785                 g_source_remove(realtime_monitor_id);
1786                 realtime_monitor_id = -1;
1787                 return FALSE;
1788                 }
1789         
1790         return TRUE;
1791 }