basic infrastructure for early error and dangerous operations checking
[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  * it should detect all possible problems with the planned operation
1625  * FIXME: add more tests
1626  */
1627
1628 gint file_data_check_ci_dest(FileData *fd)
1629 {
1630         gint ret = CHANGE_OK;
1631         
1632         g_assert(fd->change);
1633         
1634         if (fd->change->dest &&
1635             strcmp(fd->change->dest, fd->path) != 0 &&
1636             isname(fd->change->dest))
1637                 {
1638                 ret |= CHANGE_DEST_EXISTS;
1639                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1640                 }
1641         
1642         if (!access_file(fd->path, R_OK))
1643                 {
1644                 ret |= CHANGE_NO_PERM;
1645                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1646                 }
1647                 
1648         fd->change->error = ret;
1649         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1650
1651         return ret;
1652 }
1653
1654  
1655 gint file_data_sc_check_ci_dest(FileData *fd)
1656 {
1657         GList *work;
1658         int ret;
1659
1660         ret = file_data_check_ci_dest(fd);
1661
1662         work = fd->sidecar_files;
1663         while (work)
1664                 {
1665                 FileData *sfd = work->data;
1666
1667                 ret |= file_data_check_ci_dest(sfd);
1668                 work = work->next;
1669                 }
1670
1671         return ret;
1672 }
1673
1674
1675
1676
1677 /*
1678  * perform the change described by FileFataChangeInfo
1679  * it is used for internal operations, 
1680  * this function actually operates with files on the filesystem
1681  * it should implement safe delete
1682  */
1683  
1684 static gboolean file_data_perform_move(FileData *fd)
1685 {
1686         g_assert(!strcmp(fd->change->source, fd->path));
1687         return move_file(fd->change->source, fd->change->dest);
1688 }
1689
1690 static gboolean file_data_perform_copy(FileData *fd)
1691 {
1692         g_assert(!strcmp(fd->change->source, fd->path));
1693         return copy_file(fd->change->source, fd->change->dest);
1694 }
1695
1696 static gboolean file_data_perform_delete(FileData *fd)
1697 {
1698         if (isdir(fd->path) && !islink(fd->path))
1699                 return rmdir_utf8(fd->path);
1700         else
1701                 return unlink_file(fd->path);
1702 }
1703
1704 static gboolean file_data_perform_ci(FileData *fd)
1705 {
1706         FileDataChangeType type = fd->change->type;
1707         switch (type)
1708                 {
1709                 case FILEDATA_CHANGE_MOVE:
1710                         return file_data_perform_move(fd);
1711                 case FILEDATA_CHANGE_COPY:
1712                         return file_data_perform_copy(fd);
1713                 case FILEDATA_CHANGE_RENAME:
1714                         return file_data_perform_move(fd); /* the same as move */
1715                 case FILEDATA_CHANGE_DELETE:
1716                         return file_data_perform_delete(fd);
1717                 case FILEDATA_CHANGE_UNSPECIFIED:
1718                         /* nothing to do here */
1719                         break;
1720                 }
1721         return TRUE;
1722 }
1723
1724
1725
1726 gboolean file_data_sc_perform_ci(FileData *fd)
1727 {
1728         GList *work;
1729         gboolean ret = TRUE;
1730         FileDataChangeType type = fd->change->type;
1731         
1732         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1733
1734         work = fd->sidecar_files;
1735         while (work)
1736                 {
1737                 FileData *sfd = work->data;
1738                 
1739                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1740                 work = work->next;
1741                 }
1742         
1743         if (!file_data_perform_ci(fd)) ret = FALSE;
1744         
1745         return ret;
1746 }
1747
1748 /*
1749  * updates FileData structure according to FileDataChangeInfo
1750  */
1751
1752 static void file_data_apply_ci(FileData *fd)
1753 {
1754         FileDataChangeType type = fd->change->type;
1755         
1756         /* FIXME delete ?*/
1757         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1758                 {
1759                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1760                 file_data_planned_change_remove(fd);
1761                 file_data_set_path(fd, fd->change->dest);
1762                 }
1763         file_data_increment_version(fd);
1764         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1765 }
1766
1767 gint file_data_sc_apply_ci(FileData *fd)
1768 {
1769         GList *work;
1770         FileDataChangeType type = fd->change->type;
1771         
1772         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1773
1774         work = fd->sidecar_files;
1775         while (work)
1776                 {
1777                 FileData *sfd = work->data;
1778                 
1779                 file_data_apply_ci(sfd);
1780                 work = work->next;
1781                 }
1782         
1783         file_data_apply_ci(fd);
1784         
1785         return TRUE;
1786 }
1787
1788 /*
1789  * notify other modules about the change described by FileFataChangeInfo
1790  */
1791  
1792 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1793    FIXME do we need the ignore_list? It looks like a workaround for ineffective
1794    implementation in view_file_list.c */
1795
1796
1797
1798
1799 typedef struct _NotifyData NotifyData;
1800
1801 struct _NotifyData {
1802         FileDataNotifyFunc func;
1803         gpointer data;
1804         NotifyPriority priority;
1805         };
1806
1807 static GList *notify_func_list = NULL;
1808
1809 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1810 {
1811         NotifyData *nda = (NotifyData *)a;
1812         NotifyData *ndb = (NotifyData *)b;
1813
1814         if (nda->priority < ndb->priority) return -1;
1815         if (nda->priority > ndb->priority) return 1;
1816         return 0;
1817 }
1818
1819 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1820 {
1821         NotifyData *nd;
1822         
1823         nd = g_new(NotifyData, 1);
1824         nd->func = func;
1825         nd->data = data;
1826         nd->priority = priority;
1827
1828         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1829         DEBUG_1("Notify func registered: %p", nd);
1830         
1831         return TRUE;
1832 }
1833
1834 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1835 {
1836         GList *work = notify_func_list;
1837         
1838         while (work)
1839                 {
1840                 NotifyData *nd = (NotifyData *)work->data;
1841         
1842                 if (nd->func == func && nd->data == data)
1843                         {
1844                         notify_func_list = g_list_delete_link(notify_func_list, work);
1845                         g_free(nd);
1846                         DEBUG_1("Notify func unregistered: %p", nd);
1847                         return TRUE;
1848                         }
1849                 work = work->next;
1850                 }
1851
1852         return FALSE;
1853 }
1854
1855
1856 void file_data_send_notification(FileData *fd, NotifyType type)
1857 {
1858         GList *work = notify_func_list;
1859
1860         while (work)
1861                 {
1862                 NotifyData *nd = (NotifyData *)work->data;
1863                 
1864                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1865                 nd->func(fd, type, nd->data);
1866                 work = work->next;
1867                 }
1868 }
1869
1870 static GHashTable *file_data_monitor_pool = NULL;
1871 static gint realtime_monitor_id = -1;
1872
1873 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1874 {
1875         FileData *fd = key;
1876
1877         file_data_check_changed_files(fd);
1878         
1879         DEBUG_1("monitor %s", fd->path);
1880 }
1881
1882 static gboolean realtime_monitor_cb(gpointer data)
1883 {
1884         if (!options->update_on_time_change) return TRUE;
1885         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1886         return TRUE;
1887 }
1888
1889 gint file_data_register_real_time_monitor(FileData *fd)
1890 {
1891         gint count = 0;
1892         
1893         file_data_ref(fd);
1894         
1895         if (!file_data_monitor_pool)
1896                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1897         
1898         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1899
1900         DEBUG_1("Register realtime %d %s", count, fd->path);
1901         
1902         count++;
1903         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1904         
1905         if (realtime_monitor_id == -1)
1906                 {
1907                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1908                 }
1909         
1910         return TRUE;
1911 }
1912
1913 gint file_data_unregister_real_time_monitor(FileData *fd)
1914 {
1915         gint count;
1916
1917         g_assert(file_data_monitor_pool);
1918         
1919         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1920         
1921         DEBUG_1("Unregister realtime %d %s", count, fd->path);
1922         
1923         g_assert(count > 0);
1924         
1925         count--;
1926         
1927         if (count == 0)
1928                 g_hash_table_remove(file_data_monitor_pool, fd);
1929         else
1930                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1931
1932         file_data_unref(fd);
1933         
1934         if (g_hash_table_size(file_data_monitor_pool) == 0)
1935                 {
1936                 g_source_remove(realtime_monitor_id);
1937                 realtime_monitor_id = -1;
1938                 return FALSE;
1939                 }
1940         
1941         return TRUE;
1942 }