make sure fd is referenced
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2012 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 #include "metadata.h"
22 #include "trash.h"
23 #include "histogram.h"
24
25 #include <errno.h>
26
27 static GHashTable *file_data_pool = NULL;
28 static GHashTable *file_data_planned_change_hash = NULL;
29
30 static gint sidecar_file_priority(const gchar *extension);
31 static void file_data_check_sidecars(const GList *basename_list);
32 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
33
34
35
36 /*
37  *-----------------------------------------------------------------------------
38  * text conversion utils
39  *-----------------------------------------------------------------------------
40  */
41
42 gchar *text_from_size(gint64 size)
43 {
44         gchar *a, *b;
45         gchar *s, *d;
46         gint l, n, i;
47
48         /* what I would like to use is printf("%'d", size)
49          * BUT: not supported on every libc :(
50          */
51         if (size > G_MAXUINT)
52                 {
53                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
54                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
55                 }
56         else
57                 {
58                 a = g_strdup_printf("%d", (guint)size);
59                 }
60         l = strlen(a);
61         n = (l - 1)/ 3;
62         if (n < 1) return a;
63
64         b = g_new(gchar, l + n + 1);
65
66         s = a;
67         d = b;
68         i = l - n * 3;
69         while (*s != '\0')
70                 {
71                 if (i < 1)
72                         {
73                         i = 3;
74                         *d = ',';
75                         d++;
76                         }
77
78                 *d = *s;
79                 s++;
80                 d++;
81                 i--;
82                 }
83         *d = '\0';
84
85         g_free(a);
86         return b;
87 }
88
89 gchar *text_from_size_abrev(gint64 size)
90 {
91         if (size < (gint64)1024)
92                 {
93                 return g_strdup_printf(_("%d bytes"), (gint)size);
94                 }
95         if (size < (gint64)1048576)
96                 {
97                 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
98                 }
99         if (size < (gint64)1073741824)
100                 {
101                 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
102                 }
103
104         /* to avoid overflowing the gdouble, do division in two steps */
105         size /= 1048576;
106         return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
107 }
108
109 /* note: returned string is valid until next call to text_from_time() */
110 const gchar *text_from_time(time_t t)
111 {
112         static gchar *ret = NULL;
113         gchar buf[128];
114         gint buflen;
115         struct tm *btime;
116         GError *error = NULL;
117
118         btime = localtime(&t);
119
120         /* the %x warning about 2 digit years is not an error */
121         buflen = strftime(buf, sizeof(buf), "%x %X", btime);
122         if (buflen < 1) return "";
123
124         g_free(ret);
125         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
126         if (error)
127                 {
128                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
129                 g_error_free(error);
130                 return "";
131                 }
132
133         return ret;
134 }
135
136 /*
137  *-----------------------------------------------------------------------------
138  * changed files detection and notification 
139  *-----------------------------------------------------------------------------
140  */
141
142 void file_data_increment_version(FileData *fd)
143 {
144         fd->version++;
145         fd->valid_marks = 0;
146         if (fd->parent) 
147                 {
148                 fd->parent->version++;
149                 fd->parent->valid_marks = 0;
150                 }
151 }
152
153 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
154 {
155         if (fd->size != st->st_size ||
156             fd->date != st->st_mtime)
157                 {
158                 fd->size = st->st_size;
159                 fd->date = st->st_mtime;
160                 fd->mode = st->st_mode;
161                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
162                 fd->thumb_pixbuf = NULL;
163                 file_data_increment_version(fd);
164                 file_data_send_notification(fd, NOTIFY_REREAD);
165                 return TRUE;
166                 }
167         return FALSE;
168 }
169
170 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
171 {
172         gboolean ret = FALSE;
173         GList *work;
174         
175         ret = file_data_check_changed_single_file(fd, st);
176
177         work = fd->sidecar_files;
178         while (work)
179                 {
180                 FileData *sfd = work->data;
181                 struct stat st;
182                 work = work->next;
183
184                 if (!stat_utf8(sfd->path, &st))
185                         {
186                         fd->size = 0;
187                         fd->date = 0;
188                         file_data_ref(sfd);
189                         file_data_disconnect_sidecar_file(fd, sfd);
190                         ret = TRUE;
191                         file_data_increment_version(sfd);
192                         file_data_send_notification(sfd, NOTIFY_REREAD);
193                         file_data_unref(sfd);
194                         continue;
195                         }
196
197                 ret |= file_data_check_changed_files_recursive(sfd, &st);
198                 }
199         return ret;
200 }
201
202
203 gboolean file_data_check_changed_files(FileData *fd)
204 {
205         gboolean ret = FALSE;
206         struct stat st;
207         
208         if (fd->parent) fd = fd->parent;
209
210         if (!stat_utf8(fd->path, &st))
211                 {
212                 GList *sidecars;
213                 GList *work;
214                 FileData *sfd = NULL;
215
216                 /* parent is missing, we have to rebuild whole group */
217                 ret = TRUE;
218                 fd->size = 0;
219                 fd->date = 0;
220                 
221                 /* file_data_disconnect_sidecar_file might delete the file,
222                    we have to keep the reference to prevent this */
223                 sidecars = filelist_copy(fd->sidecar_files);
224                 file_data_ref(fd);
225                 work = sidecars;
226                 while (work)
227                         {
228                         sfd = work->data;
229                         work = work->next;
230                 
231                         file_data_disconnect_sidecar_file(fd, sfd);
232                         }
233                 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
234                 /* now we can release the sidecars */
235                 filelist_free(sidecars);
236                 file_data_increment_version(fd);
237                 file_data_send_notification(fd, NOTIFY_REREAD);
238                 file_data_unref(fd);
239                 }
240         else
241                 {
242                 ret |= file_data_check_changed_files_recursive(fd, &st);
243                 }
244
245         return ret;
246 }
247
248 /*
249  *-----------------------------------------------------------------------------
250  * file name, extension, sorting, ... 
251  *-----------------------------------------------------------------------------
252  */
253
254 static void file_data_set_collate_keys(FileData *fd)
255 {
256         gchar *caseless_name;
257         gchar *valid_name;
258
259         valid_name = g_filename_display_name(fd->name);
260         caseless_name = g_utf8_casefold(valid_name, -1);
261
262         g_free(fd->collate_key_name);
263         g_free(fd->collate_key_name_nocase);
264
265 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
266         fd->collate_key_name = g_utf8_collate_key_for_filename(valid_name, -1);
267         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
268 #else
269         fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
270         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
271 #endif
272         g_free(valid_name);
273         g_free(caseless_name);
274 }
275
276 static void file_data_set_path(FileData *fd, const gchar *path)
277 {
278         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
279         g_assert(file_data_pool);
280
281         g_free(fd->path);
282
283         if (fd->original_path)
284                 {
285                 g_hash_table_remove(file_data_pool, fd->original_path);
286                 g_free(fd->original_path);
287                 }
288
289         g_assert(!g_hash_table_lookup(file_data_pool, path));
290
291         fd->original_path = g_strdup(path);
292         g_hash_table_insert(file_data_pool, fd->original_path, fd);
293
294         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
295                 {
296                 fd->path = g_strdup(path);
297                 fd->name = fd->path;
298                 fd->extension = fd->name + 1;
299                 file_data_set_collate_keys(fd);
300                 return;
301                 }
302
303         fd->path = g_strdup(path);
304         fd->name = filename_from_path(fd->path);
305
306         if (strcmp(fd->name, "..") == 0)
307                 {
308                 gchar *dir = remove_level_from_path(path);
309                 g_free(fd->path);
310                 fd->path = remove_level_from_path(dir);
311                 g_free(dir);
312                 fd->name = "..";
313                 fd->extension = fd->name + 2;
314                 file_data_set_collate_keys(fd);
315                 return;
316                 }
317         else if (strcmp(fd->name, ".") == 0)
318                 {
319                 g_free(fd->path);
320                 fd->path = remove_level_from_path(path);
321                 fd->name = ".";
322                 fd->extension = fd->name + 1;
323                 file_data_set_collate_keys(fd);
324                 return;
325                 }
326
327         fd->extension = registered_extension_from_path(fd->path);
328         if (fd->extension == NULL)
329                 {
330                 fd->extension = fd->name + strlen(fd->name);
331                 }
332                 
333         fd->sidecar_priority = sidecar_file_priority(fd->extension);
334         file_data_set_collate_keys(fd);
335 }
336
337 /*
338  *-----------------------------------------------------------------------------
339  * create or reuse Filedata
340  *-----------------------------------------------------------------------------
341  */
342
343 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
344 {
345         FileData *fd;
346
347         DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
348
349         if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE; 
350
351         if (!file_data_pool)
352                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
353
354         fd = g_hash_table_lookup(file_data_pool, path_utf8);
355         if (fd)
356                 {
357                 file_data_ref(fd);
358                 }
359                 
360         if (!fd && file_data_planned_change_hash)
361                 {
362                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
363                 if (fd)
364                         {
365                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
366                         file_data_ref(fd);
367                         file_data_apply_ci(fd);
368                         }
369                 }
370                 
371         if (fd)
372                 {
373                 gboolean changed;
374                 
375                 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
376                 
377                 
378                 changed = file_data_check_changed_single_file(fd, st);
379
380                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
381                 
382                 return fd;
383                 }
384
385         fd = g_new0(FileData, 1);
386         
387         fd->size = st->st_size;
388         fd->date = st->st_mtime;
389         fd->mode = st->st_mode;
390         fd->ref = 1;
391         fd->magick = 0x12345678;
392         
393         if (disable_sidecars) fd->disable_grouping = TRUE;
394
395         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
396
397         return fd;
398 }
399
400 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
401 {
402         gchar *path_utf8 = path_to_utf8(path);
403         FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
404
405         g_free(path_utf8);
406         return ret;
407 }
408
409 FileData *file_data_new_no_grouping(const gchar *path_utf8)
410 {
411         struct stat st;
412
413         if (!stat_utf8(path_utf8, &st))
414                 {
415                 st.st_size = 0;
416                 st.st_mtime = 0;
417                 }
418
419         return file_data_new(path_utf8, &st, TRUE);
420 }
421
422 FileData *file_data_new_dir(const gchar *path_utf8)
423 {
424         struct stat st;
425
426         if (!stat_utf8(path_utf8, &st))
427                 {
428                 st.st_size = 0;
429                 st.st_mtime = 0;
430                 }
431         else
432                 /* dir or non-existing yet */
433                 g_assert(S_ISDIR(st.st_mode));
434                 
435         return file_data_new(path_utf8, &st, TRUE);
436 }
437
438 /*
439  *-----------------------------------------------------------------------------
440  * reference counting
441  *-----------------------------------------------------------------------------
442  */
443
444 #ifdef DEBUG_FILEDATA
445 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
446 #else
447 FileData *file_data_ref(FileData *fd)
448 #endif
449 {
450         if (fd == NULL) return NULL;
451 #ifdef DEBUG_FILEDATA
452         if (fd->magick != 0x12345678)
453                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
454 #endif
455         g_assert(fd->magick == 0x12345678);
456         fd->ref++;
457
458 #ifdef DEBUG_FILEDATA
459         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
460 #else
461         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
462 #endif
463         return fd;
464 }
465
466 static void file_data_free(FileData *fd)
467 {
468         g_assert(fd->magick == 0x12345678);
469         g_assert(fd->ref == 0);
470
471         metadata_cache_free(fd);
472         g_hash_table_remove(file_data_pool, fd->original_path);
473
474         g_free(fd->path);
475         g_free(fd->original_path);
476         g_free(fd->collate_key_name);
477         g_free(fd->collate_key_name_nocase);
478         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
479         histmap_free(fd->histmap);
480         
481         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
482
483         file_data_change_info_free(NULL, fd);
484         g_free(fd);
485 }
486
487 #ifdef DEBUG_FILEDATA
488 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
489 #else
490 void file_data_unref(FileData *fd)
491 #endif
492 {
493         if (fd == NULL) return;
494 #ifdef DEBUG_FILEDATA
495         if (fd->magick != 0x12345678)
496                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
497 #endif
498         g_assert(fd->magick == 0x12345678);
499         
500         fd->ref--;
501 #ifdef DEBUG_FILEDATA
502         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
503 #else
504         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
505 #endif
506         if (fd->ref == 0)
507                 {
508                 GList *work;
509                 FileData *parent = fd->parent ? fd->parent : fd;
510                 
511                 if (parent->ref > 0) return;
512
513                 work = parent->sidecar_files;
514                 while (work)
515                         {
516                         FileData *sfd = work->data;
517                         if (sfd->ref > 0) return;
518                         work = work->next;
519                         }
520
521                 /* none of parent/children is referenced, we can free everything */
522
523                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
524
525                 work = parent->sidecar_files;
526                 while (work)
527                         {
528                         FileData *sfd = work->data;
529                         file_data_free(sfd);
530                         work = work->next;
531                         }
532
533                 g_list_free(parent->sidecar_files);
534                 parent->sidecar_files = NULL;
535
536                 file_data_free(parent);
537                 }
538 }
539
540
541
542 /*
543  *-----------------------------------------------------------------------------
544  * sidecar file info struct
545  *-----------------------------------------------------------------------------
546  */
547
548 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
549 {
550         const FileData *fda = a;
551         const FileData *fdb = b;
552         
553         if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
554         if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
555         
556         return strcmp(fdb->extension, fda->extension);
557 }
558
559
560 static gint sidecar_file_priority(const gchar *extension)
561 {
562         gint i = 1;
563         GList *work;
564
565         if (extension == NULL)
566                 return 0;
567
568         work = sidecar_ext_get_list();
569
570         while (work) {
571                 gchar *ext = work->data;
572                 
573                 work = work->next;
574                 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
575                 i++;
576         }
577         return 0;
578 }
579
580 static void file_data_check_sidecars(const GList *basename_list)
581 {
582         /* basename_list contains the new group - first is the parent, then sorted sidecars */
583         /* all files in the list have ref count > 0 */ 
584
585         const GList *work;
586         GList *s_work, *new_sidecars;
587         FileData *parent_fd;
588
589         if (!basename_list) return;
590
591
592         DEBUG_2("basename start");
593         work = basename_list;
594         while (work)
595                 {
596                 FileData *fd = work->data;
597                 work = work->next;
598                 g_assert(fd->magick == 0x12345678);
599                 DEBUG_2("basename: %p %s", fd, fd->name);
600                 if (fd->parent) 
601                         {
602                         g_assert(fd->parent->magick == 0x12345678);
603                         DEBUG_2("                  parent: %p", fd->parent);
604                         }
605                 s_work = fd->sidecar_files;
606                 while (s_work)
607                         {
608                         FileData *sfd = s_work->data;
609                         s_work = s_work->next;
610                         g_assert(sfd->magick == 0x12345678);
611                         DEBUG_2("                  sidecar: %p %s", sfd, sfd->name);
612                         }
613                 
614                 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
615                 }
616
617         parent_fd = basename_list->data;
618
619         /* check if the second and next entries of basename_list are already connected
620            as sidecars of the first entry (parent_fd) */
621         work = basename_list->next;
622         s_work = parent_fd->sidecar_files;
623         
624         while (work && s_work)
625                 {
626                 if (work->data != s_work->data) break;
627                 work = work->next;
628                 s_work = s_work->next;
629                 }
630                 
631         if (!work && !s_work) 
632                 {
633                 DEBUG_2("basename no change");
634                 return; /* no change in grouping */
635                 }
636         
637         /* we have to regroup it */
638         
639         /* first, disconnect everything and send notification*/
640
641         work = basename_list;
642         while (work)
643                 {
644                 FileData *fd = work->data;
645                 work = work->next;
646                 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
647                 
648                 if (fd->parent)
649                         {
650                         FileData *old_parent = fd->parent;
651                         g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
652                         file_data_ref(old_parent);
653                         file_data_disconnect_sidecar_file(old_parent, fd);
654                         file_data_send_notification(old_parent, NOTIFY_REREAD);
655                         file_data_unref(old_parent);
656                         }
657                 
658                 while (fd->sidecar_files)
659                         {
660                         FileData *sfd = fd->sidecar_files->data;
661                         g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
662                         file_data_ref(sfd);
663                         file_data_disconnect_sidecar_file(fd, sfd);
664                         file_data_send_notification(sfd, NOTIFY_REREAD);
665                         file_data_unref(sfd);
666                         }
667                 file_data_send_notification(fd, NOTIFY_GROUPING);
668                 
669                 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
670                 }
671
672         /* now we can form the new group */
673         work = basename_list->next;
674         new_sidecars = NULL;
675         while (work)
676                 {
677                 FileData *sfd = work->data;
678                 g_assert(sfd->magick == 0x12345678);
679                 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
680                 sfd->parent = parent_fd;
681                 new_sidecars = g_list_prepend(new_sidecars, sfd);
682                 work = work->next;
683                 }
684         g_assert(parent_fd->sidecar_files == NULL);
685         parent_fd->sidecar_files = g_list_reverse(new_sidecars);
686         DEBUG_1("basename group changed for %s", parent_fd->path);
687 }
688
689
690 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
691 {
692         g_assert(target->magick == 0x12345678);
693         g_assert(sfd->magick == 0x12345678);
694         g_assert(g_list_find(target->sidecar_files, sfd));
695
696         file_data_ref(target);
697         file_data_ref(sfd);
698
699         g_assert(sfd->parent == target);
700         
701         file_data_increment_version(sfd); /* increments both sfd and target */
702
703         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
704         sfd->parent = NULL;
705
706         file_data_unref(target);
707         file_data_unref(sfd);
708 }
709
710 /* disables / enables grouping for particular file, sends UPDATE notification */
711 void file_data_disable_grouping(FileData *fd, gboolean disable)
712 {
713         if (!fd->disable_grouping == !disable) return;
714         
715         fd->disable_grouping = !!disable;
716         
717         if (disable)
718                 {
719                 if (fd->parent)
720                         {
721                         FileData *parent = file_data_ref(fd->parent);
722                         file_data_disconnect_sidecar_file(parent, fd);
723                         file_data_send_notification(parent, NOTIFY_GROUPING);
724                         file_data_unref(parent);
725                         }
726                 else if (fd->sidecar_files)
727                         {
728                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
729                         GList *work = sidecar_files;
730                         while (work)
731                                 {
732                                 FileData *sfd = work->data;
733                                 work = work->next;
734                                 file_data_disconnect_sidecar_file(fd, sfd);
735                                 file_data_send_notification(sfd, NOTIFY_GROUPING);
736                                 }
737                         file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
738                         filelist_free(sidecar_files);
739                         }
740                 else
741                         {
742                         file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
743                         }
744                 }
745         else
746                 {
747                 file_data_increment_version(fd);
748                 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
749                 }
750         file_data_send_notification(fd, NOTIFY_GROUPING);
751 }
752
753 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
754 {
755         GList *work;
756         
757         work = fd_list;
758         while (work)
759                 {
760                 FileData *fd = work->data;
761                 
762                 file_data_disable_grouping(fd, disable);
763                 work = work->next;
764                 }
765 }
766
767
768
769 /*
770  *-----------------------------------------------------------------------------
771  * filelist sorting
772  *-----------------------------------------------------------------------------
773  */
774
775 static SortType filelist_sort_method = SORT_NONE;
776 static gboolean filelist_sort_ascend = TRUE;
777
778
779 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
780 {
781         gint ret;
782         if (!filelist_sort_ascend)
783                 {
784                 FileData *tmp = fa;
785                 fa = fb;
786                 fb = tmp;
787                 }
788
789         switch (filelist_sort_method)
790                 {
791                 case SORT_NAME:
792                         break;
793                 case SORT_SIZE:
794                         if (fa->size < fb->size) return -1;
795                         if (fa->size > fb->size) return 1;
796                         /* fall back to name */
797                         break;
798                 case SORT_TIME:
799                         if (fa->date < fb->date) return -1;
800                         if (fa->date > fb->date) return 1;
801                         /* fall back to name */
802                         break;
803 #ifdef HAVE_STRVERSCMP
804                 case SORT_NUMBER:
805                         ret = strverscmp(fa->name, fb->name);
806                         if (ret != 0) return ret;
807                         break;
808 #endif
809                 default:
810                         break;
811                 }
812
813         if (options->file_sort.case_sensitive)
814                 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
815         else
816                 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
817
818         if (ret != 0) return ret;
819         
820         /* do not return 0 unless the files are really the same 
821            file_data_pool ensures that original_path is unique 
822         */
823         return strcmp(fa->original_path, fb->original_path);
824 }
825
826 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
827 {
828         filelist_sort_method = method;
829         filelist_sort_ascend = ascend;
830         return filelist_sort_compare_filedata(fa, fb);
831 }
832
833 static gint filelist_sort_file_cb(gpointer a, gpointer b)
834 {
835         return filelist_sort_compare_filedata(a, b);
836 }
837
838 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
839 {
840         filelist_sort_method = method;
841         filelist_sort_ascend = ascend;
842         return g_list_sort(list, cb);
843 }
844
845 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
846 {
847         filelist_sort_method = method;
848         filelist_sort_ascend = ascend;
849         return g_list_insert_sorted(list, data, cb);
850 }
851
852 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
853 {
854         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
855 }
856
857 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
858 {
859         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
860 }
861
862 /*
863  *-----------------------------------------------------------------------------
864  * basename hash - grouping of sidecars in filelist
865  *-----------------------------------------------------------------------------
866  */
867
868
869 static GHashTable *file_data_basename_hash_new(void)
870 {
871         return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
872 }
873
874 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
875 {
876         GList *list;
877         gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
878
879         list = g_hash_table_lookup(basename_hash, basename);
880         
881         if (!g_list_find(list, fd))
882                 {
883                 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
884                 g_hash_table_insert(basename_hash, basename, list);
885                 }
886         else 
887                 {
888                 g_free(basename);
889                 }
890         return list;
891 }
892
893 #if 0
894 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
895 {
896         GList *list;
897         gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
898         
899         list = g_hash_table_lookup(basename_hash, basename);
900         
901         if (!g_list_find(list, fd)) return;
902         
903         list = g_list_remove(list, fd);
904         file_data_unref(fd);
905         
906         if (list)
907                 {
908                 g_hash_table_insert(basename_hash, basename, list);
909                 }
910         else 
911                 {
912                 g_hash_table_remove(basename_hash, basename);
913                 g_free(basename);
914                 }
915 }
916 #endif
917
918 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
919 {
920         filelist_free((GList *)value);
921 }
922
923 static void file_data_basename_hash_free(GHashTable *basename_hash)
924 {
925         g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL); 
926         g_hash_table_destroy(basename_hash);
927 }
928
929 /*
930  *-----------------------------------------------------------------------------
931  * handling sidecars in filelist
932  *-----------------------------------------------------------------------------
933  */
934
935 static GList *filelist_filter_out_sidecars(GList *flist)
936 {
937         GList *work = flist;
938         GList *flist_filtered = NULL;
939
940         while (work)
941                 {
942                 FileData *fd = work->data;
943         
944                 work = work->next;
945                 if (fd->parent) /* remove fd's that are children */
946                         file_data_unref(fd);
947                 else
948                         flist_filtered = g_list_prepend(flist_filtered, fd);
949                 }
950         g_list_free(flist);
951
952         return flist_filtered;
953 }
954
955 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
956 {
957         GList *basename_list = (GList *)value;
958         file_data_check_sidecars(basename_list);
959 }
960
961
962 static gboolean is_hidden_file(const gchar *name)
963 {
964         if (name[0] != '.') return FALSE;
965         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
966         return TRUE;
967 }
968
969 /*
970  *-----------------------------------------------------------------------------
971  * the main filelist function
972  *-----------------------------------------------------------------------------
973  */
974
975 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
976 {
977         DIR *dp;
978         struct dirent *dir;
979         gchar *pathl;
980         GList *dlist = NULL;
981         GList *flist = NULL;
982         gint (*stat_func)(const gchar *path, struct stat *buf);
983         GHashTable *basename_hash = NULL;
984
985         g_assert(files || dirs);
986
987         if (files) *files = NULL;
988         if (dirs) *dirs = NULL;
989
990         pathl = path_from_utf8(dir_path);
991         if (!pathl) return FALSE;
992
993         dp = opendir(pathl);
994         if (dp == NULL)
995                 {
996                 g_free(pathl);
997                 return FALSE;
998                 }
999
1000         if (files) basename_hash = file_data_basename_hash_new();
1001
1002         if (follow_symlinks)
1003                 stat_func = stat;
1004         else
1005                 stat_func = lstat;
1006
1007         while ((dir = readdir(dp)) != NULL)
1008                 {
1009                 struct stat ent_sbuf;
1010                 const gchar *name = dir->d_name;
1011                 gchar *filepath;
1012
1013                 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1014                         continue;
1015
1016                 filepath = g_build_filename(pathl, name, NULL);
1017                 if (stat_func(filepath, &ent_sbuf) >= 0)
1018                         {
1019                         if (S_ISDIR(ent_sbuf.st_mode))
1020                                 {
1021                                 /* we ignore the .thumbnails dir for cleanliness */
1022                                 if (dirs &&
1023                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1024                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1025                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1026                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1027                                         {
1028                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1029                                         }
1030                                 }
1031                         else
1032                                 {
1033                                 if (files && filter_name_exists(name))
1034                                         {
1035                                         FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1036                                         flist = g_list_prepend(flist, fd);
1037                                         if (fd->sidecar_priority && !fd->disable_grouping) 
1038                                                 {
1039                                                 file_data_basename_hash_insert(basename_hash, fd);
1040                                                 }
1041                                         }
1042                                 }
1043                         }
1044                 else
1045                         {
1046                         if (errno == EOVERFLOW)
1047                                 {
1048                                 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1049                                 }
1050                         }
1051                 g_free(filepath);
1052                 }
1053
1054         closedir(dp);
1055         
1056         g_free(pathl);
1057
1058         if (dirs) *dirs = dlist;
1059         if (files) 
1060                 {
1061                 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL); 
1062
1063                 *files = filelist_filter_out_sidecars(flist);
1064                 }
1065         if (basename_hash) file_data_basename_hash_free(basename_hash);
1066
1067         return TRUE;
1068 }
1069
1070 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1071 {
1072         return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1073 }
1074
1075 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1076 {
1077         return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1078 }
1079
1080 FileData *file_data_new_group(const gchar *path_utf8)
1081 {
1082         gchar *dir;
1083         struct stat st;
1084         FileData *fd;
1085         GList *files;
1086
1087         if (!stat_utf8(path_utf8, &st))
1088                 {
1089                 st.st_size = 0;
1090                 st.st_mtime = 0;
1091                 }
1092
1093         if (S_ISDIR(st.st_mode))
1094                 return file_data_new(path_utf8, &st, TRUE);
1095         
1096         dir = remove_level_from_path(path_utf8);
1097         
1098         filelist_read_real(dir, &files, NULL, TRUE);
1099         
1100         fd = g_hash_table_lookup(file_data_pool, path_utf8);
1101         g_assert(fd);
1102         file_data_ref(fd);
1103         
1104         filelist_free(files);
1105         g_free(dir);
1106         return fd;
1107 }
1108
1109
1110 void filelist_free(GList *list)
1111 {
1112         GList *work;
1113
1114         work = list;
1115         while (work)
1116                 {
1117                 file_data_unref((FileData *)work->data);
1118                 work = work->next;
1119                 }
1120
1121         g_list_free(list);
1122 }
1123
1124
1125 GList *filelist_copy(GList *list)
1126 {
1127         GList *new_list = NULL;
1128         GList *work;
1129
1130         work = list;
1131         while (work)
1132                 {
1133                 FileData *fd;
1134
1135                 fd = work->data;
1136                 work = work->next;
1137
1138                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1139                 }
1140
1141         return g_list_reverse(new_list);
1142 }
1143
1144 GList *filelist_from_path_list(GList *list)
1145 {
1146         GList *new_list = NULL;
1147         GList *work;
1148
1149         work = list;
1150         while (work)
1151                 {
1152                 gchar *path;
1153
1154                 path = work->data;
1155                 work = work->next;
1156
1157                 new_list = g_list_prepend(new_list, file_data_new_group(path));
1158                 }
1159
1160         return g_list_reverse(new_list);
1161 }
1162
1163 GList *filelist_to_path_list(GList *list)
1164 {
1165         GList *new_list = NULL;
1166         GList *work;
1167
1168         work = list;
1169         while (work)
1170                 {
1171                 FileData *fd;
1172
1173                 fd = work->data;
1174                 work = work->next;
1175
1176                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1177                 }
1178
1179         return g_list_reverse(new_list);
1180 }
1181
1182 GList *filelist_filter(GList *list, gboolean is_dir_list)
1183 {
1184         GList *work;
1185
1186         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1187
1188         work = list;
1189         while (work)
1190                 {
1191                 FileData *fd = (FileData *)(work->data);
1192                 const gchar *name = fd->name;
1193
1194                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1195                     (!is_dir_list && !filter_name_exists(name)) ||
1196                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1197                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1198                         {
1199                         GList *link = work;
1200                         
1201                         list = g_list_remove_link(list, link);
1202                         file_data_unref(fd);
1203                         g_list_free(link);
1204                         }
1205         
1206                 work = work->next;
1207                 }
1208
1209         return list;
1210 }
1211
1212 /*
1213  *-----------------------------------------------------------------------------
1214  * filelist recursive
1215  *-----------------------------------------------------------------------------
1216  */
1217
1218 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1219 {
1220         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1221 }
1222
1223 GList *filelist_sort_path(GList *list)
1224 {
1225         return g_list_sort(list, filelist_sort_path_cb);
1226 }
1227
1228 static void filelist_recursive_append(GList **list, GList *dirs)
1229 {
1230         GList *work;
1231
1232         work = dirs;
1233         while (work)
1234                 {
1235                 FileData *fd = (FileData *)(work->data);
1236                 GList *f;
1237                 GList *d;
1238
1239                 if (filelist_read(fd, &f, &d))
1240                         {
1241                         f = filelist_filter(f, FALSE);
1242                         f = filelist_sort_path(f);
1243                         *list = g_list_concat(*list, f);
1244
1245                         d = filelist_filter(d, TRUE);
1246                         d = filelist_sort_path(d);
1247                         filelist_recursive_append(list, d);
1248                         filelist_free(d);
1249                         }
1250
1251                 work = work->next;
1252                 }
1253 }
1254
1255 GList *filelist_recursive(FileData *dir_fd)
1256 {
1257         GList *list;
1258         GList *d;
1259
1260         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1261         list = filelist_filter(list, FALSE);
1262         list = filelist_sort_path(list);
1263
1264         d = filelist_filter(d, TRUE);
1265         d = filelist_sort_path(d);
1266         filelist_recursive_append(&list, d);
1267         filelist_free(d);
1268
1269         return list;
1270 }
1271
1272 /*
1273  *-----------------------------------------------------------------------------
1274  * file modification support
1275  *-----------------------------------------------------------------------------
1276  */
1277
1278
1279 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1280 {
1281         if (!fdci && fd) fdci = fd->change;
1282
1283         if (!fdci) return;
1284
1285         g_free(fdci->source);
1286         g_free(fdci->dest);
1287
1288         g_free(fdci);
1289
1290         if (fd) fd->change = NULL;
1291 }
1292
1293 static gboolean file_data_can_write_directly(FileData *fd)
1294 {
1295         return filter_name_is_writable(fd->extension);
1296 }
1297
1298 static gboolean file_data_can_write_sidecar(FileData *fd)
1299 {
1300         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1301 }
1302
1303 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1304 {
1305         gchar *sidecar_path = NULL;
1306         GList *work;
1307         
1308         if (!file_data_can_write_sidecar(fd)) return NULL;
1309         
1310         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1311         while (work)
1312                 {
1313                 FileData *sfd = work->data;
1314                 work = work->next;
1315                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1316                         {
1317                         sidecar_path = g_strdup(sfd->path);
1318                         break;
1319                         }
1320                 }
1321         
1322         if (!existing_only && !sidecar_path)
1323                 {
1324                 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1325                 sidecar_path = g_strconcat(base, ".xmp", NULL);
1326                 g_free(base);
1327                 }
1328
1329         return sidecar_path;
1330 }
1331
1332 /*
1333  * marks and orientation
1334  */
1335
1336 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1337 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1338 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1339 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1340
1341 gboolean file_data_get_mark(FileData *fd, gint n)
1342 {
1343         gboolean valid = (fd->valid_marks & (1 << n));
1344         
1345         if (file_data_get_mark_func[n] && !valid) 
1346                 {
1347                 guint old = fd->marks;
1348                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1349                 
1350                 if (!value != !(fd->marks & (1 << n))) 
1351                         {
1352                         fd->marks = fd->marks ^ (1 << n);
1353                         }
1354                 
1355                 fd->valid_marks |= (1 << n);
1356                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1357                         {
1358                         file_data_unref(fd);
1359                         }
1360                 else if (!old && fd->marks)
1361                         {
1362                         file_data_ref(fd);
1363                         }
1364                 }
1365
1366         return !!(fd->marks & (1 << n));
1367 }
1368
1369 guint file_data_get_marks(FileData *fd)
1370 {
1371         gint i;
1372         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1373         return fd->marks;
1374 }
1375
1376 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1377 {
1378         guint old;
1379         if (!value == !file_data_get_mark(fd, n)) return;
1380         
1381         if (file_data_set_mark_func[n]) 
1382                 {
1383                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1384                 }
1385         
1386         old = fd->marks;
1387
1388         fd->marks = fd->marks ^ (1 << n);
1389         
1390         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1391                 {
1392                 file_data_unref(fd);
1393                 }
1394         else if (!old && fd->marks)
1395                 {
1396                 file_data_ref(fd);
1397                 }
1398         
1399         file_data_increment_version(fd);
1400         file_data_send_notification(fd, NOTIFY_MARKS);
1401 }
1402
1403 gboolean file_data_filter_marks(FileData *fd, guint filter)
1404 {
1405         gint i;
1406         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1407         return ((fd->marks & filter) == filter);
1408 }
1409
1410 GList *file_data_filter_marks_list(GList *list, guint filter)
1411 {
1412         GList *work;
1413
1414         work = list;
1415         while (work)
1416                 {
1417                 FileData *fd = work->data;
1418                 GList *link = work;
1419                 work = work->next;
1420
1421                 if (!file_data_filter_marks(fd, filter))
1422                         {
1423                         list = g_list_remove_link(list, link);
1424                         file_data_unref(fd);
1425                         g_list_free(link);
1426                         }
1427                 }
1428
1429         return list;
1430 }
1431
1432 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1433 {
1434         FileData *fd = value;
1435         file_data_increment_version(fd);
1436         file_data_send_notification(fd, NOTIFY_MARKS);
1437 }
1438
1439 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1440 {
1441         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1442         
1443         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1444                 
1445         file_data_get_mark_func[n] = get_mark_func;
1446         file_data_set_mark_func[n] = set_mark_func;
1447         file_data_mark_func_data[n] = data;
1448         file_data_destroy_mark_func[n] = notify;
1449
1450         if (get_mark_func)
1451                 {
1452                 /* this effectively changes all known files */
1453                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1454                 }
1455
1456         return TRUE;
1457 }
1458
1459 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1460 {
1461         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1462         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1463         if (data) *data = file_data_mark_func_data[n];
1464 }
1465
1466 gint file_data_get_user_orientation(FileData *fd)
1467 {
1468         return fd->user_orientation;
1469 }
1470
1471 void file_data_set_user_orientation(FileData *fd, gint value)
1472 {
1473         if (fd->user_orientation == value) return;
1474
1475         fd->user_orientation = value;
1476         file_data_increment_version(fd);
1477         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1478 }
1479
1480
1481 /*
1482  * file_data    - operates on the given fd
1483  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1484  */
1485
1486
1487 /* return list of sidecar file extensions in a string */
1488 gchar *file_data_sc_list_to_string(FileData *fd)
1489 {
1490         GList *work;
1491         GString *result = g_string_new("");
1492
1493         work = fd->sidecar_files;
1494         while (work)
1495                 {
1496                 FileData *sfd = work->data;
1497
1498                 result = g_string_append(result, "+ ");
1499                 result = g_string_append(result, sfd->extension);
1500                 work = work->next;
1501                 if (work) result = g_string_append_c(result, ' ');
1502                 }
1503
1504         return g_string_free(result, FALSE);
1505 }
1506
1507
1508
1509 /*
1510  * add FileDataChangeInfo (see typedefs.h) for the given operation
1511  * uses file_data_add_change_info
1512  *
1513  * fails if the fd->change already exists - change operations can't run in parallel
1514  * fd->change_info works as a lock
1515  *
1516  * dest can be NULL - in this case the current name is used for now, it will
1517  * be changed later
1518  */
1519
1520 /*
1521    FileDataChangeInfo types:
1522    COPY
1523    MOVE   - path is changed, name may be changed too
1524    RENAME - path remains unchanged, name is changed
1525             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1526             sidecar names are changed too, extensions are not changed
1527    DELETE
1528    UPDATE - file size, date or grouping has been changed
1529 */
1530
1531 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1532 {
1533         FileDataChangeInfo *fdci;
1534
1535         if (fd->change) return FALSE;
1536
1537         fdci = g_new0(FileDataChangeInfo, 1);
1538
1539         fdci->type = type;
1540
1541         if (src)
1542                 fdci->source = g_strdup(src);
1543         else
1544                 fdci->source = g_strdup(fd->path);
1545
1546         if (dest)
1547                 fdci->dest = g_strdup(dest);
1548
1549         fd->change = fdci;
1550         
1551         return TRUE;
1552 }
1553
1554 static void file_data_planned_change_remove(FileData *fd)
1555 {
1556         if (file_data_planned_change_hash &&
1557             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1558                 {
1559                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1560                         {
1561                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1562                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1563                         file_data_unref(fd);
1564                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1565                                 {
1566                                 g_hash_table_destroy(file_data_planned_change_hash);
1567                                 file_data_planned_change_hash = NULL;
1568                                 DEBUG_1("planned change: empty");
1569                                 }
1570                         }
1571                 }
1572 }
1573
1574
1575 void file_data_free_ci(FileData *fd)
1576 {
1577         FileDataChangeInfo *fdci = fd->change;
1578
1579         if (!fdci) return;
1580
1581         file_data_planned_change_remove(fd);
1582         
1583         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1584
1585         g_free(fdci->source);
1586         g_free(fdci->dest);
1587
1588         g_free(fdci);
1589
1590         fd->change = NULL;
1591 }
1592
1593 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1594 {
1595         FileDataChangeInfo *fdci = fd->change;
1596         if (!fdci) return;
1597         fdci->regroup_when_finished = enable;
1598 }
1599
1600 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1601 {
1602         GList *work;
1603
1604         if (fd->parent) fd = fd->parent;
1605         
1606         if (fd->change) return FALSE;
1607         
1608         work = fd->sidecar_files;
1609         while (work)
1610                 {
1611                 FileData *sfd = work->data;
1612                 
1613                 if (sfd->change) return FALSE;
1614                 work = work->next;
1615                 }
1616
1617         file_data_add_ci(fd, type, NULL, NULL);
1618         
1619         work = fd->sidecar_files;
1620         while (work)
1621                 {
1622                 FileData *sfd = work->data;
1623                 
1624                 file_data_add_ci(sfd, type, NULL, NULL);
1625                 work = work->next;
1626                 }
1627                 
1628         return TRUE;
1629 }
1630
1631 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1632 {
1633         GList *work;
1634         
1635         if (fd->parent) fd = fd->parent;
1636         
1637         if (!fd->change || fd->change->type != type) return FALSE;
1638         
1639         work = fd->sidecar_files;
1640         while (work)
1641                 {
1642                 FileData *sfd = work->data;
1643
1644                 if (!sfd->change || sfd->change->type != type) return FALSE;
1645                 work = work->next;
1646                 }
1647
1648         return TRUE;
1649 }
1650
1651
1652 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1653 {
1654         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1655         file_data_sc_update_ci_copy(fd, dest_path);
1656         return TRUE;
1657 }
1658
1659 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1660 {
1661         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1662         file_data_sc_update_ci_move(fd, dest_path);
1663         return TRUE;
1664 }
1665
1666 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1667 {
1668         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1669         file_data_sc_update_ci_rename(fd, dest_path);
1670         return TRUE;
1671 }
1672
1673 gboolean file_data_sc_add_ci_delete(FileData *fd)
1674 {
1675         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1676 }
1677
1678 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1679 {
1680         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1681         file_data_sc_update_ci_unspecified(fd, dest_path);
1682         return TRUE;
1683 }
1684
1685 gboolean file_data_add_ci_write_metadata(FileData *fd)
1686 {
1687         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1688 }
1689
1690 void file_data_sc_free_ci(FileData *fd)
1691 {
1692         GList *work;
1693
1694         if (fd->parent) fd = fd->parent;
1695         
1696         file_data_free_ci(fd);
1697         
1698         work = fd->sidecar_files;
1699         while (work)
1700                 {
1701                 FileData *sfd = work->data;
1702         
1703                 file_data_free_ci(sfd);
1704                 work = work->next;
1705                 }
1706 }
1707
1708 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1709 {
1710         GList *work;
1711         gboolean ret = TRUE;
1712
1713         work = fd_list;
1714         while (work)
1715                 {
1716                 FileData *fd = work->data;
1717         
1718                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1719                 work = work->next;
1720                 }
1721
1722         return ret;
1723 }
1724
1725 static void file_data_sc_revert_ci_list(GList *fd_list)
1726 {
1727         GList *work;
1728         
1729         work = fd_list;
1730         while (work)
1731                 {
1732                 FileData *fd = work->data;
1733                 
1734                 file_data_sc_free_ci(fd);
1735                 work = work->prev;
1736                 }
1737 }
1738
1739 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1740 {
1741         GList *work;
1742         
1743         work = fd_list;
1744         while (work)
1745                 {
1746                 FileData *fd = work->data;
1747                 
1748                 if (!func(fd, dest))
1749                         {
1750                         file_data_sc_revert_ci_list(work->prev);
1751                         return FALSE;
1752                         }
1753                 work = work->next;
1754                 }
1755         
1756         return TRUE;
1757 }
1758
1759 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1760 {
1761         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1762 }
1763
1764 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1765 {
1766         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1767 }
1768
1769 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1770 {
1771         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1772 }
1773
1774 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1775 {
1776         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1777 }
1778
1779 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1780 {
1781         GList *work;
1782         gboolean ret = TRUE;
1783
1784         work = fd_list;
1785         while (work)
1786                 {
1787                 FileData *fd = work->data;
1788         
1789                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1790                 work = work->next;
1791                 }
1792
1793         return ret;
1794 }
1795
1796 void file_data_free_ci_list(GList *fd_list)
1797 {
1798         GList *work;
1799         
1800         work = fd_list;
1801         while (work)
1802                 {
1803                 FileData *fd = work->data;
1804                 
1805                 file_data_free_ci(fd);
1806                 work = work->next;
1807                 }
1808 }
1809
1810 void file_data_sc_free_ci_list(GList *fd_list)
1811 {
1812         GList *work;
1813         
1814         work = fd_list;
1815         while (work)
1816                 {
1817                 FileData *fd = work->data;
1818                 
1819                 file_data_sc_free_ci(fd);
1820                 work = work->next;
1821                 }
1822 }
1823
1824 /*
1825  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1826  * fails if fd->change does not exist or the change type does not match
1827  */
1828
1829 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1830 {
1831         FileDataChangeType type = fd->change->type;
1832         
1833         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1834                 {
1835                 FileData *ofd;
1836                 
1837                 if (!file_data_planned_change_hash)
1838                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1839                 
1840                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1841                         {
1842                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1843                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1844                         file_data_unref(fd);
1845                         }
1846
1847                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1848                 if (ofd != fd)
1849                         {
1850                         if (ofd)
1851                                 {
1852                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1853                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1854                                 file_data_unref(ofd);
1855                                 }
1856                         
1857                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1858                         file_data_ref(fd);
1859                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1860                         }
1861                 }
1862 }
1863
1864 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1865 {
1866         gchar *old_path = fd->change->dest;
1867
1868         fd->change->dest = g_strdup(dest_path);
1869         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1870         g_free(old_path);
1871 }
1872
1873 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1874 {
1875         const gchar *extension = extension_from_path(fd->change->source);
1876         gchar *base = remove_extension_from_path(dest_path);
1877         gchar *old_path = fd->change->dest;
1878         
1879         fd->change->dest = g_strconcat(base, extension, NULL);
1880         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1881         
1882         g_free(old_path);
1883         g_free(base);
1884 }
1885
1886 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1887 {
1888         GList *work;
1889         gchar *dest_path_full = NULL;
1890         
1891         if (fd->parent) fd = fd->parent;
1892         
1893         if (!dest_path)
1894                 {
1895                 dest_path = fd->path;
1896                 }
1897         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1898                 {
1899                 gchar *dir = remove_level_from_path(fd->path);
1900                 
1901                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1902                 g_free(dir);
1903                 dest_path = dest_path_full;
1904                 }
1905         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1906                 {
1907                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1908                 dest_path = dest_path_full;
1909                 }
1910                 
1911         file_data_update_ci_dest(fd, dest_path);
1912         
1913         work = fd->sidecar_files;
1914         while (work)
1915                 {
1916                 FileData *sfd = work->data;
1917                 
1918                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1919                 work = work->next;
1920                 }
1921         
1922         g_free(dest_path_full);
1923 }
1924
1925 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1926 {
1927         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1928         file_data_sc_update_ci(fd, dest_path);
1929         return TRUE;
1930 }
1931
1932 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1933 {
1934         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1935 }
1936         
1937 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1938 {
1939         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1940 }
1941
1942 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1943 {
1944         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1945 }
1946
1947 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1948 {
1949         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1950 }
1951
1952 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1953                                                       const gchar *dest,
1954                                                       gboolean (*func)(FileData *, const gchar *))
1955 {
1956         GList *work;
1957         gboolean ret = TRUE;
1958         
1959         work = fd_list;
1960         while (work)
1961                 {
1962                 FileData *fd = work->data;
1963                 
1964                 if (!func(fd, dest)) ret = FALSE;
1965                 work = work->next;
1966                 }
1967         
1968         return ret;
1969 }
1970
1971 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1972 {
1973         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1974 }
1975
1976 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1977 {
1978         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1979 }
1980
1981 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1982 {
1983         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1984 }
1985
1986
1987 /*
1988  * verify source and dest paths - dest image exists, etc.
1989  * it should detect all possible problems with the planned operation
1990  */
1991
1992 gint file_data_verify_ci(FileData *fd)
1993 {
1994         gint ret = CHANGE_OK;
1995         gchar *dir;
1996         
1997         if (!fd->change)
1998                 {
1999                 DEBUG_1("Change checked: no change info: %s", fd->path);
2000                 return ret;
2001                 }
2002
2003         if (!isname(fd->path))
2004                 {
2005                 /* this probably should not happen */
2006                 ret |= CHANGE_NO_SRC;
2007                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2008                 return ret;
2009                 }
2010                 
2011         dir = remove_level_from_path(fd->path);
2012         
2013         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2014             fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2015             fd->change->type != FILEDATA_CHANGE_RENAME &&
2016             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2017             fd->modified_xmp)
2018                 {
2019                 ret |= CHANGE_WARN_UNSAVED_META;
2020                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2021                 }
2022         
2023         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2024             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2025             !access_file(fd->path, R_OK))
2026                 {
2027                 ret |= CHANGE_NO_READ_PERM;
2028                 DEBUG_1("Change checked: no read permission: %s", fd->path);
2029                 }
2030         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2031                  !access_file(dir, W_OK))
2032                 {
2033                 ret |= CHANGE_NO_WRITE_PERM_DIR;
2034                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2035                 }
2036         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2037                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2038                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2039                  !access_file(fd->path, W_OK))
2040                 {
2041                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2042                 DEBUG_1("Change checked: no write permission: %s", fd->path);
2043                 }
2044         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2045            - that means that there are no hard errors and warnings can be disabled
2046            - the destination is determined during the check
2047         */
2048         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2049                 {
2050                 /* determine destination file */
2051                 gboolean have_dest = FALSE;
2052                 gchar *dest_dir = NULL;
2053                 
2054                 if (options->metadata.save_in_image_file)
2055                         {
2056                         if (file_data_can_write_directly(fd)) 
2057                                 {
2058                                 /* we can write the file directly */
2059                                 if (access_file(fd->path, W_OK))
2060                                         {
2061                                         have_dest = TRUE;
2062                                         }
2063                                 else
2064                                         {
2065                                         if (options->metadata.warn_on_write_problems)
2066                                                 {
2067                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2068                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2069                                                 }
2070                                         }
2071                                 }
2072                         else if (file_data_can_write_sidecar(fd)) 
2073                                 {
2074                                 /* we can write sidecar */
2075                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2076                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2077                                         {
2078                                         file_data_update_ci_dest(fd, sidecar);
2079                                         have_dest = TRUE;
2080                                         }
2081                                 else
2082                                         {
2083                                         if (options->metadata.warn_on_write_problems)
2084                                                 {
2085                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2086                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2087                                                 }
2088                                         }
2089                                 g_free(sidecar);
2090                                 }
2091                         }
2092                 
2093                 if (!have_dest)
2094                         {
2095                         /* write private metadata file under ~/.geeqie */
2096
2097                         /* If an existing metadata file exists, we will try writing to
2098                          * it's location regardless of the user's preference.
2099                          */
2100                         gchar *metadata_path = NULL;
2101 #ifdef HAVE_EXIV2
2102                         /* but ignore XMP if we are not able to write it */
2103                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2104 #endif
2105                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2106                         
2107                         if (metadata_path && !access_file(metadata_path, W_OK))
2108                                 {
2109                                 g_free(metadata_path);
2110                                 metadata_path = NULL;
2111                                 }
2112
2113                         if (!metadata_path)
2114                                 {
2115                                 mode_t mode = 0755;
2116
2117                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2118                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2119                                         {
2120                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2121                         
2122                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
2123                                         g_free(filename);
2124                                         }
2125                                 }
2126                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2127                                 {
2128                                 file_data_update_ci_dest(fd, metadata_path);
2129                                 have_dest = TRUE;
2130                                 }
2131                         else
2132                                 {
2133                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
2134                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2135                                 }
2136                         g_free(metadata_path);
2137                         }
2138                 g_free(dest_dir);
2139                 }
2140                 
2141         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2142                 {
2143                 gboolean same;
2144                 gchar *dest_dir;
2145                         
2146                 same = (strcmp(fd->path, fd->change->dest) == 0);
2147
2148                 if (!same)
2149                         {
2150                         const gchar *dest_ext = extension_from_path(fd->change->dest);
2151                         if (!dest_ext) dest_ext = "";
2152
2153                         if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2154                                 {
2155                                 ret |= CHANGE_WARN_CHANGED_EXT;
2156                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2157                                 }
2158                         }
2159                 else
2160                         {
2161                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2162                                 {
2163                                 ret |= CHANGE_WARN_SAME;
2164                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2165                                 }
2166                         }
2167
2168                 dest_dir = remove_level_from_path(fd->change->dest);
2169
2170                 if (!isdir(dest_dir))
2171                         {
2172                         ret |= CHANGE_NO_DEST_DIR;
2173                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2174                         }
2175                 else if (!access_file(dest_dir, W_OK))
2176                         {
2177                         ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2178                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2179                         }
2180                 else if (!same)
2181                         {
2182                         if (isfile(fd->change->dest))
2183                                 {
2184                                 if (!access_file(fd->change->dest, W_OK))
2185                                         {
2186                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2187                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2188                                         }
2189                                 else
2190                                         {
2191                                         ret |= CHANGE_WARN_DEST_EXISTS;
2192                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2193                                         }
2194                                 }
2195                         else if (isdir(fd->change->dest))
2196                                 {
2197                                 ret |= CHANGE_DEST_EXISTS;
2198                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2199                                 }
2200                         }
2201
2202                 g_free(dest_dir);
2203                 }
2204                 
2205         fd->change->error = ret;
2206         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2207
2208         g_free(dir);
2209         return ret;
2210 }
2211
2212
2213 gint file_data_sc_verify_ci(FileData *fd)
2214 {
2215         GList *work;
2216         gint ret;
2217
2218         ret = file_data_verify_ci(fd);
2219
2220         work = fd->sidecar_files;
2221         while (work)
2222                 {
2223                 FileData *sfd = work->data;
2224
2225                 ret |= file_data_verify_ci(sfd);
2226                 work = work->next;
2227                 }
2228
2229         return ret;
2230 }
2231
2232 gchar *file_data_get_error_string(gint error)
2233 {
2234         GString *result = g_string_new("");
2235
2236         if (error & CHANGE_NO_SRC)
2237                 {
2238                 if (result->len > 0) g_string_append(result, ", ");
2239                 g_string_append(result, _("file or directory does not exist"));
2240                 }
2241
2242         if (error & CHANGE_DEST_EXISTS)
2243                 {
2244                 if (result->len > 0) g_string_append(result, ", ");
2245                 g_string_append(result, _("destination already exists"));
2246                 }
2247
2248         if (error & CHANGE_NO_WRITE_PERM_DEST)
2249                 {
2250                 if (result->len > 0) g_string_append(result, ", ");
2251                 g_string_append(result, _("destination can't be overwritten"));
2252                 }
2253
2254         if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2255                 {
2256                 if (result->len > 0) g_string_append(result, ", ");
2257                 g_string_append(result, _("destination directory is not writable"));
2258                 }
2259
2260         if (error & CHANGE_NO_DEST_DIR)
2261                 {
2262                 if (result->len > 0) g_string_append(result, ", ");
2263                 g_string_append(result, _("destination directory does not exist"));
2264                 }
2265
2266         if (error & CHANGE_NO_WRITE_PERM_DIR)
2267                 {
2268                 if (result->len > 0) g_string_append(result, ", ");
2269                 g_string_append(result, _("source directory is not writable"));
2270                 }
2271
2272         if (error & CHANGE_NO_READ_PERM)
2273                 {
2274                 if (result->len > 0) g_string_append(result, ", ");
2275                 g_string_append(result, _("no read permission"));
2276                 }
2277
2278         if (error & CHANGE_WARN_NO_WRITE_PERM)
2279                 {
2280                 if (result->len > 0) g_string_append(result, ", ");
2281                 g_string_append(result, _("file is readonly"));
2282                 }
2283
2284         if (error & CHANGE_WARN_DEST_EXISTS)
2285                 {
2286                 if (result->len > 0) g_string_append(result, ", ");
2287                 g_string_append(result, _("destination already exists and will be overwritten"));
2288                 }
2289                 
2290         if (error & CHANGE_WARN_SAME)
2291                 {
2292                 if (result->len > 0) g_string_append(result, ", ");
2293                 g_string_append(result, _("source and destination are the same"));
2294                 }
2295
2296         if (error & CHANGE_WARN_CHANGED_EXT)
2297                 {
2298                 if (result->len > 0) g_string_append(result, ", ");
2299                 g_string_append(result, _("source and destination have different extension"));
2300                 }
2301
2302         if (error & CHANGE_WARN_UNSAVED_META)
2303                 {
2304                 if (result->len > 0) g_string_append(result, ", ");
2305                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2306                 }
2307
2308         return g_string_free(result, FALSE);
2309 }
2310
2311 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2312 {
2313         GList *work;
2314         gint all_errors = 0;
2315         gint common_errors = ~0;
2316         gint num;
2317         gint *errors;
2318         gint i;
2319         
2320         if (!list) return 0;
2321         
2322         num = g_list_length(list);
2323         errors = g_new(int, num);
2324         work = list;
2325         i = 0;
2326         while (work)
2327                 {
2328                 FileData *fd;
2329                 gint error;
2330
2331                 fd = work->data;
2332                 work = work->next;
2333                         
2334                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2335                 all_errors |= error;
2336                 common_errors &= error;
2337                 
2338                 errors[i] = error;
2339                 
2340                 i++;
2341                 }
2342         
2343         if (desc && all_errors)
2344                 {
2345                 GList *work;
2346                 GString *result = g_string_new("");
2347                 
2348                 if (common_errors)
2349                         {
2350                         gchar *str = file_data_get_error_string(common_errors);
2351                         g_string_append(result, str);
2352                         g_string_append(result, "\n");
2353                         g_free(str);
2354                         }
2355                 
2356                 work = list;
2357                 i = 0;
2358                 while (work)
2359                         {
2360                         FileData *fd;
2361                         gint error;
2362
2363                         fd = work->data;
2364                         work = work->next;
2365                         
2366                         error = errors[i] & ~common_errors;
2367                         
2368                         if (error)
2369                                 {
2370                                 gchar *str = file_data_get_error_string(error);
2371                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2372                                 g_free(str);
2373                                 }
2374                         i++;
2375                         }
2376                 *desc = g_string_free(result, FALSE);
2377                 }
2378
2379         g_free(errors);
2380         return all_errors;
2381 }
2382
2383
2384 /*
2385  * perform the change described by FileFataChangeInfo
2386  * it is used for internal operations,
2387  * this function actually operates with files on the filesystem
2388  * it should implement safe delete
2389  */
2390
2391 static gboolean file_data_perform_move(FileData *fd)
2392 {
2393         g_assert(!strcmp(fd->change->source, fd->path));
2394         return move_file(fd->change->source, fd->change->dest);
2395 }
2396
2397 static gboolean file_data_perform_copy(FileData *fd)
2398 {
2399         g_assert(!strcmp(fd->change->source, fd->path));
2400         return copy_file(fd->change->source, fd->change->dest);
2401 }
2402
2403 static gboolean file_data_perform_delete(FileData *fd)
2404 {
2405         if (isdir(fd->path) && !islink(fd->path))
2406                 return rmdir_utf8(fd->path);
2407         else
2408                 if (options->file_ops.safe_delete_enable)
2409                         return file_util_safe_unlink(fd->path);
2410                 else
2411                         return unlink_file(fd->path);
2412 }
2413
2414 gboolean file_data_perform_ci(FileData *fd)
2415 {
2416         FileDataChangeType type = fd->change->type;
2417
2418         switch (type)
2419                 {
2420                 case FILEDATA_CHANGE_MOVE:
2421                         return file_data_perform_move(fd);
2422                 case FILEDATA_CHANGE_COPY:
2423                         return file_data_perform_copy(fd);
2424                 case FILEDATA_CHANGE_RENAME:
2425                         return file_data_perform_move(fd); /* the same as move */
2426                 case FILEDATA_CHANGE_DELETE:
2427                         return file_data_perform_delete(fd);
2428                 case FILEDATA_CHANGE_WRITE_METADATA:
2429                         return metadata_write_perform(fd);
2430                 case FILEDATA_CHANGE_UNSPECIFIED:
2431                         /* nothing to do here */
2432                         break;
2433                 }
2434         return TRUE;
2435 }
2436
2437
2438
2439 gboolean file_data_sc_perform_ci(FileData *fd)
2440 {
2441         GList *work;
2442         gboolean ret = TRUE;
2443         FileDataChangeType type = fd->change->type;
2444         
2445         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2446
2447         work = fd->sidecar_files;
2448         while (work)
2449                 {
2450                 FileData *sfd = work->data;
2451                 
2452                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2453                 work = work->next;
2454                 }
2455         
2456         if (!file_data_perform_ci(fd)) ret = FALSE;
2457         
2458         return ret;
2459 }
2460
2461 /*
2462  * updates FileData structure according to FileDataChangeInfo
2463  */
2464
2465 gboolean file_data_apply_ci(FileData *fd)
2466 {
2467         FileDataChangeType type = fd->change->type;
2468
2469         /* FIXME delete ?*/
2470         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2471                 {
2472                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2473                 file_data_planned_change_remove(fd);
2474                 
2475                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2476                         {
2477                         /* this change overwrites another file which is already known to other modules
2478                            renaming fd would create duplicate FileData structure
2479                            the best thing we can do is nothing
2480                            FIXME: maybe we could copy stuff like marks
2481                         */
2482                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2483                         }
2484                 else
2485                         {
2486                         file_data_set_path(fd, fd->change->dest);
2487                         }
2488                 }
2489         file_data_increment_version(fd);
2490         file_data_send_notification(fd, NOTIFY_CHANGE);
2491         
2492         return TRUE;
2493 }
2494
2495 gboolean file_data_sc_apply_ci(FileData *fd)
2496 {
2497         GList *work;
2498         FileDataChangeType type = fd->change->type;
2499         
2500         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2501
2502         work = fd->sidecar_files;
2503         while (work)
2504                 {
2505                 FileData *sfd = work->data;
2506                 
2507                 file_data_apply_ci(sfd);
2508                 work = work->next;
2509                 }
2510         
2511         file_data_apply_ci(fd);
2512         
2513         return TRUE;
2514 }
2515
2516 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2517 {
2518         GList *work;
2519         if (fd->parent) fd = fd->parent;
2520         if (!g_list_find(list, fd)) return FALSE;
2521         
2522         work = fd->sidecar_files;
2523         while (work)
2524                 {
2525                 if (!g_list_find(list, work->data)) return FALSE;
2526                 work = work->next;
2527                 }
2528         return TRUE;
2529 }
2530
2531 #if 0
2532 static gboolean file_data_list_dump(GList *list)
2533 {
2534         GList *work, *work2;
2535
2536         work = list;
2537         while (work)
2538                 {
2539                 FileData *fd = work->data;
2540                 printf("%s\n", fd->name);
2541                 work2 = fd->sidecar_files;
2542                 while (work2)
2543                         {
2544                         FileData *fd = work2->data;
2545                         printf("       %s\n", fd->name);
2546                         work2 = work2->next;
2547                         }
2548                 work = work->next;
2549                 }
2550         return TRUE;
2551 }
2552 #endif
2553
2554 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2555 {
2556         GList *out = NULL;
2557         GList *work = list;
2558
2559         /* change partial groups to independent files */
2560         if (ungroup)
2561                 {
2562                 while (work)
2563                         {
2564                         FileData *fd = work->data;
2565                         work = work->next;
2566                 
2567                         if (!file_data_list_contains_whole_group(list, fd)) 
2568                                 {
2569                                 file_data_disable_grouping(fd, TRUE);
2570                                 if (ungrouped_list) 
2571                                         {
2572                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2573                                         }
2574                                 }
2575                         }
2576                 }
2577         
2578         /* remove sidecars from the list, 
2579            they can be still acessed via main_fd->sidecar_files */
2580         work = list;
2581         while (work)
2582                 {
2583                 FileData *fd = work->data;
2584                 work = work->next;
2585                 
2586                 if (!fd->parent ||
2587                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2588                         {
2589                         out = g_list_prepend(out, file_data_ref(fd));
2590                         }
2591                 }
2592                 
2593         filelist_free(list);
2594         out = g_list_reverse(out);
2595
2596         return out;
2597 }
2598
2599
2600
2601
2602
2603 /*
2604  * notify other modules about the change described by FileDataChangeInfo
2605  */
2606
2607 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2608    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2609    implementation in view_file_list.c */
2610
2611
2612 typedef struct _NotifyIdleData NotifyIdleData;
2613
2614 struct _NotifyIdleData {
2615         FileData *fd;
2616         NotifyType type;
2617 };
2618
2619
2620 typedef struct _NotifyData NotifyData;
2621
2622 struct _NotifyData {
2623         FileDataNotifyFunc func;
2624         gpointer data;
2625         NotifyPriority priority;
2626 };
2627
2628 static GList *notify_func_list = NULL;
2629
2630 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2631 {
2632         NotifyData *nda = (NotifyData *)a;
2633         NotifyData *ndb = (NotifyData *)b;
2634
2635         if (nda->priority < ndb->priority) return -1;
2636         if (nda->priority > ndb->priority) return 1;
2637         return 0;
2638 }
2639
2640 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2641 {
2642         NotifyData *nd;
2643         GList *work = notify_func_list;
2644         
2645         while (work)
2646                 {
2647                 NotifyData *nd = (NotifyData *)work->data;
2648         
2649                 if (nd->func == func && nd->data == data)
2650                         {
2651                         g_warning("Notify func already registered");
2652                         return FALSE;
2653                         }
2654                 work = work->next;
2655                 }
2656         
2657         nd = g_new(NotifyData, 1);
2658         nd->func = func;
2659         nd->data = data;
2660         nd->priority = priority;
2661
2662         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2663         DEBUG_2("Notify func registered: %p", nd);
2664         
2665         return TRUE;
2666 }
2667
2668 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2669 {
2670         GList *work = notify_func_list;
2671         
2672         while (work)
2673                 {
2674                 NotifyData *nd = (NotifyData *)work->data;
2675         
2676                 if (nd->func == func && nd->data == data)
2677                         {
2678                         notify_func_list = g_list_delete_link(notify_func_list, work);
2679                         g_free(nd);
2680                         DEBUG_2("Notify func unregistered: %p", nd);
2681                         return TRUE;
2682                         }
2683                 work = work->next;
2684                 }
2685
2686         g_warning("Notify func not found");
2687         return FALSE;
2688 }
2689
2690
2691 gboolean file_data_send_notification_idle_cb(gpointer data)
2692 {
2693         NotifyIdleData *nid = (NotifyIdleData *)data;
2694         GList *work = notify_func_list;
2695
2696         while (work)
2697                 {
2698                 NotifyData *nd = (NotifyData *)work->data;
2699                 
2700                 nd->func(nid->fd, nid->type, nd->data);
2701                 work = work->next;
2702                 }
2703         file_data_unref(nid->fd);
2704         g_free(nid);
2705         return FALSE;
2706 }
2707
2708 void file_data_send_notification(FileData *fd, NotifyType type)
2709 {
2710         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2711         nid->fd = file_data_ref(fd);
2712         nid->type = type;
2713         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2714 }
2715
2716 static GHashTable *file_data_monitor_pool = NULL;
2717 static guint realtime_monitor_id = 0; /* event source id */
2718
2719 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2720 {
2721         FileData *fd = key;
2722
2723         file_data_check_changed_files(fd);
2724         
2725         DEBUG_1("monitor %s", fd->path);
2726 }
2727
2728 static gboolean realtime_monitor_cb(gpointer data)
2729 {
2730         if (!options->update_on_time_change) return TRUE;
2731         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2732         return TRUE;
2733 }
2734
2735 gboolean file_data_register_real_time_monitor(FileData *fd)
2736 {
2737         gint count;
2738         
2739         file_data_ref(fd);
2740         
2741         if (!file_data_monitor_pool)
2742                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2743         
2744         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2745
2746         DEBUG_1("Register realtime %d %s", count, fd->path);
2747         
2748         count++;
2749         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2750         
2751         if (!realtime_monitor_id)
2752                 {
2753                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2754                 }
2755         
2756         return TRUE;
2757 }
2758
2759 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2760 {
2761         gint count;
2762
2763         g_assert(file_data_monitor_pool);
2764         
2765         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2766         
2767         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2768         
2769         g_assert(count > 0);
2770         
2771         count--;
2772         
2773         if (count == 0)
2774                 g_hash_table_remove(file_data_monitor_pool, fd);
2775         else
2776                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2777
2778         file_data_unref(fd);
2779         
2780         if (g_hash_table_size(file_data_monitor_pool) == 0)
2781                 {
2782                 g_source_remove(realtime_monitor_id);
2783                 realtime_monitor_id = 0;
2784                 return FALSE;
2785                 }
2786         
2787         return TRUE;
2788 }
2789 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */