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