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