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