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