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