8de267c32655b10e8dd7b064d0472b6e19dbf79e
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "filedata.h"
16
17 #include "filefilter.h"
18 #include "cache.h"
19 #include "debug.h"
20 #include "rcfile.h"
21 #include "secure_save.h"
22 #include "thumb_standard.h"
23 #include "ui_fileops.h"
24
25
26 static gint sidecar_file_priority(const gchar *path);
27
28
29 /*
30  *-----------------------------------------------------------------------------
31  * text conversion utils
32  *-----------------------------------------------------------------------------
33  */
34
35 gchar *text_from_size(gint64 size)
36 {
37         gchar *a, *b;
38         gchar *s, *d;
39         gint l, n, i;
40
41         /* what I would like to use is printf("%'d", size)
42          * BUT: not supported on every libc :(
43          */
44         if (size > G_MAXUINT)
45                 {
46                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
47                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
48                 }
49         else
50                 {
51                 a = g_strdup_printf("%d", (guint)size);
52                 }
53         l = strlen(a);
54         n = (l - 1)/ 3;
55         if (n < 1) return a;
56
57         b = g_new(gchar, l + n + 1);
58
59         s = a;
60         d = b;
61         i = l - n * 3;
62         while (*s != '\0')
63                 {
64                 if (i < 1)
65                         {
66                         i = 3;
67                         *d = ',';
68                         d++;
69                         }
70
71                 *d = *s;
72                 s++;
73                 d++;
74                 i--;
75                 }
76         *d = '\0';
77
78         g_free(a);
79         return b;
80 }
81
82 gchar *text_from_size_abrev(gint64 size)
83 {
84         if (size < (gint64)1024)
85                 {
86                 return g_strdup_printf(_("%d bytes"), (gint)size);
87                 }
88         if (size < (gint64)1048576)
89                 {
90                 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
91                 }
92         if (size < (gint64)1073741824)
93                 {
94                 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
95                 }
96
97         /* to avoid overflowing the double, do division in two steps */
98         size /= 1048576;
99         return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
100 }
101
102 /* note: returned string is valid until next call to text_from_time() */
103 const gchar *text_from_time(time_t t)
104 {
105         static gchar *ret = NULL;
106         gchar buf[128];
107         gint buflen;
108         struct tm *btime;
109         GError *error = NULL;
110
111         btime = localtime(&t);
112
113         /* the %x warning about 2 digit years is not an error */
114         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
115         if (buflen < 1) return "";
116
117         g_free(ret);
118         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
119         if (error)
120                 {
121                 printf("Error converting locale strftime to UTF-8: %s\n", error->message);
122                 g_error_free(error);
123                 return "";
124                 }
125
126         return ret;
127 }
128
129 /*
130  *-----------------------------------------------------------------------------
131  * file info struct
132  *-----------------------------------------------------------------------------
133  */
134
135 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
136 static void file_data_check_sidecars(FileData *fd);
137 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
138
139
140 static void file_data_set_path(FileData *fd, const gchar *path)
141 {
142
143         if (strcmp(path, "/") == 0)
144                 {
145                 fd->path = g_strdup(path);
146                 fd->name = fd->path;
147                 fd->extension = fd->name + 1;
148                 return;
149                 }
150
151         fd->path = g_strdup(path);
152         fd->name = filename_from_path(fd->path);
153
154         if (strcmp(fd->name, "..") == 0)
155                 {
156                 gchar *dir = remove_level_from_path(path);
157                 g_free(fd->path);
158                 fd->path = remove_level_from_path(dir);
159                 g_free(dir);
160                 fd->name = "..";
161                 fd->extension = fd->name + 2;
162                 return;
163                 }
164         else if (strcmp(fd->name, ".") == 0)
165                 {
166                 g_free(fd->path);
167                 fd->path = remove_level_from_path(path);
168                 fd->name = ".";
169                 fd->extension = fd->name + 1;
170                 return;
171                 }
172
173         fd->extension = extension_from_path(fd->path);
174         if (fd->extension == NULL)
175                 fd->extension = fd->name + strlen(fd->name);
176 }
177
178 static void file_data_check_changed_files(FileData *fd, struct stat *st)
179 {
180         GList *work;
181         if (fd->size != st->st_size ||
182             fd->date != st->st_mtime)
183                 {
184                 fd->size = st->st_size;
185                 fd->date = st->st_mtime;
186                 if (fd->pixbuf) g_object_unref(fd->pixbuf);
187                 fd->pixbuf = NULL;
188                 }
189
190         work = fd->sidecar_files;
191         while (work)
192                 {
193                 FileData *sfd = work->data;
194                 struct stat st;
195
196                 if (!stat_utf8(sfd->path, &st))
197                         {
198                         file_data_disconnect_sidecar_file(fd, sfd);
199                         }
200
201                 file_data_check_changed_files(sfd, &st);
202                 work = work->next;
203                 }
204 }
205
206 static GHashTable *file_data_pool = NULL;
207
208 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
209 {
210         FileData *fd;
211
212         DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
213
214         if (!file_data_pool)
215                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
216
217         fd = g_hash_table_lookup(file_data_pool, path_utf8);
218         if (fd)
219                 {
220                 file_data_check_changed_files(fd, st);
221                 DEBUG_2("file_data_pool hit: '%s'", fd->path);
222                 return file_data_ref(fd);
223                 }
224
225         fd = g_new0(FileData, 1);
226
227         file_data_set_path(fd, path_utf8);
228
229         fd->original_path = g_strdup(path_utf8);
230         fd->size = st->st_size;
231         fd->date = st->st_mtime;
232         fd->pixbuf = NULL;
233         fd->sidecar_files = NULL;
234         fd->ref = 1;
235         fd->magick = 0x12345678;
236
237         g_hash_table_insert(file_data_pool, fd->original_path, fd);
238
239         if (check_sidecars && sidecar_file_priority(fd->extension))
240                 file_data_check_sidecars(fd);
241         return fd;
242 }
243
244 static void file_data_check_sidecars(FileData *fd)
245 {
246         int base_len = fd->extension - fd->path;
247         GString *fname = g_string_new_len(fd->path, base_len);
248         FileData *parent_fd = NULL;
249         GList *work = sidecar_ext_get_list();
250         while (work)
251                 {
252                 /* check for possible sidecar files;
253                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
254                    they have fd->ref set to 0 and file_data unref must chack and free them all together
255                    (using fd->ref would cause loops and leaks)
256                 */
257
258                 FileData *new_fd;
259
260                 gchar *ext = work->data;
261                 work = work->next;
262
263                 if (strcmp(ext, fd->extension) == 0)
264                         {
265                         new_fd = fd; /* processing the original file */
266                         }
267                 else
268                         {
269                         struct stat nst;
270                         g_string_truncate(fname, base_len);
271                         g_string_append(fname, ext);
272
273                         if (!stat_utf8(fname->str, &nst))
274                                 continue;
275
276                         new_fd = file_data_new(fname->str, &nst, FALSE);
277                         new_fd->ref--; /* do not use ref here */
278                         }
279
280                 if (!parent_fd)
281                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
282                 else
283                         file_data_merge_sidecar_files(parent_fd, new_fd);
284                 }
285         g_string_free(fname, TRUE);
286 }
287
288
289 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
290 {
291         gchar *path_utf8 = path_to_utf8(path);
292         FileData *ret = file_data_new(path_utf8, st, check_sidecars);
293         g_free(path_utf8);
294         return ret;
295 }
296
297 FileData *file_data_new_simple(const gchar *path_utf8)
298 {
299         struct stat st;
300
301         if (!stat_utf8(path_utf8, &st))
302                 {
303                 st.st_size = 0;
304                 st.st_mtime = 0;
305                 }
306
307         return file_data_new(path_utf8, &st, TRUE);
308 }
309
310 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
311 {
312         sfd->parent = target;
313         if(!g_list_find(target->sidecar_files, sfd))
314                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
315         return target;
316 }
317
318
319 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
320 {
321         GList *work;
322         file_data_add_sidecar_file(target, source);
323
324         work = source->sidecar_files;
325         while (work)
326                 {
327                 FileData *sfd = work->data;
328                 file_data_add_sidecar_file(target, sfd);
329                 work = work->next;
330                 }
331
332         g_list_free(source->sidecar_files);
333         source->sidecar_files = NULL;
334
335         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
336         return target;
337 }
338
339
340
341 FileData *file_data_ref(FileData *fd)
342 {
343         if (fd == NULL) return NULL;
344
345 //      return g_memdup(fd, sizeof(FileData));
346         g_assert(fd->magick == 0x12345678);
347         fd->ref++;
348         return fd;
349 }
350
351 static void file_data_free(FileData *fd)
352 {
353         g_assert(fd->magick == 0x12345678);
354         g_assert(fd->ref == 0);
355
356         g_hash_table_remove(file_data_pool, fd->original_path);
357
358         g_free(fd->path);
359         g_free(fd->original_path);
360         if (fd->pixbuf) g_object_unref(fd->pixbuf);
361
362
363         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
364
365         file_data_change_info_free(NULL, fd);
366         g_free(fd);
367 }
368
369 void file_data_unref(FileData *fd)
370 {
371         if (fd == NULL) return;
372         g_assert(fd->magick == 0x12345678);
373
374         fd->ref--;
375         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
376
377         if (fd->ref == 0)
378                 {
379                 FileData *parent = fd->parent ? fd->parent : fd;
380
381                 GList *work;
382
383                 if (parent->ref > 0)
384                         return;
385
386                 work = parent->sidecar_files;
387                 while (work)
388                         {
389                         FileData *sfd = work->data;
390                         if (sfd->ref > 0)
391                                 return;
392                         work = work->next;
393                         }
394
395                 /* none of parent/children is referenced, we can free everything */
396
397                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, parent->path);
398
399                 work = parent->sidecar_files;
400                 while (work)
401                         {
402                         FileData *sfd = work->data;
403                         file_data_free(sfd);
404                         work = work->next;
405                         }
406
407                 g_list_free(parent->sidecar_files);
408                 parent->sidecar_files = NULL;
409
410                 file_data_free(parent);
411
412                 }
413 }
414
415 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
416 {
417         sfd->parent = target;
418         g_assert(g_list_find(target->sidecar_files, sfd));
419
420         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
421         sfd->parent = NULL;
422
423         if (sfd->ref == 0) {
424                 file_data_free(sfd);
425                 return NULL;
426         }
427
428         return sfd;
429 }
430
431 /* compare name without extension */
432 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
433 {
434         size_t len1 = fd1->extension - fd1->name;
435         size_t len2 = fd2->extension - fd2->name;
436
437         if (len1 < len2) return -1;
438         if (len1 > len2) return 1;
439
440         return strncmp(fd1->name, fd2->name, len1);
441 }
442
443 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
444 {
445
446         FileDataChangeInfo *fdci;
447
448         if (fd->change) return FALSE;
449
450         fdci = g_new0(FileDataChangeInfo, 1);
451
452         fdci->type = type;
453
454         if (src)
455                 fdci->source = g_strdup(src);
456         else
457                 fdci->source = g_strdup(fd->path);
458
459         if (dest)
460                 fdci->dest = g_strdup(dest);
461
462         fd->change = fdci;
463         return TRUE;
464 }
465
466 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
467 {
468         if (!fdci && fd)
469                 fdci = fd->change;
470
471         if (!fdci)
472                 return;
473
474         g_free(fdci->source);
475         g_free(fdci->dest);
476
477         g_free(fdci);
478
479         if (fd)
480                 fd->change = NULL;
481 }
482
483
484
485
486 /*
487  *-----------------------------------------------------------------------------
488  * sidecar file info struct
489  *-----------------------------------------------------------------------------
490  */
491
492
493
494 static gint sidecar_file_priority(const gchar *path)
495 {
496         const char *extension = extension_from_path(path);
497         int i = 1;
498         GList *work;
499         if (extension == NULL)
500                 return 0;
501
502         work = sidecar_ext_get_list();
503
504         while (work) {
505                 gchar *ext = work->data;
506                 work = work->next;
507                 if (strcmp(extension, ext) == 0) return i;
508                 i++;
509         }
510         return 0;
511 }
512
513
514 /*
515  *-----------------------------------------------------------------------------
516  * load file list
517  *-----------------------------------------------------------------------------
518  */
519
520 static SortType filelist_sort_method = SORT_NONE;
521 static gint filelist_sort_ascend = TRUE;
522
523
524 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
525 {
526         if (!filelist_sort_ascend)
527                 {
528                 FileData *tmp = fa;
529                 fa = fb;
530                 fb = tmp;
531                 }
532
533         switch (filelist_sort_method)
534                 {
535                 case SORT_SIZE:
536                         if (fa->size < fb->size) return -1;
537                         if (fa->size > fb->size) return 1;
538                         return CASE_SORT(fa->name, fb->name); /* fall back to name */
539                         break;
540                 case SORT_TIME:
541                         if (fa->date < fb->date) return -1;
542                         if (fa->date > fb->date) return 1;
543                         return CASE_SORT(fa->name, fb->name); /* fall back to name */
544                         break;
545 #ifdef HAVE_STRVERSCMP
546                 case SORT_NUMBER:
547                         return strverscmp(fa->name, fb->name);
548                         break;
549 #endif
550                 case SORT_NAME:
551                 default:
552                         return CASE_SORT(fa->name, fb->name);
553                         break;
554                 }
555 }
556
557 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
558 {
559         filelist_sort_method = method;
560         filelist_sort_ascend = ascend;
561         return filelist_sort_compare_filedata(fa, fb);
562 }
563
564 static gint filelist_sort_file_cb(void *a, void *b)
565 {
566         return filelist_sort_compare_filedata(a, b);
567 }
568
569 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
570 {
571         filelist_sort_method = method;
572         filelist_sort_ascend = ascend;
573         return g_list_sort(list, cb);
574 }
575
576 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
577 {
578         filelist_sort_method = method;
579         filelist_sort_ascend = ascend;
580         return g_list_insert_sorted(list, data, cb);
581 }
582
583 GList *filelist_sort(GList *list, SortType method, gint ascend)
584 {
585         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
586 }
587
588 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
589 {
590         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
591 }
592
593
594 static GList *filelist_filter_out_sidecars(GList *flist)
595 {
596         GList *work = flist;
597         GList *flist_filtered = NULL;
598
599         while (work)
600                 {
601                 FileData *fd = work->data;
602                 work = work->next;
603                 if (fd->parent) /* remove fd's that are children */
604                         file_data_unref(fd);
605                 else
606                         flist_filtered = g_list_prepend(flist_filtered, fd);
607                 }
608         g_list_free(flist);
609         return flist_filtered;
610 }
611
612 static gint filelist_read_real(const gchar *path, GList **files, GList **dirs, gint follow_symlinks)
613 {
614         DIR *dp;
615         struct dirent *dir;
616         struct stat ent_sbuf;
617         gchar *pathl;
618         GList *dlist;
619         GList *flist;
620
621         dlist = NULL;
622         flist = NULL;
623
624         pathl = path_from_utf8(path);
625         if (!pathl || (dp = opendir(pathl)) == NULL)
626                 {
627                 g_free(pathl);
628                 if (files) *files = NULL;
629                 if (dirs) *dirs = NULL;
630                 return FALSE;
631                 }
632
633         /* root dir fix */
634         if (pathl[0] == '/' && pathl[1] == '\0')
635                 {
636                 g_free(pathl);
637                 pathl = g_strdup("");
638                 }
639
640         while ((dir = readdir(dp)) != NULL)
641                 {
642                 gchar *name = dir->d_name;
643                 if (options->file_filter.show_hidden_files || !ishidden(name))
644                         {
645                         gchar *filepath = g_strconcat(pathl, "/", name, NULL);
646                         if ((follow_symlinks ?
647                                 stat(filepath, &ent_sbuf) :
648                                 lstat(filepath, &ent_sbuf)) >= 0)
649                                 {
650                                 if (S_ISDIR(ent_sbuf.st_mode))
651                                         {
652                                         /* we ignore the .thumbnails dir for cleanliness */
653                                         if ((dirs) &&
654                                             !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
655                                             strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
656                                             strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
657                                             strcmp(name, THUMB_FOLDER_LOCAL) != 0)
658                                                 {
659                                                 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
660                                                 }
661                                         }
662                                 else
663                                         {
664                                         if ((files) && filter_name_exists(name))
665                                                 {
666                                                 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
667                                                 }
668                                         }
669                                 }
670                         g_free(filepath);
671                         }
672                 }
673
674         closedir(dp);
675
676         g_free(pathl);
677
678         flist = filelist_filter_out_sidecars(flist);
679
680         if (dirs) *dirs = dlist;
681         if (files) *files = flist;
682
683         return TRUE;
684 }
685
686 gint filelist_read(const gchar *path, GList **files, GList **dirs)
687 {
688         return filelist_read_real(path, files, dirs, TRUE);
689 }
690
691 gint filelist_read_lstat(const gchar *path, GList **files, GList **dirs)
692 {
693         return filelist_read_real(path, files, dirs, FALSE);
694 }
695
696 void filelist_free(GList *list)
697 {
698         GList *work;
699
700         work = list;
701         while (work)
702                 {
703                 file_data_unref((FileData *)work->data);
704                 work = work->next;
705                 }
706
707         g_list_free(list);
708 }
709
710
711 GList *filelist_copy(GList *list)
712 {
713         GList *new_list = NULL;
714         GList *work;
715
716         work = list;
717         while (work)
718                 {
719                 FileData *fd;
720
721                 fd = work->data;
722                 work = work->next;
723
724                 new_list = g_list_prepend(new_list, file_data_ref(fd));
725                 }
726
727         return g_list_reverse(new_list);
728 }
729
730 GList *filelist_from_path_list(GList *list)
731 {
732         GList *new_list = NULL;
733         GList *work;
734
735         work = list;
736         while (work)
737                 {
738                 gchar *path;
739
740                 path = work->data;
741                 work = work->next;
742
743                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
744                 }
745
746         return g_list_reverse(new_list);
747 }
748
749 GList *filelist_to_path_list(GList *list)
750 {
751         GList *new_list = NULL;
752         GList *work;
753
754         work = list;
755         while (work)
756                 {
757                 FileData *fd;
758
759                 fd = work->data;
760                 work = work->next;
761
762                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
763                 }
764
765         return g_list_reverse(new_list);
766 }
767
768 GList *filelist_filter(GList *list, gint is_dir_list)
769 {
770         GList *work;
771
772         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
773
774         work = list;
775         while (work)
776                 {
777                 FileData *fd = (FileData *)(work->data);
778                 const gchar *name = fd->name;
779
780                 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
781                     (!is_dir_list && !filter_name_exists(name)) ||
782                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
783                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
784                         {
785                         GList *link = work;
786                         work = work->next;
787                         list = g_list_remove_link(list, link);
788                         file_data_unref(fd);
789                         g_list_free(link);
790                         }
791                 else
792                         {
793                         work = work->next;
794                         }
795                 }
796
797         return list;
798 }
799
800 /*
801  *-----------------------------------------------------------------------------
802  * filelist recursive
803  *-----------------------------------------------------------------------------
804  */
805
806 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
807 {
808         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
809 }
810
811 GList *filelist_sort_path(GList *list)
812 {
813         return g_list_sort(list, filelist_sort_path_cb);
814 }
815
816 static void filelist_recursive_append(GList **list, GList *dirs)
817 {
818         GList *work;
819
820         work = dirs;
821         while (work)
822                 {
823                 FileData *fd = (FileData *)(work->data);
824                 const gchar *path = fd->path;
825                 GList *f = NULL;
826                 GList *d = NULL;
827
828                 if (filelist_read(path, &f, &d))
829                         {
830                         f = filelist_filter(f, FALSE);
831                         f = filelist_sort_path(f);
832                         *list = g_list_concat(*list, f);
833
834                         d = filelist_filter(d, TRUE);
835                         d = filelist_sort_path(d);
836                         filelist_recursive_append(list, d);
837                         filelist_free(d);
838                         }
839
840                 work = work->next;
841                 }
842 }
843
844 GList *filelist_recursive(const gchar *path)
845 {
846         GList *list = NULL;
847         GList *d = NULL;
848
849         if (!filelist_read(path, &list, &d)) return NULL;
850         list = filelist_filter(list, FALSE);
851         list = filelist_sort_path(list);
852
853         d = filelist_filter(d, TRUE);
854         d = filelist_sort_path(d);
855         filelist_recursive_append(&list, d);
856         filelist_free(d);
857
858         return list;
859 }
860
861
862
863 /*
864  * file_data    - operates on the given fd
865  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
866  */
867
868
869 /* return list of sidecar file extensions in a string */
870 gchar *file_data_sc_list_to_string(FileData *fd)
871 {
872         GList *work;
873         GString *result = g_string_new("");
874
875         work = fd->sidecar_files;
876         while (work)
877                 {
878                 FileData *sfd = work->data;
879                 result = g_string_append(result, "+ ");
880                 result = g_string_append(result, sfd->extension);
881                 work = work->next;
882                 if (work) result = g_string_append_c(result, ' ');
883                 }
884
885         return g_string_free(result, FALSE);
886 }
887
888
889 /* disables / enables grouping for particular file, sends UPDATE notification */
890 void file_data_disable_grouping(FileData *fd); // now file_data_disconnect_sidecar_file, broken
891 void file_data_disable_grouping(FileData *fd);
892
893 /* runs stat on a file and sends UPDATE notification if it has been changed */
894 void file_data_sc_update(FileData *fd);
895
896
897
898
899 /* 
900  * add FileDataChangeInfo (see typedefs.h) for the given operation 
901  * uses file_data_add_change_info
902  *
903  * fails if the fd->change already exists - change operations can't run in parallel
904  * fd->change_info works as a lock
905  *
906  * dest can be NULL - in this case the current name is used for now, it will
907  * be changed later 
908  */
909
910 /*
911    FileDataChangeInfo types:
912    COPY
913    MOVE - patch is changed, name may be changed too
914    RENAME - path remains unchanged, name is changed
915             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
916             sidecar names are changed too, extensions are not changed
917    DELETE
918    UPDATE - file size, date or grouping has been changed 
919 */
920
921 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
922 {
923
924         FileDataChangeInfo *fdci;
925
926         if (fd->change) return FALSE;
927
928         fdci = g_new0(FileDataChangeInfo, 1);
929
930         fdci->type = type;
931
932         if (src)
933                 fdci->source = g_strdup(src);
934         else
935                 fdci->source = g_strdup(fd->path);
936
937         if (dest)
938                 fdci->dest = g_strdup(dest);
939
940         fd->change = fdci;
941         
942         return TRUE;
943 }
944
945 void file_data_free_ci(FileData *fd)
946 {
947         FileDataChangeInfo *fdci = fd->change;
948
949         if (!fdci)
950                 return;
951
952         g_free(fdci->source);
953         g_free(fdci->dest);
954
955         g_free(fdci);
956
957         fd->change = NULL;
958 }
959
960  
961 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
962 {
963         GList *work;
964         if (fd->parent) fd = fd->parent;
965         
966         if (fd->change) return FALSE;
967         work = fd->sidecar_files;
968         while (work)
969                 {
970                 FileData *sfd = work->data;
971                 if (sfd->change) return FALSE;
972                 work = work->next;
973                 }
974
975         file_data_add_ci(fd, type, NULL, NULL);
976         
977         work = fd->sidecar_files;
978         while (work)
979                 {
980                 FileData *sfd = work->data;
981                 file_data_add_ci(sfd, type, NULL, NULL);
982                 work = work->next;
983                 }
984                 
985         return TRUE;    
986 }
987
988 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
989 {
990         GList *work;
991         if (fd->parent) fd = fd->parent;
992         
993         if (!fd->change) return FALSE;
994         if (fd->change->type != type) return FALSE;
995         work = fd->sidecar_files;
996         while (work)
997                 {
998                 FileData *sfd = work->data;
999                 if (!sfd->change) return FALSE;
1000                 if (sfd->change->type != type) return FALSE;
1001                 work = work->next;
1002                 }
1003         return TRUE;
1004 }
1005
1006
1007 gboolean file_data_sc_add_ci_copy(FileData *fd, gchar *dest_path)
1008 {
1009         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1010         file_data_sc_update_ci_copy(fd, dest_path);
1011         return TRUE;
1012 }
1013
1014 gboolean file_data_sc_add_ci_move(FileData *fd, gchar *dest_path)
1015 {
1016         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1017         file_data_sc_update_ci_move(fd, dest_path);
1018         return TRUE;
1019 }
1020
1021 gboolean file_data_sc_add_ci_rename(FileData *fd, gchar *dest_path)
1022 {
1023         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1024         file_data_sc_update_ci_rename(fd, dest_path);
1025         return TRUE;
1026 }
1027
1028 gboolean file_data_sc_add_ci_delete(FileData *fd)
1029 {
1030         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1031 }
1032
1033 gboolean file_data_sc_add_ci_update(FileData *fd)
1034 {
1035         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_UPDATE);
1036 }
1037
1038 void file_data_sc_free_ci(FileData *fd)
1039 {
1040         GList *work;
1041         if (fd->parent) fd = fd->parent;
1042         
1043         file_data_free_ci(fd);
1044         
1045         work = fd->sidecar_files;
1046         while (work)
1047                 {
1048                 FileData *sfd = work->data;
1049                 file_data_free_ci(sfd);
1050                 work = work->next;
1051                 }
1052 }
1053
1054
1055 /* 
1056  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1057  * fails if fd->change does not exist or the change type does not match
1058  */
1059
1060 static void file_data_update_ci_dest(FileData *fd, gchar *dest_path)
1061 {
1062         g_free(fd->change->dest);
1063         fd->change->dest = g_strdup(dest_path);
1064 }
1065
1066 static void file_data_update_ci_dest_preserve_ext(FileData *fd, gchar *dest_path)
1067 {
1068         const char *extension = extension_from_path(fd->change->source);
1069         g_free(fd->change->dest);
1070         fd->change->dest = g_strdup_printf("%*s%s", (int)(extension_from_path(dest_path) - dest_path), dest_path, extension);
1071 }
1072
1073 static void file_data_sc_update_ci(FileData *fd, gchar *dest_path)
1074 {
1075         GList *work;
1076         if (fd->parent) fd = fd->parent;
1077         
1078         file_data_update_ci_dest(fd, dest_path);
1079         work = fd->sidecar_files;
1080         while (work)
1081                 {
1082                 FileData *sfd = work->data;
1083                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1084                 work = work->next;
1085                 }
1086 }
1087
1088 gint file_data_sc_update_ci_copy(FileData *fd, gchar *dest_path)
1089 {
1090         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1091         file_data_sc_update_ci(fd, dest_path);
1092         return TRUE;
1093 }
1094         
1095 gint file_data_sc_update_ci_move(FileData *fd, gchar *dest_path)
1096 {
1097         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1098         file_data_sc_update_ci(fd, dest_path);
1099         return TRUE;
1100 }
1101
1102 gint file_data_sc_update_ci_rename(FileData *fd, gchar *dest_path)
1103 {
1104         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1105         file_data_sc_update_ci(fd, dest_path);
1106         return TRUE;
1107 }
1108
1109
1110
1111 /*
1112  * check dest paths - dest image exists, etc.
1113  * returns FIXME
1114  * it should detect all possible problems with the planned operation
1115  */
1116  
1117 gint file_data_sc_check_ci_dest(FileData *fd)
1118 {
1119 }
1120
1121
1122
1123
1124 /*
1125  * perform the change described by FileFataChangeInfo
1126  * it is used for internal operations, 
1127  * this function actually operates with files on the filesystem
1128  * it should implement safe delete
1129  */
1130  
1131 static gboolean file_data_perform_move(FileData *fd)
1132 {
1133         g_assert(!strcmp(fd->change->source, fd->path));
1134         return move_file(fd->change->source, fd->change->dest);
1135 }
1136
1137 static gboolean file_data_perform_copy(FileData *fd)
1138 {
1139         g_assert(!strcmp(fd->change->source, fd->path));
1140         return copy_file(fd->change->source, fd->change->dest);
1141 }
1142
1143 static gboolean file_data_perform_delete(FileData *fd)
1144 {
1145         return unlink_file(fd->path);
1146 }
1147
1148 static gboolean file_data_perform_ci(FileData *fd)
1149 {
1150         FileDataChangeType type = fd->change->type;
1151         switch (type)
1152                 {
1153                 case FILEDATA_CHANGE_MOVE:
1154                         return file_data_perform_move(fd);
1155                 case FILEDATA_CHANGE_COPY:
1156                         return file_data_perform_copy(fd);
1157                 case FILEDATA_CHANGE_RENAME:
1158                         return file_data_perform_move(fd); /* the same as move */
1159                 case FILEDATA_CHANGE_DELETE:
1160                         return file_data_perform_delete(fd);
1161                 case FILEDATA_CHANGE_UPDATE:
1162                         /* nothing to do here */
1163                         break;
1164                 }
1165         return TRUE;
1166 }
1167
1168
1169
1170 gboolean file_data_sc_perform_ci(FileData *fd)
1171 {
1172         GList *work;
1173         gboolean ret = TRUE;
1174         FileDataChangeType type = fd->change->type;
1175         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1176
1177         work = fd->sidecar_files;
1178         while (work)
1179                 {
1180                 FileData *sfd = work->data;
1181                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1182                 work = work->next;
1183                 }
1184         if (!file_data_perform_ci(fd)) ret = FALSE;
1185         return ret;
1186 }
1187
1188 /*
1189  * updates FileData structure according to FileDataChangeInfo
1190  */
1191  
1192 static void file_data_apply_ci(FileData *fd)
1193 {
1194         FileDataChangeType type = fd->change->type;
1195         /* FIXME delete ?*/
1196         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_COPY || type == FILEDATA_CHANGE_RENAME)
1197                 {
1198                 g_free(fd->path);
1199                 g_hash_table_remove(file_data_pool, fd->original_path);
1200                 g_free(fd->original_path);
1201                 file_data_set_path(fd, fd->change->dest);
1202                 fd->original_path = g_strdup(fd->change->dest);
1203                 g_hash_table_insert(file_data_pool, fd->original_path, fd);
1204                 }
1205 }
1206
1207 gint file_data_sc_apply_ci(FileData *fd)
1208 {
1209         GList *work;
1210         FileDataChangeType type = fd->change->type;
1211         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1212
1213         work = fd->sidecar_files;
1214         while (work)
1215                 {
1216                 FileData *sfd = work->data;
1217                 file_data_apply_ci(sfd);
1218                 work = work->next;
1219                 }
1220         file_data_apply_ci(fd);
1221         return TRUE;
1222 }
1223
1224
1225 /*
1226  * notify other modules about the change described by FileFataChangeInfo
1227  */
1228  
1229 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1230    FIXME do we need the ignore_list? It looks like a workaround for ineffective
1231    implementation in view_file_list.c */
1232
1233 void file_data_sc_send_notification(FileData *fd)
1234 {
1235 }
1236
1237