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