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