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