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