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