Add missing #ifdef for DEBUG_FD
[geeqie.git] / src / filedata.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "filedata.h"
24
25 #include "filefilter.h"
26 #include "cache.h"
27 #include "thumb-standard.h"
28 #include "ui-fileops.h"
29 #include "metadata.h"
30 #include "trash.h"
31 #include "histogram.h"
32 #include "secure-save.h"
33
34 #include "exif.h"
35 #include "misc.h"
36
37 #include <grp.h>
38
39 #ifdef DEBUG_FILEDATA
40 gint global_file_data_count = 0;
41 #endif
42
43 static GHashTable *file_data_pool = NULL;
44 static GHashTable *file_data_planned_change_hash = NULL;
45
46 static gint sidecar_file_priority(const gchar *extension);
47 static void file_data_check_sidecars(const GList *basename_list);
48 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
49
50
51 static SortType filelist_sort_method = SORT_NONE;
52 static gboolean filelist_sort_ascend = TRUE;
53
54 /*
55  *-----------------------------------------------------------------------------
56  * text conversion utils
57  *-----------------------------------------------------------------------------
58  */
59
60 gchar *text_from_size(gint64 size)
61 {
62         gchar *a, *b;
63         gchar *s, *d;
64         gint l, n, i;
65
66         /* what I would like to use is printf("%'d", size)
67          * BUT: not supported on every libc :(
68          */
69         if (size > G_MAXINT)
70                 {
71                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
72                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
73                 }
74         else
75                 {
76                 a = g_strdup_printf("%d", (guint)size);
77                 }
78         l = strlen(a);
79         n = (l - 1)/ 3;
80         if (n < 1) return a;
81
82         b = g_new(gchar, l + n + 1);
83
84         s = a;
85         d = b;
86         i = l - n * 3;
87         while (*s != '\0')
88                 {
89                 if (i < 1)
90                         {
91                         i = 3;
92                         *d = ',';
93                         d++;
94                         }
95
96                 *d = *s;
97                 s++;
98                 d++;
99                 i--;
100                 }
101         *d = '\0';
102
103         g_free(a);
104         return b;
105 }
106
107 gchar *text_from_size_abrev(gint64 size)
108 {
109         if (size < (gint64)1024)
110                 {
111                 return g_strdup_printf(_("%d bytes"), (gint)size);
112                 }
113         if (size < (gint64)1048576)
114                 {
115                 return g_strdup_printf(_("%.1f KiB"), (gdouble)size / 1024.0);
116                 }
117         if (size < (gint64)1073741824)
118                 {
119                 return g_strdup_printf(_("%.1f MiB"), (gdouble)size / 1048576.0);
120                 }
121
122         /* to avoid overflowing the gdouble, do division in two steps */
123         size /= 1048576;
124         return g_strdup_printf(_("%.1f GiB"), (gdouble)size / 1024.0);
125 }
126
127 /* note: returned string is valid until next call to text_from_time() */
128 const gchar *text_from_time(time_t t)
129 {
130         static gchar *ret = NULL;
131         gchar buf[128];
132         gint buflen;
133         struct tm *btime;
134         GError *error = NULL;
135
136         btime = localtime(&t);
137
138         /* the %x warning about 2 digit years is not an error */
139         buflen = strftime(buf, sizeof(buf), "%x %X", btime);
140         if (buflen < 1) return "";
141
142         g_free(ret);
143         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
144         if (error)
145                 {
146                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
147                 g_error_free(error);
148                 return "";
149                 }
150
151         return ret;
152 }
153
154 /*
155  *-----------------------------------------------------------------------------
156  * changed files detection and notification
157  *-----------------------------------------------------------------------------
158  */
159
160 void file_data_increment_version(FileData *fd)
161 {
162         fd->version++;
163         fd->valid_marks = 0;
164         if (fd->parent)
165                 {
166                 fd->parent->version++;
167                 fd->parent->valid_marks = 0;
168                 }
169 }
170
171 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
172 {
173         if (fd->size != st->st_size ||
174             fd->date != st->st_mtime)
175                 {
176                 fd->size = st->st_size;
177                 fd->date = st->st_mtime;
178                 fd->cdate = st->st_ctime;
179                 fd->mode = st->st_mode;
180                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
181                 fd->thumb_pixbuf = NULL;
182                 file_data_increment_version(fd);
183                 file_data_send_notification(fd, NOTIFY_REREAD);
184                 return TRUE;
185                 }
186         return FALSE;
187 }
188
189 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
190 {
191         gboolean ret = FALSE;
192         GList *work;
193
194         ret = file_data_check_changed_single_file(fd, st);
195
196         work = fd->sidecar_files;
197         while (work)
198                 {
199                 FileData *sfd = static_cast<FileData *>(work->data);
200                 struct stat st;
201                 work = work->next;
202
203                 if (!stat_utf8(sfd->path, &st))
204                         {
205                         fd->size = 0;
206                         fd->date = 0;
207                         file_data_ref(sfd);
208                         file_data_disconnect_sidecar_file(fd, sfd);
209                         ret = TRUE;
210                         file_data_increment_version(sfd);
211                         file_data_send_notification(sfd, NOTIFY_REREAD);
212                         file_data_unref(sfd);
213                         continue;
214                         }
215
216                 ret |= file_data_check_changed_files_recursive(sfd, &st);
217                 }
218         return ret;
219 }
220
221
222 gboolean file_data_check_changed_files(FileData *fd)
223 {
224         gboolean ret = FALSE;
225         struct stat st;
226
227         if (fd->parent) fd = fd->parent;
228
229         if (!stat_utf8(fd->path, &st))
230                 {
231                 GList *sidecars;
232                 GList *work;
233                 FileData *sfd = NULL;
234
235                 /* parent is missing, we have to rebuild whole group */
236                 ret = TRUE;
237                 fd->size = 0;
238                 fd->date = 0;
239
240                 /* file_data_disconnect_sidecar_file might delete the file,
241                    we have to keep the reference to prevent this */
242                 sidecars = filelist_copy(fd->sidecar_files);
243                 file_data_ref(fd);
244                 work = sidecars;
245                 while (work)
246                         {
247                         sfd = static_cast<FileData *>(work->data);
248                         work = work->next;
249
250                         file_data_disconnect_sidecar_file(fd, sfd);
251                         }
252                 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
253                 /* now we can release the sidecars */
254                 filelist_free(sidecars);
255                 file_data_increment_version(fd);
256                 file_data_send_notification(fd, NOTIFY_REREAD);
257                 file_data_unref(fd);
258                 }
259         else
260                 {
261                 ret |= file_data_check_changed_files_recursive(fd, &st);
262                 }
263
264         return ret;
265 }
266
267 /*
268  *-----------------------------------------------------------------------------
269  * file name, extension, sorting, ...
270  *-----------------------------------------------------------------------------
271  */
272
273 static void file_data_set_collate_keys(FileData *fd)
274 {
275         gchar *caseless_name;
276         gchar *valid_name;
277
278         valid_name = g_filename_display_name(fd->name);
279         caseless_name = g_utf8_casefold(valid_name, -1);
280
281         g_free(fd->collate_key_name);
282         g_free(fd->collate_key_name_nocase);
283
284         if (options->file_sort.natural)
285                 {
286                 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
287                 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
288                 }
289         else
290                 {
291                 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
292                 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
293                 }
294
295         g_free(valid_name);
296         g_free(caseless_name);
297 }
298
299 static void file_data_set_path(FileData *fd, const gchar *path)
300 {
301         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
302         g_assert(file_data_pool);
303
304         g_free(fd->path);
305
306         if (fd->original_path)
307                 {
308                 g_hash_table_remove(file_data_pool, fd->original_path);
309                 g_free(fd->original_path);
310                 }
311
312         g_assert(!g_hash_table_lookup(file_data_pool, path));
313
314         fd->original_path = g_strdup(path);
315         g_hash_table_insert(file_data_pool, fd->original_path, fd);
316
317         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
318                 {
319                 fd->path = g_strdup(path);
320                 fd->name = fd->path;
321                 fd->extension = fd->name + 1;
322                 file_data_set_collate_keys(fd);
323                 return;
324                 }
325
326         fd->path = g_strdup(path);
327         fd->name = filename_from_path(fd->path);
328
329         if (strcmp(fd->name, "..") == 0)
330                 {
331                 gchar *dir = remove_level_from_path(path);
332                 g_free(fd->path);
333                 fd->path = remove_level_from_path(dir);
334                 g_free(dir);
335                 fd->name = "..";
336                 fd->extension = fd->name + 2;
337                 file_data_set_collate_keys(fd);
338                 return;
339                 }
340         else if (strcmp(fd->name, ".") == 0)
341                 {
342                 g_free(fd->path);
343                 fd->path = remove_level_from_path(path);
344                 fd->name = ".";
345                 fd->extension = fd->name + 1;
346                 file_data_set_collate_keys(fd);
347                 return;
348                 }
349
350         fd->extension = registered_extension_from_path(fd->path);
351         if (fd->extension == NULL)
352                 {
353                 fd->extension = fd->name + strlen(fd->name);
354                 }
355
356         fd->sidecar_priority = sidecar_file_priority(fd->extension);
357         file_data_set_collate_keys(fd);
358 }
359
360 /*
361  *-----------------------------------------------------------------------------
362  * create or reuse Filedata
363  *-----------------------------------------------------------------------------
364  */
365
366 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
367 {
368         FileData *fd;
369         struct passwd *user;
370         struct group *group;
371
372         DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
373
374         if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
375
376         if (!file_data_pool)
377                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
378
379         fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
380         if (fd)
381                 {
382                 file_data_ref(fd);
383                 }
384
385         if (!fd && file_data_planned_change_hash)
386                 {
387                 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
388                 if (fd)
389                         {
390                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
391                         if (!isfile(fd->path))
392                                 {
393                                 file_data_ref(fd);
394                                 file_data_apply_ci(fd);
395                                 }
396                         else
397                                 {
398                                 fd = NULL;
399                                 }
400                         }
401                 }
402
403         if (fd)
404                 {
405                 gboolean changed;
406
407                 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
408
409
410                 changed = file_data_check_changed_single_file(fd, st);
411
412                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
413
414                 return fd;
415                 }
416
417         fd = g_new0(FileData, 1);
418 #ifdef DEBUG_FILEDATA
419         global_file_data_count++;
420         DEBUG_2("file data count++: %d", global_file_data_count);
421 #endif
422
423         fd->size = st->st_size;
424         fd->date = st->st_mtime;
425         fd->cdate = st->st_ctime;
426         fd->mode = st->st_mode;
427         fd->ref = 1;
428         fd->magick = FD_MAGICK;
429         fd->exifdate = 0;
430         fd->rating = STAR_RATING_NOT_READ;
431         fd->format_class = filter_file_get_class(path_utf8);
432         fd->page_num = 0;
433         fd->page_total = 0;
434
435         user = getpwuid(st->st_uid);
436         if (!user)
437                 {
438                 fd->owner = g_strdup_printf("%u", st->st_uid);
439                 }
440         else
441                 {
442                 fd->owner = g_strdup(user->pw_name);
443                 }
444
445         group = getgrgid(st->st_gid);
446         if (!group)
447                 {
448                 fd->group = g_strdup_printf("%u", st->st_gid);
449                 }
450         else
451                 {
452                 fd->group = g_strdup(group->gr_name);
453                 }
454
455         fd->sym_link = get_symbolic_link(path_utf8);
456
457         if (disable_sidecars) fd->disable_grouping = TRUE;
458
459         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
460
461         return fd;
462 }
463
464 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
465 {
466         gchar *path_utf8 = path_to_utf8(path);
467         FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
468
469         g_free(path_utf8);
470         return ret;
471 }
472
473 FileData *file_data_new_simple(const gchar *path_utf8)
474 {
475         struct stat st;
476         FileData *fd;
477
478         if (!stat_utf8(path_utf8, &st))
479                 {
480                 st.st_size = 0;
481                 st.st_mtime = 0;
482                 }
483
484         fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
485         if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
486         if (fd)
487                 {
488                 file_data_ref(fd);
489                 }
490
491         return fd;
492 }
493
494 void read_exif_time_data(FileData *file)
495 {
496         if (file->exifdate > 0)
497                 {
498                 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
499                 return;
500                 }
501
502         if (!file->exif)
503                 {
504                 exif_read_fd(file);
505                 }
506
507         if (file->exif)
508                 {
509                 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
510                 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
511
512                 if (tmp)
513                         {
514                         struct tm time_str;
515                         uint year, month, day, hour, min, sec;
516
517                         sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
518                         time_str.tm_year  = year - 1900;
519                         time_str.tm_mon   = month - 1;
520                         time_str.tm_mday  = day;
521                         time_str.tm_hour  = hour;
522                         time_str.tm_min   = min;
523                         time_str.tm_sec   = sec;
524                         time_str.tm_isdst = 0;
525
526                         file->exifdate = mktime(&time_str);
527                         g_free(tmp);
528                         }
529                 }
530 }
531
532 void read_exif_time_digitized_data(FileData *file)
533 {
534         if (file->exifdate_digitized > 0)
535                 {
536                 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
537                 return;
538                 }
539
540         if (!file->exif)
541                 {
542                 exif_read_fd(file);
543                 }
544
545         if (file->exif)
546                 {
547                 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
548                 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
549
550                 if (tmp)
551                         {
552                         struct tm time_str;
553                         uint year, month, day, hour, min, sec;
554
555                         sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
556                         time_str.tm_year  = year - 1900;
557                         time_str.tm_mon   = month - 1;
558                         time_str.tm_mday  = day;
559                         time_str.tm_hour  = hour;
560                         time_str.tm_min   = min;
561                         time_str.tm_sec   = sec;
562                         time_str.tm_isdst = 0;
563
564                         file->exifdate_digitized = mktime(&time_str);
565                         g_free(tmp);
566                         }
567                 }
568 }
569
570 void read_rating_data(FileData *file)
571 {
572         gchar *rating_str;
573
574         rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
575         if (rating_str)
576                 {
577                 file->rating = atoi(rating_str);
578                 g_free(rating_str);
579                 }
580         else
581                 {
582                 file->rating = 0;
583                 }
584 }
585
586 //void set_exif_time_data(GList *files)
587 //{
588         //DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
589
590         //while (files)
591                 //{
592                 //FileData *file = files->data;
593
594                 //read_exif_time_data(file);
595                 //files = files->next;
596                 //}
597 //}
598
599 //void set_exif_time_digitized_data(GList *files)
600 //{
601         //DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
602
603         //while (files)
604                 //{
605                 //FileData *file = files->data;
606
607                 //read_exif_time_digitized_data(file);
608                 //files = files->next;
609                 //}
610 //}
611
612 //void set_rating_data(GList *files)
613 //{
614         //gchar *rating_str;
615         //DEBUG_1("%s set_rating_data: ...", get_exec_time());
616
617         //while (files)
618                 //{
619                 //FileData *file = files->data;
620                 //rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
621                 //if (rating_str )
622                         //{
623                         //file->rating = atoi(rating_str);
624                         //g_free(rating_str);
625                         //}
626                 //files = files->next;
627                 //}
628 //}
629
630 FileData *file_data_new_no_grouping(const gchar *path_utf8)
631 {
632         struct stat st;
633
634         if (!stat_utf8(path_utf8, &st))
635                 {
636                 st.st_size = 0;
637                 st.st_mtime = 0;
638                 }
639
640         return file_data_new(path_utf8, &st, TRUE);
641 }
642
643 FileData *file_data_new_dir(const gchar *path_utf8)
644 {
645         struct stat st;
646
647         if (!stat_utf8(path_utf8, &st))
648                 {
649                 st.st_size = 0;
650                 st.st_mtime = 0;
651                 }
652         else
653                 /* dir or non-existing yet */
654                 g_assert(S_ISDIR(st.st_mode));
655
656         return file_data_new(path_utf8, &st, TRUE);
657 }
658
659 /*
660  *-----------------------------------------------------------------------------
661  * reference counting
662  *-----------------------------------------------------------------------------
663  */
664
665 #ifdef DEBUG_FILEDATA
666 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
667 #else
668 FileData *file_data_ref(FileData *fd)
669 #endif
670 {
671         if (fd == NULL) return NULL;
672         if (fd->magick != FD_MAGICK)
673 #ifdef DEBUG_FILEDATA
674                 log_printf("Error: fd magick mismatch @ %s:%d  fd=%p", file, line, (void *)fd);
675 #else
676                 log_printf("Error: fd magick mismatch fd=%p", fd);
677 #endif
678         g_assert(fd->magick == FD_MAGICK);
679         fd->ref++;
680
681 #ifdef DEBUG_FILEDATA
682         DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
683 #else
684         DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
685 #endif
686         return fd;
687 }
688
689 /**
690  * @brief Print ref. count and image name
691  * @param  
692  * 
693  * Print image ref. count and full path name of all images in
694  * the file_data_pool.
695  *
696  * Used only by DEBUG_FD()
697  */
698 void file_data_dump()
699 {
700 #ifdef DEBUG_FILEDATA
701         FileData *fd;
702         GList *list;
703
704         if (file_data_pool)
705                 {
706                 list = g_hash_table_get_values(file_data_pool);
707
708                 log_printf("%d", global_file_data_count);
709                 log_printf("%d", g_list_length(list));
710
711                 while (list)
712                         {
713                         fd = static_cast<FileData *>(list->data);
714                         log_printf("%-4d %s", fd->ref, fd->path);
715                         list = list->next;
716                         }
717
718                 g_list_free(list);
719                 }
720 #endif
721 }
722
723 static void file_data_free(FileData *fd)
724 {
725         g_assert(fd->magick == FD_MAGICK);
726         g_assert(fd->ref == 0);
727         g_assert(!fd->locked);
728
729 #ifdef DEBUG_FILEDATA
730         global_file_data_count--;
731         DEBUG_2("file data count--: %d", global_file_data_count);
732 #endif
733
734         metadata_cache_free(fd);
735         g_hash_table_remove(file_data_pool, fd->original_path);
736
737         g_free(fd->path);
738         g_free(fd->original_path);
739         g_free(fd->collate_key_name);
740         g_free(fd->collate_key_name_nocase);
741         g_free(fd->extended_extension);
742         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
743         histmap_free(fd->histmap);
744         g_free(fd->owner);
745         g_free(fd->group);
746         g_free(fd->sym_link);
747         g_free(fd->format_name);
748         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
749
750         file_data_change_info_free(NULL, fd);
751         g_free(fd);
752 }
753
754 /**
755  * @brief Checks if the FileData is referenced
756  *
757  * Checks the refcount and whether the FileData is locked.
758  */
759 static gboolean file_data_check_has_ref(FileData *fd)
760 {
761         return fd->ref > 0 || fd->locked;
762 }
763
764 /**
765  * @brief Consider freeing a FileData.
766  *
767  * This function will free a FileData and its children provided that neither its parent nor it has
768  * a positive refcount, and provided that neither is locked.
769  */
770 static void file_data_consider_free(FileData *fd)
771 {
772         GList *work;
773         FileData *parent = fd->parent ? fd->parent : fd;
774
775         g_assert(fd->magick == FD_MAGICK);
776         if (file_data_check_has_ref(fd)) return;
777         if (file_data_check_has_ref(parent)) return;
778
779         work = parent->sidecar_files;
780         while (work)
781                 {
782                 FileData *sfd = static_cast<FileData *>(work->data);
783                 if (file_data_check_has_ref(sfd)) return;
784                 work = work->next;
785                 }
786
787         /* Neither the parent nor the siblings are referenced, so we can free everything */
788         DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
789                 fd->path, fd->parent ? parent->path : "-");
790
791         work = parent->sidecar_files;
792         while (work)
793                 {
794                 FileData *sfd = static_cast<FileData *>(work->data);
795                 file_data_free(sfd);
796                 work = work->next;
797                 }
798
799         g_list_free(parent->sidecar_files);
800         parent->sidecar_files = NULL;
801
802         file_data_free(parent);
803 }
804
805 #ifdef DEBUG_FILEDATA
806 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
807 #else
808 void file_data_unref(FileData *fd)
809 #endif
810 {
811         if (fd == NULL) return;
812         if (fd->magick != FD_MAGICK)
813 #ifdef DEBUG_FILEDATA
814                 log_printf("Error: fd magick mismatch @ %s:%d  fd=%p", file, line, (void *)fd);
815 #else
816                 log_printf("Error: fd magick mismatch fd=%p", fd);
817 #endif
818         g_assert(fd->magick == FD_MAGICK);
819
820         fd->ref--;
821 #ifdef DEBUG_FILEDATA
822         DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
823                 file, line);
824 #else
825         DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
826 #endif
827
828         // Free FileData if it's no longer ref'd
829         file_data_consider_free(fd);
830 }
831
832 /**
833  * @brief Lock the FileData in memory.
834  *
835  * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
836  * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
837  * even if the code would continue to function properly even if the FileData were freed.  Code that
838  * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
839  * <p />
840  * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
841  * file_data_lock, a single call to file_data_unlock will unlock the FileData.
842  */
843 void file_data_lock(FileData *fd)
844 {
845         if (fd == NULL) return;
846         if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
847
848         g_assert(fd->magick == FD_MAGICK);
849         fd->locked = TRUE;
850
851         DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
852 }
853
854 /**
855  * @brief Reset the maintain-FileData-in-memory lock
856  *
857  * This again allows the FileData to be freed when its refcount drops to zero.  Automatically frees
858  * the FileData if its refcount is already zero (which will happen if the lock is the only thing
859  * keeping it from being freed.
860  */
861 void file_data_unlock(FileData *fd)
862 {
863         if (fd == NULL) return;
864         if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
865
866         g_assert(fd->magick == FD_MAGICK);
867         fd->locked = FALSE;
868
869         // Free FileData if it's no longer ref'd
870         file_data_consider_free(fd);
871 }
872
873 /**
874  * @brief Lock all of the FileDatas in the provided list
875  *
876  * @see file_data_lock(#FileData)
877  */
878 void file_data_lock_list(GList *list)
879 {
880         GList *work;
881
882         work = list;
883         while (work)
884                 {
885                 FileData *fd = static_cast<FileData *>(work->data);
886                 work = work->next;
887                 file_data_lock(fd);
888                 }
889 }
890
891 /**
892  * @brief Unlock all of the FileDatas in the provided list
893  *
894  * @see #file_data_unlock(#FileData)
895  */
896 void file_data_unlock_list(GList *list)
897 {
898         GList *work;
899
900         work = list;
901         while (work)
902                 {
903                 FileData *fd = static_cast<FileData *>(work->data);
904                 work = work->next;
905                 file_data_unlock(fd);
906                 }
907 }
908
909 /*
910  *-----------------------------------------------------------------------------
911  * sidecar file info struct
912  *-----------------------------------------------------------------------------
913  */
914
915 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
916 {
917         const FileData *fda = static_cast<const FileData *>(a);
918         const FileData *fdb = static_cast<const FileData *>(b);
919
920         if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
921         if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
922
923         return strcmp(fdb->extension, fda->extension);
924 }
925
926
927 static gint sidecar_file_priority(const gchar *extension)
928 {
929         gint i = 1;
930         GList *work;
931
932         if (extension == NULL)
933                 return 0;
934
935         work = sidecar_ext_get_list();
936
937         while (work) {
938                 gchar *ext = static_cast<gchar *>(work->data);
939
940                 work = work->next;
941                 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
942                 i++;
943         }
944         return 0;
945 }
946
947 static void file_data_check_sidecars(const GList *basename_list)
948 {
949         /* basename_list contains the new group - first is the parent, then sorted sidecars */
950         /* all files in the list have ref count > 0 */
951
952         const GList *work;
953         GList *s_work, *new_sidecars;
954         FileData *parent_fd;
955
956         if (!basename_list) return;
957
958
959         DEBUG_2("basename start");
960         work = basename_list;
961         while (work)
962                 {
963                 FileData *fd = static_cast<FileData *>(work->data);
964                 work = work->next;
965                 g_assert(fd->magick == FD_MAGICK);
966                 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
967                 if (fd->parent)
968                         {
969                         g_assert(fd->parent->magick == FD_MAGICK);
970                         DEBUG_2("                  parent: %p", (void *)fd->parent);
971                         }
972                 s_work = fd->sidecar_files;
973                 while (s_work)
974                         {
975                         FileData *sfd = static_cast<FileData *>(s_work->data);
976                         s_work = s_work->next;
977                         g_assert(sfd->magick == FD_MAGICK);
978                         DEBUG_2("                  sidecar: %p %s", (void *)sfd, sfd->name);
979                         }
980
981                 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
982                 }
983
984         parent_fd = static_cast<FileData *>(basename_list->data);
985
986         /* check if the second and next entries of basename_list are already connected
987            as sidecars of the first entry (parent_fd) */
988         work = basename_list->next;
989         s_work = parent_fd->sidecar_files;
990
991         while (work && s_work)
992                 {
993                 if (work->data != s_work->data) break;
994                 work = work->next;
995                 s_work = s_work->next;
996                 }
997
998         if (!work && !s_work)
999                 {
1000                 DEBUG_2("basename no change");
1001                 return; /* no change in grouping */
1002                 }
1003
1004         /* we have to regroup it */
1005
1006         /* first, disconnect everything and send notification*/
1007
1008         work = basename_list;
1009         while (work)
1010                 {
1011                 FileData *fd = static_cast<FileData *>(work->data);
1012                 work = work->next;
1013                 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
1014
1015                 if (fd->parent)
1016                         {
1017                         FileData *old_parent = fd->parent;
1018                         g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
1019                         file_data_ref(old_parent);
1020                         file_data_disconnect_sidecar_file(old_parent, fd);
1021                         file_data_send_notification(old_parent, NOTIFY_REREAD);
1022                         file_data_unref(old_parent);
1023                         }
1024
1025                 while (fd->sidecar_files)
1026                         {
1027                         FileData *sfd = static_cast<FileData *>(fd->sidecar_files->data);
1028                         g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
1029                         file_data_ref(sfd);
1030                         file_data_disconnect_sidecar_file(fd, sfd);
1031                         file_data_send_notification(sfd, NOTIFY_REREAD);
1032                         file_data_unref(sfd);
1033                         }
1034                 file_data_send_notification(fd, NOTIFY_GROUPING);
1035
1036                 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1037                 }
1038
1039         /* now we can form the new group */
1040         work = basename_list->next;
1041         new_sidecars = NULL;
1042         while (work)
1043                 {
1044                 FileData *sfd = static_cast<FileData *>(work->data);
1045                 g_assert(sfd->magick == FD_MAGICK);
1046                 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1047                 sfd->parent = parent_fd;
1048                 new_sidecars = g_list_prepend(new_sidecars, sfd);
1049                 work = work->next;
1050                 }
1051         g_assert(parent_fd->sidecar_files == NULL);
1052         parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1053         DEBUG_1("basename group changed for %s", parent_fd->path);
1054 }
1055
1056
1057 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1058 {
1059         g_assert(target->magick == FD_MAGICK);
1060         g_assert(sfd->magick == FD_MAGICK);
1061         g_assert(g_list_find(target->sidecar_files, sfd));
1062
1063         file_data_ref(target);
1064         file_data_ref(sfd);
1065
1066         g_assert(sfd->parent == target);
1067
1068         file_data_increment_version(sfd); /* increments both sfd and target */
1069
1070         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1071         sfd->parent = NULL;
1072         g_free(sfd->extended_extension);
1073         sfd->extended_extension = NULL;
1074
1075         file_data_unref(target);
1076         file_data_unref(sfd);
1077 }
1078
1079 /* disables / enables grouping for particular file, sends UPDATE notification */
1080 void file_data_disable_grouping(FileData *fd, gboolean disable)
1081 {
1082         if (!fd->disable_grouping == !disable) return;
1083
1084         fd->disable_grouping = !!disable;
1085
1086         if (disable)
1087                 {
1088                 if (fd->parent)
1089                         {
1090                         FileData *parent = file_data_ref(fd->parent);
1091                         file_data_disconnect_sidecar_file(parent, fd);
1092                         file_data_send_notification(parent, NOTIFY_GROUPING);
1093                         file_data_unref(parent);
1094                         }
1095                 else if (fd->sidecar_files)
1096                         {
1097                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
1098                         GList *work = sidecar_files;
1099                         while (work)
1100                                 {
1101                                 FileData *sfd = static_cast<FileData *>(work->data);
1102                                 work = work->next;
1103                                 file_data_disconnect_sidecar_file(fd, sfd);
1104                                 file_data_send_notification(sfd, NOTIFY_GROUPING);
1105                                 }
1106                         file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1107                         filelist_free(sidecar_files);
1108                         }
1109                 else
1110                         {
1111                         file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1112                         }
1113                 }
1114         else
1115                 {
1116                 file_data_increment_version(fd);
1117                 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1118                 }
1119         file_data_send_notification(fd, NOTIFY_GROUPING);
1120 }
1121
1122 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1123 {
1124         GList *work;
1125
1126         work = fd_list;
1127         while (work)
1128                 {
1129                 FileData *fd = static_cast<FileData *>(work->data);
1130
1131                 file_data_disable_grouping(fd, disable);
1132                 work = work->next;
1133                 }
1134 }
1135
1136
1137
1138 /*
1139  *-----------------------------------------------------------------------------
1140  * filelist sorting
1141  *-----------------------------------------------------------------------------
1142  */
1143
1144
1145 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1146 {
1147         gint ret;
1148         if (!filelist_sort_ascend)
1149                 {
1150                 FileData *tmp = fa;
1151                 fa = fb;
1152                 fb = tmp;
1153                 }
1154
1155         switch (filelist_sort_method)
1156                 {
1157                 case SORT_NAME:
1158                         break;
1159                 case SORT_SIZE:
1160                         if (fa->size < fb->size) return -1;
1161                         if (fa->size > fb->size) return 1;
1162                         /* fall back to name */
1163                         break;
1164                 case SORT_TIME:
1165                         if (fa->date < fb->date) return -1;
1166                         if (fa->date > fb->date) return 1;
1167                         /* fall back to name */
1168                         break;
1169                 case SORT_CTIME:
1170                         if (fa->cdate < fb->cdate) return -1;
1171                         if (fa->cdate > fb->cdate) return 1;
1172                         /* fall back to name */
1173                         break;
1174                 case SORT_EXIFTIME:
1175                         if (fa->exifdate < fb->exifdate) return -1;
1176                         if (fa->exifdate > fb->exifdate) return 1;
1177                         /* fall back to name */
1178                         break;
1179                 case SORT_EXIFTIMEDIGITIZED:
1180                         if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1181                         if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1182                         /* fall back to name */
1183                         break;
1184                 case SORT_RATING:
1185                         if (fa->rating < fb->rating) return -1;
1186                         if (fa->rating > fb->rating) return 1;
1187                         /* fall back to name */
1188                         break;
1189                 case SORT_CLASS:
1190                         if (fa->format_class < fb->format_class) return -1;
1191                         if (fa->format_class > fb->format_class) return 1;
1192                         /* fall back to name */
1193                         break;
1194                 default:
1195                         break;
1196                 }
1197
1198         if (options->file_sort.case_sensitive)
1199                 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1200         else
1201                 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1202
1203         if (ret != 0) return ret;
1204
1205         /* do not return 0 unless the files are really the same
1206            file_data_pool ensures that original_path is unique
1207         */
1208         return strcmp(fa->original_path, fb->original_path);
1209 }
1210
1211 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1212 {
1213         filelist_sort_method = method;
1214         filelist_sort_ascend = ascend;
1215         return filelist_sort_compare_filedata(fa, fb);
1216 }
1217
1218 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1219 {
1220         return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1221 }
1222
1223 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1224 {
1225         filelist_sort_method = method;
1226         filelist_sort_ascend = ascend;
1227         return g_list_sort(list, cb);
1228 }
1229
1230 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1231 {
1232         filelist_sort_method = method;
1233         filelist_sort_ascend = ascend;
1234         return g_list_insert_sorted(list, data, cb);
1235 }
1236
1237 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1238 {
1239         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1240 }
1241
1242 //GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1243 //{
1244         //return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1245 //}
1246
1247 /*
1248  *-----------------------------------------------------------------------------
1249  * basename hash - grouping of sidecars in filelist
1250  *-----------------------------------------------------------------------------
1251  */
1252
1253
1254 static GHashTable *file_data_basename_hash_new(void)
1255 {
1256         return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1257 }
1258
1259 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1260 {
1261         GList *list;
1262         gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1263
1264         list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1265
1266         if (!list)
1267                 {
1268                 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1269                 const gchar *parent_extension = registered_extension_from_path(basename);
1270
1271                 if (parent_extension)
1272                         {
1273                         DEBUG_1("TG: parent extension %s",parent_extension);
1274                         gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1275                         DEBUG_1("TG: parent basename %s",parent_basename);
1276                         FileData *parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1277                         if (parent_fd)
1278                                 {
1279                                 DEBUG_1("TG: parent fd found");
1280                                 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1281                                 if (!g_list_find(list, parent_fd))
1282                                         {
1283                                         DEBUG_1("TG: parent fd doesn't fit");
1284                                         g_free(parent_basename);
1285                                         list = NULL;
1286                                         }
1287                                 else
1288                                         {
1289                                         g_free(basename);
1290                                         basename = parent_basename;
1291                                         fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1292                                         }
1293                                 }
1294                         }
1295                 }
1296
1297         if (!g_list_find(list, fd))
1298                 {
1299                 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1300                 g_hash_table_insert(basename_hash, basename, list);
1301                 }
1302         else
1303                 {
1304                 g_free(basename);
1305                 }
1306         return list;
1307 }
1308
1309 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1310 {
1311         file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1312 }
1313
1314 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1315 {
1316         filelist_free((GList *)value);
1317 }
1318
1319 static void file_data_basename_hash_free(GHashTable *basename_hash)
1320 {
1321         g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1322         g_hash_table_destroy(basename_hash);
1323 }
1324
1325 /*
1326  *-----------------------------------------------------------------------------
1327  * handling sidecars in filelist
1328  *-----------------------------------------------------------------------------
1329  */
1330
1331 static GList *filelist_filter_out_sidecars(GList *flist)
1332 {
1333         GList *work = flist;
1334         GList *flist_filtered = NULL;
1335
1336         while (work)
1337                 {
1338                 FileData *fd = static_cast<FileData *>(work->data);
1339
1340                 work = work->next;
1341                 if (fd->parent) /* remove fd's that are children */
1342                         file_data_unref(fd);
1343                 else
1344                         flist_filtered = g_list_prepend(flist_filtered, fd);
1345                 }
1346         g_list_free(flist);
1347
1348         return flist_filtered;
1349 }
1350
1351 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1352 {
1353         GList *basename_list = (GList *)value;
1354         file_data_check_sidecars(basename_list);
1355 }
1356
1357
1358 static gboolean is_hidden_file(const gchar *name)
1359 {
1360         if (name[0] != '.') return FALSE;
1361         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1362         return TRUE;
1363 }
1364
1365 /*
1366  *-----------------------------------------------------------------------------
1367  * the main filelist function
1368  *-----------------------------------------------------------------------------
1369  */
1370
1371 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1372 {
1373         DIR *dp;
1374         struct dirent *dir;
1375         gchar *pathl;
1376         GList *dlist = NULL;
1377         GList *flist = NULL;
1378         GList *xmp_files = NULL;
1379         gint (*stat_func)(const gchar *path, struct stat *buf);
1380         GHashTable *basename_hash = NULL;
1381
1382         g_assert(files || dirs);
1383
1384         if (files) *files = NULL;
1385         if (dirs) *dirs = NULL;
1386
1387         pathl = path_from_utf8(dir_path);
1388         if (!pathl) return FALSE;
1389
1390         dp = opendir(pathl);
1391         if (dp == NULL)
1392                 {
1393                 g_free(pathl);
1394                 return FALSE;
1395                 }
1396
1397         if (files) basename_hash = file_data_basename_hash_new();
1398
1399         if (follow_symlinks)
1400                 stat_func = stat;
1401         else
1402                 stat_func = lstat;
1403
1404         while ((dir = readdir(dp)) != NULL)
1405                 {
1406                 struct stat ent_sbuf;
1407                 const gchar *name = dir->d_name;
1408                 gchar *filepath;
1409
1410                 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1411                         continue;
1412
1413                 filepath = g_build_filename(pathl, name, NULL);
1414                 if (stat_func(filepath, &ent_sbuf) >= 0)
1415                         {
1416                         if (S_ISDIR(ent_sbuf.st_mode))
1417                                 {
1418                                 /* we ignore the .thumbnails dir for cleanliness */
1419                                 if (dirs &&
1420                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1421                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1422                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1423                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1424                                         {
1425                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1426                                         }
1427                                 }
1428                         else
1429                                 {
1430                                 if (files && filter_name_exists(name))
1431                                         {
1432                                         FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1433                                         flist = g_list_prepend(flist, fd);
1434                                         if (fd->sidecar_priority && !fd->disable_grouping)
1435                                                 {
1436                                                 if (strcmp(fd->extension, ".xmp") != 0)
1437                                                         file_data_basename_hash_insert(basename_hash, fd);
1438                                                 else
1439                                                         xmp_files = g_list_append(xmp_files, fd);
1440                                                 }
1441                                         }
1442                                 }
1443                         }
1444                 else
1445                         {
1446                         if (errno == EOVERFLOW)
1447                                 {
1448                                 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1449                                 }
1450                         }
1451                 g_free(filepath);
1452                 }
1453
1454         closedir(dp);
1455
1456         g_free(pathl);
1457
1458         if (xmp_files)
1459                 {
1460                 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1461                 g_list_free(xmp_files);
1462                 }
1463
1464         if (dirs) *dirs = dlist;
1465
1466         if (files)
1467                 {
1468                 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1469
1470                 *files = filelist_filter_out_sidecars(flist);
1471                 }
1472         if (basename_hash) file_data_basename_hash_free(basename_hash);
1473
1474         return TRUE;
1475 }
1476
1477 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1478 {
1479         return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1480 }
1481
1482 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1483 {
1484         return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1485 }
1486
1487 FileData *file_data_new_group(const gchar *path_utf8)
1488 {
1489         gchar *dir;
1490         struct stat st;
1491         FileData *fd;
1492         GList *files;
1493
1494         if (!file_data_pool)
1495                 {
1496                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1497                 }
1498
1499         if (!stat_utf8(path_utf8, &st))
1500                 {
1501                 st.st_size = 0;
1502                 st.st_mtime = 0;
1503                 }
1504
1505         if (S_ISDIR(st.st_mode))
1506                 return file_data_new(path_utf8, &st, TRUE);
1507
1508         dir = remove_level_from_path(path_utf8);
1509
1510         filelist_read_real(dir, &files, NULL, TRUE);
1511
1512         fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1513         if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1514         if (fd)
1515                 {
1516                 file_data_ref(fd);
1517                 }
1518
1519         filelist_free(files);
1520         g_free(dir);
1521         return fd;
1522 }
1523
1524
1525 void filelist_free(GList *list)
1526 {
1527         GList *work;
1528
1529         work = list;
1530         while (work)
1531                 {
1532                 file_data_unref((FileData *)work->data);
1533                 work = work->next;
1534                 }
1535
1536         g_list_free(list);
1537 }
1538
1539
1540 GList *filelist_copy(GList *list)
1541 {
1542         GList *new_list = NULL;
1543         GList *work;
1544
1545         work = list;
1546         while (work)
1547                 {
1548                 FileData *fd;
1549
1550                 fd = static_cast<FileData *>(work->data);
1551                 work = work->next;
1552
1553                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1554                 }
1555
1556         return g_list_reverse(new_list);
1557 }
1558
1559 GList *filelist_from_path_list(GList *list)
1560 {
1561         GList *new_list = NULL;
1562         GList *work;
1563
1564         work = list;
1565         while (work)
1566                 {
1567                 gchar *path;
1568
1569                 path = static_cast<gchar *>(work->data);
1570                 work = work->next;
1571
1572                 new_list = g_list_prepend(new_list, file_data_new_group(path));
1573                 }
1574
1575         return g_list_reverse(new_list);
1576 }
1577
1578 GList *filelist_to_path_list(GList *list)
1579 {
1580         GList *new_list = NULL;
1581         GList *work;
1582
1583         work = list;
1584         while (work)
1585                 {
1586                 FileData *fd;
1587
1588                 fd = static_cast<FileData *>(work->data);
1589                 work = work->next;
1590
1591                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1592                 }
1593
1594         return g_list_reverse(new_list);
1595 }
1596
1597 GList *filelist_filter(GList *list, gboolean is_dir_list)
1598 {
1599         GList *work;
1600
1601         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1602
1603         work = list;
1604         while (work)
1605                 {
1606                 FileData *fd = (FileData *)(work->data);
1607                 const gchar *name = fd->name;
1608
1609                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1610                     (!is_dir_list && !filter_name_exists(name)) ||
1611                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1612                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1613                         {
1614                         GList *link = work;
1615
1616                         list = g_list_remove_link(list, link);
1617                         file_data_unref(fd);
1618                         g_list_free(link);
1619                         }
1620
1621                 work = work->next;
1622                 }
1623
1624         return list;
1625 }
1626
1627 /*
1628  *-----------------------------------------------------------------------------
1629  * filelist recursive
1630  *-----------------------------------------------------------------------------
1631  */
1632
1633 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1634 {
1635         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1636 }
1637
1638 GList *filelist_sort_path(GList *list)
1639 {
1640         return g_list_sort(list, filelist_sort_path_cb);
1641 }
1642
1643 static void filelist_recursive_append(GList **list, GList *dirs)
1644 {
1645         GList *work;
1646
1647         work = dirs;
1648         while (work)
1649                 {
1650                 FileData *fd = (FileData *)(work->data);
1651                 GList *f;
1652                 GList *d;
1653
1654                 if (filelist_read(fd, &f, &d))
1655                         {
1656                         f = filelist_filter(f, FALSE);
1657                         f = filelist_sort_path(f);
1658                         *list = g_list_concat(*list, f);
1659
1660                         d = filelist_filter(d, TRUE);
1661                         d = filelist_sort_path(d);
1662                         filelist_recursive_append(list, d);
1663                         filelist_free(d);
1664                         }
1665
1666                 work = work->next;
1667                 }
1668 }
1669
1670 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1671 {
1672         GList *work;
1673
1674         work = dirs;
1675         while (work)
1676                 {
1677                 FileData *fd = (FileData *)(work->data);
1678                 GList *f;
1679                 GList *d;
1680
1681                 if (filelist_read(fd, &f, &d))
1682                         {
1683                         f = filelist_filter(f, FALSE);
1684                         f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1685                         *list = g_list_concat(*list, f);
1686
1687                         d = filelist_filter(d, TRUE);
1688                         d = filelist_sort_path(d);
1689                         filelist_recursive_append_full(list, d, method, ascend);
1690                         filelist_free(d);
1691                         }
1692
1693                 work = work->next;
1694                 }
1695 }
1696
1697 GList *filelist_recursive(FileData *dir_fd)
1698 {
1699         GList *list;
1700         GList *d;
1701
1702         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1703         list = filelist_filter(list, FALSE);
1704         list = filelist_sort_path(list);
1705
1706         d = filelist_filter(d, TRUE);
1707         d = filelist_sort_path(d);
1708         filelist_recursive_append(&list, d);
1709         filelist_free(d);
1710
1711         return list;
1712 }
1713
1714 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1715 {
1716         GList *list;
1717         GList *d;
1718
1719         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1720         list = filelist_filter(list, FALSE);
1721         list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1722
1723         d = filelist_filter(d, TRUE);
1724         d = filelist_sort_path(d);
1725         filelist_recursive_append_full(&list, d, method, ascend);
1726         filelist_free(d);
1727
1728         return list;
1729 }
1730
1731 /*
1732  *-----------------------------------------------------------------------------
1733  * file modification support
1734  *-----------------------------------------------------------------------------
1735  */
1736
1737
1738 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1739 {
1740         if (!fdci && fd) fdci = fd->change;
1741
1742         if (!fdci) return;
1743
1744         g_free(fdci->source);
1745         g_free(fdci->dest);
1746
1747         g_free(fdci);
1748
1749         if (fd) fd->change = NULL;
1750 }
1751
1752 static gboolean file_data_can_write_directly(FileData *fd)
1753 {
1754         return filter_name_is_writable(fd->extension);
1755 }
1756
1757 static gboolean file_data_can_write_sidecar(FileData *fd)
1758 {
1759         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1760 }
1761
1762 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1763 {
1764         gchar *sidecar_path = NULL;
1765         GList *work;
1766
1767         if (!file_data_can_write_sidecar(fd)) return NULL;
1768
1769         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1770         gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1771         while (work)
1772                 {
1773                 FileData *sfd = static_cast<FileData *>(work->data);
1774                 work = work->next;
1775                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1776                         {
1777                         sidecar_path = g_strdup(sfd->path);
1778                         break;
1779                         }
1780                 }
1781         g_free(extended_extension);
1782
1783         if (!existing_only && !sidecar_path)
1784                 {
1785                 if (options->metadata.sidecar_extended_name)
1786                         sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1787                 else
1788                         {
1789                         gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1790                         sidecar_path = g_strconcat(base, ".xmp", NULL);
1791                         g_free(base);
1792                         }
1793                 }
1794
1795         return sidecar_path;
1796 }
1797
1798 /*
1799  * marks and orientation
1800  */
1801
1802 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1803 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1804 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1805 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1806
1807 gboolean file_data_get_mark(FileData *fd, gint n)
1808 {
1809         gboolean valid = (fd->valid_marks & (1 << n));
1810
1811         if (file_data_get_mark_func[n] && !valid)
1812                 {
1813                 guint old = fd->marks;
1814                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1815
1816                 if (!value != !(fd->marks & (1 << n)))
1817                         {
1818                         fd->marks = fd->marks ^ (1 << n);
1819                         }
1820
1821                 fd->valid_marks |= (1 << n);
1822                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1823                         {
1824                         file_data_unref(fd);
1825                         }
1826                 else if (!old && fd->marks)
1827                         {
1828                         file_data_ref(fd);
1829                         }
1830                 }
1831
1832         return !!(fd->marks & (1 << n));
1833 }
1834
1835 guint file_data_get_marks(FileData *fd)
1836 {
1837         gint i;
1838         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1839         return fd->marks;
1840 }
1841
1842 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1843 {
1844         guint old;
1845         if (!value == !file_data_get_mark(fd, n)) return;
1846
1847         if (file_data_set_mark_func[n])
1848                 {
1849                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1850                 }
1851
1852         old = fd->marks;
1853
1854         fd->marks = fd->marks ^ (1 << n);
1855
1856         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1857                 {
1858                 file_data_unref(fd);
1859                 }
1860         else if (!old && fd->marks)
1861                 {
1862                 file_data_ref(fd);
1863                 }
1864
1865         file_data_increment_version(fd);
1866         file_data_send_notification(fd, NOTIFY_MARKS);
1867 }
1868
1869 gboolean file_data_filter_marks(FileData *fd, guint filter)
1870 {
1871         gint i;
1872         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1873         return ((fd->marks & filter) == filter);
1874 }
1875
1876 GList *file_data_filter_marks_list(GList *list, guint filter)
1877 {
1878         GList *work;
1879
1880         work = list;
1881         while (work)
1882                 {
1883                 FileData *fd = static_cast<FileData *>(work->data);
1884                 GList *link = work;
1885                 work = work->next;
1886
1887                 if (!file_data_filter_marks(fd, filter))
1888                         {
1889                         list = g_list_remove_link(list, link);
1890                         file_data_unref(fd);
1891                         g_list_free(link);
1892                         }
1893                 }
1894
1895         return list;
1896 }
1897
1898 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1899 {
1900         return g_regex_match(filter, fd->name, (GRegexMatchFlags)0, NULL);
1901 }
1902
1903 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1904 {
1905         GList *work;
1906
1907         work = list;
1908         while (work)
1909                 {
1910                 FileData *fd = static_cast<FileData *>(work->data);
1911                 GList *link = work;
1912                 work = work->next;
1913
1914                 if (!file_data_filter_file_filter(fd, filter))
1915                         {
1916                         list = g_list_remove_link(list, link);
1917                         file_data_unref(fd);
1918                         g_list_free(link);
1919                         }
1920                 }
1921
1922         return list;
1923 }
1924
1925 static gboolean file_data_filter_class(FileData *fd, guint filter)
1926 {
1927         gint i;
1928
1929         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1930                 {
1931                 if (filter & (1 << i))
1932                         {
1933                         if ((FileFormatClass)i == filter_file_get_class(fd->path))
1934                                 {
1935                                 return TRUE;
1936                                 }
1937                         }
1938                 }
1939
1940         return FALSE;
1941 }
1942
1943 GList *file_data_filter_class_list(GList *list, guint filter)
1944 {
1945         GList *work;
1946
1947         work = list;
1948         while (work)
1949                 {
1950                 FileData *fd = static_cast<FileData *>(work->data);
1951                 GList *link = work;
1952                 work = work->next;
1953
1954                 if (!file_data_filter_class(fd, filter))
1955                         {
1956                         list = g_list_remove_link(list, link);
1957                         file_data_unref(fd);
1958                         g_list_free(link);
1959                         }
1960                 }
1961
1962         return list;
1963 }
1964
1965 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1966 {
1967         FileData *fd = static_cast<FileData *>(value);
1968         file_data_increment_version(fd);
1969         file_data_send_notification(fd, NOTIFY_MARKS);
1970 }
1971
1972 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1973 {
1974         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1975
1976         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1977
1978         file_data_get_mark_func[n] = get_mark_func;
1979         file_data_set_mark_func[n] = set_mark_func;
1980         file_data_mark_func_data[n] = data;
1981         file_data_destroy_mark_func[n] = notify;
1982
1983         if (get_mark_func && file_data_pool)
1984                 {
1985                 /* this effectively changes all known files */
1986                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1987                 }
1988
1989         return TRUE;
1990 }
1991
1992 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1993 {
1994         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1995         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1996         if (data) *data = file_data_mark_func_data[n];
1997 }
1998
1999 //gint file_data_get_user_orientation(FileData *fd)
2000 //{
2001         //return fd->user_orientation;
2002 //}
2003
2004 //void file_data_set_user_orientation(FileData *fd, gint value)
2005 //{
2006         //if (fd->user_orientation == value) return;
2007
2008         //fd->user_orientation = value;
2009         //file_data_increment_version(fd);
2010         //file_data_send_notification(fd, NOTIFY_ORIENTATION);
2011 //}
2012
2013
2014 /*
2015  * file_data    - operates on the given fd
2016  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2017  */
2018
2019
2020 /* return list of sidecar file extensions in a string */
2021 gchar *file_data_sc_list_to_string(FileData *fd)
2022 {
2023         GList *work;
2024         GString *result = g_string_new("");
2025
2026         work = fd->sidecar_files;
2027         while (work)
2028                 {
2029                 FileData *sfd = static_cast<FileData *>(work->data);
2030
2031                 result = g_string_append(result, "+ ");
2032                 result = g_string_append(result, sfd->extension);
2033                 work = work->next;
2034                 if (work) result = g_string_append_c(result, ' ');
2035                 }
2036
2037         return g_string_free(result, FALSE);
2038 }
2039
2040
2041
2042 /*
2043  * add FileDataChangeInfo (see typedefs.h) for the given operation
2044  * uses file_data_add_change_info
2045  *
2046  * fails if the fd->change already exists - change operations can't run in parallel
2047  * fd->change_info works as a lock
2048  *
2049  * dest can be NULL - in this case the current name is used for now, it will
2050  * be changed later
2051  */
2052
2053 /*
2054    FileDataChangeInfo types:
2055    COPY
2056    MOVE   - path is changed, name may be changed too
2057    RENAME - path remains unchanged, name is changed
2058             extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2059             sidecar names are changed too, extensions are not changed
2060    DELETE
2061    UPDATE - file size, date or grouping has been changed
2062 */
2063
2064 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2065 {
2066         FileDataChangeInfo *fdci;
2067
2068         if (fd->change) return FALSE;
2069
2070         fdci = g_new0(FileDataChangeInfo, 1);
2071
2072         fdci->type = type;
2073
2074         if (src)
2075                 fdci->source = g_strdup(src);
2076         else
2077                 fdci->source = g_strdup(fd->path);
2078
2079         if (dest)
2080                 fdci->dest = g_strdup(dest);
2081
2082         fd->change = fdci;
2083
2084         return TRUE;
2085 }
2086
2087 static void file_data_planned_change_remove(FileData *fd)
2088 {
2089         if (file_data_planned_change_hash &&
2090             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2091                 {
2092                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2093                         {
2094                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2095                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2096                         file_data_unref(fd);
2097                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
2098                                 {
2099                                 g_hash_table_destroy(file_data_planned_change_hash);
2100                                 file_data_planned_change_hash = NULL;
2101                                 DEBUG_1("planned change: empty");
2102                                 }
2103                         }
2104                 }
2105 }
2106
2107
2108 void file_data_free_ci(FileData *fd)
2109 {
2110         FileDataChangeInfo *fdci = fd->change;
2111
2112         if (!fdci) return;
2113
2114         file_data_planned_change_remove(fd);
2115
2116         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2117
2118         g_free(fdci->source);
2119         g_free(fdci->dest);
2120
2121         g_free(fdci);
2122
2123         fd->change = NULL;
2124 }
2125
2126 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2127 {
2128         FileDataChangeInfo *fdci = fd->change;
2129         if (!fdci) return;
2130         fdci->regroup_when_finished = enable;
2131 }
2132
2133 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2134 {
2135         GList *work;
2136
2137         if (fd->parent) fd = fd->parent;
2138
2139         if (fd->change) return FALSE;
2140
2141         work = fd->sidecar_files;
2142         while (work)
2143                 {
2144                 FileData *sfd = static_cast<FileData *>(work->data);
2145
2146                 if (sfd->change) return FALSE;
2147                 work = work->next;
2148                 }
2149
2150         file_data_add_ci(fd, type, NULL, NULL);
2151
2152         work = fd->sidecar_files;
2153         while (work)
2154                 {
2155                 FileData *sfd = static_cast<FileData *>(work->data);
2156
2157                 file_data_add_ci(sfd, type, NULL, NULL);
2158                 work = work->next;
2159                 }
2160
2161         return TRUE;
2162 }
2163
2164 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2165 {
2166         GList *work;
2167
2168         if (fd->parent) fd = fd->parent;
2169
2170         if (!fd->change || fd->change->type != type) return FALSE;
2171
2172         work = fd->sidecar_files;
2173         while (work)
2174                 {
2175                 FileData *sfd = static_cast<FileData *>(work->data);
2176
2177                 if (!sfd->change || sfd->change->type != type) return FALSE;
2178                 work = work->next;
2179                 }
2180
2181         return TRUE;
2182 }
2183
2184
2185 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2186 {
2187         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2188         file_data_sc_update_ci_copy(fd, dest_path);
2189         return TRUE;
2190 }
2191
2192 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2193 {
2194         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2195         file_data_sc_update_ci_move(fd, dest_path);
2196         return TRUE;
2197 }
2198
2199 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2200 {
2201         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2202         file_data_sc_update_ci_rename(fd, dest_path);
2203         return TRUE;
2204 }
2205
2206 gboolean file_data_sc_add_ci_delete(FileData *fd)
2207 {
2208         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2209 }
2210
2211 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2212 {
2213         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2214         file_data_sc_update_ci_unspecified(fd, dest_path);
2215         return TRUE;
2216 }
2217
2218 gboolean file_data_add_ci_write_metadata(FileData *fd)
2219 {
2220         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2221 }
2222
2223 void file_data_sc_free_ci(FileData *fd)
2224 {
2225         GList *work;
2226
2227         if (fd->parent) fd = fd->parent;
2228
2229         file_data_free_ci(fd);
2230
2231         work = fd->sidecar_files;
2232         while (work)
2233                 {
2234                 FileData *sfd = static_cast<FileData *>(work->data);
2235
2236                 file_data_free_ci(sfd);
2237                 work = work->next;
2238                 }
2239 }
2240
2241 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2242 {
2243         GList *work;
2244         gboolean ret = TRUE;
2245
2246         work = fd_list;
2247         while (work)
2248                 {
2249                 FileData *fd = static_cast<FileData *>(work->data);
2250
2251                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2252                 work = work->next;
2253                 }
2254
2255         return ret;
2256 }
2257
2258 static void file_data_sc_revert_ci_list(GList *fd_list)
2259 {
2260         GList *work;
2261
2262         work = fd_list;
2263         while (work)
2264                 {
2265                 FileData *fd = static_cast<FileData *>(work->data);
2266
2267                 file_data_sc_free_ci(fd);
2268                 work = work->prev;
2269                 }
2270 }
2271
2272 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2273 {
2274         GList *work;
2275
2276         work = fd_list;
2277         while (work)
2278                 {
2279                 FileData *fd = static_cast<FileData *>(work->data);
2280
2281                 if (!func(fd, dest))
2282                         {
2283                         file_data_sc_revert_ci_list(work->prev);
2284                         return FALSE;
2285                         }
2286                 work = work->next;
2287                 }
2288
2289         return TRUE;
2290 }
2291
2292 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2293 {
2294         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2295 }
2296
2297 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2298 {
2299         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2300 }
2301
2302 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2303 {
2304         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2305 }
2306
2307 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2308 {
2309         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2310 }
2311
2312 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2313 {
2314         GList *work;
2315         gboolean ret = TRUE;
2316
2317         work = fd_list;
2318         while (work)
2319                 {
2320                 FileData *fd = static_cast<FileData *>(work->data);
2321
2322                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2323                 work = work->next;
2324                 }
2325
2326         return ret;
2327 }
2328
2329 void file_data_free_ci_list(GList *fd_list)
2330 {
2331         GList *work;
2332
2333         work = fd_list;
2334         while (work)
2335                 {
2336                 FileData *fd = static_cast<FileData *>(work->data);
2337
2338                 file_data_free_ci(fd);
2339                 work = work->next;
2340                 }
2341 }
2342
2343 void file_data_sc_free_ci_list(GList *fd_list)
2344 {
2345         GList *work;
2346
2347         work = fd_list;
2348         while (work)
2349                 {
2350                 FileData *fd = static_cast<FileData *>(work->data);
2351
2352                 file_data_sc_free_ci(fd);
2353                 work = work->next;
2354                 }
2355 }
2356
2357 /*
2358  * update existing fd->change, it will be used from dialog callbacks for interactive editing
2359  * fails if fd->change does not exist or the change type does not match
2360  */
2361
2362 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2363 {
2364         FileDataChangeType type = fd->change->type;
2365
2366         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2367                 {
2368                 FileData *ofd;
2369
2370                 if (!file_data_planned_change_hash)
2371                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2372
2373                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2374                         {
2375                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2376                         g_hash_table_remove(file_data_planned_change_hash, old_path);
2377                         file_data_unref(fd);
2378                         }
2379
2380                 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2381                 if (ofd != fd)
2382                         {
2383                         if (ofd)
2384                                 {
2385                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2386                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
2387                                 file_data_unref(ofd);
2388                                 }
2389
2390                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2391                         file_data_ref(fd);
2392                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2393                         }
2394                 }
2395 }
2396
2397 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2398 {
2399         gchar *old_path = fd->change->dest;
2400
2401         fd->change->dest = g_strdup(dest_path);
2402         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2403         g_free(old_path);
2404 }
2405
2406 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2407 {
2408         const gchar *extension = registered_extension_from_path(fd->change->source);
2409         gchar *base = remove_extension_from_path(dest_path);
2410         gchar *old_path = fd->change->dest;
2411
2412         fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2413         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2414
2415         g_free(old_path);
2416         g_free(base);
2417 }
2418
2419 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2420 {
2421         GList *work;
2422         gchar *dest_path_full = NULL;
2423
2424         if (fd->parent) fd = fd->parent;
2425
2426         if (!dest_path)
2427                 {
2428                 dest_path = fd->path;
2429                 }
2430         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2431                 {
2432                 gchar *dir = remove_level_from_path(fd->path);
2433
2434                 dest_path_full = g_build_filename(dir, dest_path, NULL);
2435                 g_free(dir);
2436                 dest_path = dest_path_full;
2437                 }
2438         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2439                 {
2440                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2441                 dest_path = dest_path_full;
2442                 }
2443
2444         file_data_update_ci_dest(fd, dest_path);
2445
2446         work = fd->sidecar_files;
2447         while (work)
2448                 {
2449                 FileData *sfd = static_cast<FileData *>(work->data);
2450
2451                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2452                 work = work->next;
2453                 }
2454
2455         g_free(dest_path_full);
2456 }
2457
2458 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2459 {
2460         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2461         file_data_sc_update_ci(fd, dest_path);
2462         return TRUE;
2463 }
2464
2465 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2466 {
2467         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2468 }
2469
2470 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2471 {
2472         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2473 }
2474
2475 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2476 {
2477         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2478 }
2479
2480 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2481 {
2482         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2483 }
2484
2485 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2486                                                       const gchar *dest,
2487                                                       gboolean (*func)(FileData *, const gchar *))
2488 {
2489         GList *work;
2490         gboolean ret = TRUE;
2491
2492         work = fd_list;
2493         while (work)
2494                 {
2495                 FileData *fd = static_cast<FileData *>(work->data);
2496
2497                 if (!func(fd, dest)) ret = FALSE;
2498                 work = work->next;
2499                 }
2500
2501         return ret;
2502 }
2503
2504 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2505 {
2506         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2507 }
2508
2509 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2510 {
2511         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2512 }
2513
2514 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2515 {
2516         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2517 }
2518
2519
2520 /*
2521  * verify source and dest paths - dest image exists, etc.
2522  * it should detect all possible problems with the planned operation
2523  */
2524
2525 gint file_data_verify_ci(FileData *fd, GList *list)
2526 {
2527         gint ret = CHANGE_OK;
2528         gchar *dir;
2529         GList *work = NULL;
2530         FileData *fd1 = NULL;
2531
2532         if (!fd->change)
2533                 {
2534                 DEBUG_1("Change checked: no change info: %s", fd->path);
2535                 return ret;
2536                 }
2537
2538         if (!isname(fd->path))
2539                 {
2540                 /* this probably should not happen */
2541                 ret |= CHANGE_NO_SRC;
2542                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2543                 return ret;
2544                 }
2545
2546         dir = remove_level_from_path(fd->path);
2547
2548         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2549             fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2550             fd->change->type != FILEDATA_CHANGE_RENAME &&
2551             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2552             fd->modified_xmp)
2553                 {
2554                 ret |= CHANGE_WARN_UNSAVED_META;
2555                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2556                 }
2557
2558         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2559             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2560             !access_file(fd->path, R_OK))
2561                 {
2562                 ret |= CHANGE_NO_READ_PERM;
2563                 DEBUG_1("Change checked: no read permission: %s", fd->path);
2564                 }
2565         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2566                  !access_file(dir, W_OK))
2567                 {
2568                 ret |= CHANGE_NO_WRITE_PERM_DIR;
2569                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2570                 }
2571         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2572                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2573                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2574                  !access_file(fd->path, W_OK))
2575                 {
2576                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2577                 DEBUG_1("Change checked: no write permission: %s", fd->path);
2578                 }
2579         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2580            - that means that there are no hard errors and warnings can be disabled
2581            - the destination is determined during the check
2582         */
2583         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2584                 {
2585                 /* determine destination file */
2586                 gboolean have_dest = FALSE;
2587                 gchar *dest_dir = NULL;
2588
2589                 if (options->metadata.save_in_image_file)
2590                         {
2591                         if (file_data_can_write_directly(fd))
2592                                 {
2593                                 /* we can write the file directly */
2594                                 if (access_file(fd->path, W_OK))
2595                                         {
2596                                         have_dest = TRUE;
2597                                         }
2598                                 else
2599                                         {
2600                                         if (options->metadata.warn_on_write_problems)
2601                                                 {
2602                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2603                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2604                                                 }
2605                                         }
2606                                 }
2607                         else if (file_data_can_write_sidecar(fd))
2608                                 {
2609                                 /* we can write sidecar */
2610                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2611                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2612                                         {
2613                                         file_data_update_ci_dest(fd, sidecar);
2614                                         have_dest = TRUE;
2615                                         }
2616                                 else
2617                                         {
2618                                         if (options->metadata.warn_on_write_problems)
2619                                                 {
2620                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2621                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2622                                                 }
2623                                         }
2624                                 g_free(sidecar);
2625                                 }
2626                         }
2627
2628                 if (!have_dest)
2629                         {
2630                         /* write private metadata file under ~/.geeqie */
2631
2632                         /* If an existing metadata file exists, we will try writing to
2633                          * it's location regardless of the user's preference.
2634                          */
2635                         gchar *metadata_path = NULL;
2636 #ifdef HAVE_EXIV2
2637                         /* but ignore XMP if we are not able to write it */
2638                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2639 #endif
2640                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2641
2642                         if (metadata_path && !access_file(metadata_path, W_OK))
2643                                 {
2644                                 g_free(metadata_path);
2645                                 metadata_path = NULL;
2646                                 }
2647
2648                         if (!metadata_path)
2649                                 {
2650                                 mode_t mode = 0755;
2651
2652                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2653                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2654                                         {
2655                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2656
2657                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
2658                                         g_free(filename);
2659                                         }
2660                                 }
2661                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2662                                 {
2663                                 file_data_update_ci_dest(fd, metadata_path);
2664                                 have_dest = TRUE;
2665                                 }
2666                         else
2667                                 {
2668                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
2669                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2670                                 }
2671                         g_free(metadata_path);
2672                         }
2673                 g_free(dest_dir);
2674                 }
2675
2676         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2677                 {
2678                 gboolean same;
2679                 gchar *dest_dir;
2680
2681                 same = (strcmp(fd->path, fd->change->dest) == 0);
2682
2683                 if (!same)
2684                         {
2685                         const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2686                         if (!dest_ext) dest_ext = "";
2687                         if (!options->file_filter.disable_file_extension_checks)
2688                                 {
2689                                 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2690                                         {
2691                                         ret |= CHANGE_WARN_CHANGED_EXT;
2692                                         DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2693                                         }
2694                                 }
2695                         }
2696                 else
2697                         {
2698                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2699                                 {
2700                                 ret |= CHANGE_WARN_SAME;
2701                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2702                                 }
2703                         }
2704
2705                 dest_dir = remove_level_from_path(fd->change->dest);
2706
2707                 if (!isdir(dest_dir))
2708                         {
2709                         ret |= CHANGE_NO_DEST_DIR;
2710                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2711                         }
2712                 else if (!access_file(dest_dir, W_OK))
2713                         {
2714                         ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2715                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2716                         }
2717                 else if (!same)
2718                         {
2719                         if (isfile(fd->change->dest))
2720                                 {
2721                                 if (!access_file(fd->change->dest, W_OK))
2722                                         {
2723                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2724                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2725                                         }
2726                                 else
2727                                         {
2728                                         ret |= CHANGE_WARN_DEST_EXISTS;
2729                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2730                                         }
2731                                 }
2732                         else if (isdir(fd->change->dest))
2733                                 {
2734                                 ret |= CHANGE_DEST_EXISTS;
2735                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2736                                 }
2737                         }
2738
2739                 g_free(dest_dir);
2740                 }
2741
2742         /* During a rename operation, check if another planned destination file has
2743          * the same filename
2744          */
2745         if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2746                                 fd->change->type == FILEDATA_CHANGE_COPY ||
2747                                 fd->change->type == FILEDATA_CHANGE_MOVE)
2748                 {
2749                 work = list;
2750                 while (work)
2751                         {
2752                         fd1 = static_cast<FileData *>(work->data);
2753                         work = work->next;
2754                         if (fd1 != NULL && fd != fd1 )
2755                                 {
2756                                 if (!strcmp(fd->change->dest, fd1->change->dest))
2757                                         {
2758                                         ret |= CHANGE_DUPLICATE_DEST;
2759                                         }
2760                                 }
2761                         }
2762                 }
2763
2764         fd->change->error = ret;
2765         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2766
2767         g_free(dir);
2768         return ret;
2769 }
2770
2771
2772 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2773 {
2774         GList *work;
2775         gint ret;
2776
2777         ret = file_data_verify_ci(fd, list);
2778
2779         work = fd->sidecar_files;
2780         while (work)
2781                 {
2782                 FileData *sfd = static_cast<FileData *>(work->data);
2783
2784                 ret |= file_data_verify_ci(sfd, list);
2785                 work = work->next;
2786                 }
2787
2788         return ret;
2789 }
2790
2791 gchar *file_data_get_error_string(gint error)
2792 {
2793         GString *result = g_string_new("");
2794
2795         if (error & CHANGE_NO_SRC)
2796                 {
2797                 if (result->len > 0) g_string_append(result, ", ");
2798                 g_string_append(result, _("file or directory does not exist"));
2799                 }
2800
2801         if (error & CHANGE_DEST_EXISTS)
2802                 {
2803                 if (result->len > 0) g_string_append(result, ", ");
2804                 g_string_append(result, _("destination already exists"));
2805                 }
2806
2807         if (error & CHANGE_NO_WRITE_PERM_DEST)
2808                 {
2809                 if (result->len > 0) g_string_append(result, ", ");
2810                 g_string_append(result, _("destination can't be overwritten"));
2811                 }
2812
2813         if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2814                 {
2815                 if (result->len > 0) g_string_append(result, ", ");
2816                 g_string_append(result, _("destination directory is not writable"));
2817                 }
2818
2819         if (error & CHANGE_NO_DEST_DIR)
2820                 {
2821                 if (result->len > 0) g_string_append(result, ", ");
2822                 g_string_append(result, _("destination directory does not exist"));
2823                 }
2824
2825         if (error & CHANGE_NO_WRITE_PERM_DIR)
2826                 {
2827                 if (result->len > 0) g_string_append(result, ", ");
2828                 g_string_append(result, _("source directory is not writable"));
2829                 }
2830
2831         if (error & CHANGE_NO_READ_PERM)
2832                 {
2833                 if (result->len > 0) g_string_append(result, ", ");
2834                 g_string_append(result, _("no read permission"));
2835                 }
2836
2837         if (error & CHANGE_WARN_NO_WRITE_PERM)
2838                 {
2839                 if (result->len > 0) g_string_append(result, ", ");
2840                 g_string_append(result, _("file is readonly"));
2841                 }
2842
2843         if (error & CHANGE_WARN_DEST_EXISTS)
2844                 {
2845                 if (result->len > 0) g_string_append(result, ", ");
2846                 g_string_append(result, _("destination already exists and will be overwritten"));
2847                 }
2848
2849         if (error & CHANGE_WARN_SAME)
2850                 {
2851                 if (result->len > 0) g_string_append(result, ", ");
2852                 g_string_append(result, _("source and destination are the same"));
2853                 }
2854
2855         if (error & CHANGE_WARN_CHANGED_EXT)
2856                 {
2857                 if (result->len > 0) g_string_append(result, ", ");
2858                 g_string_append(result, _("source and destination have different extension"));
2859                 }
2860
2861         if (error & CHANGE_WARN_UNSAVED_META)
2862                 {
2863                 if (result->len > 0) g_string_append(result, ", ");
2864                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2865                 }
2866
2867         if (error & CHANGE_DUPLICATE_DEST)
2868                 {
2869                 if (result->len > 0) g_string_append(result, ", ");
2870                 g_string_append(result, _("another destination file has the same filename"));
2871                 }
2872
2873         return g_string_free(result, FALSE);
2874 }
2875
2876 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2877 {
2878         GList *work;
2879         gint all_errors = 0;
2880         gint common_errors = ~0;
2881         gint num;
2882         gint *errors;
2883         gint i;
2884
2885         if (!list) return 0;
2886
2887         num = g_list_length(list);
2888         errors = g_new(int, num);
2889         work = list;
2890         i = 0;
2891         while (work)
2892                 {
2893                 FileData *fd;
2894                 gint error;
2895
2896                 fd = static_cast<FileData *>(work->data);
2897                 work = work->next;
2898
2899                 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2900                 all_errors |= error;
2901                 common_errors &= error;
2902
2903                 errors[i] = error;
2904
2905                 i++;
2906                 }
2907
2908         if (desc && all_errors)
2909                 {
2910                 GList *work;
2911                 GString *result = g_string_new("");
2912
2913                 if (common_errors)
2914                         {
2915                         gchar *str = file_data_get_error_string(common_errors);
2916                         g_string_append(result, str);
2917                         g_string_append(result, "\n");
2918                         g_free(str);
2919                         }
2920
2921                 work = list;
2922                 i = 0;
2923                 while (work)
2924                         {
2925                         FileData *fd;
2926                         gint error;
2927
2928                         fd = static_cast<FileData *>(work->data);
2929                         work = work->next;
2930
2931                         error = errors[i] & ~common_errors;
2932
2933                         if (error)
2934                                 {
2935                                 gchar *str = file_data_get_error_string(error);
2936                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2937                                 g_free(str);
2938                                 }
2939                         i++;
2940                         }
2941                 *desc = g_string_free(result, FALSE);
2942                 }
2943
2944         g_free(errors);
2945         return all_errors;
2946 }
2947
2948
2949 /*
2950  * perform the change described by FileFataChangeInfo
2951  * it is used for internal operations,
2952  * this function actually operates with files on the filesystem
2953  * it should implement safe delete
2954  */
2955
2956 static gboolean file_data_perform_move(FileData *fd)
2957 {
2958         g_assert(!strcmp(fd->change->source, fd->path));
2959         return move_file(fd->change->source, fd->change->dest);
2960 }
2961
2962 static gboolean file_data_perform_copy(FileData *fd)
2963 {
2964         g_assert(!strcmp(fd->change->source, fd->path));
2965         return copy_file(fd->change->source, fd->change->dest);
2966 }
2967
2968 static gboolean file_data_perform_delete(FileData *fd)
2969 {
2970         if (isdir(fd->path) && !islink(fd->path))
2971                 return rmdir_utf8(fd->path);
2972         else
2973                 if (options->file_ops.safe_delete_enable)
2974                         return file_util_safe_unlink(fd->path);
2975                 else
2976                         return unlink_file(fd->path);
2977 }
2978
2979 gboolean file_data_perform_ci(FileData *fd)
2980 {
2981         FileDataChangeType type = fd->change->type;
2982
2983         switch (type)
2984                 {
2985                 case FILEDATA_CHANGE_MOVE:
2986                         return file_data_perform_move(fd);
2987                 case FILEDATA_CHANGE_COPY:
2988                         return file_data_perform_copy(fd);
2989                 case FILEDATA_CHANGE_RENAME:
2990                         return file_data_perform_move(fd); /* the same as move */
2991                 case FILEDATA_CHANGE_DELETE:
2992                         return file_data_perform_delete(fd);
2993                 case FILEDATA_CHANGE_WRITE_METADATA:
2994                         return metadata_write_perform(fd);
2995                 case FILEDATA_CHANGE_UNSPECIFIED:
2996                         /* nothing to do here */
2997                         break;
2998                 }
2999         return TRUE;
3000 }
3001
3002
3003
3004 gboolean file_data_sc_perform_ci(FileData *fd)
3005 {
3006         GList *work;
3007         gboolean ret = TRUE;
3008         FileDataChangeType type = fd->change->type;
3009
3010         if (!file_data_sc_check_ci(fd, type)) return FALSE;
3011
3012         work = fd->sidecar_files;
3013         while (work)
3014                 {
3015                 FileData *sfd = static_cast<FileData *>(work->data);
3016
3017                 if (!file_data_perform_ci(sfd)) ret = FALSE;
3018                 work = work->next;
3019                 }
3020
3021         if (!file_data_perform_ci(fd)) ret = FALSE;
3022
3023         return ret;
3024 }
3025
3026 /*
3027  * updates FileData structure according to FileDataChangeInfo
3028  */
3029
3030 gboolean file_data_apply_ci(FileData *fd)
3031 {
3032         FileDataChangeType type = fd->change->type;
3033
3034         /** @FIXME delete ?*/
3035         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3036                 {
3037                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3038                 file_data_planned_change_remove(fd);
3039
3040                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3041                         {
3042                         /* this change overwrites another file which is already known to other modules
3043                            renaming fd would create duplicate FileData structure
3044                            the best thing we can do is nothing
3045                         */
3046                         /**  @FIXME maybe we could copy stuff like marks
3047                         */
3048                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3049                         }
3050                 else
3051                         {
3052                         file_data_set_path(fd, fd->change->dest);
3053                         }
3054                 }
3055         file_data_increment_version(fd);
3056         file_data_send_notification(fd, NOTIFY_CHANGE);
3057
3058         return TRUE;
3059 }
3060
3061 gboolean file_data_sc_apply_ci(FileData *fd)
3062 {
3063         GList *work;
3064         FileDataChangeType type = fd->change->type;
3065
3066         if (!file_data_sc_check_ci(fd, type)) return FALSE;
3067
3068         work = fd->sidecar_files;
3069         while (work)
3070                 {
3071                 FileData *sfd = static_cast<FileData *>(work->data);
3072
3073                 file_data_apply_ci(sfd);
3074                 work = work->next;
3075                 }
3076
3077         file_data_apply_ci(fd);
3078
3079         return TRUE;
3080 }
3081
3082 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3083 {
3084         GList *work;
3085         if (fd->parent) fd = fd->parent;
3086         if (!g_list_find(list, fd)) return FALSE;
3087
3088         work = fd->sidecar_files;
3089         while (work)
3090                 {
3091                 if (!g_list_find(list, work->data)) return FALSE;
3092                 work = work->next;
3093                 }
3094         return TRUE;
3095 }
3096
3097 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3098 {
3099         GList *out = NULL;
3100         GList *work = list;
3101
3102         /* change partial groups to independent files */
3103         if (ungroup)
3104                 {
3105                 while (work)
3106                         {
3107                         FileData *fd = static_cast<FileData *>(work->data);
3108                         work = work->next;
3109
3110                         if (!file_data_list_contains_whole_group(list, fd))
3111                                 {
3112                                 file_data_disable_grouping(fd, TRUE);
3113                                 if (ungrouped_list)
3114                                         {
3115                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3116                                         }
3117                                 }
3118                         }
3119                 }
3120
3121         /* remove sidecars from the list,
3122            they can be still accessed via main_fd->sidecar_files */
3123         work = list;
3124         while (work)
3125                 {
3126                 FileData *fd = static_cast<FileData *>(work->data);
3127                 work = work->next;
3128
3129                 if (!fd->parent ||
3130                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3131                         {
3132                         out = g_list_prepend(out, file_data_ref(fd));
3133                         }
3134                 }
3135
3136         filelist_free(list);
3137         out = g_list_reverse(out);
3138
3139         return out;
3140 }
3141
3142
3143
3144
3145
3146 /*
3147  * notify other modules about the change described by FileDataChangeInfo
3148  */
3149
3150 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3151 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3152    implementation in view-file-list.cc */
3153
3154
3155 typedef struct _NotifyIdleData NotifyIdleData;
3156
3157 struct _NotifyIdleData {
3158         FileData *fd;
3159         NotifyType type;
3160 };
3161
3162
3163 typedef struct _NotifyData NotifyData;
3164
3165 struct _NotifyData {
3166         FileDataNotifyFunc func;
3167         gpointer data;
3168         NotifyPriority priority;
3169 };
3170
3171 static GList *notify_func_list = NULL;
3172
3173 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3174 {
3175         NotifyData *nda = (NotifyData *)a;
3176         NotifyData *ndb = (NotifyData *)b;
3177
3178         if (nda->priority < ndb->priority) return -1;
3179         if (nda->priority > ndb->priority) return 1;
3180         return 0;
3181 }
3182
3183 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3184 {
3185         NotifyData *nd;
3186         GList *work = notify_func_list;
3187
3188         while (work)
3189                 {
3190                 NotifyData *nd = (NotifyData *)work->data;
3191
3192                 if (nd->func == func && nd->data == data)
3193                         {
3194                         g_warning("Notify func already registered");
3195                         return FALSE;
3196                         }
3197                 work = work->next;
3198                 }
3199
3200         nd = g_new(NotifyData, 1);
3201         nd->func = func;
3202         nd->data = data;
3203         nd->priority = priority;
3204
3205         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3206         DEBUG_2("Notify func registered: %p", (void *)nd);
3207
3208         return TRUE;
3209 }
3210
3211 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3212 {
3213         GList *work = notify_func_list;
3214
3215         while (work)
3216                 {
3217                 NotifyData *nd = (NotifyData *)work->data;
3218
3219                 if (nd->func == func && nd->data == data)
3220                         {
3221                         notify_func_list = g_list_delete_link(notify_func_list, work);
3222                         g_free(nd);
3223                         DEBUG_2("Notify func unregistered: %p", (void *)nd);
3224                         return TRUE;
3225                         }
3226                 work = work->next;
3227                 }
3228
3229         g_warning("Notify func not found");
3230         return FALSE;
3231 }
3232
3233
3234 //gboolean file_data_send_notification_idle_cb(gpointer data)
3235 //{
3236         //NotifyIdleData *nid = (NotifyIdleData *)data;
3237         //GList *work = notify_func_list;
3238
3239         //while (work)
3240                 //{
3241                 //NotifyData *nd = (NotifyData *)work->data;
3242
3243                 //nd->func(nid->fd, nid->type, nd->data);
3244                 //work = work->next;
3245                 //}
3246         //file_data_unref(nid->fd);
3247         //g_free(nid);
3248         //return FALSE;
3249 //}
3250
3251 void file_data_send_notification(FileData *fd, NotifyType type)
3252 {
3253         GList *work = notify_func_list;
3254
3255         while (work)
3256                 {
3257                 NotifyData *nd = (NotifyData *)work->data;
3258
3259                 nd->func(fd, type, nd->data);
3260                 work = work->next;
3261                 }
3262     /*
3263         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3264         nid->fd = file_data_ref(fd);
3265         nid->type = type;
3266         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3267     */
3268 }
3269
3270 static GHashTable *file_data_monitor_pool = NULL;
3271 static guint realtime_monitor_id = 0; /* event source id */
3272
3273 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3274 {
3275         FileData *fd = static_cast<FileData *>(key);
3276
3277         file_data_check_changed_files(fd);
3278
3279         DEBUG_1("monitor %s", fd->path);
3280 }
3281
3282 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3283 {
3284         if (!options->update_on_time_change) return TRUE;
3285         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3286         return TRUE;
3287 }
3288
3289 gboolean file_data_register_real_time_monitor(FileData *fd)
3290 {
3291         gint count;
3292
3293         file_data_ref(fd);
3294
3295         if (!file_data_monitor_pool)
3296                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3297
3298         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3299
3300         DEBUG_1("Register realtime %d %s", count, fd->path);
3301
3302         count++;
3303         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3304
3305         if (!realtime_monitor_id)
3306                 {
3307                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3308                 }
3309
3310         return TRUE;
3311 }
3312
3313 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3314 {
3315         gint count;
3316
3317         g_assert(file_data_monitor_pool);
3318
3319         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3320
3321         DEBUG_1("Unregister realtime %d %s", count, fd->path);
3322
3323         g_assert(count > 0);
3324
3325         count--;
3326
3327         if (count == 0)
3328                 g_hash_table_remove(file_data_monitor_pool, fd);
3329         else
3330                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3331
3332         file_data_unref(fd);
3333
3334         if (g_hash_table_size(file_data_monitor_pool) == 0)
3335                 {
3336                 g_source_remove(realtime_monitor_id);
3337                 realtime_monitor_id = 0;
3338                 return FALSE;
3339                 }
3340
3341         return TRUE;
3342 }
3343
3344 /*
3345  *-----------------------------------------------------------------------------
3346  * Saving marks list, clearing marks
3347  * Uses file_data_pool
3348  *-----------------------------------------------------------------------------
3349  */
3350
3351 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3352 {
3353         gchar *file_name = static_cast<gchar *>(key);
3354         GString *result = static_cast<GString *>(userdata);
3355         FileData *fd;
3356
3357         if (isfile(file_name))
3358                 {
3359                 fd = static_cast<FileData *>(value);
3360                 if (fd && fd->marks > 0)
3361                         {
3362                         g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3363                         }
3364                 }
3365 }
3366
3367 gboolean marks_list_load(const gchar *path)
3368 {
3369         FILE *f;
3370         gchar s_buf[1024];
3371         gchar *pathl;
3372         gchar *file_path;
3373         gchar *marks_value;
3374
3375         pathl = path_from_utf8(path);
3376         f = fopen(pathl, "r");
3377         g_free(pathl);
3378         if (!f) return FALSE;
3379
3380         /* first line must start with Marks comment */
3381         if (!fgets(s_buf, sizeof(s_buf), f) ||
3382                                         strncmp(s_buf, "#Marks", 6) != 0)
3383                 {
3384                 fclose(f);
3385                 return FALSE;
3386                 }
3387
3388         while (fgets(s_buf, sizeof(s_buf), f))
3389                 {
3390                 if (s_buf[0]=='#') continue;
3391                         file_path = strtok(s_buf, ",");
3392                         marks_value = strtok(NULL, ",");
3393                         if (isfile(file_path))
3394                                 {
3395                                 FileData *fd = file_data_new_no_grouping(file_path);
3396                                 file_data_ref(fd);
3397                                 gint n = 0;
3398                                 while (n <= 9)
3399                                         {
3400                                         gint mark_no = 1 << n;
3401                                         if (atoi(marks_value) & mark_no)
3402                                                 {
3403                                                 file_data_set_mark(fd, n , 1);
3404                                                 }
3405                                         n++;
3406                                         }
3407                                 }
3408                 }
3409
3410         fclose(f);
3411         return TRUE;
3412 }
3413
3414 gboolean marks_list_save(gchar *path, gboolean save)
3415 {
3416         SecureSaveInfo *ssi;
3417         gchar *pathl;
3418         GString  *marks = g_string_new("");
3419
3420         pathl = path_from_utf8(path);
3421         ssi = secure_open(pathl);
3422         g_free(pathl);
3423         if (!ssi)
3424                 {
3425                 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3426                 return FALSE;
3427                 }
3428
3429         secure_fprintf(ssi, "#Marks lists\n");
3430
3431         if (save)
3432                 {
3433                 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3434                 }
3435         secure_fprintf(ssi, "%s", marks->str);
3436         g_string_free(marks, FALSE);
3437
3438         secure_fprintf(ssi, "#end\n");
3439         return (secure_close(ssi) == 0);
3440 }
3441
3442 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3443 {
3444         gchar *file_name = static_cast<gchar *>(key);
3445         gint mark_no;
3446         gint n;
3447         FileData *fd;
3448
3449         if (isfile(file_name))
3450                 {
3451                 fd = static_cast<FileData *>(value);
3452                 if (fd && fd->marks > 0)
3453                         {
3454                         n = 0;
3455                         while (n <= 9)
3456                                 {
3457                                 mark_no = 1 << n;
3458                                 if (fd->marks & mark_no)
3459                                         {
3460                                         file_data_set_mark(fd, n , 0);
3461                                         }
3462                                 n++;
3463                                 }
3464                         }
3465                 }
3466 }
3467
3468 void marks_clear_all()
3469 {
3470         g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3471 }
3472
3473 void file_data_set_page_num(FileData *fd, gint page_num)
3474 {
3475         if (fd->page_total > 1 && page_num < 0)
3476                 {
3477                 fd->page_num = fd->page_total - 1;
3478                 }
3479         else if (fd->page_total > 1 && page_num <= fd->page_total)
3480                 {
3481                 fd->page_num = page_num - 1;
3482                 }
3483         else
3484                 {
3485                 fd->page_num = 0;
3486                 }
3487         file_data_send_notification(fd, NOTIFY_REREAD);
3488 }
3489
3490 void file_data_inc_page_num(FileData *fd)
3491 {
3492         if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3493                 {
3494                 fd->page_num = fd->page_num + 1;
3495                 }
3496         else if (fd->page_total == 0)
3497                 {
3498                 fd->page_num = fd->page_num + 1;
3499                 }
3500         file_data_send_notification(fd, NOTIFY_REREAD);
3501 }
3502
3503 void file_data_dec_page_num(FileData *fd)
3504 {
3505         if (fd->page_num > 0)
3506                 {
3507                 fd->page_num = fd->page_num - 1;
3508                 }
3509         file_data_send_notification(fd, NOTIFY_REREAD);
3510 }
3511
3512 void file_data_set_page_total(FileData *fd, gint page_total)
3513 {
3514         fd->page_total = page_total;
3515 }
3516
3517 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */