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