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