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