Merge remote-tracking branch 'origin/merge-requests/5'
[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) fd = file_data_new(path_utf8, &st, TRUE);
1154         if (fd)
1155                 {
1156                 file_data_ref(fd);
1157                 }
1158
1159         filelist_free(files);
1160         g_free(dir);
1161         return fd;
1162 }
1163
1164
1165 void filelist_free(GList *list)
1166 {
1167         GList *work;
1168
1169         work = list;
1170         while (work)
1171                 {
1172                 file_data_unref((FileData *)work->data);
1173                 work = work->next;
1174                 }
1175
1176         g_list_free(list);
1177 }
1178
1179
1180 GList *filelist_copy(GList *list)
1181 {
1182         GList *new_list = NULL;
1183         GList *work;
1184
1185         work = list;
1186         while (work)
1187                 {
1188                 FileData *fd;
1189
1190                 fd = work->data;
1191                 work = work->next;
1192
1193                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1194                 }
1195
1196         return g_list_reverse(new_list);
1197 }
1198
1199 GList *filelist_from_path_list(GList *list)
1200 {
1201         GList *new_list = NULL;
1202         GList *work;
1203
1204         work = list;
1205         while (work)
1206                 {
1207                 gchar *path;
1208
1209                 path = work->data;
1210                 work = work->next;
1211
1212                 new_list = g_list_prepend(new_list, file_data_new_group(path));
1213                 }
1214
1215         return g_list_reverse(new_list);
1216 }
1217
1218 GList *filelist_to_path_list(GList *list)
1219 {
1220         GList *new_list = NULL;
1221         GList *work;
1222
1223         work = list;
1224         while (work)
1225                 {
1226                 FileData *fd;
1227
1228                 fd = work->data;
1229                 work = work->next;
1230
1231                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1232                 }
1233
1234         return g_list_reverse(new_list);
1235 }
1236
1237 GList *filelist_filter(GList *list, gboolean is_dir_list)
1238 {
1239         GList *work;
1240
1241         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1242
1243         work = list;
1244         while (work)
1245                 {
1246                 FileData *fd = (FileData *)(work->data);
1247                 const gchar *name = fd->name;
1248
1249                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1250                     (!is_dir_list && !filter_name_exists(name)) ||
1251                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1252                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1253                         {
1254                         GList *link = work;
1255
1256                         list = g_list_remove_link(list, link);
1257                         file_data_unref(fd);
1258                         g_list_free(link);
1259                         }
1260
1261                 work = work->next;
1262                 }
1263
1264         return list;
1265 }
1266
1267 /*
1268  *-----------------------------------------------------------------------------
1269  * filelist recursive
1270  *-----------------------------------------------------------------------------
1271  */
1272
1273 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1274 {
1275         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1276 }
1277
1278 GList *filelist_sort_path(GList *list)
1279 {
1280         return g_list_sort(list, filelist_sort_path_cb);
1281 }
1282
1283 static void filelist_recursive_append(GList **list, GList *dirs)
1284 {
1285         GList *work;
1286
1287         work = dirs;
1288         while (work)
1289                 {
1290                 FileData *fd = (FileData *)(work->data);
1291                 GList *f;
1292                 GList *d;
1293
1294                 if (filelist_read(fd, &f, &d))
1295                         {
1296                         f = filelist_filter(f, FALSE);
1297                         f = filelist_sort_path(f);
1298                         *list = g_list_concat(*list, f);
1299
1300                         d = filelist_filter(d, TRUE);
1301                         d = filelist_sort_path(d);
1302                         filelist_recursive_append(list, d);
1303                         filelist_free(d);
1304                         }
1305
1306                 work = work->next;
1307                 }
1308 }
1309
1310 GList *filelist_recursive(FileData *dir_fd)
1311 {
1312         GList *list;
1313         GList *d;
1314
1315         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1316         list = filelist_filter(list, FALSE);
1317         list = filelist_sort_path(list);
1318
1319         d = filelist_filter(d, TRUE);
1320         d = filelist_sort_path(d);
1321         filelist_recursive_append(&list, d);
1322         filelist_free(d);
1323
1324         return list;
1325 }
1326
1327 /*
1328  *-----------------------------------------------------------------------------
1329  * file modification support
1330  *-----------------------------------------------------------------------------
1331  */
1332
1333
1334 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1335 {
1336         if (!fdci && fd) fdci = fd->change;
1337
1338         if (!fdci) return;
1339
1340         g_free(fdci->source);
1341         g_free(fdci->dest);
1342
1343         g_free(fdci);
1344
1345         if (fd) fd->change = NULL;
1346 }
1347
1348 static gboolean file_data_can_write_directly(FileData *fd)
1349 {
1350         return filter_name_is_writable(fd->extension);
1351 }
1352
1353 static gboolean file_data_can_write_sidecar(FileData *fd)
1354 {
1355         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1356 }
1357
1358 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1359 {
1360         gchar *sidecar_path = NULL;
1361         GList *work;
1362
1363         if (!file_data_can_write_sidecar(fd)) return NULL;
1364
1365         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1366         while (work)
1367                 {
1368                 FileData *sfd = work->data;
1369                 work = work->next;
1370                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1371                         {
1372                         sidecar_path = g_strdup(sfd->path);
1373                         break;
1374                         }
1375                 }
1376
1377         if (!existing_only && !sidecar_path)
1378                 {
1379                 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1380                 sidecar_path = g_strconcat(base, ".xmp", NULL);
1381                 g_free(base);
1382                 }
1383
1384         return sidecar_path;
1385 }
1386
1387 /*
1388  * marks and orientation
1389  */
1390
1391 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1392 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1393 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1394 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1395
1396 gboolean file_data_get_mark(FileData *fd, gint n)
1397 {
1398         gboolean valid = (fd->valid_marks & (1 << n));
1399
1400         if (file_data_get_mark_func[n] && !valid)
1401                 {
1402                 guint old = fd->marks;
1403                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1404
1405                 if (!value != !(fd->marks & (1 << n)))
1406                         {
1407                         fd->marks = fd->marks ^ (1 << n);
1408                         }
1409
1410                 fd->valid_marks |= (1 << n);
1411                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1412                         {
1413                         file_data_unref(fd);
1414                         }
1415                 else if (!old && fd->marks)
1416                         {
1417                         file_data_ref(fd);
1418                         }
1419                 }
1420
1421         return !!(fd->marks & (1 << n));
1422 }
1423
1424 guint file_data_get_marks(FileData *fd)
1425 {
1426         gint i;
1427         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1428         return fd->marks;
1429 }
1430
1431 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1432 {
1433         guint old;
1434         if (!value == !file_data_get_mark(fd, n)) return;
1435
1436         if (file_data_set_mark_func[n])
1437                 {
1438                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1439                 }
1440
1441         old = fd->marks;
1442
1443         fd->marks = fd->marks ^ (1 << n);
1444
1445         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1446                 {
1447                 file_data_unref(fd);
1448                 }
1449         else if (!old && fd->marks)
1450                 {
1451                 file_data_ref(fd);
1452                 }
1453
1454         file_data_increment_version(fd);
1455         file_data_send_notification(fd, NOTIFY_MARKS);
1456 }
1457
1458 gboolean file_data_filter_marks(FileData *fd, guint filter)
1459 {
1460         gint i;
1461         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1462         return ((fd->marks & filter) == filter);
1463 }
1464
1465 GList *file_data_filter_marks_list(GList *list, guint filter)
1466 {
1467         GList *work;
1468
1469         work = list;
1470         while (work)
1471                 {
1472                 FileData *fd = work->data;
1473                 GList *link = work;
1474                 work = work->next;
1475
1476                 if (!file_data_filter_marks(fd, filter))
1477                         {
1478                         list = g_list_remove_link(list, link);
1479                         file_data_unref(fd);
1480                         g_list_free(link);
1481                         }
1482                 }
1483
1484         return list;
1485 }
1486
1487 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1488 {
1489         FileData *fd = value;
1490         file_data_increment_version(fd);
1491         file_data_send_notification(fd, NOTIFY_MARKS);
1492 }
1493
1494 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1495 {
1496         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1497
1498         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1499
1500         file_data_get_mark_func[n] = get_mark_func;
1501         file_data_set_mark_func[n] = set_mark_func;
1502         file_data_mark_func_data[n] = data;
1503         file_data_destroy_mark_func[n] = notify;
1504
1505         if (get_mark_func)
1506                 {
1507                 /* this effectively changes all known files */
1508                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1509                 }
1510
1511         return TRUE;
1512 }
1513
1514 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1515 {
1516         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1517         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1518         if (data) *data = file_data_mark_func_data[n];
1519 }
1520
1521 gint file_data_get_user_orientation(FileData *fd)
1522 {
1523         return fd->user_orientation;
1524 }
1525
1526 void file_data_set_user_orientation(FileData *fd, gint value)
1527 {
1528         if (fd->user_orientation == value) return;
1529
1530         fd->user_orientation = value;
1531         file_data_increment_version(fd);
1532         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1533 }
1534
1535
1536 /*
1537  * file_data    - operates on the given fd
1538  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1539  */
1540
1541
1542 /* return list of sidecar file extensions in a string */
1543 gchar *file_data_sc_list_to_string(FileData *fd)
1544 {
1545         GList *work;
1546         GString *result = g_string_new("");
1547
1548         work = fd->sidecar_files;
1549         while (work)
1550                 {
1551                 FileData *sfd = work->data;
1552
1553                 result = g_string_append(result, "+ ");
1554                 result = g_string_append(result, sfd->extension);
1555                 work = work->next;
1556                 if (work) result = g_string_append_c(result, ' ');
1557                 }
1558
1559         return g_string_free(result, FALSE);
1560 }
1561
1562
1563
1564 /*
1565  * add FileDataChangeInfo (see typedefs.h) for the given operation
1566  * uses file_data_add_change_info
1567  *
1568  * fails if the fd->change already exists - change operations can't run in parallel
1569  * fd->change_info works as a lock
1570  *
1571  * dest can be NULL - in this case the current name is used for now, it will
1572  * be changed later
1573  */
1574
1575 /*
1576    FileDataChangeInfo types:
1577    COPY
1578    MOVE   - path is changed, name may be changed too
1579    RENAME - path remains unchanged, name is changed
1580             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1581             sidecar names are changed too, extensions are not changed
1582    DELETE
1583    UPDATE - file size, date or grouping has been changed
1584 */
1585
1586 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1587 {
1588         FileDataChangeInfo *fdci;
1589
1590         if (fd->change) return FALSE;
1591
1592         fdci = g_new0(FileDataChangeInfo, 1);
1593
1594         fdci->type = type;
1595
1596         if (src)
1597                 fdci->source = g_strdup(src);
1598         else
1599                 fdci->source = g_strdup(fd->path);
1600
1601         if (dest)
1602                 fdci->dest = g_strdup(dest);
1603
1604         fd->change = fdci;
1605
1606         return TRUE;
1607 }
1608
1609 static void file_data_planned_change_remove(FileData *fd)
1610 {
1611         if (file_data_planned_change_hash &&
1612             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1613                 {
1614                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1615                         {
1616                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1617                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1618                         file_data_unref(fd);
1619                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1620                                 {
1621                                 g_hash_table_destroy(file_data_planned_change_hash);
1622                                 file_data_planned_change_hash = NULL;
1623                                 DEBUG_1("planned change: empty");
1624                                 }
1625                         }
1626                 }
1627 }
1628
1629
1630 void file_data_free_ci(FileData *fd)
1631 {
1632         FileDataChangeInfo *fdci = fd->change;
1633
1634         if (!fdci) return;
1635
1636         file_data_planned_change_remove(fd);
1637
1638         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1639
1640         g_free(fdci->source);
1641         g_free(fdci->dest);
1642
1643         g_free(fdci);
1644
1645         fd->change = NULL;
1646 }
1647
1648 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1649 {
1650         FileDataChangeInfo *fdci = fd->change;
1651         if (!fdci) return;
1652         fdci->regroup_when_finished = enable;
1653 }
1654
1655 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1656 {
1657         GList *work;
1658
1659         if (fd->parent) fd = fd->parent;
1660
1661         if (fd->change) return FALSE;
1662
1663         work = fd->sidecar_files;
1664         while (work)
1665                 {
1666                 FileData *sfd = work->data;
1667
1668                 if (sfd->change) return FALSE;
1669                 work = work->next;
1670                 }
1671
1672         file_data_add_ci(fd, type, NULL, NULL);
1673
1674         work = fd->sidecar_files;
1675         while (work)
1676                 {
1677                 FileData *sfd = work->data;
1678
1679                 file_data_add_ci(sfd, type, NULL, NULL);
1680                 work = work->next;
1681                 }
1682
1683         return TRUE;
1684 }
1685
1686 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1687 {
1688         GList *work;
1689
1690         if (fd->parent) fd = fd->parent;
1691
1692         if (!fd->change || fd->change->type != type) return FALSE;
1693
1694         work = fd->sidecar_files;
1695         while (work)
1696                 {
1697                 FileData *sfd = work->data;
1698
1699                 if (!sfd->change || sfd->change->type != type) return FALSE;
1700                 work = work->next;
1701                 }
1702
1703         return TRUE;
1704 }
1705
1706
1707 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1708 {
1709         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1710         file_data_sc_update_ci_copy(fd, dest_path);
1711         return TRUE;
1712 }
1713
1714 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1715 {
1716         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1717         file_data_sc_update_ci_move(fd, dest_path);
1718         return TRUE;
1719 }
1720
1721 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1722 {
1723         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1724         file_data_sc_update_ci_rename(fd, dest_path);
1725         return TRUE;
1726 }
1727
1728 gboolean file_data_sc_add_ci_delete(FileData *fd)
1729 {
1730         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1731 }
1732
1733 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1734 {
1735         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1736         file_data_sc_update_ci_unspecified(fd, dest_path);
1737         return TRUE;
1738 }
1739
1740 gboolean file_data_add_ci_write_metadata(FileData *fd)
1741 {
1742         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1743 }
1744
1745 void file_data_sc_free_ci(FileData *fd)
1746 {
1747         GList *work;
1748
1749         if (fd->parent) fd = fd->parent;
1750
1751         file_data_free_ci(fd);
1752
1753         work = fd->sidecar_files;
1754         while (work)
1755                 {
1756                 FileData *sfd = work->data;
1757
1758                 file_data_free_ci(sfd);
1759                 work = work->next;
1760                 }
1761 }
1762
1763 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1764 {
1765         GList *work;
1766         gboolean ret = TRUE;
1767
1768         work = fd_list;
1769         while (work)
1770                 {
1771                 FileData *fd = work->data;
1772
1773                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1774                 work = work->next;
1775                 }
1776
1777         return ret;
1778 }
1779
1780 static void file_data_sc_revert_ci_list(GList *fd_list)
1781 {
1782         GList *work;
1783
1784         work = fd_list;
1785         while (work)
1786                 {
1787                 FileData *fd = work->data;
1788
1789                 file_data_sc_free_ci(fd);
1790                 work = work->prev;
1791                 }
1792 }
1793
1794 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1795 {
1796         GList *work;
1797
1798         work = fd_list;
1799         while (work)
1800                 {
1801                 FileData *fd = work->data;
1802
1803                 if (!func(fd, dest))
1804                         {
1805                         file_data_sc_revert_ci_list(work->prev);
1806                         return FALSE;
1807                         }
1808                 work = work->next;
1809                 }
1810
1811         return TRUE;
1812 }
1813
1814 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1815 {
1816         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1817 }
1818
1819 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1820 {
1821         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1822 }
1823
1824 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1825 {
1826         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1827 }
1828
1829 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1830 {
1831         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1832 }
1833
1834 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1835 {
1836         GList *work;
1837         gboolean ret = TRUE;
1838
1839         work = fd_list;
1840         while (work)
1841                 {
1842                 FileData *fd = work->data;
1843
1844                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1845                 work = work->next;
1846                 }
1847
1848         return ret;
1849 }
1850
1851 void file_data_free_ci_list(GList *fd_list)
1852 {
1853         GList *work;
1854
1855         work = fd_list;
1856         while (work)
1857                 {
1858                 FileData *fd = work->data;
1859
1860                 file_data_free_ci(fd);
1861                 work = work->next;
1862                 }
1863 }
1864
1865 void file_data_sc_free_ci_list(GList *fd_list)
1866 {
1867         GList *work;
1868
1869         work = fd_list;
1870         while (work)
1871                 {
1872                 FileData *fd = work->data;
1873
1874                 file_data_sc_free_ci(fd);
1875                 work = work->next;
1876                 }
1877 }
1878
1879 /*
1880  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1881  * fails if fd->change does not exist or the change type does not match
1882  */
1883
1884 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1885 {
1886         FileDataChangeType type = fd->change->type;
1887
1888         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1889                 {
1890                 FileData *ofd;
1891
1892                 if (!file_data_planned_change_hash)
1893                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1894
1895                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1896                         {
1897                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1898                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1899                         file_data_unref(fd);
1900                         }
1901
1902                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1903                 if (ofd != fd)
1904                         {
1905                         if (ofd)
1906                                 {
1907                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1908                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1909                                 file_data_unref(ofd);
1910                                 }
1911
1912                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1913                         file_data_ref(fd);
1914                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1915                         }
1916                 }
1917 }
1918
1919 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1920 {
1921         gchar *old_path = fd->change->dest;
1922
1923         fd->change->dest = g_strdup(dest_path);
1924         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1925         g_free(old_path);
1926 }
1927
1928 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1929 {
1930         const gchar *extension = extension_from_path(fd->change->source);
1931         gchar *base = remove_extension_from_path(dest_path);
1932         gchar *old_path = fd->change->dest;
1933
1934         fd->change->dest = g_strconcat(base, extension, NULL);
1935         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1936
1937         g_free(old_path);
1938         g_free(base);
1939 }
1940
1941 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1942 {
1943         GList *work;
1944         gchar *dest_path_full = NULL;
1945
1946         if (fd->parent) fd = fd->parent;
1947
1948         if (!dest_path)
1949                 {
1950                 dest_path = fd->path;
1951                 }
1952         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1953                 {
1954                 gchar *dir = remove_level_from_path(fd->path);
1955
1956                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1957                 g_free(dir);
1958                 dest_path = dest_path_full;
1959                 }
1960         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1961                 {
1962                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1963                 dest_path = dest_path_full;
1964                 }
1965
1966         file_data_update_ci_dest(fd, dest_path);
1967
1968         work = fd->sidecar_files;
1969         while (work)
1970                 {
1971                 FileData *sfd = work->data;
1972
1973                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1974                 work = work->next;
1975                 }
1976
1977         g_free(dest_path_full);
1978 }
1979
1980 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1981 {
1982         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1983         file_data_sc_update_ci(fd, dest_path);
1984         return TRUE;
1985 }
1986
1987 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1988 {
1989         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1990 }
1991
1992 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1993 {
1994         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1995 }
1996
1997 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1998 {
1999         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2000 }
2001
2002 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2003 {
2004         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2005 }
2006
2007 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2008                                                       const gchar *dest,
2009                                                       gboolean (*func)(FileData *, const gchar *))
2010 {
2011         GList *work;
2012         gboolean ret = TRUE;
2013
2014         work = fd_list;
2015         while (work)
2016                 {
2017                 FileData *fd = work->data;
2018
2019                 if (!func(fd, dest)) ret = FALSE;
2020                 work = work->next;
2021                 }
2022
2023         return ret;
2024 }
2025
2026 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2027 {
2028         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2029 }
2030
2031 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2032 {
2033         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2034 }
2035
2036 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2037 {
2038         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2039 }
2040
2041
2042 /*
2043  * verify source and dest paths - dest image exists, etc.
2044  * it should detect all possible problems with the planned operation
2045  */
2046
2047 gint file_data_verify_ci(FileData *fd)
2048 {
2049         gint ret = CHANGE_OK;
2050         gchar *dir;
2051
2052         if (!fd->change)
2053                 {
2054                 DEBUG_1("Change checked: no change info: %s", fd->path);
2055                 return ret;
2056                 }
2057
2058         if (!isname(fd->path))
2059                 {
2060                 /* this probably should not happen */
2061                 ret |= CHANGE_NO_SRC;
2062                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2063                 return ret;
2064                 }
2065
2066         dir = remove_level_from_path(fd->path);
2067
2068         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2069             fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2070             fd->change->type != FILEDATA_CHANGE_RENAME &&
2071             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2072             fd->modified_xmp)
2073                 {
2074                 ret |= CHANGE_WARN_UNSAVED_META;
2075                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2076                 }
2077
2078         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2079             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2080             !access_file(fd->path, R_OK))
2081                 {
2082                 ret |= CHANGE_NO_READ_PERM;
2083                 DEBUG_1("Change checked: no read permission: %s", fd->path);
2084                 }
2085         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2086                  !access_file(dir, W_OK))
2087                 {
2088                 ret |= CHANGE_NO_WRITE_PERM_DIR;
2089                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2090                 }
2091         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2092                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2093                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2094                  !access_file(fd->path, W_OK))
2095                 {
2096                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2097                 DEBUG_1("Change checked: no write permission: %s", fd->path);
2098                 }
2099         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2100            - that means that there are no hard errors and warnings can be disabled
2101            - the destination is determined during the check
2102         */
2103         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2104                 {
2105                 /* determine destination file */
2106                 gboolean have_dest = FALSE;
2107                 gchar *dest_dir = NULL;
2108
2109                 if (options->metadata.save_in_image_file)
2110                         {
2111                         if (file_data_can_write_directly(fd))
2112                                 {
2113                                 /* we can write the file directly */
2114                                 if (access_file(fd->path, W_OK))
2115                                         {
2116                                         have_dest = TRUE;
2117                                         }
2118                                 else
2119                                         {
2120                                         if (options->metadata.warn_on_write_problems)
2121                                                 {
2122                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2123                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2124                                                 }
2125                                         }
2126                                 }
2127                         else if (file_data_can_write_sidecar(fd))
2128                                 {
2129                                 /* we can write sidecar */
2130                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2131                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2132                                         {
2133                                         file_data_update_ci_dest(fd, sidecar);
2134                                         have_dest = TRUE;
2135                                         }
2136                                 else
2137                                         {
2138                                         if (options->metadata.warn_on_write_problems)
2139                                                 {
2140                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2141                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2142                                                 }
2143                                         }
2144                                 g_free(sidecar);
2145                                 }
2146                         }
2147
2148                 if (!have_dest)
2149                         {
2150                         /* write private metadata file under ~/.geeqie */
2151
2152                         /* If an existing metadata file exists, we will try writing to
2153                          * it's location regardless of the user's preference.
2154                          */
2155                         gchar *metadata_path = NULL;
2156 #ifdef HAVE_EXIV2
2157                         /* but ignore XMP if we are not able to write it */
2158                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2159 #endif
2160                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2161
2162                         if (metadata_path && !access_file(metadata_path, W_OK))
2163                                 {
2164                                 g_free(metadata_path);
2165                                 metadata_path = NULL;
2166                                 }
2167
2168                         if (!metadata_path)
2169                                 {
2170                                 mode_t mode = 0755;
2171
2172                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2173                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2174                                         {
2175                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2176
2177                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
2178                                         g_free(filename);
2179                                         }
2180                                 }
2181                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2182                                 {
2183                                 file_data_update_ci_dest(fd, metadata_path);
2184                                 have_dest = TRUE;
2185                                 }
2186                         else
2187                                 {
2188                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
2189                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2190                                 }
2191                         g_free(metadata_path);
2192                         }
2193                 g_free(dest_dir);
2194                 }
2195
2196         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2197                 {
2198                 gboolean same;
2199                 gchar *dest_dir;
2200
2201                 same = (strcmp(fd->path, fd->change->dest) == 0);
2202
2203                 if (!same)
2204                         {
2205                         const gchar *dest_ext = extension_from_path(fd->change->dest);
2206                         if (!dest_ext) dest_ext = "";
2207
2208                         if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2209                                 {
2210                                 ret |= CHANGE_WARN_CHANGED_EXT;
2211                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2212                                 }
2213                         }
2214                 else
2215                         {
2216                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2217                                 {
2218                                 ret |= CHANGE_WARN_SAME;
2219                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2220                                 }
2221                         }
2222
2223                 dest_dir = remove_level_from_path(fd->change->dest);
2224
2225                 if (!isdir(dest_dir))
2226                         {
2227                         ret |= CHANGE_NO_DEST_DIR;
2228                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2229                         }
2230                 else if (!access_file(dest_dir, W_OK))
2231                         {
2232                         ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2233                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2234                         }
2235                 else if (!same)
2236                         {
2237                         if (isfile(fd->change->dest))
2238                                 {
2239                                 if (!access_file(fd->change->dest, W_OK))
2240                                         {
2241                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2242                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2243                                         }
2244                                 else
2245                                         {
2246                                         ret |= CHANGE_WARN_DEST_EXISTS;
2247                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2248                                         }
2249                                 }
2250                         else if (isdir(fd->change->dest))
2251                                 {
2252                                 ret |= CHANGE_DEST_EXISTS;
2253                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2254                                 }
2255                         }
2256
2257                 g_free(dest_dir);
2258                 }
2259
2260         fd->change->error = ret;
2261         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2262
2263         g_free(dir);
2264         return ret;
2265 }
2266
2267
2268 gint file_data_sc_verify_ci(FileData *fd)
2269 {
2270         GList *work;
2271         gint ret;
2272
2273         ret = file_data_verify_ci(fd);
2274
2275         work = fd->sidecar_files;
2276         while (work)
2277                 {
2278                 FileData *sfd = work->data;
2279
2280                 ret |= file_data_verify_ci(sfd);
2281                 work = work->next;
2282                 }
2283
2284         return ret;
2285 }
2286
2287 gchar *file_data_get_error_string(gint error)
2288 {
2289         GString *result = g_string_new("");
2290
2291         if (error & CHANGE_NO_SRC)
2292                 {
2293                 if (result->len > 0) g_string_append(result, ", ");
2294                 g_string_append(result, _("file or directory does not exist"));
2295                 }
2296
2297         if (error & CHANGE_DEST_EXISTS)
2298                 {
2299                 if (result->len > 0) g_string_append(result, ", ");
2300                 g_string_append(result, _("destination already exists"));
2301                 }
2302
2303         if (error & CHANGE_NO_WRITE_PERM_DEST)
2304                 {
2305                 if (result->len > 0) g_string_append(result, ", ");
2306                 g_string_append(result, _("destination can't be overwritten"));
2307                 }
2308
2309         if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2310                 {
2311                 if (result->len > 0) g_string_append(result, ", ");
2312                 g_string_append(result, _("destination directory is not writable"));
2313                 }
2314
2315         if (error & CHANGE_NO_DEST_DIR)
2316                 {
2317                 if (result->len > 0) g_string_append(result, ", ");
2318                 g_string_append(result, _("destination directory does not exist"));
2319                 }
2320
2321         if (error & CHANGE_NO_WRITE_PERM_DIR)
2322                 {
2323                 if (result->len > 0) g_string_append(result, ", ");
2324                 g_string_append(result, _("source directory is not writable"));
2325                 }
2326
2327         if (error & CHANGE_NO_READ_PERM)
2328                 {
2329                 if (result->len > 0) g_string_append(result, ", ");
2330                 g_string_append(result, _("no read permission"));
2331                 }
2332
2333         if (error & CHANGE_WARN_NO_WRITE_PERM)
2334                 {
2335                 if (result->len > 0) g_string_append(result, ", ");
2336                 g_string_append(result, _("file is readonly"));
2337                 }
2338
2339         if (error & CHANGE_WARN_DEST_EXISTS)
2340                 {
2341                 if (result->len > 0) g_string_append(result, ", ");
2342                 g_string_append(result, _("destination already exists and will be overwritten"));
2343                 }
2344
2345         if (error & CHANGE_WARN_SAME)
2346                 {
2347                 if (result->len > 0) g_string_append(result, ", ");
2348                 g_string_append(result, _("source and destination are the same"));
2349                 }
2350
2351         if (error & CHANGE_WARN_CHANGED_EXT)
2352                 {
2353                 if (result->len > 0) g_string_append(result, ", ");
2354                 g_string_append(result, _("source and destination have different extension"));
2355                 }
2356
2357         if (error & CHANGE_WARN_UNSAVED_META)
2358                 {
2359                 if (result->len > 0) g_string_append(result, ", ");
2360                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2361                 }
2362
2363         return g_string_free(result, FALSE);
2364 }
2365
2366 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2367 {
2368         GList *work;
2369         gint all_errors = 0;
2370         gint common_errors = ~0;
2371         gint num;
2372         gint *errors;
2373         gint i;
2374
2375         if (!list) return 0;
2376
2377         num = g_list_length(list);
2378         errors = g_new(int, num);
2379         work = list;
2380         i = 0;
2381         while (work)
2382                 {
2383                 FileData *fd;
2384                 gint error;
2385
2386                 fd = work->data;
2387                 work = work->next;
2388
2389                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2390                 all_errors |= error;
2391                 common_errors &= error;
2392
2393                 errors[i] = error;
2394
2395                 i++;
2396                 }
2397
2398         if (desc && all_errors)
2399                 {
2400                 GList *work;
2401                 GString *result = g_string_new("");
2402
2403                 if (common_errors)
2404                         {
2405                         gchar *str = file_data_get_error_string(common_errors);
2406                         g_string_append(result, str);
2407                         g_string_append(result, "\n");
2408                         g_free(str);
2409                         }
2410
2411                 work = list;
2412                 i = 0;
2413                 while (work)
2414                         {
2415                         FileData *fd;
2416                         gint error;
2417
2418                         fd = work->data;
2419                         work = work->next;
2420
2421                         error = errors[i] & ~common_errors;
2422
2423                         if (error)
2424                                 {
2425                                 gchar *str = file_data_get_error_string(error);
2426                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2427                                 g_free(str);
2428                                 }
2429                         i++;
2430                         }
2431                 *desc = g_string_free(result, FALSE);
2432                 }
2433
2434         g_free(errors);
2435         return all_errors;
2436 }
2437
2438
2439 /*
2440  * perform the change described by FileFataChangeInfo
2441  * it is used for internal operations,
2442  * this function actually operates with files on the filesystem
2443  * it should implement safe delete
2444  */
2445
2446 static gboolean file_data_perform_move(FileData *fd)
2447 {
2448         g_assert(!strcmp(fd->change->source, fd->path));
2449         return move_file(fd->change->source, fd->change->dest);
2450 }
2451
2452 static gboolean file_data_perform_copy(FileData *fd)
2453 {
2454         g_assert(!strcmp(fd->change->source, fd->path));
2455         return copy_file(fd->change->source, fd->change->dest);
2456 }
2457
2458 static gboolean file_data_perform_delete(FileData *fd)
2459 {
2460         if (isdir(fd->path) && !islink(fd->path))
2461                 return rmdir_utf8(fd->path);
2462         else
2463                 if (options->file_ops.safe_delete_enable)
2464                         return file_util_safe_unlink(fd->path);
2465                 else
2466                         return unlink_file(fd->path);
2467 }
2468
2469 gboolean file_data_perform_ci(FileData *fd)
2470 {
2471         FileDataChangeType type = fd->change->type;
2472
2473         switch (type)
2474                 {
2475                 case FILEDATA_CHANGE_MOVE:
2476                         return file_data_perform_move(fd);
2477                 case FILEDATA_CHANGE_COPY:
2478                         return file_data_perform_copy(fd);
2479                 case FILEDATA_CHANGE_RENAME:
2480                         return file_data_perform_move(fd); /* the same as move */
2481                 case FILEDATA_CHANGE_DELETE:
2482                         return file_data_perform_delete(fd);
2483                 case FILEDATA_CHANGE_WRITE_METADATA:
2484                         return metadata_write_perform(fd);
2485                 case FILEDATA_CHANGE_UNSPECIFIED:
2486                         /* nothing to do here */
2487                         break;
2488                 }
2489         return TRUE;
2490 }
2491
2492
2493
2494 gboolean file_data_sc_perform_ci(FileData *fd)
2495 {
2496         GList *work;
2497         gboolean ret = TRUE;
2498         FileDataChangeType type = fd->change->type;
2499
2500         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2501
2502         work = fd->sidecar_files;
2503         while (work)
2504                 {
2505                 FileData *sfd = work->data;
2506
2507                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2508                 work = work->next;
2509                 }
2510
2511         if (!file_data_perform_ci(fd)) ret = FALSE;
2512
2513         return ret;
2514 }
2515
2516 /*
2517  * updates FileData structure according to FileDataChangeInfo
2518  */
2519
2520 gboolean file_data_apply_ci(FileData *fd)
2521 {
2522         FileDataChangeType type = fd->change->type;
2523
2524         /* FIXME delete ?*/
2525         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2526                 {
2527                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2528                 file_data_planned_change_remove(fd);
2529
2530                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2531                         {
2532                         /* this change overwrites another file which is already known to other modules
2533                            renaming fd would create duplicate FileData structure
2534                            the best thing we can do is nothing
2535                            FIXME: maybe we could copy stuff like marks
2536                         */
2537                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2538                         }
2539                 else
2540                         {
2541                         file_data_set_path(fd, fd->change->dest);
2542                         }
2543                 }
2544         file_data_increment_version(fd);
2545         file_data_send_notification(fd, NOTIFY_CHANGE);
2546
2547         return TRUE;
2548 }
2549
2550 gboolean file_data_sc_apply_ci(FileData *fd)
2551 {
2552         GList *work;
2553         FileDataChangeType type = fd->change->type;
2554
2555         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2556
2557         work = fd->sidecar_files;
2558         while (work)
2559                 {
2560                 FileData *sfd = work->data;
2561
2562                 file_data_apply_ci(sfd);
2563                 work = work->next;
2564                 }
2565
2566         file_data_apply_ci(fd);
2567
2568         return TRUE;
2569 }
2570
2571 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2572 {
2573         GList *work;
2574         if (fd->parent) fd = fd->parent;
2575         if (!g_list_find(list, fd)) return FALSE;
2576
2577         work = fd->sidecar_files;
2578         while (work)
2579                 {
2580                 if (!g_list_find(list, work->data)) return FALSE;
2581                 work = work->next;
2582                 }
2583         return TRUE;
2584 }
2585
2586 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2587 {
2588         GList *out = NULL;
2589         GList *work = list;
2590
2591         /* change partial groups to independent files */
2592         if (ungroup)
2593                 {
2594                 while (work)
2595                         {
2596                         FileData *fd = work->data;
2597                         work = work->next;
2598
2599                         if (!file_data_list_contains_whole_group(list, fd))
2600                                 {
2601                                 file_data_disable_grouping(fd, TRUE);
2602                                 if (ungrouped_list)
2603                                         {
2604                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2605                                         }
2606                                 }
2607                         }
2608                 }
2609
2610         /* remove sidecars from the list,
2611            they can be still acessed via main_fd->sidecar_files */
2612         work = list;
2613         while (work)
2614                 {
2615                 FileData *fd = work->data;
2616                 work = work->next;
2617
2618                 if (!fd->parent ||
2619                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2620                         {
2621                         out = g_list_prepend(out, file_data_ref(fd));
2622                         }
2623                 }
2624
2625         filelist_free(list);
2626         out = g_list_reverse(out);
2627
2628         return out;
2629 }
2630
2631
2632
2633
2634
2635 /*
2636  * notify other modules about the change described by FileDataChangeInfo
2637  */
2638
2639 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2640    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2641    implementation in view_file_list.c */
2642
2643
2644 typedef struct _NotifyIdleData NotifyIdleData;
2645
2646 struct _NotifyIdleData {
2647         FileData *fd;
2648         NotifyType type;
2649 };
2650
2651
2652 typedef struct _NotifyData NotifyData;
2653
2654 struct _NotifyData {
2655         FileDataNotifyFunc func;
2656         gpointer data;
2657         NotifyPriority priority;
2658 };
2659
2660 static GList *notify_func_list = NULL;
2661
2662 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2663 {
2664         NotifyData *nda = (NotifyData *)a;
2665         NotifyData *ndb = (NotifyData *)b;
2666
2667         if (nda->priority < ndb->priority) return -1;
2668         if (nda->priority > ndb->priority) return 1;
2669         return 0;
2670 }
2671
2672 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2673 {
2674         NotifyData *nd;
2675         GList *work = notify_func_list;
2676
2677         while (work)
2678                 {
2679                 NotifyData *nd = (NotifyData *)work->data;
2680
2681                 if (nd->func == func && nd->data == data)
2682                         {
2683                         g_warning("Notify func already registered");
2684                         return FALSE;
2685                         }
2686                 work = work->next;
2687                 }
2688
2689         nd = g_new(NotifyData, 1);
2690         nd->func = func;
2691         nd->data = data;
2692         nd->priority = priority;
2693
2694         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2695         DEBUG_2("Notify func registered: %p", nd);
2696
2697         return TRUE;
2698 }
2699
2700 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2701 {
2702         GList *work = notify_func_list;
2703
2704         while (work)
2705                 {
2706                 NotifyData *nd = (NotifyData *)work->data;
2707
2708                 if (nd->func == func && nd->data == data)
2709                         {
2710                         notify_func_list = g_list_delete_link(notify_func_list, work);
2711                         g_free(nd);
2712                         DEBUG_2("Notify func unregistered: %p", nd);
2713                         return TRUE;
2714                         }
2715                 work = work->next;
2716                 }
2717
2718         g_warning("Notify func not found");
2719         return FALSE;
2720 }
2721
2722
2723 gboolean file_data_send_notification_idle_cb(gpointer data)
2724 {
2725         NotifyIdleData *nid = (NotifyIdleData *)data;
2726         GList *work = notify_func_list;
2727
2728         while (work)
2729                 {
2730                 NotifyData *nd = (NotifyData *)work->data;
2731
2732                 nd->func(nid->fd, nid->type, nd->data);
2733                 work = work->next;
2734                 }
2735         file_data_unref(nid->fd);
2736         g_free(nid);
2737         return FALSE;
2738 }
2739
2740 void file_data_send_notification(FileData *fd, NotifyType type)
2741 {
2742         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2743         nid->fd = file_data_ref(fd);
2744         nid->type = type;
2745         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2746 }
2747
2748 static GHashTable *file_data_monitor_pool = NULL;
2749 static guint realtime_monitor_id = 0; /* event source id */
2750
2751 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2752 {
2753         FileData *fd = key;
2754
2755         file_data_check_changed_files(fd);
2756
2757         DEBUG_1("monitor %s", fd->path);
2758 }
2759
2760 static gboolean realtime_monitor_cb(gpointer data)
2761 {
2762         if (!options->update_on_time_change) return TRUE;
2763         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2764         return TRUE;
2765 }
2766
2767 gboolean file_data_register_real_time_monitor(FileData *fd)
2768 {
2769         gint count;
2770
2771         file_data_ref(fd);
2772
2773         if (!file_data_monitor_pool)
2774                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2775
2776         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2777
2778         DEBUG_1("Register realtime %d %s", count, fd->path);
2779
2780         count++;
2781         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2782
2783         if (!realtime_monitor_id)
2784                 {
2785                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2786                 }
2787
2788         return TRUE;
2789 }
2790
2791 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2792 {
2793         gint count;
2794
2795         g_assert(file_data_monitor_pool);
2796
2797         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2798
2799         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2800
2801         g_assert(count > 0);
2802
2803         count--;
2804
2805         if (count == 0)
2806                 g_hash_table_remove(file_data_monitor_pool, fd);
2807         else
2808                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2809
2810         file_data_unref(fd);
2811
2812         if (g_hash_table_size(file_data_monitor_pool) == 0)
2813                 {
2814                 g_source_remove(realtime_monitor_id);
2815                 realtime_monitor_id = 0;
2816                 return FALSE;
2817                 }
2818
2819         return TRUE;
2820 }
2821 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */