Fri Sep 1 02:12:45 2006 John Ellis <johne@verizon.net>
[geeqie.git] / src / collect-io.c
1 /*
2  * GQview
3  * (C) 2004 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12
13 #include "gqview.h"
14 #include "collect-io.h"
15
16 #include "collect.h"
17 #include "layout_util.h"
18 #include "rcfile.h"
19 #include "thumb.h"
20 #include "ui_fileops.h"
21
22
23 #define GQVIEW_COLLECTION_MARKER "#GQview"
24
25 #define GQVIEW_COLLECTION_FAIL_MIN     300
26 #define GQVIEW_COLLECTION_FAIL_PERCENT 98
27
28
29 static void collection_load_thumb_step(CollectionData *cd);
30
31
32 static gint scan_geometry(gchar *buffer, gint *x, gint *y, gint *w, gint *h)
33 {
34         gint nx, ny, nw, nh;
35
36         if(sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
37
38         *x = nx;
39         *y = ny;
40         *w = nw;
41         *h = nh;
42
43         return TRUE;
44 }
45
46 static gint collection_load_private(CollectionData *cd, const gchar *path, gint append, gint flush)
47 {
48         gchar s_buf[2048];
49         FILE *f;
50         gchar *pathl;
51         gint official = FALSE;
52         gint success = TRUE;
53         guint total = 0;
54         guint fail = 0;
55
56         collection_load_stop(cd);
57
58         if (flush) collect_manager_flush();
59
60         if (!append)
61                 {
62                 collection_list_free(cd->list);
63                 cd->list = NULL;
64                 }
65
66         if (!path && !cd->path) return FALSE;
67
68         if (!path) path = cd->path;
69
70         /* load it */
71         pathl = path_from_utf8(path);
72         f = fopen(pathl, "r");
73         g_free(pathl);
74         if (!f)
75                 {
76                 printf("Failed to open collection file: \"%s\"\n", path);
77                 return FALSE;
78                 }
79
80         while (fgets(s_buf, sizeof(s_buf), f))
81                 {
82                 gchar *buf;
83                 if (s_buf[0]=='#')
84                         {
85                         if (strncasecmp(s_buf, GQVIEW_COLLECTION_MARKER, strlen(GQVIEW_COLLECTION_MARKER)) == 0)
86                                 {
87                                 /* Looks like an official collection, allow unchecked input.
88                                  * All this does is allow adding files that may not exist,
89                                  * which is needed for the collection manager to work.
90                                  * Also unofficial files abort after too many invalid entries.
91                                  */
92                                 official = TRUE;
93                                 }
94                         else if (strncmp(s_buf, "#geometry:", 10 ) == 0 &&
95                             scan_geometry(s_buf + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h) )
96                                 {
97                                 cd->window_read = TRUE;
98                                 }
99                         continue;
100                         }
101                 if (s_buf[0]=='\n') continue;
102
103                 buf = quoted_value(s_buf);
104                 if (buf)
105                         {
106                         gint valid;
107
108                         valid = (buf[0] == '/' && collection_add_check(cd, buf, FALSE, flush));
109                         g_free(buf);
110
111                         total++;
112                         if (!valid && !official)
113                                 {
114                                 fail++;
115                                 if (fail > GQVIEW_COLLECTION_FAIL_MIN &&
116                                     fail * 100 / total > GQVIEW_COLLECTION_FAIL_PERCENT)
117                                         {
118                                         printf("Too many invalid filenames in unoffical collection file, closing: %s\n", path);
119                                         success = FALSE;
120                                         break;
121                                         }
122                                 }
123                         }
124                 }
125
126         fclose(f);
127
128         cd->list = collection_list_sort(cd->list, cd->sort_method);
129         if (!append) cd->changed = FALSE;
130
131         return success;
132 }
133
134 gint collection_load(CollectionData *cd, const gchar *path, gint append)
135 {
136         if (collection_load_private(cd, path, append, TRUE))
137                 {
138                 layout_recent_add_path(cd->path);
139                 return TRUE;
140                 }
141
142         return FALSE;
143 }
144
145 static void collection_load_thumb_do(CollectionData *cd)
146 {
147         GdkPixbuf *pixbuf;
148
149         if (!cd->thumb_loader || !g_list_find(cd->list, cd->thumb_info)) return;
150
151         pixbuf = thumb_loader_get_pixbuf(cd->thumb_loader, TRUE);
152         collection_info_set_thumb(cd->thumb_info, pixbuf);
153         g_object_unref(pixbuf);
154
155         if (cd->info_updated_func) cd->info_updated_func(cd, cd->thumb_info, cd->info_updated_data);
156 }
157
158 static void collection_load_thumb_error_cb(ThumbLoader *tl, gpointer data)
159 {
160         CollectionData *cd = data;
161
162         collection_load_thumb_do(cd);
163         collection_load_thumb_step(cd);
164 }
165
166 static void collection_load_thumb_done_cb(ThumbLoader *tl, gpointer data)
167 {
168         CollectionData *cd = data;
169
170         collection_load_thumb_do(cd);
171         collection_load_thumb_step(cd);
172 }
173
174 static void collection_load_thumb_step(CollectionData *cd)
175 {
176         GList *work;
177         CollectInfo *ci;
178
179         if (!cd->list)
180                 {
181                 collection_load_stop(cd);
182                 return;
183                 }
184
185         work = cd->list;
186         ci = work->data;
187         work = work->next;
188         /* find first unloaded thumb */
189         while (work && ci->pixbuf)
190                 {
191                 ci = work->data;
192                 work = work->next;
193                 }
194
195         if (!ci || ci->pixbuf)
196                 {
197                 /* done */
198                 collection_load_stop(cd);
199
200                 /* send a NULL CollectInfo to notify end */
201                 if (cd->info_updated_func) cd->info_updated_func(cd, NULL, cd->info_updated_data);
202
203                 return;
204                 }
205
206         /* setup loader and call it */
207         cd->thumb_info = ci;
208         thumb_loader_free(cd->thumb_loader);
209         cd->thumb_loader = thumb_loader_new(thumb_max_width, thumb_max_height);
210         thumb_loader_set_callbacks(cd->thumb_loader,
211                                    collection_load_thumb_done_cb,
212                                    collection_load_thumb_error_cb,
213                                    NULL,
214                                    cd);
215
216         /* start it */
217         if (!thumb_loader_start(cd->thumb_loader, ci->path))
218                 {
219                 /* error, handle it, do next */
220                 if (debug) printf("error loading thumb for %s\n", ci->path);
221                 collection_load_thumb_do(cd);
222                 collection_load_thumb_step(cd);
223                 }
224 }
225
226 void collection_load_thumb_idle(CollectionData *cd)
227 {
228         if (!cd->thumb_loader) collection_load_thumb_step(cd);
229 }
230
231 gint collection_load_begin(CollectionData *cd, const gchar *path, gint append)
232 {
233         if (!collection_load(cd, path, append)) return FALSE;
234
235         collection_load_thumb_idle(cd);
236
237         return TRUE;
238 }
239
240 void collection_load_stop(CollectionData *cd)
241 {
242         if (!cd->thumb_loader) return;
243
244         thumb_loader_free(cd->thumb_loader);
245         cd->thumb_loader = NULL;
246 }
247
248 static gint collection_save_private(CollectionData *cd, const gchar *path)
249 {
250         FILE *f;
251         GList *work;
252         gchar *tmp_path;
253         gchar *pathl;
254         mode_t save_mask;
255
256         if (!path && !cd->path) return FALSE;
257
258         if (!path)
259                 {
260                 path = cd->path;
261                 }
262
263         tmp_path = unique_filename(path, ".tmp", "_", 3);
264         if (!tmp_path) return FALSE;
265
266         pathl = path_from_utf8(tmp_path);
267         save_mask = umask(0077);
268         f = fopen(pathl, "w");
269         umask(save_mask);
270         g_free(pathl);
271
272         if (!f)
273                 {
274                 /* file open failed */
275                 printf("failed to open collection (write) \"%s\"\n", tmp_path);
276                 g_free(tmp_path);
277                 return FALSE;
278                 }
279
280         fprintf(f, "%s collection\n", GQVIEW_COLLECTION_MARKER);
281         fprintf(f, "#created with GQview version %s\n", VERSION);
282
283         collection_update_geometry(cd);
284         if (cd->window_read)
285                 {
286                 fprintf(f, "#geometry: %d %d %d %d\n", cd->window_x, cd->window_y, cd->window_w, cd->window_h);
287                 }
288
289         work = cd->list;
290         while (work)
291                 {
292                 CollectInfo *ci = work->data;
293                 if (fprintf(f, "\"%s\"\n", ci->path) < 0)
294                         {
295                         fclose(f);
296                         printf("Error writing to %s\n", tmp_path);
297                         unlink_file(tmp_path);
298                         g_free(tmp_path);
299                         return FALSE;
300                         }
301                 work = work->next;
302                 }
303
304         fprintf(f, "#end\n");
305
306         fclose(f);
307
308         copy_file_attributes(path, tmp_path, TRUE, FALSE);
309         if (!rename_file(tmp_path, path))
310                 {
311                 printf("collection save unable to rename %s to %s\n", tmp_path, path);
312                 unlink_file(tmp_path);
313                 g_free(tmp_path);
314                 return FALSE;
315                 }
316
317         g_free(tmp_path);
318
319         if (!cd->path || strcmp(path, cd->path) != 0)
320                 {
321                 gchar *buf = cd->path;
322                 cd->path = g_strdup(path);
323                 path = cd->path;
324                 g_free(buf);
325
326                 g_free(cd->name);
327                 cd->name = g_strdup(filename_from_path(cd->path));
328
329                 collection_path_changed(cd);
330                 }
331
332         cd->changed = FALSE;
333
334         return TRUE;
335 }
336
337 gint collection_save(CollectionData *cd, const gchar *path)
338 {
339         if (collection_save_private(cd, path))
340                 {
341                 layout_recent_add_path(cd->path);
342                 return TRUE;
343                 }
344
345         return FALSE;
346 }
347
348 gint collection_load_only_geometry(CollectionData *cd, const gchar *path)
349 {
350         gchar s_buf[2048];
351         FILE *f;
352         gchar *pathl;
353
354         if (!path && !cd->path) return FALSE;
355
356         if (!path) path = cd->path;
357
358         /* load it */
359         pathl = path_from_utf8(path);
360         f = fopen(pathl, "r");
361         g_free(pathl);
362         if (!f) return FALSE;
363
364         while (fgets(s_buf, sizeof(s_buf), f))
365                 {
366                 if (s_buf[0]=='#' &&
367                     strncmp(s_buf, "#geometry:", 10 ) == 0 &&
368                     scan_geometry(s_buf + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h) )
369                         {
370                         cd->window_read = TRUE;
371                         fclose(f);
372                         return TRUE;
373                         }
374                 }
375         fclose(f);
376         return FALSE;
377 }
378
379
380 /*
381  *-------------------------------------------------------------------
382  * collection manager
383  *-------------------------------------------------------------------
384  */
385
386 #define COLLECT_MANAGER_ACTIONS_PER_IDLE 1000
387 #define COLLECT_MANAGER_FLUSH_DELAY      10000
388
389 typedef struct _CollectManagerEntry CollectManagerEntry;
390 struct _CollectManagerEntry
391 {
392         gchar *path;
393         GList *action_list;
394 };
395
396 typedef enum {
397         COLLECTION_MANAGER_UPDATE,
398         COLLECTION_MANAGER_ADD,
399         COLLECTION_MANAGER_REMOVE
400 } CollectManagerType;
401
402 typedef struct _CollectManagerAction CollectManagerAction;
403 struct _CollectManagerAction
404 {
405         gchar *oldpath;
406         gchar *newpath;
407
408         CollectManagerType type;
409
410         gint ref;
411 };
412
413
414 static GList *collection_manager_entry_list = NULL;
415 static GList *collection_manager_action_list = NULL;
416 static GList *collection_manager_action_tail = NULL;
417 static gint collection_manager_timer_id = -1;
418
419
420 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
421                                                         CollectManagerType type)
422 {
423         CollectManagerAction *action;
424
425         action = g_new0(CollectManagerAction, 1);
426         action->ref = 1;
427
428         action->oldpath = g_strdup(oldpath);
429         action->newpath = g_strdup(newpath);
430
431         action->type = type;
432
433         return action;
434 }
435
436 static void collect_manager_action_ref(CollectManagerAction *action)
437 {
438         action->ref++;
439 }
440
441 static void collect_manager_action_unref(CollectManagerAction *action)
442 {
443         action->ref--;
444
445         if (action->ref > 0) return;
446
447         g_free(action->oldpath);
448         g_free(action->newpath);
449         g_free(action);
450 }
451
452 static CollectManagerEntry *collect_manager_entry_new(const gchar *path)
453 {
454         CollectManagerEntry *entry;
455
456         entry = g_new0(CollectManagerEntry, 1);
457         entry->path = g_strdup(path);
458         entry->action_list = NULL;
459
460         collection_manager_entry_list = g_list_append(collection_manager_entry_list, entry);
461
462         return entry;
463 }
464
465 static void collect_manager_entry_free(CollectManagerEntry *entry)
466 {
467         GList *work;
468
469         collection_manager_entry_list = g_list_remove(collection_manager_entry_list, entry);
470
471         work = entry->action_list;
472         while (work)
473                 {
474                 CollectManagerAction *action;
475
476                 action = work->data;
477                 work = work->next;
478
479                 collect_manager_action_unref(action);
480                 }
481         g_list_free(entry->action_list);
482
483         g_free(entry->path);
484         g_free(entry);
485 }
486
487 static void collect_manager_refresh(void)
488 {
489         GList *list = NULL;
490         GList *work;
491         gchar *base;
492
493         base = g_strconcat(homedir(), "/", GQVIEW_RC_DIR_COLLECTIONS, NULL);
494         path_list(base, &list, NULL);
495         g_free(base);
496
497         work = collection_manager_entry_list;
498         while (work && list)
499                 {
500                 CollectManagerEntry *entry;
501                 GList *list_step;
502
503                 entry = work->data;
504                 work = work->next;
505
506                 list_step = list;
507                 while (list_step && entry)
508                         {
509                         gchar *path;
510
511                         path = list_step->data;
512                         list_step = list_step->next;
513
514                         if (strcmp(path, entry->path) == 0)
515                                 {
516                                 list = g_list_remove(list, path);
517                                 g_free(path);
518
519                                 entry = NULL;
520                                 }
521                         else
522                                 {
523                                 collect_manager_entry_free(entry);
524                                 }
525                         }
526                 }
527
528         work = list;
529         while (work)
530                 {
531                 gchar *path;
532
533                 path = work->data;
534                 work = work->next;
535
536                 collect_manager_entry_new(path);
537                 g_free(path);
538                 }
539
540         g_list_free(list);
541 }
542
543 static void collect_manager_process_actions(gint max)
544 {
545         if (debug && collection_manager_action_list)
546                 {
547                 printf("collection manager processing actions\n");
548                 }
549
550         while (collection_manager_action_list != NULL && max > 0)
551                 {
552                 CollectManagerAction *action;
553                 GList *work;
554
555                 action = collection_manager_action_list->data;
556                 work = collection_manager_entry_list;
557                 while (work)
558                         {
559                         CollectManagerEntry *entry;
560
561                         entry = work->data;
562                         work = work->next;
563
564                         if (action->type == COLLECTION_MANAGER_UPDATE)
565                                 {
566                                 entry->action_list = g_list_prepend(entry->action_list, action);
567                                 collect_manager_action_ref(action);
568                                 }
569                         else if (action->oldpath && action->newpath &&
570                                  strcmp(action->newpath, entry->path) == 0)
571                                 {
572                                 /* convert action to standard add format */
573                                 g_free(action->newpath);
574                                 if (action->type == COLLECTION_MANAGER_ADD)
575                                         {
576                                         action->newpath = action->oldpath;
577                                         action->oldpath = NULL;
578                                         }
579                                 else if (action->type == COLLECTION_MANAGER_REMOVE)
580                                         {
581                                         action->newpath = NULL;
582                                         }
583
584                                 entry->action_list = g_list_prepend(entry->action_list, action);
585                                 collect_manager_action_ref(action);
586                                 }
587
588                         max--;
589                         }
590
591                 if (action->type != COLLECTION_MANAGER_UPDATE &&
592                     action->oldpath && action->newpath)
593                         {
594                         printf("collection manager failed to %s %s for collection %s\n",
595                                 (action->type == COLLECTION_MANAGER_ADD) ? "add" : "remove",
596                                 action->oldpath, action->newpath);
597                         }
598
599                 if (collection_manager_action_tail == collection_manager_action_list)
600                         {
601                         collection_manager_action_tail = NULL;
602                         }
603                 collection_manager_action_list = g_list_remove(collection_manager_action_list, action);
604                 collect_manager_action_unref(action);
605                 }
606 }
607
608 static gint collect_manager_process_entry(CollectManagerEntry *entry)
609 {
610         CollectionData *cd;
611         gint success;
612         GList *work;
613
614         if (!entry->action_list) return FALSE;
615
616         cd = collection_new(entry->path);
617         success = collection_load_private(cd, entry->path, FALSE, FALSE);
618
619         work = g_list_last(entry->action_list);
620         while (work)
621                 {
622                 CollectManagerAction *action;
623
624                 action = work->data;
625                 work = work->prev;
626
627                 if (!action->oldpath)
628                         {
629                         /* add image */
630                         if (collection_list_find(cd->list, action->newpath) == NULL)
631                                 {
632                                 collection_add_check(cd, action->newpath, FALSE, FALSE);
633                                 }
634                         }
635                 else if (action->newpath)
636                         {
637                         /* rename image */
638                         while (collection_rename(cd, action->oldpath, action->newpath));
639                         }
640                 else
641                         {
642                         /* remove image */
643                         while (collection_remove(cd, action->oldpath));
644                         }
645                 collect_manager_action_unref(action);
646                 }
647
648         if (success && cd->changed)
649                 {
650                 collection_save_private(cd, entry->path);
651                 if (debug) printf("collection manager updated: %s\n", entry->path);
652                 }
653         collection_unref(cd);
654
655         g_list_free(entry->action_list);
656         entry->action_list = NULL;
657
658         return TRUE;
659 }
660
661 static gint collect_manager_process_entry_list(void)
662 {
663         GList *work;
664
665         work = collection_manager_entry_list;
666         while (work)
667                 {
668                 CollectManagerEntry *entry;
669
670                 entry = work->data;
671                 work = work->next;
672                 if (collect_manager_process_entry(entry)) return TRUE;
673                 }
674
675         return FALSE;
676 }
677
678 static gint collect_manager_process_cb(gpointer data)
679 {
680         if (collection_manager_action_list) collect_manager_refresh();
681         collect_manager_process_actions(COLLECT_MANAGER_ACTIONS_PER_IDLE);
682         if (collection_manager_action_list) return TRUE;
683
684         if (collect_manager_process_entry_list()) return TRUE;
685
686         if (debug) printf("collection manager is up to date\n");
687         return FALSE;
688 }
689
690 static gint collect_manager_timer_cb(gpointer data)
691 {
692         if (debug) printf("collection manager timer expired\n");
693
694         g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, NULL, NULL);
695
696         collection_manager_timer_id = -1;
697         return FALSE;
698 }
699
700 static void collect_manager_timer_push(gint stop)
701 {
702         if (collection_manager_timer_id != -1)
703                 {
704                 if (!stop) return;
705
706                 g_source_remove(collection_manager_timer_id);
707                 collection_manager_timer_id = -1;
708                 }
709
710         if (!stop)
711                 {
712                 collection_manager_timer_id = g_timeout_add(COLLECT_MANAGER_FLUSH_DELAY,
713                                                             collect_manager_timer_cb, NULL);
714                 if (debug) printf("collection manager timer started\n");
715                 }
716 }
717
718 static void collect_manager_add_action(CollectManagerAction *action)
719 {
720         if (!action) return;
721
722         /* we keep track of the list's tail to keep this a n(1) operation */
723
724         if (collection_manager_action_tail)
725                 {
726                 collection_manager_action_tail = g_list_append(collection_manager_action_tail, action);
727                 collection_manager_action_tail = collection_manager_action_tail->next;
728                 }
729         else
730                 {
731                 collection_manager_action_list = g_list_append(collection_manager_action_list, action);
732                 collection_manager_action_tail = collection_manager_action_list;
733                 }
734
735         collect_manager_timer_push(FALSE);
736 }
737
738 void collect_manager_moved(const gchar *oldpath, const gchar *newpath)
739 {
740         CollectManagerAction *action;
741
742         action = collect_manager_action_new(oldpath, newpath, COLLECTION_MANAGER_UPDATE);
743         collect_manager_add_action(action);
744 }
745
746 void collect_manager_add(const gchar *path, const gchar *collection)
747 {
748         CollectManagerAction *action;
749         CollectWindow *cw;
750
751         if (!path || !collection) return;
752
753         cw = collection_window_find_by_path(collection);
754         if (cw)
755                 {
756                 if (collection_list_find(cw->cd->list, path) == NULL)
757                         {
758                         collection_add(cw->cd, path, FALSE);
759                         }
760                 return;
761                 }
762
763         action = collect_manager_action_new(path, collection, COLLECTION_MANAGER_ADD);
764         collect_manager_add_action(action);
765 }
766
767 void collect_manager_remove(const gchar *path, const gchar *collection)
768 {
769         CollectManagerAction *action;
770         CollectWindow *cw;
771
772         if (!path || !collection) return;
773
774         cw = collection_window_find_by_path(collection);
775         if (cw)
776                 {
777                 while (collection_remove(cw->cd, path));
778                 return;
779                 }
780
781         action = collect_manager_action_new(path, collection, COLLECTION_MANAGER_REMOVE);
782         collect_manager_add_action(action);
783 }
784
785 void collect_manager_flush(void)
786 {
787         collect_manager_timer_push(TRUE);
788
789         if (debug) printf("collection manager flushing\n");
790         while (collect_manager_process_cb(NULL));
791 }
792