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