regroup sidecar files when an operation on partial group is finished
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2009 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 "thumb_standard.h"
20 #include "ui_fileops.h"
21 #include "metadata.h"
22 #include "trash.h"
23 #include "histogram.h"
24
25
26 static GHashTable *file_data_pool = NULL;
27 static GHashTable *file_data_planned_change_hash = NULL;
28 static GHashTable *file_data_basename_hash = NULL;
29
30 static gint sidecar_file_priority(const gchar *path);
31
32
33 /*
34  *-----------------------------------------------------------------------------
35  * text conversion utils
36  *-----------------------------------------------------------------------------
37  */
38
39 gchar *text_from_size(gint64 size)
40 {
41         gchar *a, *b;
42         gchar *s, *d;
43         gint l, n, i;
44
45         /* what I would like to use is printf("%'d", size)
46          * BUT: not supported on every libc :(
47          */
48         if (size > G_MAXUINT)
49                 {
50                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
51                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
52                 }
53         else
54                 {
55                 a = g_strdup_printf("%d", (guint)size);
56                 }
57         l = strlen(a);
58         n = (l - 1)/ 3;
59         if (n < 1) return a;
60
61         b = g_new(gchar, l + n + 1);
62
63         s = a;
64         d = b;
65         i = l - n * 3;
66         while (*s != '\0')
67                 {
68                 if (i < 1)
69                         {
70                         i = 3;
71                         *d = ',';
72                         d++;
73                         }
74
75                 *d = *s;
76                 s++;
77                 d++;
78                 i--;
79                 }
80         *d = '\0';
81
82         g_free(a);
83         return b;
84 }
85
86 gchar *text_from_size_abrev(gint64 size)
87 {
88         if (size < (gint64)1024)
89                 {
90                 return g_strdup_printf(_("%d bytes"), (gint)size);
91                 }
92         if (size < (gint64)1048576)
93                 {
94                 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
95                 }
96         if (size < (gint64)1073741824)
97                 {
98                 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
99                 }
100
101         /* to avoid overflowing the gdouble, do division in two steps */
102         size /= 1048576;
103         return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
104 }
105
106 /* note: returned string is valid until next call to text_from_time() */
107 const gchar *text_from_time(time_t t)
108 {
109         static gchar *ret = NULL;
110         gchar buf[128];
111         gint buflen;
112         struct tm *btime;
113         GError *error = NULL;
114
115         btime = localtime(&t);
116
117         /* the %x warning about 2 digit years is not an error */
118         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
119         if (buflen < 1) return "";
120
121         g_free(ret);
122         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
123         if (error)
124                 {
125                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
126                 g_error_free(error);
127                 return "";
128                 }
129
130         return ret;
131 }
132
133 /*
134  *-----------------------------------------------------------------------------
135  * file info struct
136  *-----------------------------------------------------------------------------
137  */
138
139 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
140 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars);
141 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
142
143
144 void file_data_increment_version(FileData *fd)
145 {
146         fd->version++;
147         fd->valid_marks = 0;
148         if (fd->parent) 
149                 {
150                 fd->parent->version++;
151                 fd->parent->valid_marks = 0;
152                 }
153 }
154
155 static void file_data_basename_hash_insert(FileData *fd)
156 {
157         GList *list;
158         const gchar *ext = extension_from_path(fd->path);
159         gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
160         if (!file_data_basename_hash)
161                 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
162         
163         list = g_hash_table_lookup(file_data_basename_hash, basename);
164         
165         if (!g_list_find(list, fd))
166                 {
167                 list = g_list_prepend(list, fd);
168                 g_hash_table_insert(file_data_basename_hash, basename, list);
169                 }
170         else 
171                 {
172                 g_free(basename);
173                 }
174 }
175
176 static void file_data_basename_hash_remove(FileData *fd)
177 {
178         GList *list;
179         const gchar *ext = extension_from_path(fd->path);
180         gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
181         if (!file_data_basename_hash)
182                 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
183         
184         list = g_hash_table_lookup(file_data_basename_hash, basename);
185         
186         list = g_list_remove(list, fd);
187         
188         if (list)
189                 {
190                 g_hash_table_insert(file_data_basename_hash, basename, list);
191                 }
192         else 
193                 {
194                 g_hash_table_remove(file_data_basename_hash, basename);
195                 g_free(basename);
196                 }
197 }
198
199 static void file_data_set_collate_keys(FileData *fd)
200 {
201         gchar *caseless_name;
202
203         caseless_name = g_utf8_casefold(fd->name, -1);
204
205         g_free(fd->collate_key_name);
206         g_free(fd->collate_key_name_nocase);
207
208 #if GLIB_CHECK_VERSION(2, 8, 0)
209         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
210         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
211 #else
212         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
213         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
214 #endif
215         g_free(caseless_name);
216 }
217
218 static void file_data_set_path(FileData *fd, const gchar *path)
219 {
220         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
221         g_assert(file_data_pool);
222
223         if (fd->path) file_data_basename_hash_remove(fd);
224         
225         g_free(fd->path);
226
227         if (fd->original_path)
228                 {
229                 g_hash_table_remove(file_data_pool, fd->original_path);
230                 g_free(fd->original_path);
231                 }
232
233         g_assert(!g_hash_table_lookup(file_data_pool, path));
234
235         fd->original_path = g_strdup(path);
236         g_hash_table_insert(file_data_pool, fd->original_path, fd);
237
238         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
239                 {
240                 fd->path = g_strdup(path);
241                 fd->name = fd->path;
242                 fd->extension = fd->name + 1;
243                 file_data_set_collate_keys(fd);
244                 return;
245                 }
246
247         fd->path = g_strdup(path);
248         fd->name = filename_from_path(fd->path);
249
250         if (strcmp(fd->name, "..") == 0)
251                 {
252                 gchar *dir = remove_level_from_path(path);
253                 g_free(fd->path);
254                 fd->path = remove_level_from_path(dir);
255                 g_free(dir);
256                 fd->name = "..";
257                 fd->extension = fd->name + 2;
258                 file_data_set_collate_keys(fd);
259                 return;
260                 }
261         else if (strcmp(fd->name, ".") == 0)
262                 {
263                 g_free(fd->path);
264                 fd->path = remove_level_from_path(path);
265                 fd->name = ".";
266                 fd->extension = fd->name + 1;
267                 file_data_set_collate_keys(fd);
268                 return;
269                 }
270
271         fd->extension = extension_from_path(fd->path);
272         if (fd->extension == NULL)
273                 {
274                 fd->extension = fd->name + strlen(fd->name);
275                 }
276
277         file_data_basename_hash_insert(fd); /* we can ignore the special cases above - they don't have extensions */
278
279         file_data_set_collate_keys(fd);
280 }
281
282 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
283 {
284         gboolean ret = FALSE;
285         GList *work;
286         
287         if (fd->size != st->st_size ||
288             fd->date != st->st_mtime)
289                 {
290                 fd->size = st->st_size;
291                 fd->date = st->st_mtime;
292                 fd->mode = st->st_mode;
293                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
294                 fd->thumb_pixbuf = NULL;
295                 file_data_increment_version(fd);
296                 file_data_send_notification(fd, NOTIFY_REREAD);
297                 ret = TRUE;
298                 }
299
300         work = fd->sidecar_files;
301         while (work)
302                 {
303                 FileData *sfd = work->data;
304                 struct stat st;
305                 work = work->next;
306
307                 if (!stat_utf8(sfd->path, &st))
308                         {
309                         fd->size = 0;
310                         fd->date = 0;
311                         file_data_disconnect_sidecar_file(fd, sfd);
312                         ret = TRUE;
313                         continue;
314                         }
315
316                 ret |= file_data_check_changed_files_recursive(sfd, &st);
317                 }
318         return ret;
319 }
320
321
322 gboolean file_data_check_changed_files(FileData *fd)
323 {
324         gboolean ret = FALSE;
325         struct stat st;
326         
327         if (fd->parent) fd = fd->parent;
328
329         if (!stat_utf8(fd->path, &st))
330                 {
331                 GList *work;
332                 FileData *sfd = NULL;
333
334                 /* parent is missing, we have to rebuild whole group */
335                 ret = TRUE;
336                 fd->size = 0;
337                 fd->date = 0;
338                 
339                 work = fd->sidecar_files;
340                 while (work)
341                         {
342                         sfd = work->data;
343                         work = work->next;
344                 
345                         file_data_disconnect_sidecar_file(fd, sfd);
346                         }
347                 if (sfd) file_data_check_sidecars(sfd, FALSE); /* this will group the sidecars back together */
348                 file_data_send_notification(fd, NOTIFY_REREAD);
349                 }
350         else
351                 {
352                 ret |= file_data_check_changed_files_recursive(fd, &st);
353                 }
354
355         return ret;
356 }
357
358 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
359 {
360         FileData *fd;
361
362         DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, stat_sidecars);
363
364         if (!file_data_pool)
365                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
366
367         fd = g_hash_table_lookup(file_data_pool, path_utf8);
368         if (fd)
369                 {
370                 file_data_ref(fd);
371                 }
372                 
373         if (!fd && file_data_planned_change_hash)
374                 {
375                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
376                 if (fd)
377                         {
378                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
379                         file_data_ref(fd);
380                         file_data_apply_ci(fd);
381                         }
382                 }
383                 
384         if (fd)
385                 {
386                 gboolean changed;
387                 
388                 if (fd->parent)
389                         changed = file_data_check_changed_files(fd);
390                 else
391                         changed = file_data_check_changed_files_recursive(fd, st);
392                 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
393                         file_data_check_sidecars(fd, stat_sidecars);
394                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
395                 
396                 return fd;
397                 }
398
399         fd = g_new0(FileData, 1);
400         
401         fd->size = st->st_size;
402         fd->date = st->st_mtime;
403         fd->mode = st->st_mode;
404         fd->ref = 1;
405         fd->magick = 0x12345678;
406
407         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
408
409         if (check_sidecars)
410                 file_data_check_sidecars(fd, stat_sidecars);
411
412         return fd;
413 }
414
415 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars)
416 {
417         gint base_len;
418         GString *fname;
419         FileData *parent_fd = NULL;
420         GList *work;
421         GList *basename_list = NULL;
422
423         if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
424                 return;
425
426         base_len = fd->extension - fd->path;
427         fname = g_string_new_len(fd->path, base_len);
428
429         if (!stat_sidecars)
430                 {
431                 basename_list = g_hash_table_lookup(file_data_basename_hash, fname->str);
432                 }
433
434         work = sidecar_ext_get_list();
435
436         while (work)
437                 {
438                 /* check for possible sidecar files;
439                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
440                    they have fd->ref set to 0 and file_data unref must chack and free them all together
441                    (using fd->ref would cause loops and leaks)
442                 */
443
444                 FileData *new_fd;
445                 gchar *ext = work->data;
446
447                 work = work->next;
448
449                 if (g_ascii_strcasecmp(ext, fd->extension) == 0)
450                         {
451                         new_fd = fd; /* processing the original file */
452                         }
453                 else
454                         {
455                         if (stat_sidecars)
456                                 {
457                                 struct stat nst;
458                                 g_string_truncate(fname, base_len);
459                                 if (!stat_utf8_case_insensitive_ext(fname, ext, &nst))
460                                         continue;
461                                 new_fd = file_data_new(fname->str, &nst, FALSE, FALSE);
462                                 }
463                         else
464                                 {
465                                 GList *work2 = basename_list;
466                                 new_fd = NULL;
467                                 
468                                 while (work2)
469                                         {
470                                         FileData *sfd = work2->data;
471                                         if (g_ascii_strcasecmp(ext, sfd->extension) == 0) 
472                                                 {
473                                                 new_fd = file_data_ref(sfd);
474                                                 break;
475                                                 }
476                                         work2 = work2->next;
477                                         }
478                                         
479                                 if (!new_fd) continue;
480                                 }
481                         
482                         if (new_fd->disable_grouping)
483                                 {
484                                 file_data_unref(new_fd);
485                                 continue;
486                                 }
487                         
488                         new_fd->ref--; /* do not use ref here */
489                         }
490
491                 if (!parent_fd)
492                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
493                 else
494                         file_data_merge_sidecar_files(parent_fd, new_fd);
495                 }
496         g_string_free(fname, TRUE);
497 }
498
499
500 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
501 {
502         gchar *path_utf8 = path_to_utf8(path);
503         FileData *ret = file_data_new(path_utf8, st, check_sidecars, stat_sidecars);
504
505         g_free(path_utf8);
506         return ret;
507 }
508
509 FileData *file_data_new_simple(const gchar *path_utf8)
510 {
511         struct stat st;
512
513         if (!stat_utf8(path_utf8, &st))
514                 {
515                 st.st_size = 0;
516                 st.st_mtime = 0;
517                 }
518
519         return file_data_new(path_utf8, &st, TRUE, TRUE);
520 }
521
522 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
523 {
524         sfd->parent = target;
525         if (!g_list_find(target->sidecar_files, sfd))
526                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
527         file_data_increment_version(sfd); /* increments both sfd and target */
528         return target;
529 }
530
531
532 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
533 {
534         GList *work;
535         
536         file_data_add_sidecar_file(target, source);
537
538         work = source->sidecar_files;
539         while (work)
540                 {
541                 FileData *sfd = work->data;
542                 file_data_add_sidecar_file(target, sfd);
543                 work = work->next;
544                 }
545
546         g_list_free(source->sidecar_files);
547         source->sidecar_files = NULL;
548
549         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
550         
551         return target;
552 }
553
554 #ifdef DEBUG_FILEDATA
555 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
556 #else
557 FileData *file_data_ref(FileData *fd)
558 #endif
559 {
560         if (fd == NULL) return NULL;
561 #ifdef DEBUG_FILEDATA
562         if (fd->magick != 0x12345678)
563                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
564 #endif
565         g_assert(fd->magick == 0x12345678);
566         fd->ref++;
567
568 #ifdef DEBUG_FILEDATA
569         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
570 #else
571         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
572 #endif
573         return fd;
574 }
575
576 static void file_data_free(FileData *fd)
577 {
578         g_assert(fd->magick == 0x12345678);
579         g_assert(fd->ref == 0);
580
581         if (fd->path) file_data_basename_hash_remove(fd);
582         g_hash_table_remove(file_data_pool, fd->original_path);
583
584         g_free(fd->path);
585         g_free(fd->original_path);
586         g_free(fd->collate_key_name);
587         g_free(fd->collate_key_name_nocase);
588         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
589         histmap_free(fd->histmap);
590         
591         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
592
593         file_data_change_info_free(NULL, fd);
594         g_free(fd);
595 }
596
597 #ifdef DEBUG_FILEDATA
598 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
599 #else
600 void file_data_unref(FileData *fd)
601 #endif
602 {
603         if (fd == NULL) return;
604 #ifdef DEBUG_FILEDATA
605         if (fd->magick != 0x12345678)
606                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
607 #endif
608         g_assert(fd->magick == 0x12345678);
609         
610         fd->ref--;
611 #ifdef DEBUG_FILEDATA
612         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
613 #else
614         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
615 #endif
616         if (fd->ref == 0)
617                 {
618                 GList *work;
619                 FileData *parent = fd->parent ? fd->parent : fd;
620                 
621                 if (parent->ref > 0) return;
622
623                 work = parent->sidecar_files;
624                 while (work)
625                         {
626                         FileData *sfd = work->data;
627                         if (sfd->ref > 0) return;
628                         work = work->next;
629                         }
630
631                 /* none of parent/children is referenced, we can free everything */
632
633                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
634
635                 work = parent->sidecar_files;
636                 while (work)
637                         {
638                         FileData *sfd = work->data;
639                         file_data_free(sfd);
640                         work = work->next;
641                         }
642
643                 g_list_free(parent->sidecar_files);
644                 parent->sidecar_files = NULL;
645
646                 file_data_free(parent);
647                 }
648 }
649
650 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
651 {
652         sfd->parent = target;
653         g_assert(g_list_find(target->sidecar_files, sfd));
654         
655         file_data_increment_version(sfd); /* increments both sfd and target */
656
657         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
658         sfd->parent = NULL;
659
660         if (sfd->ref == 0)
661                 {
662                 file_data_free(sfd);
663                 return NULL;
664                 }
665
666         return sfd;
667 }
668
669 /* disables / enables grouping for particular file, sends UPDATE notification */
670 void file_data_disable_grouping(FileData *fd, gboolean disable)
671 {
672         if (!fd->disable_grouping == !disable) return;
673         
674         fd->disable_grouping = !!disable;
675         
676         if (disable)
677                 {
678                 if (fd->parent)
679                         {
680                         FileData *parent = file_data_ref(fd->parent);
681                         file_data_disconnect_sidecar_file(parent, fd);
682                         file_data_send_notification(parent, NOTIFY_GROUPING);
683                         file_data_unref(parent);
684                         }
685                 else if (fd->sidecar_files)
686                         {
687                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
688                         GList *work = sidecar_files;
689                         while (work)
690                                 {
691                                 FileData *sfd = work->data;
692                                 work = work->next;
693                                 file_data_disconnect_sidecar_file(fd, sfd);
694                                 file_data_send_notification(sfd, NOTIFY_GROUPING);
695                                 }
696                         file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
697                         filelist_free(sidecar_files);
698                         }
699                 else
700                         {
701                         file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
702                         }
703                 }
704         else
705                 {
706                 file_data_increment_version(fd);
707                 file_data_check_sidecars(fd, FALSE);
708                 }
709         file_data_send_notification(fd, NOTIFY_GROUPING);
710 }
711
712 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
713 {
714         GList *work;
715         
716         work = fd_list;
717         while (work)
718                 {
719                 FileData *fd = work->data;
720                 
721                 file_data_disable_grouping(fd, disable);
722                 work = work->next;
723                 }
724 }
725
726
727 /* compare name without extension */
728 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
729 {
730         size_t len1 = fd1->extension - fd1->name;
731         size_t len2 = fd2->extension - fd2->name;
732
733         if (len1 < len2) return -1;
734         if (len1 > len2) return 1;
735
736         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
737 }
738
739 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
740 {
741         if (!fdci && fd) fdci = fd->change;
742
743         if (!fdci) return;
744
745         g_free(fdci->source);
746         g_free(fdci->dest);
747
748         g_free(fdci);
749
750         if (fd) fd->change = NULL;
751 }
752
753 static gboolean file_data_can_write_directly(FileData *fd)
754 {
755         return filter_name_is_writable(fd->extension);
756 }
757
758 static gboolean file_data_can_write_sidecar(FileData *fd)
759 {
760         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
761 }
762
763 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
764 {
765         gchar *sidecar_path = NULL;
766         GList *work;
767         
768         if (!file_data_can_write_sidecar(fd)) return NULL;
769         
770         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
771         while (work)
772                 {
773                 FileData *sfd = work->data;
774                 work = work->next;
775                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
776                         {
777                         sidecar_path = g_strdup(sfd->path);
778                         break;
779                         }
780                 }
781         
782         if (!existing_only && !sidecar_path)
783                 {
784                 gchar *base = remove_extension_from_path(fd->path);
785                 sidecar_path = g_strconcat(base, ".xmp", NULL);
786                 g_free(base);
787                 }
788
789         return sidecar_path;
790 }
791
792
793 /*
794  *-----------------------------------------------------------------------------
795  * sidecar file info struct
796  *-----------------------------------------------------------------------------
797  */
798
799
800
801 static gint sidecar_file_priority(const gchar *path)
802 {
803         const gchar *extension = extension_from_path(path);
804         gint i = 1;
805         GList *work;
806
807         if (extension == NULL)
808                 return 0;
809
810         work = sidecar_ext_get_list();
811
812         while (work) {
813                 gchar *ext = work->data;
814                 
815                 work = work->next;
816                 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
817                 i++;
818         }
819         return 0;
820 }
821
822
823 /*
824  *-----------------------------------------------------------------------------
825  * load file list
826  *-----------------------------------------------------------------------------
827  */
828
829 static SortType filelist_sort_method = SORT_NONE;
830 static gboolean filelist_sort_ascend = TRUE;
831
832
833 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
834 {
835         if (!filelist_sort_ascend)
836                 {
837                 FileData *tmp = fa;
838                 fa = fb;
839                 fb = tmp;
840                 }
841
842         switch (filelist_sort_method)
843                 {
844                 case SORT_NAME:
845                         break;
846                 case SORT_SIZE:
847                         if (fa->size < fb->size) return -1;
848                         if (fa->size > fb->size) return 1;
849                         /* fall back to name */
850                         break;
851                 case SORT_TIME:
852                         if (fa->date < fb->date) return -1;
853                         if (fa->date > fb->date) return 1;
854                         /* fall back to name */
855                         break;
856 #ifdef HAVE_STRVERSCMP
857                 case SORT_NUMBER:
858                         return strverscmp(fa->name, fb->name);
859                         break;
860 #endif
861                 default:
862                         break;
863                 }
864
865         if (options->file_sort.case_sensitive)
866                 return strcmp(fa->collate_key_name, fb->collate_key_name);
867         else
868                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
869 }
870
871 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
872 {
873         filelist_sort_method = method;
874         filelist_sort_ascend = ascend;
875         return filelist_sort_compare_filedata(fa, fb);
876 }
877
878 static gint filelist_sort_file_cb(gpointer a, gpointer b)
879 {
880         return filelist_sort_compare_filedata(a, b);
881 }
882
883 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
884 {
885         filelist_sort_method = method;
886         filelist_sort_ascend = ascend;
887         return g_list_sort(list, cb);
888 }
889
890 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
891 {
892         filelist_sort_method = method;
893         filelist_sort_ascend = ascend;
894         return g_list_insert_sorted(list, data, cb);
895 }
896
897 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
898 {
899         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
900 }
901
902 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
903 {
904         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
905 }
906
907
908 static GList *filelist_filter_out_sidecars(GList *flist)
909 {
910         GList *work = flist;
911         GList *flist_filtered = NULL;
912
913         while (work)
914                 {
915                 FileData *fd = work->data;
916         
917                 work = work->next;
918                 if (fd->parent) /* remove fd's that are children */
919                         file_data_unref(fd);
920                 else
921                         flist_filtered = g_list_prepend(flist_filtered, fd);
922                 }
923         g_list_free(flist);
924
925         return flist_filtered;
926 }
927
928 static gboolean is_hidden_file(const gchar *name)
929 {
930         if (name[0] != '.') return FALSE;
931         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
932         return TRUE;
933 }
934
935 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
936 {
937         DIR *dp;
938         struct dirent *dir;
939         gchar *pathl;
940         GList *dlist = NULL;
941         GList *flist = NULL;
942         gint (*stat_func)(const gchar *path, struct stat *buf);
943
944         g_assert(files || dirs);
945
946         if (files) *files = NULL;
947         if (dirs) *dirs = NULL;
948
949         pathl = path_from_utf8(dir_fd->path);
950         if (!pathl) return FALSE;
951
952         dp = opendir(pathl);
953         if (dp == NULL)
954                 {
955                 g_free(pathl);
956                 return FALSE;
957                 }
958
959         if (follow_symlinks)
960                 stat_func = stat;
961         else
962                 stat_func = lstat;
963
964         while ((dir = readdir(dp)) != NULL)
965                 {
966                 struct stat ent_sbuf;
967                 const gchar *name = dir->d_name;
968                 gchar *filepath;
969
970                 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
971                         continue;
972
973                 filepath = g_build_filename(pathl, name, NULL);
974                 if (stat_func(filepath, &ent_sbuf) >= 0)
975                         {
976                         if (S_ISDIR(ent_sbuf.st_mode))
977                                 {
978                                 /* we ignore the .thumbnails dir for cleanliness */
979                                 if (dirs &&
980                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
981                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
982                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
983                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
984                                         {
985                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, FALSE));
986                                         }
987                                 }
988                         else
989                                 {
990                                 if (files && filter_name_exists(name))
991                                         {
992                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, FALSE));
993                                         }
994                                 }
995                         }
996                 g_free(filepath);
997                 }
998
999         closedir(dp);
1000         
1001         g_free(pathl);
1002
1003         if (dirs) *dirs = dlist;
1004         if (files) *files = filelist_filter_out_sidecars(flist);
1005
1006         return TRUE;
1007 }
1008
1009 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1010 {
1011         return filelist_read_real(dir_fd, files, dirs, TRUE);
1012 }
1013
1014 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1015 {
1016         return filelist_read_real(dir_fd, files, dirs, FALSE);
1017 }
1018
1019 void filelist_free(GList *list)
1020 {
1021         GList *work;
1022
1023         work = list;
1024         while (work)
1025                 {
1026                 file_data_unref((FileData *)work->data);
1027                 work = work->next;
1028                 }
1029
1030         g_list_free(list);
1031 }
1032
1033
1034 GList *filelist_copy(GList *list)
1035 {
1036         GList *new_list = NULL;
1037         GList *work;
1038
1039         work = list;
1040         while (work)
1041                 {
1042                 FileData *fd;
1043
1044                 fd = work->data;
1045                 work = work->next;
1046
1047                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1048                 }
1049
1050         return g_list_reverse(new_list);
1051 }
1052
1053 GList *filelist_from_path_list(GList *list)
1054 {
1055         GList *new_list = NULL;
1056         GList *work;
1057
1058         work = list;
1059         while (work)
1060                 {
1061                 gchar *path;
1062
1063                 path = work->data;
1064                 work = work->next;
1065
1066                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1067                 }
1068
1069         return g_list_reverse(new_list);
1070 }
1071
1072 GList *filelist_to_path_list(GList *list)
1073 {
1074         GList *new_list = NULL;
1075         GList *work;
1076
1077         work = list;
1078         while (work)
1079                 {
1080                 FileData *fd;
1081
1082                 fd = work->data;
1083                 work = work->next;
1084
1085                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1086                 }
1087
1088         return g_list_reverse(new_list);
1089 }
1090
1091 GList *filelist_filter(GList *list, gboolean is_dir_list)
1092 {
1093         GList *work;
1094
1095         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1096
1097         work = list;
1098         while (work)
1099                 {
1100                 FileData *fd = (FileData *)(work->data);
1101                 const gchar *name = fd->name;
1102
1103                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1104                     (!is_dir_list && !filter_name_exists(name)) ||
1105                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1106                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1107                         {
1108                         GList *link = work;
1109                         
1110                         list = g_list_remove_link(list, link);
1111                         file_data_unref(fd);
1112                         g_list_free(link);
1113                         }
1114         
1115                 work = work->next;
1116                 }
1117
1118         return list;
1119 }
1120
1121 /*
1122  *-----------------------------------------------------------------------------
1123  * filelist recursive
1124  *-----------------------------------------------------------------------------
1125  */
1126
1127 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1128 {
1129         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1130 }
1131
1132 GList *filelist_sort_path(GList *list)
1133 {
1134         return g_list_sort(list, filelist_sort_path_cb);
1135 }
1136
1137 static void filelist_recursive_append(GList **list, GList *dirs)
1138 {
1139         GList *work;
1140
1141         work = dirs;
1142         while (work)
1143                 {
1144                 FileData *fd = (FileData *)(work->data);
1145                 GList *f;
1146                 GList *d;
1147
1148                 if (filelist_read(fd, &f, &d))
1149                         {
1150                         f = filelist_filter(f, FALSE);
1151                         f = filelist_sort_path(f);
1152                         *list = g_list_concat(*list, f);
1153
1154                         d = filelist_filter(d, TRUE);
1155                         d = filelist_sort_path(d);
1156                         filelist_recursive_append(list, d);
1157                         filelist_free(d);
1158                         }
1159
1160                 work = work->next;
1161                 }
1162 }
1163
1164 GList *filelist_recursive(FileData *dir_fd)
1165 {
1166         GList *list;
1167         GList *d;
1168
1169         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1170         list = filelist_filter(list, FALSE);
1171         list = filelist_sort_path(list);
1172
1173         d = filelist_filter(d, TRUE);
1174         d = filelist_sort_path(d);
1175         filelist_recursive_append(&list, d);
1176         filelist_free(d);
1177
1178         return list;
1179 }
1180
1181
1182 /*
1183  * marks and orientation
1184  */
1185
1186 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1187 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1188 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1189 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1190
1191 gboolean file_data_get_mark(FileData *fd, gint n)
1192 {
1193         gboolean valid = (fd->valid_marks & (1 << n));
1194         
1195         if (file_data_get_mark_func[n] && !valid) 
1196                 {
1197                 guint old = fd->marks;
1198                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1199                 
1200                 if (!value != !(fd->marks & (1 << n))) 
1201                         {
1202                         fd->marks = fd->marks ^ (1 << n);
1203                         }
1204                 
1205                 fd->valid_marks |= (1 << n);
1206                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1207                         {
1208                         file_data_unref(fd);
1209                         }
1210                 else if (!old && fd->marks)
1211                         {
1212                         file_data_ref(fd);
1213                         }
1214                 }
1215
1216         return !!(fd->marks & (1 << n));
1217 }
1218
1219 guint file_data_get_marks(FileData *fd)
1220 {
1221         gint i;
1222         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1223         return fd->marks;
1224 }
1225
1226 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1227 {
1228         guint old;
1229         if (!value == !file_data_get_mark(fd, n)) return;
1230         
1231         if (file_data_set_mark_func[n]) 
1232                 {
1233                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1234                 }
1235         
1236         old = fd->marks;
1237
1238         fd->marks = fd->marks ^ (1 << n);
1239         
1240         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1241                 {
1242                 file_data_unref(fd);
1243                 }
1244         else if (!old && fd->marks)
1245                 {
1246                 file_data_ref(fd);
1247                 }
1248         
1249         file_data_increment_version(fd);
1250         file_data_send_notification(fd, NOTIFY_MARKS);
1251 }
1252
1253 gboolean file_data_filter_marks(FileData *fd, guint filter)
1254 {
1255         gint i;
1256         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1257         return ((fd->marks & filter) == filter);
1258 }
1259
1260 GList *file_data_filter_marks_list(GList *list, guint filter)
1261 {
1262         GList *work;
1263
1264         work = list;
1265         while (work)
1266                 {
1267                 FileData *fd = work->data;
1268                 GList *link = work;
1269                 work = work->next;
1270
1271                 if (!file_data_filter_marks(fd, filter))
1272                         {
1273                         list = g_list_remove_link(list, link);
1274                         file_data_unref(fd);
1275                         g_list_free(link);
1276                         }
1277                 }
1278
1279         return list;
1280 }
1281
1282 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1283 {
1284         FileData *fd = value;
1285         file_data_increment_version(fd);
1286         file_data_send_notification(fd, NOTIFY_MARKS);
1287 }
1288
1289 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1290 {
1291         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1292         
1293         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1294                 
1295         file_data_get_mark_func[n] = get_mark_func;
1296         file_data_set_mark_func[n] = set_mark_func;
1297         file_data_mark_func_data[n] = data;
1298         file_data_destroy_mark_func[n] = notify;
1299
1300         if (get_mark_func)
1301                 {
1302                 /* this effectively changes all known files */
1303                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1304                 }
1305
1306         return TRUE;
1307 }
1308
1309 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1310 {
1311         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1312         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1313         if (data) *data = file_data_mark_func_data[n];
1314 }
1315
1316 gint file_data_get_user_orientation(FileData *fd)
1317 {
1318         return fd->user_orientation;
1319 }
1320
1321 void file_data_set_user_orientation(FileData *fd, gint value)
1322 {
1323         if (fd->user_orientation == value) return;
1324
1325         fd->user_orientation = value;
1326         file_data_increment_version(fd);
1327         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1328 }
1329
1330
1331 /*
1332  * file_data    - operates on the given fd
1333  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1334  */
1335
1336
1337 /* return list of sidecar file extensions in a string */
1338 gchar *file_data_sc_list_to_string(FileData *fd)
1339 {
1340         GList *work;
1341         GString *result = g_string_new("");
1342
1343         work = fd->sidecar_files;
1344         while (work)
1345                 {
1346                 FileData *sfd = work->data;
1347
1348                 result = g_string_append(result, "+ ");
1349                 result = g_string_append(result, sfd->extension);
1350                 work = work->next;
1351                 if (work) result = g_string_append_c(result, ' ');
1352                 }
1353
1354         return g_string_free(result, FALSE);
1355 }
1356
1357
1358
1359 /*
1360  * add FileDataChangeInfo (see typedefs.h) for the given operation
1361  * uses file_data_add_change_info
1362  *
1363  * fails if the fd->change already exists - change operations can't run in parallel
1364  * fd->change_info works as a lock
1365  *
1366  * dest can be NULL - in this case the current name is used for now, it will
1367  * be changed later
1368  */
1369
1370 /*
1371    FileDataChangeInfo types:
1372    COPY
1373    MOVE   - path is changed, name may be changed too
1374    RENAME - path remains unchanged, name is changed
1375             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1376             sidecar names are changed too, extensions are not changed
1377    DELETE
1378    UPDATE - file size, date or grouping has been changed
1379 */
1380
1381 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1382 {
1383         FileDataChangeInfo *fdci;
1384
1385         if (fd->change) return FALSE;
1386
1387         fdci = g_new0(FileDataChangeInfo, 1);
1388
1389         fdci->type = type;
1390
1391         if (src)
1392                 fdci->source = g_strdup(src);
1393         else
1394                 fdci->source = g_strdup(fd->path);
1395
1396         if (dest)
1397                 fdci->dest = g_strdup(dest);
1398
1399         fd->change = fdci;
1400         
1401         return TRUE;
1402 }
1403
1404 static void file_data_planned_change_remove(FileData *fd)
1405 {
1406         if (file_data_planned_change_hash &&
1407             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1408                 {
1409                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1410                         {
1411                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1412                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1413                         file_data_unref(fd);
1414                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1415                                 {
1416                                 g_hash_table_destroy(file_data_planned_change_hash);
1417                                 file_data_planned_change_hash = NULL;
1418                                 DEBUG_1("planned change: empty");
1419                                 }
1420                         }
1421                 }
1422 }
1423
1424
1425 void file_data_free_ci(FileData *fd)
1426 {
1427         FileDataChangeInfo *fdci = fd->change;
1428
1429         if (!fdci) return;
1430
1431         file_data_planned_change_remove(fd);
1432         
1433         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1434
1435         g_free(fdci->source);
1436         g_free(fdci->dest);
1437
1438         g_free(fdci);
1439
1440         fd->change = NULL;
1441 }
1442
1443 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1444 {
1445         FileDataChangeInfo *fdci = fd->change;
1446         if (!fdci) return;
1447         fdci->regroup_when_finished = enable;
1448 }
1449
1450 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1451 {
1452         GList *work;
1453
1454         if (fd->parent) fd = fd->parent;
1455         
1456         if (fd->change) return FALSE;
1457         
1458         work = fd->sidecar_files;
1459         while (work)
1460                 {
1461                 FileData *sfd = work->data;
1462                 
1463                 if (sfd->change) return FALSE;
1464                 work = work->next;
1465                 }
1466
1467         file_data_add_ci(fd, type, NULL, NULL);
1468         
1469         work = fd->sidecar_files;
1470         while (work)
1471                 {
1472                 FileData *sfd = work->data;
1473                 
1474                 file_data_add_ci(sfd, type, NULL, NULL);
1475                 work = work->next;
1476                 }
1477                 
1478         return TRUE;
1479 }
1480
1481 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1482 {
1483         GList *work;
1484         
1485         if (fd->parent) fd = fd->parent;
1486         
1487         if (!fd->change || fd->change->type != type) return FALSE;
1488         
1489         work = fd->sidecar_files;
1490         while (work)
1491                 {
1492                 FileData *sfd = work->data;
1493
1494                 if (!sfd->change || sfd->change->type != type) return FALSE;
1495                 work = work->next;
1496                 }
1497
1498         return TRUE;
1499 }
1500
1501
1502 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1503 {
1504         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1505         file_data_sc_update_ci_copy(fd, dest_path);
1506         return TRUE;
1507 }
1508
1509 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1510 {
1511         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1512         file_data_sc_update_ci_move(fd, dest_path);
1513         return TRUE;
1514 }
1515
1516 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1517 {
1518         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1519         file_data_sc_update_ci_rename(fd, dest_path);
1520         return TRUE;
1521 }
1522
1523 gboolean file_data_sc_add_ci_delete(FileData *fd)
1524 {
1525         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1526 }
1527
1528 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1529 {
1530         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1531         file_data_sc_update_ci_unspecified(fd, dest_path);
1532         return TRUE;
1533 }
1534
1535 gboolean file_data_add_ci_write_metadata(FileData *fd)
1536 {
1537         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1538 }
1539
1540 void file_data_sc_free_ci(FileData *fd)
1541 {
1542         GList *work;
1543
1544         if (fd->parent) fd = fd->parent;
1545         
1546         file_data_free_ci(fd);
1547         
1548         work = fd->sidecar_files;
1549         while (work)
1550                 {
1551                 FileData *sfd = work->data;
1552         
1553                 file_data_free_ci(sfd);
1554                 work = work->next;
1555                 }
1556 }
1557
1558 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1559 {
1560         GList *work;
1561         gboolean ret = TRUE;
1562
1563         work = fd_list;
1564         while (work)
1565                 {
1566                 FileData *fd = work->data;
1567         
1568                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1569                 work = work->next;
1570                 }
1571
1572         return ret;
1573 }
1574
1575 static void file_data_sc_revert_ci_list(GList *fd_list)
1576 {
1577         GList *work;
1578         
1579         work = fd_list;
1580         while (work)
1581                 {
1582                 FileData *fd = work->data;
1583                 
1584                 file_data_sc_free_ci(fd);
1585                 work = work->prev;
1586                 }
1587 }
1588
1589 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1590 {
1591         GList *work;
1592         
1593         work = fd_list;
1594         while (work)
1595                 {
1596                 FileData *fd = work->data;
1597                 
1598                 if (!func(fd, dest))
1599                         {
1600                         file_data_sc_revert_ci_list(work->prev);
1601                         return FALSE;
1602                         }
1603                 work = work->next;
1604                 }
1605         
1606         return TRUE;
1607 }
1608
1609 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1610 {
1611         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1612 }
1613
1614 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1615 {
1616         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1617 }
1618
1619 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1620 {
1621         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1622 }
1623
1624 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1625 {
1626         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1627 }
1628
1629 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1630 {
1631         GList *work;
1632         gboolean ret = TRUE;
1633
1634         work = fd_list;
1635         while (work)
1636                 {
1637                 FileData *fd = work->data;
1638         
1639                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1640                 work = work->next;
1641                 }
1642
1643         return ret;
1644 }
1645
1646 void file_data_free_ci_list(GList *fd_list)
1647 {
1648         GList *work;
1649         
1650         work = fd_list;
1651         while (work)
1652                 {
1653                 FileData *fd = work->data;
1654                 
1655                 file_data_free_ci(fd);
1656                 work = work->next;
1657                 }
1658 }
1659
1660 void file_data_sc_free_ci_list(GList *fd_list)
1661 {
1662         GList *work;
1663         
1664         work = fd_list;
1665         while (work)
1666                 {
1667                 FileData *fd = work->data;
1668                 
1669                 file_data_sc_free_ci(fd);
1670                 work = work->next;
1671                 }
1672 }
1673
1674 /*
1675  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1676  * fails if fd->change does not exist or the change type does not match
1677  */
1678
1679 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1680 {
1681         FileDataChangeType type = fd->change->type;
1682         
1683         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1684                 {
1685                 FileData *ofd;
1686                 
1687                 if (!file_data_planned_change_hash)
1688                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1689                 
1690                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1691                         {
1692                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1693                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1694                         file_data_unref(fd);
1695                         }
1696
1697                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1698                 if (ofd != fd)
1699                         {
1700                         if (ofd)
1701                                 {
1702                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1703                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1704                                 file_data_unref(ofd);
1705                                 }
1706                         
1707                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1708                         file_data_ref(fd);
1709                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1710                         }
1711                 }
1712 }
1713
1714 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1715 {
1716         gchar *old_path = fd->change->dest;
1717
1718         fd->change->dest = g_strdup(dest_path);
1719         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1720         g_free(old_path);
1721 }
1722
1723 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1724 {
1725         const gchar *extension = extension_from_path(fd->change->source);
1726         gchar *base = remove_extension_from_path(dest_path);
1727         gchar *old_path = fd->change->dest;
1728         
1729         fd->change->dest = g_strconcat(base, extension, NULL);
1730         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1731         
1732         g_free(old_path);
1733         g_free(base);
1734 }
1735
1736 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1737 {
1738         GList *work;
1739         gchar *dest_path_full = NULL;
1740         
1741         if (fd->parent) fd = fd->parent;
1742         
1743         if (!dest_path)
1744                 {
1745                 dest_path = fd->path;
1746                 }
1747         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1748                 {
1749                 gchar *dir = remove_level_from_path(fd->path);
1750                 
1751                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1752                 g_free(dir);
1753                 dest_path = dest_path_full;
1754                 }
1755         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1756                 {
1757                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1758                 dest_path = dest_path_full;
1759                 }
1760                 
1761         file_data_update_ci_dest(fd, dest_path);
1762         
1763         work = fd->sidecar_files;
1764         while (work)
1765                 {
1766                 FileData *sfd = work->data;
1767                 
1768                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1769                 work = work->next;
1770                 }
1771         
1772         g_free(dest_path_full);
1773 }
1774
1775 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1776 {
1777         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1778         file_data_sc_update_ci(fd, dest_path);
1779         return TRUE;
1780 }
1781
1782 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1783 {
1784         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1785 }
1786         
1787 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1788 {
1789         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1790 }
1791
1792 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1793 {
1794         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1795 }
1796
1797 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1798 {
1799         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1800 }
1801
1802 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1803                                                       const gchar *dest,
1804                                                       gboolean (*func)(FileData *, const gchar *))
1805 {
1806         GList *work;
1807         gboolean ret = TRUE;
1808         
1809         work = fd_list;
1810         while (work)
1811                 {
1812                 FileData *fd = work->data;
1813                 
1814                 if (!func(fd, dest)) ret = FALSE;
1815                 work = work->next;
1816                 }
1817         
1818         return ret;
1819 }
1820
1821 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1822 {
1823         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1824 }
1825
1826 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1827 {
1828         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1829 }
1830
1831 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1832 {
1833         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1834 }
1835
1836
1837 /*
1838  * verify source and dest paths - dest image exists, etc.
1839  * it should detect all possible problems with the planned operation
1840  */
1841
1842 gint file_data_verify_ci(FileData *fd)
1843 {
1844         gint ret = CHANGE_OK;
1845         gchar *dir;
1846         
1847         if (!fd->change)
1848                 {
1849                 DEBUG_1("Change checked: no change info: %s", fd->path);
1850                 return ret;
1851                 }
1852
1853         if (!isname(fd->path))
1854                 {
1855                 /* this probably should not happen */
1856                 ret |= CHANGE_NO_SRC;
1857                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1858                 return ret;
1859                 }
1860                 
1861         dir = remove_level_from_path(fd->path);
1862         
1863         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1864             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1865             !access_file(fd->path, R_OK))
1866                 {
1867                 ret |= CHANGE_NO_READ_PERM;
1868                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1869                 }
1870         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1871                  !access_file(dir, W_OK))
1872                 {
1873                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1874                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1875                 }
1876         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1877                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1878                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1879                  !access_file(fd->path, W_OK))
1880                 {
1881                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1882                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1883                 }
1884         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1885            - that means that there are no hard errors and warnings can be disabled
1886            - the destination is determined during the check
1887         */
1888         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1889                 {
1890                 /* determine destination file */
1891                 gboolean have_dest = FALSE;
1892                 gchar *dest_dir = NULL;
1893                 
1894                 if (options->metadata.save_in_image_file)
1895                         {
1896                         if (file_data_can_write_directly(fd)) 
1897                                 {
1898                                 /* we can write the file directly */
1899                                 if (access_file(fd->path, W_OK))
1900                                         {
1901                                         have_dest = TRUE;
1902                                         }
1903                                 else
1904                                         {
1905                                         if (options->metadata.warn_on_write_problems)
1906                                                 {
1907                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1908                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1909                                                 }
1910                                         }
1911                                 }
1912                         else if (file_data_can_write_sidecar(fd)) 
1913                                 {
1914                                 /* we can write sidecar */
1915                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1916                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1917                                         {
1918                                         file_data_update_ci_dest(fd, sidecar);
1919                                         have_dest = TRUE;
1920                                         }
1921                                 else
1922                                         {
1923                                         if (options->metadata.warn_on_write_problems)
1924                                                 {
1925                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1926                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1927                                                 }
1928                                         }
1929                                 g_free(sidecar);
1930                                 }
1931                         }
1932                 
1933                 if (!have_dest)
1934                         {
1935                         /* write private metadata file under ~/.geeqie */
1936
1937                         /* If an existing metadata file exists, we will try writing to
1938                          * it's location regardless of the user's preference.
1939                          */
1940                         gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1941                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1942                         
1943                         if (metadata_path && !access_file(metadata_path, W_OK))
1944                                 {
1945                                 g_free(metadata_path);
1946                                 metadata_path = NULL;
1947                                 }
1948
1949                         if (!metadata_path)
1950                                 {
1951                                 mode_t mode = 0755;
1952
1953                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1954                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1955                                         {
1956                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1957                         
1958                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
1959                                         g_free(filename);
1960                                         }
1961                                 }
1962                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1963                                 {
1964                                 file_data_update_ci_dest(fd, metadata_path);
1965                                 have_dest = TRUE;
1966                                 }
1967                         else
1968                                 {
1969                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
1970                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1971                                 }
1972                         g_free(metadata_path);
1973                         }
1974                 g_free(dest_dir);
1975                 }
1976                 
1977         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1978                 {
1979                 gboolean same;
1980                 gchar *dest_dir;
1981                         
1982                 same = (strcmp(fd->path, fd->change->dest) == 0);
1983
1984                 if (!same)
1985                         {
1986                         const gchar *dest_ext = extension_from_path(fd->change->dest);
1987                         if (!dest_ext) dest_ext = "";
1988
1989                         if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
1990                                 {
1991                                 ret |= CHANGE_WARN_CHANGED_EXT;
1992                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1993                                 }
1994                         }
1995                 else
1996                         {
1997                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1998                                 {
1999                                 ret |= CHANGE_WARN_SAME;
2000                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2001                                 }
2002                         }
2003
2004                 dest_dir = remove_level_from_path(fd->change->dest);
2005
2006                 if (!isdir(dest_dir))
2007                         {
2008                         ret |= CHANGE_NO_DEST_DIR;
2009                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2010                         }
2011                 else if (!access_file(dest_dir, W_OK))
2012                         {
2013                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2014                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2015                         }
2016                 else if (!same)
2017                         {
2018                         if (isfile(fd->change->dest))
2019                                 {
2020                                 if (!access_file(fd->change->dest, W_OK))
2021                                         {
2022                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2023                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2024                                         }
2025                                 else
2026                                         {
2027                                         ret |= CHANGE_WARN_DEST_EXISTS;
2028                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2029                                         }
2030                                 }
2031                         else if (isdir(fd->change->dest))
2032                                 {
2033                                 ret |= CHANGE_DEST_EXISTS;
2034                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2035                                 }
2036                         }
2037
2038                 g_free(dest_dir);
2039                 }
2040                 
2041         fd->change->error = ret;
2042         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2043
2044         g_free(dir);
2045         return ret;
2046 }
2047
2048
2049 gint file_data_sc_verify_ci(FileData *fd)
2050 {
2051         GList *work;
2052         gint ret;
2053
2054         ret = file_data_verify_ci(fd);
2055
2056         work = fd->sidecar_files;
2057         while (work)
2058                 {
2059                 FileData *sfd = work->data;
2060
2061                 ret |= file_data_verify_ci(sfd);
2062                 work = work->next;
2063                 }
2064
2065         return ret;
2066 }
2067
2068 gchar *file_data_get_error_string(gint error)
2069 {
2070         GString *result = g_string_new("");
2071
2072         if (error & CHANGE_NO_SRC)
2073                 {
2074                 if (result->len > 0) g_string_append(result, ", ");
2075                 g_string_append(result, _("file or directory does not exist"));
2076                 }
2077
2078         if (error & CHANGE_DEST_EXISTS)
2079                 {
2080                 if (result->len > 0) g_string_append(result, ", ");
2081                 g_string_append(result, _("destination already exists"));
2082                 }
2083
2084         if (error & CHANGE_NO_WRITE_PERM_DEST)
2085                 {
2086                 if (result->len > 0) g_string_append(result, ", ");
2087                 g_string_append(result, _("destination can't be overwritten"));
2088                 }
2089
2090         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2091                 {
2092                 if (result->len > 0) g_string_append(result, ", ");
2093                 g_string_append(result, _("destination directory is not writable"));
2094                 }
2095
2096         if (error & CHANGE_NO_DEST_DIR)
2097                 {
2098                 if (result->len > 0) g_string_append(result, ", ");
2099                 g_string_append(result, _("destination directory does not exist"));
2100                 }
2101
2102         if (error & CHANGE_NO_WRITE_PERM_DIR)
2103                 {
2104                 if (result->len > 0) g_string_append(result, ", ");
2105                 g_string_append(result, _("source directory is not writable"));
2106                 }
2107
2108         if (error & CHANGE_NO_READ_PERM)
2109                 {
2110                 if (result->len > 0) g_string_append(result, ", ");
2111                 g_string_append(result, _("no read permission"));
2112                 }
2113
2114         if (error & CHANGE_WARN_NO_WRITE_PERM)
2115                 {
2116                 if (result->len > 0) g_string_append(result, ", ");
2117                 g_string_append(result, _("file is readonly"));
2118                 }
2119
2120         if (error & CHANGE_WARN_DEST_EXISTS)
2121                 {
2122                 if (result->len > 0) g_string_append(result, ", ");
2123                 g_string_append(result, _("destination already exists and will be overwritten"));
2124                 }
2125                 
2126         if (error & CHANGE_WARN_SAME)
2127                 {
2128                 if (result->len > 0) g_string_append(result, ", ");
2129                 g_string_append(result, _("source and destination are the same"));
2130                 }
2131
2132         if (error & CHANGE_WARN_CHANGED_EXT)
2133                 {
2134                 if (result->len > 0) g_string_append(result, ", ");
2135                 g_string_append(result, _("source and destination have different extension"));
2136                 }
2137
2138         return g_string_free(result, FALSE);
2139 }
2140
2141 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2142 {
2143         GList *work;
2144         gint all_errors = 0;
2145         gint common_errors = ~0;
2146         gint num;
2147         gint *errors;
2148         gint i;
2149         
2150         if (!list) return 0;
2151         
2152         num = g_list_length(list);
2153         errors = g_new(int, num);
2154         work = list;
2155         i = 0;
2156         while (work)
2157                 {
2158                 FileData *fd;
2159                 gint error;
2160
2161                 fd = work->data;
2162                 work = work->next;
2163                         
2164                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2165                 all_errors |= error;
2166                 common_errors &= error;
2167                 
2168                 errors[i] = error;
2169                 
2170                 i++;
2171                 }
2172         
2173         if (desc && all_errors)
2174                 {
2175                 GList *work;
2176                 GString *result = g_string_new("");
2177                 
2178                 if (common_errors)
2179                         {
2180                         gchar *str = file_data_get_error_string(common_errors);
2181                         g_string_append(result, str);
2182                         g_string_append(result, "\n");
2183                         g_free(str);
2184                         }
2185                 
2186                 work = list;
2187                 i = 0;
2188                 while (work)
2189                         {
2190                         FileData *fd;
2191                         gint error;
2192
2193                         fd = work->data;
2194                         work = work->next;
2195                         
2196                         error = errors[i] & ~common_errors;
2197                         
2198                         if (error)
2199                                 {
2200                                 gchar *str = file_data_get_error_string(error);
2201                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2202                                 g_free(str);
2203                                 }
2204                         i++;
2205                         }
2206                 *desc = g_string_free(result, FALSE);
2207                 }
2208
2209         g_free(errors);
2210         return all_errors;
2211 }
2212
2213
2214 /*
2215  * perform the change described by FileFataChangeInfo
2216  * it is used for internal operations,
2217  * this function actually operates with files on the filesystem
2218  * it should implement safe delete
2219  */
2220
2221 static gboolean file_data_perform_move(FileData *fd)
2222 {
2223         g_assert(!strcmp(fd->change->source, fd->path));
2224         return move_file(fd->change->source, fd->change->dest);
2225 }
2226
2227 static gboolean file_data_perform_copy(FileData *fd)
2228 {
2229         g_assert(!strcmp(fd->change->source, fd->path));
2230         return copy_file(fd->change->source, fd->change->dest);
2231 }
2232
2233 static gboolean file_data_perform_delete(FileData *fd)
2234 {
2235         if (isdir(fd->path) && !islink(fd->path))
2236                 return rmdir_utf8(fd->path);
2237         else
2238                 if (options->file_ops.safe_delete_enable)
2239                         return file_util_safe_unlink(fd->path);
2240                 else
2241                         return unlink_file(fd->path);
2242 }
2243
2244 gboolean file_data_perform_ci(FileData *fd)
2245 {
2246         FileDataChangeType type = fd->change->type;
2247
2248         switch (type)
2249                 {
2250                 case FILEDATA_CHANGE_MOVE:
2251                         return file_data_perform_move(fd);
2252                 case FILEDATA_CHANGE_COPY:
2253                         return file_data_perform_copy(fd);
2254                 case FILEDATA_CHANGE_RENAME:
2255                         return file_data_perform_move(fd); /* the same as move */
2256                 case FILEDATA_CHANGE_DELETE:
2257                         return file_data_perform_delete(fd);
2258                 case FILEDATA_CHANGE_WRITE_METADATA:
2259                         return metadata_write_perform(fd);
2260                 case FILEDATA_CHANGE_UNSPECIFIED:
2261                         /* nothing to do here */
2262                         break;
2263                 }
2264         return TRUE;
2265 }
2266
2267
2268
2269 gboolean file_data_sc_perform_ci(FileData *fd)
2270 {
2271         GList *work;
2272         gboolean ret = TRUE;
2273         FileDataChangeType type = fd->change->type;
2274         
2275         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2276
2277         work = fd->sidecar_files;
2278         while (work)
2279                 {
2280                 FileData *sfd = work->data;
2281                 
2282                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2283                 work = work->next;
2284                 }
2285         
2286         if (!file_data_perform_ci(fd)) ret = FALSE;
2287         
2288         return ret;
2289 }
2290
2291 /*
2292  * updates FileData structure according to FileDataChangeInfo
2293  */
2294
2295 gboolean file_data_apply_ci(FileData *fd)
2296 {
2297         FileDataChangeType type = fd->change->type;
2298
2299         /* FIXME delete ?*/
2300         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2301                 {
2302                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2303                 file_data_planned_change_remove(fd);
2304                 
2305                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2306                         {
2307                         /* this change overwrites another file which is already known to other modules
2308                            renaming fd would create duplicate FileData structure
2309                            the best thing we can do is nothing
2310                            FIXME: maybe we could copy stuff like marks
2311                         */
2312                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2313                         }
2314                 else
2315                         {
2316                         file_data_set_path(fd, fd->change->dest);
2317                         }
2318                 }
2319         file_data_increment_version(fd);
2320         file_data_send_notification(fd, NOTIFY_CHANGE);
2321         
2322         return TRUE;
2323 }
2324
2325 gboolean file_data_sc_apply_ci(FileData *fd)
2326 {
2327         GList *work;
2328         FileDataChangeType type = fd->change->type;
2329         
2330         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2331
2332         work = fd->sidecar_files;
2333         while (work)
2334                 {
2335                 FileData *sfd = work->data;
2336                 
2337                 file_data_apply_ci(sfd);
2338                 work = work->next;
2339                 }
2340         
2341         file_data_apply_ci(fd);
2342         
2343         return TRUE;
2344 }
2345
2346 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2347 {
2348         GList *work;
2349         if (fd->parent) fd = fd->parent;
2350         if (!g_list_find(list, fd)) return FALSE;
2351         
2352         work = fd->sidecar_files;
2353         while (work)
2354                 {
2355                 if (!g_list_find(list, work->data)) return FALSE;
2356                 work = work->next;
2357                 }
2358         return TRUE;
2359 }
2360
2361 #if 0
2362 static gboolean file_data_list_dump(GList *list)
2363 {
2364         GList *work, *work2;
2365
2366         work = list;
2367         while (work)
2368                 {
2369                 FileData *fd = work->data;
2370                 printf("%s\n", fd->name);
2371                 work2 = fd->sidecar_files;
2372                 while (work2)
2373                         {
2374                         FileData *fd = work2->data;
2375                         printf("       %s\n", fd->name);
2376                         work2 = work2->next;
2377                         }
2378                 work = work->next;
2379                 }
2380         return TRUE;
2381 }
2382 #endif
2383
2384 GList *file_data_process_groups_in_selection(GList *list, GList **ungrouped_list)
2385 {
2386         GList *out = NULL;
2387         GList *work = list;
2388
2389         /* change partial groups to independent files */
2390         while (work)
2391                 {
2392                 FileData *fd = work->data;
2393                 work = work->next;
2394                 
2395                 if (!file_data_list_contains_whole_group(list, fd)) 
2396                         {
2397                         file_data_disable_grouping(fd, TRUE);
2398                         if (ungrouped_list) 
2399                                 {
2400                                 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2401                                 }
2402                         }
2403                 }
2404         
2405         /* remove sidecars from the list, 
2406            they can be still acessed via main_fd->sidecar_files */
2407         work = list;
2408         while (work)
2409                 {
2410                 FileData *fd = work->data;
2411                 work = work->next;
2412                 
2413                 if (!fd->parent)
2414                         {
2415                         out = g_list_prepend(out, fd);
2416                         }
2417                 else
2418                         {
2419                         file_data_unref(fd);
2420                         }
2421                 }
2422                 
2423         g_list_free(list);
2424         out = g_list_reverse(out);
2425
2426         return out;
2427 }
2428
2429
2430
2431
2432
2433 /*
2434  * notify other modules about the change described by FileDataChangeInfo
2435  */
2436
2437 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2438    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2439    implementation in view_file_list.c */
2440
2441
2442
2443
2444 typedef struct _NotifyData NotifyData;
2445
2446 struct _NotifyData {
2447         FileDataNotifyFunc func;
2448         gpointer data;
2449         NotifyPriority priority;
2450 };
2451
2452 static GList *notify_func_list = NULL;
2453
2454 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2455 {
2456         NotifyData *nda = (NotifyData *)a;
2457         NotifyData *ndb = (NotifyData *)b;
2458
2459         if (nda->priority < ndb->priority) return -1;
2460         if (nda->priority > ndb->priority) return 1;
2461         return 0;
2462 }
2463
2464 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2465 {
2466         NotifyData *nd;
2467         
2468         nd = g_new(NotifyData, 1);
2469         nd->func = func;
2470         nd->data = data;
2471         nd->priority = priority;
2472
2473         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2474         DEBUG_2("Notify func registered: %p", nd);
2475         
2476         return TRUE;
2477 }
2478
2479 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2480 {
2481         GList *work = notify_func_list;
2482         
2483         while (work)
2484                 {
2485                 NotifyData *nd = (NotifyData *)work->data;
2486         
2487                 if (nd->func == func && nd->data == data)
2488                         {
2489                         notify_func_list = g_list_delete_link(notify_func_list, work);
2490                         g_free(nd);
2491                         DEBUG_2("Notify func unregistered: %p", nd);
2492                         return TRUE;
2493                         }
2494                 work = work->next;
2495                 }
2496
2497         return FALSE;
2498 }
2499
2500
2501 void file_data_send_notification(FileData *fd, NotifyType type)
2502 {
2503         GList *work = notify_func_list;
2504
2505         while (work)
2506                 {
2507                 NotifyData *nd = (NotifyData *)work->data;
2508                 
2509                 nd->func(fd, type, nd->data);
2510                 work = work->next;
2511                 }
2512 }
2513
2514 static GHashTable *file_data_monitor_pool = NULL;
2515 static guint realtime_monitor_id = 0; /* event source id */
2516
2517 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2518 {
2519         FileData *fd = key;
2520
2521         file_data_check_changed_files(fd);
2522         
2523         DEBUG_1("monitor %s", fd->path);
2524 }
2525
2526 static gboolean realtime_monitor_cb(gpointer data)
2527 {
2528         if (!options->update_on_time_change) return TRUE;
2529         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2530         return TRUE;
2531 }
2532
2533 gboolean file_data_register_real_time_monitor(FileData *fd)
2534 {
2535         gint count;
2536         
2537         file_data_ref(fd);
2538         
2539         if (!file_data_monitor_pool)
2540                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2541         
2542         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2543
2544         DEBUG_1("Register realtime %d %s", count, fd->path);
2545         
2546         count++;
2547         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2548         
2549         if (!realtime_monitor_id)
2550                 {
2551                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2552                 }
2553         
2554         return TRUE;
2555 }
2556
2557 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2558 {
2559         gint count;
2560
2561         g_assert(file_data_monitor_pool);
2562         
2563         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2564         
2565         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2566         
2567         g_assert(count > 0);
2568         
2569         count--;
2570         
2571         if (count == 0)
2572                 g_hash_table_remove(file_data_monitor_pool, fd);
2573         else
2574                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2575
2576         file_data_unref(fd);
2577         
2578         if (g_hash_table_size(file_data_monitor_pool) == 0)
2579                 {
2580                 g_source_remove(realtime_monitor_id);
2581                 realtime_monitor_id = 0;
2582                 return FALSE;
2583                 }
2584         
2585         return TRUE;
2586 }
2587 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */