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