use the workflow in utilops.c for metadata approving and writting
[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 /*
1127  * file_data    - operates on the given fd
1128  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1129  */
1130
1131
1132 /* return list of sidecar file extensions in a string */
1133 gchar *file_data_sc_list_to_string(FileData *fd)
1134 {
1135         GList *work;
1136         GString *result = g_string_new("");
1137
1138         work = fd->sidecar_files;
1139         while (work)
1140                 {
1141                 FileData *sfd = work->data;
1142
1143                 result = g_string_append(result, "+ ");
1144                 result = g_string_append(result, sfd->extension);
1145                 work = work->next;
1146                 if (work) result = g_string_append_c(result, ' ');
1147                 }
1148
1149         return g_string_free(result, FALSE);
1150 }
1151
1152
1153
1154 /*
1155  * add FileDataChangeInfo (see typedefs.h) for the given operation
1156  * uses file_data_add_change_info
1157  *
1158  * fails if the fd->change already exists - change operations can't run in parallel
1159  * fd->change_info works as a lock
1160  *
1161  * dest can be NULL - in this case the current name is used for now, it will
1162  * be changed later
1163  */
1164
1165 /*
1166    FileDataChangeInfo types:
1167    COPY
1168    MOVE   - path is changed, name may be changed too
1169    RENAME - path remains unchanged, name is changed
1170             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1171             sidecar names are changed too, extensions are not changed
1172    DELETE
1173    UPDATE - file size, date or grouping has been changed
1174 */
1175
1176 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1177 {
1178         FileDataChangeInfo *fdci;
1179
1180         if (fd->change) return FALSE;
1181
1182         fdci = g_new0(FileDataChangeInfo, 1);
1183
1184         fdci->type = type;
1185
1186         if (src)
1187                 fdci->source = g_strdup(src);
1188         else
1189                 fdci->source = g_strdup(fd->path);
1190
1191         if (dest)
1192                 fdci->dest = g_strdup(dest);
1193
1194         fd->change = fdci;
1195         
1196         return TRUE;
1197 }
1198
1199 static void file_data_planned_change_remove(FileData *fd)
1200 {
1201         if (file_data_planned_change_hash &&
1202             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1203                 {
1204                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1205                         {
1206                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1207                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1208                         file_data_unref(fd);
1209                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1210                                 {
1211                                 g_hash_table_destroy(file_data_planned_change_hash);
1212                                 file_data_planned_change_hash = NULL;
1213                                 DEBUG_1("planned change: empty");
1214                                 }
1215                         }
1216                 }
1217 }
1218
1219
1220 void file_data_free_ci(FileData *fd)
1221 {
1222         FileDataChangeInfo *fdci = fd->change;
1223
1224         if (!fdci)
1225                 return;
1226
1227         file_data_planned_change_remove(fd);
1228
1229         g_free(fdci->source);
1230         g_free(fdci->dest);
1231
1232         g_free(fdci);
1233
1234         fd->change = NULL;
1235 }
1236
1237
1238 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1239 {
1240         GList *work;
1241
1242         if (fd->parent) fd = fd->parent;
1243         
1244         if (fd->change) return FALSE;
1245         
1246         work = fd->sidecar_files;
1247         while (work)
1248                 {
1249                 FileData *sfd = work->data;
1250                 
1251                 if (sfd->change) return FALSE;
1252                 work = work->next;
1253                 }
1254
1255         file_data_add_ci(fd, type, NULL, NULL);
1256         
1257         work = fd->sidecar_files;
1258         while (work)
1259                 {
1260                 FileData *sfd = work->data;
1261                 
1262                 file_data_add_ci(sfd, type, NULL, NULL);
1263                 work = work->next;
1264                 }
1265                 
1266         return TRUE;
1267 }
1268
1269 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1270 {
1271         GList *work;
1272         
1273         if (fd->parent) fd = fd->parent;
1274         
1275         if (!fd->change || fd->change->type != type) return FALSE;
1276         
1277         work = fd->sidecar_files;
1278         while (work)
1279                 {
1280                 FileData *sfd = work->data;
1281
1282                 if (!sfd->change || sfd->change->type != type) return FALSE;
1283                 work = work->next;
1284                 }
1285
1286         return TRUE;
1287 }
1288
1289
1290 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1291 {
1292         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1293         file_data_sc_update_ci_copy(fd, dest_path);
1294         return TRUE;
1295 }
1296
1297 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1298 {
1299         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1300         file_data_sc_update_ci_move(fd, dest_path);
1301         return TRUE;
1302 }
1303
1304 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1305 {
1306         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1307         file_data_sc_update_ci_rename(fd, dest_path);
1308         return TRUE;
1309 }
1310
1311 gboolean file_data_sc_add_ci_delete(FileData *fd)
1312 {
1313         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1314 }
1315
1316 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1317 {
1318         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1319         file_data_sc_update_ci_unspecified(fd, dest_path);
1320         return TRUE;
1321 }
1322
1323 gboolean file_data_add_ci_write_metadata(FileData *fd)
1324 {
1325         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1326 }
1327
1328 void file_data_sc_free_ci(FileData *fd)
1329 {
1330         GList *work;
1331
1332         if (fd->parent) fd = fd->parent;
1333         
1334         file_data_free_ci(fd);
1335         
1336         work = fd->sidecar_files;
1337         while (work)
1338                 {
1339                 FileData *sfd = work->data;
1340         
1341                 file_data_free_ci(sfd);
1342                 work = work->next;
1343                 }
1344 }
1345
1346 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1347 {
1348         GList *work;
1349         gboolean ret = TRUE;
1350
1351         work = fd_list;
1352         while (work)
1353                 {
1354                 FileData *fd = work->data;
1355         
1356                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1357                 work = work->next;
1358                 }
1359
1360         return ret;
1361 }
1362
1363 static void file_data_sc_revert_ci_list(GList *fd_list)
1364 {
1365         GList *work;
1366         
1367         work = fd_list;
1368         while (work)
1369                 {
1370                 FileData *fd = work->data;
1371                 
1372                 file_data_sc_free_ci(fd);
1373                 work = work->prev;
1374                 }
1375 }
1376
1377 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1378 {
1379         GList *work;
1380         
1381         work = fd_list;
1382         while (work)
1383                 {
1384                 FileData *fd = work->data;
1385                 
1386                 if (!func(fd, dest))
1387                         {
1388                         file_data_sc_revert_ci_list(work->prev);
1389                         return FALSE;
1390                         }
1391                 work = work->next;
1392                 }
1393         
1394         return TRUE;
1395 }
1396
1397 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1398 {
1399         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1400 }
1401
1402 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1403 {
1404         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1405 }
1406
1407 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1408 {
1409         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1410 }
1411
1412 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1413 {
1414         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1415 }
1416
1417 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1418 {
1419         GList *work;
1420         gboolean ret = TRUE;
1421
1422         work = fd_list;
1423         while (work)
1424                 {
1425                 FileData *fd = work->data;
1426         
1427                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1428                 work = work->next;
1429                 }
1430
1431         return ret;
1432 }
1433
1434 void file_data_free_ci_list(GList *fd_list)
1435 {
1436         GList *work;
1437         
1438         work = fd_list;
1439         while (work)
1440                 {
1441                 FileData *fd = work->data;
1442                 
1443                 file_data_free_ci(fd);
1444                 work = work->next;
1445                 }
1446 }
1447
1448 void file_data_sc_free_ci_list(GList *fd_list)
1449 {
1450         GList *work;
1451         
1452         work = fd_list;
1453         while (work)
1454                 {
1455                 FileData *fd = work->data;
1456                 
1457                 file_data_sc_free_ci(fd);
1458                 work = work->next;
1459                 }
1460 }
1461
1462 /*
1463  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1464  * fails if fd->change does not exist or the change type does not match
1465  */
1466
1467 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1468 {
1469         FileDataChangeType type = fd->change->type;
1470         
1471         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1472                 {
1473                 FileData *ofd;
1474                 
1475                 if (!file_data_planned_change_hash)
1476                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1477                 
1478                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1479                         {
1480                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1481                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1482                         file_data_unref(fd);
1483                         }
1484
1485                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1486                 if (ofd != fd)
1487                         {
1488                         if (ofd)
1489                                 {
1490                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1491                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1492                                 file_data_unref(ofd);
1493                                 }
1494                         
1495                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1496                         file_data_ref(fd);
1497                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1498                         }
1499                 }
1500 }
1501
1502 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1503 {
1504         gchar *old_path = fd->change->dest;
1505
1506         fd->change->dest = g_strdup(dest_path);
1507         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1508         g_free(old_path);
1509 }
1510
1511 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1512 {
1513         const gchar *extension = extension_from_path(fd->change->source);
1514         gchar *base = remove_extension_from_path(dest_path);
1515         gchar *old_path = fd->change->dest;
1516         
1517         fd->change->dest = g_strconcat(base, extension, NULL);
1518         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1519         
1520         g_free(old_path);
1521         g_free(base);
1522 }
1523
1524 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1525 {
1526         GList *work;
1527         gchar *dest_path_full = NULL;
1528         
1529         if (fd->parent) fd = fd->parent;
1530         
1531         if (!dest_path)
1532                 {
1533                 dest_path = fd->path;
1534                 }
1535         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1536                 {
1537                 gchar *dir = remove_level_from_path(fd->path);
1538                 
1539                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1540                 g_free(dir);
1541                 dest_path = dest_path_full;
1542                 }
1543         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1544                 {
1545                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1546                 dest_path = dest_path_full;
1547                 }
1548                 
1549         file_data_update_ci_dest(fd, dest_path);
1550         
1551         work = fd->sidecar_files;
1552         while (work)
1553                 {
1554                 FileData *sfd = work->data;
1555                 
1556                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1557                 work = work->next;
1558                 }
1559         
1560         g_free(dest_path_full);
1561 }
1562
1563 static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1564 {
1565         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1566         file_data_sc_update_ci(fd, dest_path);
1567         return TRUE;
1568 }
1569
1570 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1571 {
1572         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1573 }
1574         
1575 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1576 {
1577         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1578 }
1579
1580 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1581 {
1582         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1583 }
1584
1585 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1586 {
1587         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1588 }
1589
1590 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1591                                                       const gchar *dest,
1592                                                       gboolean (*func)(FileData *, const gchar *))
1593 {
1594         GList *work;
1595         gboolean ret = TRUE;
1596         
1597         work = fd_list;
1598         while (work)
1599                 {
1600                 FileData *fd = work->data;
1601                 
1602                 if (!func(fd, dest)) ret = FALSE;
1603                 work = work->next;
1604                 }
1605         
1606         return ret;
1607 }
1608
1609 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1610 {
1611         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1612 }
1613
1614 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1615 {
1616         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1617 }
1618
1619 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1620 {
1621         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1622 }
1623
1624
1625 /*
1626  * verify source and dest paths - dest image exists, etc.
1627  * it should detect all possible problems with the planned operation
1628  */
1629
1630 gint file_data_verify_ci(FileData *fd)
1631 {
1632         gint ret = CHANGE_OK;
1633         gchar *dir;
1634         
1635         if (!fd->change)
1636                 {
1637                 DEBUG_1("Change checked: no change info: %s", fd->path);
1638                 return ret;
1639                 }
1640
1641         if (!isname(fd->path))
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             !access_file(fd->path, R_OK))
1653                 {
1654                 ret |= CHANGE_NO_READ_PERM;
1655                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1656                 }
1657         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1658                  !access_file(dir, W_OK))
1659                 {
1660                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1661                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1662                 }
1663         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1664                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1665                  !access_file(fd->path, W_OK))
1666                 {
1667                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1668                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1669                 }
1670
1671         if (fd->change->dest)
1672                 {
1673                 gboolean same;
1674                 gchar *dest_dir;
1675                         
1676                 same = (strcmp(fd->path, fd->change->dest) == 0);
1677
1678                 if (!same)
1679                         {
1680                         const gchar *dest_ext = extension_from_path(fd->change->dest);
1681                         if (!dest_ext) dest_ext = "";
1682
1683                         if (strcasecmp(fd->extension, dest_ext) != 0)
1684                                 {
1685                                 ret |= CHANGE_WARN_CHANGED_EXT;
1686                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1687                                 }
1688                         }
1689                 else
1690                         {
1691                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1692                                 {
1693                                 ret |= CHANGE_WARN_SAME;
1694                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1695                                 }
1696                         }
1697
1698                 dest_dir = remove_level_from_path(fd->change->dest);
1699
1700                 if (!isdir(dest_dir))
1701                         {
1702                         ret |= CHANGE_NO_DEST_DIR;
1703                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1704                         }
1705                 else if (!access_file(dest_dir, W_OK))
1706                         {
1707                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1708                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1709                         }
1710                 else if (!same)
1711                         {
1712                         if (isfile(fd->change->dest))
1713                                 {
1714                                 if (!access_file(fd->change->dest, W_OK))
1715                                         {
1716                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
1717                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1718                                         }
1719                                 else
1720                                         {
1721                                         ret |= CHANGE_WARN_DEST_EXISTS;
1722                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1723                                         }
1724                                 }
1725                         else if (isdir(fd->change->dest))
1726                                 {
1727                                 ret |= CHANGE_DEST_EXISTS;
1728                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1729                                 }
1730                         }
1731
1732                 g_free(dest_dir);
1733                 }
1734                 
1735         fd->change->error = ret;
1736         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1737
1738         g_free(dir);
1739         return ret;
1740 }
1741
1742
1743 gint file_data_sc_verify_ci(FileData *fd)
1744 {
1745         GList *work;
1746         gint ret;
1747
1748         ret = file_data_verify_ci(fd);
1749
1750         work = fd->sidecar_files;
1751         while (work)
1752                 {
1753                 FileData *sfd = work->data;
1754
1755                 ret |= file_data_verify_ci(sfd);
1756                 work = work->next;
1757                 }
1758
1759         return ret;
1760 }
1761
1762 gchar *file_data_get_error_string(gint error)
1763 {
1764         GString *result = g_string_new("");
1765
1766         if (error & CHANGE_NO_SRC)
1767                 {
1768                 if (result->len > 0) g_string_append(result, ", ");
1769                 g_string_append(result, _("file or directory does not exist"));
1770                 }
1771
1772         if (error & CHANGE_DEST_EXISTS)
1773                 {
1774                 if (result->len > 0) g_string_append(result, ", ");
1775                 g_string_append(result, _("destination already exists"));
1776                 }
1777
1778         if (error & CHANGE_NO_WRITE_PERM_DEST)
1779                 {
1780                 if (result->len > 0) g_string_append(result, ", ");
1781                 g_string_append(result, _("destination can't be overwritten"));
1782                 }
1783
1784         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1785                 {
1786                 if (result->len > 0) g_string_append(result, ", ");
1787                 g_string_append(result, _("destination directory is not writable"));
1788                 }
1789
1790         if (error & CHANGE_NO_DEST_DIR)
1791                 {
1792                 if (result->len > 0) g_string_append(result, ", ");
1793                 g_string_append(result, _("destination directory does not exist"));
1794                 }
1795
1796         if (error & CHANGE_NO_WRITE_PERM_DIR)
1797                 {
1798                 if (result->len > 0) g_string_append(result, ", ");
1799                 g_string_append(result, _("source directory is not writable"));
1800                 }
1801
1802         if (error & CHANGE_NO_READ_PERM)
1803                 {
1804                 if (result->len > 0) g_string_append(result, ", ");
1805                 g_string_append(result, _("no read permission"));
1806                 }
1807
1808         if (error & CHANGE_WARN_NO_WRITE_PERM)
1809                 {
1810                 if (result->len > 0) g_string_append(result, ", ");
1811                 g_string_append(result, _("file is readonly"));
1812                 }
1813
1814         if (error & CHANGE_WARN_DEST_EXISTS)
1815                 {
1816                 if (result->len > 0) g_string_append(result, ", ");
1817                 g_string_append(result, _("destination already exists and will be overwritten"));
1818                 }
1819                 
1820         if (error & CHANGE_WARN_SAME)
1821                 {
1822                 if (result->len > 0) g_string_append(result, ", ");
1823                 g_string_append(result, _("source and destination are the same"));
1824                 }
1825
1826         if (error & CHANGE_WARN_CHANGED_EXT)
1827                 {
1828                 if (result->len > 0) g_string_append(result, ", ");
1829                 g_string_append(result, _("source and destination have different extension"));
1830                 }
1831
1832         return g_string_free(result, FALSE);
1833 }
1834
1835 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
1836 {
1837         GList *work;
1838         gint all_errors = 0;
1839         gint common_errors = ~0;
1840         gint num;
1841         gint *errors;
1842         gint i;
1843         
1844         if (!list) return 0;
1845         
1846         num = g_list_length(list);
1847         errors = g_new(int, num);
1848         work = list;
1849         i = 0;
1850         while (work)
1851                 {
1852                 FileData *fd;
1853                 gint error;
1854
1855                 fd = work->data;
1856                 work = work->next;
1857                         
1858                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
1859                 all_errors |= error;
1860                 common_errors &= error;
1861                 
1862                 errors[i] = error;
1863                 
1864                 i++;
1865                 }
1866         
1867         if (desc && all_errors)
1868                 {
1869                 GList *work;
1870                 GString *result = g_string_new("");
1871                 
1872                 if (common_errors)
1873                         {
1874                         gchar *str = file_data_get_error_string(common_errors);
1875                         g_string_append(result, str);
1876                         g_string_append(result, "\n");
1877                         g_free(str);
1878                         }
1879                 
1880                 work = list;
1881                 i = 0;
1882                 while (work)
1883                         {
1884                         FileData *fd;
1885                         gint error;
1886
1887                         fd = work->data;
1888                         work = work->next;
1889                         
1890                         error = errors[i] & ~common_errors;
1891                         
1892                         if (error)
1893                                 {
1894                                 gchar *str = file_data_get_error_string(error);
1895                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1896                                 g_free(str);
1897                                 }
1898                         i++;
1899                         }
1900                 *desc = g_string_free(result, FALSE);
1901                 }
1902
1903         g_free(errors);
1904         return all_errors;
1905 }
1906
1907
1908 /*
1909  * perform the change described by FileFataChangeInfo
1910  * it is used for internal operations,
1911  * this function actually operates with files on the filesystem
1912  * it should implement safe delete
1913  */
1914
1915 static gboolean file_data_perform_move(FileData *fd)
1916 {
1917         g_assert(!strcmp(fd->change->source, fd->path));
1918         return move_file(fd->change->source, fd->change->dest);
1919 }
1920
1921 static gboolean file_data_perform_copy(FileData *fd)
1922 {
1923         g_assert(!strcmp(fd->change->source, fd->path));
1924         return copy_file(fd->change->source, fd->change->dest);
1925 }
1926
1927 static gboolean file_data_perform_delete(FileData *fd)
1928 {
1929         if (isdir(fd->path) && !islink(fd->path))
1930                 return rmdir_utf8(fd->path);
1931         else
1932                 return unlink_file(fd->path);
1933 }
1934
1935 gboolean file_data_perform_ci(FileData *fd)
1936 {
1937         FileDataChangeType type = fd->change->type;
1938         switch (type)
1939                 {
1940                 case FILEDATA_CHANGE_MOVE:
1941                         return file_data_perform_move(fd);
1942                 case FILEDATA_CHANGE_COPY:
1943                         return file_data_perform_copy(fd);
1944                 case FILEDATA_CHANGE_RENAME:
1945                         return file_data_perform_move(fd); /* the same as move */
1946                 case FILEDATA_CHANGE_DELETE:
1947                         return file_data_perform_delete(fd);
1948                 case FILEDATA_CHANGE_WRITE_METADATA:
1949                         return metadata_write_perform(fd);
1950                 case FILEDATA_CHANGE_UNSPECIFIED:
1951                         /* nothing to do here */
1952                         break;
1953                 }
1954         return TRUE;
1955 }
1956
1957
1958
1959 gboolean file_data_sc_perform_ci(FileData *fd)
1960 {
1961         GList *work;
1962         gboolean ret = TRUE;
1963         FileDataChangeType type = fd->change->type;
1964         
1965         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1966
1967         work = fd->sidecar_files;
1968         while (work)
1969                 {
1970                 FileData *sfd = work->data;
1971                 
1972                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1973                 work = work->next;
1974                 }
1975         
1976         if (!file_data_perform_ci(fd)) ret = FALSE;
1977         
1978         return ret;
1979 }
1980
1981 /*
1982  * updates FileData structure according to FileDataChangeInfo
1983  */
1984
1985 gint file_data_apply_ci(FileData *fd)
1986 {
1987         FileDataChangeType type = fd->change->type;
1988
1989         /* FIXME delete ?*/
1990         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1991                 {
1992                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1993                 file_data_planned_change_remove(fd);
1994                 
1995                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1996                         {
1997                         /* this change overwrites another file which is already known to other modules
1998                            renaming fd would create duplicate FileData structure
1999                            the best thing we can do is nothing
2000                            FIXME: maybe we could copy stuff like marks
2001                         */
2002                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2003                         }
2004                 else
2005                         {
2006                         file_data_set_path(fd, fd->change->dest);
2007                         }
2008                 }
2009         file_data_increment_version(fd);
2010         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
2011         
2012         return TRUE;
2013 }
2014
2015 gint file_data_sc_apply_ci(FileData *fd)
2016 {
2017         GList *work;
2018         FileDataChangeType type = fd->change->type;
2019         
2020         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2021
2022         work = fd->sidecar_files;
2023         while (work)
2024                 {
2025                 FileData *sfd = work->data;
2026                 
2027                 file_data_apply_ci(sfd);
2028                 work = work->next;
2029                 }
2030         
2031         file_data_apply_ci(fd);
2032         
2033         return TRUE;
2034 }
2035
2036 /*
2037  * notify other modules about the change described by FileFataChangeInfo
2038  */
2039
2040 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2041    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2042    implementation in view_file_list.c */
2043
2044
2045
2046
2047 typedef struct _NotifyData NotifyData;
2048
2049 struct _NotifyData {
2050         FileDataNotifyFunc func;
2051         gpointer data;
2052         NotifyPriority priority;
2053         };
2054
2055 static GList *notify_func_list = NULL;
2056
2057 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2058 {
2059         NotifyData *nda = (NotifyData *)a;
2060         NotifyData *ndb = (NotifyData *)b;
2061
2062         if (nda->priority < ndb->priority) return -1;
2063         if (nda->priority > ndb->priority) return 1;
2064         return 0;
2065 }
2066
2067 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2068 {
2069         NotifyData *nd;
2070         
2071         nd = g_new(NotifyData, 1);
2072         nd->func = func;
2073         nd->data = data;
2074         nd->priority = priority;
2075
2076         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2077         DEBUG_1("Notify func registered: %p", nd);
2078         
2079         return TRUE;
2080 }
2081
2082 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2083 {
2084         GList *work = notify_func_list;
2085         
2086         while (work)
2087                 {
2088                 NotifyData *nd = (NotifyData *)work->data;
2089         
2090                 if (nd->func == func && nd->data == data)
2091                         {
2092                         notify_func_list = g_list_delete_link(notify_func_list, work);
2093                         g_free(nd);
2094                         DEBUG_1("Notify func unregistered: %p", nd);
2095                         return TRUE;
2096                         }
2097                 work = work->next;
2098                 }
2099
2100         return FALSE;
2101 }
2102
2103
2104 void file_data_send_notification(FileData *fd, NotifyType type)
2105 {
2106         GList *work = notify_func_list;
2107
2108         while (work)
2109                 {
2110                 NotifyData *nd = (NotifyData *)work->data;
2111                 
2112                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2113                 nd->func(fd, type, nd->data);
2114                 work = work->next;
2115                 }
2116 }
2117
2118 static GHashTable *file_data_monitor_pool = NULL;
2119 static gint realtime_monitor_id = -1;
2120
2121 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2122 {
2123         FileData *fd = key;
2124
2125         file_data_check_changed_files(fd);
2126         
2127         DEBUG_1("monitor %s", fd->path);
2128 }
2129
2130 static gboolean realtime_monitor_cb(gpointer data)
2131 {
2132         if (!options->update_on_time_change) return TRUE;
2133         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2134         return TRUE;
2135 }
2136
2137 gint file_data_register_real_time_monitor(FileData *fd)
2138 {
2139         gint count;
2140         
2141         file_data_ref(fd);
2142         
2143         if (!file_data_monitor_pool)
2144                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2145         
2146         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2147
2148         DEBUG_1("Register realtime %d %s", count, fd->path);
2149         
2150         count++;
2151         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2152         
2153         if (realtime_monitor_id == -1)
2154                 {
2155                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2156                 }
2157         
2158         return TRUE;
2159 }
2160
2161 gint file_data_unregister_real_time_monitor(FileData *fd)
2162 {
2163         gint count;
2164
2165         g_assert(file_data_monitor_pool);
2166         
2167         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2168         
2169         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2170         
2171         g_assert(count > 0);
2172         
2173         count--;
2174         
2175         if (count == 0)
2176                 g_hash_table_remove(file_data_monitor_pool, fd);
2177         else
2178                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2179
2180         file_data_unref(fd);
2181         
2182         if (g_hash_table_size(file_data_monitor_pool) == 0)
2183                 {
2184                 g_source_remove(realtime_monitor_id);
2185                 realtime_monitor_id = -1;
2186                 return FALSE;
2187                 }
2188         
2189         return TRUE;
2190 }
2191 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */