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