- Set LIBS instead of LDFLAGS (LIBS are appended while LDFLAGS are prepended
[gliv.git] / src / collection.c
blobaf757c159930e36ff9bfd6ecd3ce13189233a990
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@gmail.com>
21 /********************
22 * GLiv collections *
23 ********************/
26 * +--------------------------------+
27 * | The .gliv format specification |
28 * +--------------------------------+
30 * Newlines are here just for clarity. All numbers are represented in a string.
31 * So 123 is actually represented: '1''2''3''\0'
32 * PixelData is a char* so it should be endian independant, testing will tell...
36 * GlivCollectionFile ::=
37 * "GLiv <COLLECTION>"
38 * Version
39 * FileCount
40 * Entry*
41 * "</COLLECTION>"
44 * Version ::=
45 * "1" (Currently there is only one version)
48 * Entry ::=
49 * "<FILE>"
50 * PathLength
51 * Path
52 * MaybeThumb
53 * "</FILE>"
56 * MaybeThumb ::=
57 * "0" | Thumb
59 * Thumb ::=
60 * "1"
61 * HashKeyLength
62 * HashKey
63 * GdkColorspace
64 * AlphaChannel
65 * BitsPerSample
66 * ThumbWidth
67 * ThumbHeight
68 * RowStride
69 * PixelDataLength
70 * PixelData
74 * Path, and HashKey are '0' terminated strings.
75 * FileCount, PathLength, HashKeyLength, GdkColorspace, AlphaChannel,
76 * BitsPerSample, ThumbWidth, ThumbHeight, RowStride and PixelDataLength
77 * are '\0' terminated strings representing decimal numbers.
80 #include <unistd.h> /* isatty() */
81 #include <sys/types.h> /* size_t, off_t */
82 #include <sys/mman.h> /* mmap(), PROT_READ, MAP_PRIVATE, ... */
83 #include <stdio.h> /* FILE, fopen(), fread(), fwrite(), ... */
84 #include <string.h> /* strlen(), strrchr() */
85 #include <errno.h> /* errno */
86 #include <ctype.h> /* isdigit() */
87 #include <time.h> /* time_t, time() */
89 #include "gliv.h"
90 #include "collection.h"
91 #include "math_floats.h" /* log10f() */
92 #include "large_files.h"
93 #include "messages.h"
94 #include "tree.h"
95 #include "thumbnails.h"
96 #include "str_utils.h"
97 #include "files_list.h"
98 #include "next_image.h"
99 #include "gliv-image.h"
100 #include "formats.h"
101 #include "loading.h"
102 #include "decompression.h"
103 #include "callbacks.h"
104 #include "windows.h"
105 #include "options.h"
107 /* Max filename length in the progress window. */
108 #define MAX_DISPLAYED_NAME_LENGTH 30
110 extern rt_struct *rt;
111 extern options_struct *options;
112 extern GlivImage *current_image;
114 static gchar *get_filename(const gchar * label, gboolean save)
116 GtkFileChooserAction action;
117 const gchar *stock_button;
118 GtkFileChooser *chooser;
119 gint response;
120 gchar *filename = NULL;
122 if (save) {
123 action = GTK_FILE_CHOOSER_ACTION_SAVE;
124 stock_button = GTK_STOCK_SAVE;
125 } else {
126 action = GTK_FILE_CHOOSER_ACTION_OPEN;
127 stock_button = GTK_STOCK_OPEN;
130 chooser = GTK_FILE_CHOOSER(gtk_file_chooser_dialog_new(label,
131 NULL,
132 action,
133 GTK_STOCK_CANCEL,
134 GTK_RESPONSE_CANCEL,
135 stock_button,
136 GTK_RESPONSE_ACCEPT,
137 NULL));
139 response = run_modal_dialog(GTK_DIALOG(chooser));
141 if (response == GTK_RESPONSE_ACCEPT)
142 filename = gtk_file_chooser_get_filename(chooser);
144 gtk_widget_destroy(GTK_WIDGET(chooser));
145 return filename;
148 static gchar *get_collection_destination(void)
150 gchar *filename;
152 filename = get_filename(_("Choose a file to save the collection"), TRUE);
153 if (filename != NULL && g_file_test(filename, G_FILE_TEST_EXISTS)) {
154 GtkMessageDialog *overwrite =
155 GTK_MESSAGE_DIALOG(gtk_message_dialog_new(NULL,
156 GTK_DIALOG_MODAL,
157 GTK_MESSAGE_QUESTION,
158 GTK_BUTTONS_YES_NO,
159 _("Overwrite \"%s\" ?"),
160 filename));
162 gint response = run_modal_dialog(GTK_DIALOG(overwrite));
163 if (response != GTK_RESPONSE_YES) {
164 g_free(filename);
165 filename = NULL;
168 gtk_widget_destroy(GTK_WIDGET(overwrite));
171 return filename;
174 /*** Progress dialog ***/
176 static GtkLabel *file_label;
177 static GtkLabel *ratio_label;
178 static GtkProgressBar *progress;
179 static GtkWindow *progress_window;
180 static GtkLabel *elapsed_label, *total_label, *remaining_label;
181 static time_t start_time;
183 static gboolean set_true(gboolean * ptr)
185 *ptr = TRUE;
186 return FALSE;
189 static GtkLabel *add_time_display(GtkTable * table, const gchar * text,
190 gint pos)
192 GtkLabel *name_label, *time_label;
194 name_label = GTK_LABEL(gtk_label_new(text));
195 gtk_widget_show(GTK_WIDGET(name_label));
197 time_label = GTK_LABEL(gtk_label_new(NULL));
198 gtk_widget_show(GTK_WIDGET(time_label));
200 gtk_table_attach_defaults(table, GTK_WIDGET(name_label),
201 0, 1, pos, pos + 1);
203 gtk_table_attach_defaults(table, GTK_WIDGET(time_label),
204 1, 2, pos, pos + 1);
206 return time_label;
209 static void create_progress_dialog(const gchar * filename, gpointer cancel_data,
210 gboolean is_saving)
212 GtkButton *cancel;
213 GtkTable *table;
214 gchar *title;
216 file_label = GTK_LABEL(gtk_label_new(NULL));
217 gtk_label_set_ellipsize(file_label, PANGO_ELLIPSIZE_MIDDLE);
218 gtk_widget_show(GTK_WIDGET(file_label));
220 ratio_label = GTK_LABEL(gtk_label_new(NULL));
221 gtk_widget_show(GTK_WIDGET(ratio_label));
224 progress = GTK_PROGRESS_BAR(gtk_progress_bar_new());
225 gtk_widget_show(GTK_WIDGET(progress));
227 cancel = GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_CANCEL));
228 g_signal_connect_swapped(cancel, "clicked", G_CALLBACK(set_true),
229 cancel_data);
231 gtk_widget_show(GTK_WIDGET(cancel));
233 table = GTK_TABLE(gtk_table_new(7, 2, TRUE));
234 gtk_table_attach_defaults(table, GTK_WIDGET(file_label), 0, 2, 0, 1);
235 gtk_table_attach_defaults(table, GTK_WIDGET(ratio_label), 0, 2, 1, 2);
236 gtk_table_attach_defaults(table, GTK_WIDGET(progress), 0, 2, 2, 3);
238 elapsed_label = add_time_display(table, _("Elapsed time"), 3);
239 remaining_label = add_time_display(table, _("Remaining time"), 4);
240 total_label = add_time_display(table, _("Total time"), 5);
242 gtk_table_attach_defaults(table, GTK_WIDGET(cancel), 1, 2, 6, 7);
244 gtk_widget_show(GTK_WIDGET(table));
246 if (is_saving)
247 title = g_strdup_printf(_("Saving collection: %s"),
248 filename_to_utf8(filename));
249 else
250 title = g_strdup_printf(_("Loading collection: %s"),
251 filename_to_utf8(filename));
253 progress_window = new_window(title);
254 g_free(title);
256 g_signal_connect_swapped(progress_window, "delete-event",
257 G_CALLBACK(set_true), cancel_data);
259 gtk_container_add(GTK_CONTAINER(progress_window), GTK_WIDGET(table));
260 gtk_widget_show(GTK_WIDGET(progress_window));
261 start_time = time(NULL);
264 static gchar *time2str(time_t t)
266 gint hours = t / 3600;
267 gint minutes = (t - hours * 3600) / 60;
268 gint seconds = t % 60;
270 return g_strdup_printf("%02d:%02d:%02d", hours, minutes, seconds);
273 static void update_times(time_t elapsed_time,
274 time_t remaining_time, time_t total_time)
276 gchar *text;
278 text = time2str(elapsed_time);
279 gtk_label_set_text(elapsed_label, text);
280 g_free(text);
282 text = time2str(remaining_time);
283 gtk_label_set_text(remaining_label, text);
284 g_free(text);
286 text = time2str(total_time);
287 gtk_label_set_text(total_label, text);
288 g_free(text);
291 static gboolean need_refresh_progress(gboolean last_time)
293 static GTimeVal last = { 0, 0 };
294 GTimeVal now;
295 glong usec_diff;
297 if (last_time) {
298 last.tv_sec = 0;
299 last.tv_usec = 0;
300 return TRUE;
303 if (last.tv_sec == 0 && last.tv_usec == 0) {
304 g_get_current_time(&last);
305 return TRUE;
308 g_get_current_time(&now);
309 usec_diff = (now.tv_sec - last.tv_sec) * G_USEC_PER_SEC +
310 now.tv_usec - last.tv_usec;
312 if (usec_diff < G_USEC_PER_SEC / 10)
313 /* Lower the redrawing load. */
314 return FALSE;
316 last = now;
317 return TRUE;
320 static FILE *get_terminal_output(void)
322 static FILE *print;
323 static gboolean print_ok = FALSE;
325 if (!print_ok) {
326 if (isatty(fileno(stdout)))
327 print = stdout;
329 else if (isatty(fileno(stderr)))
330 print = stderr;
332 else
333 print = NULL;
335 print_ok = TRUE;
338 return print;
341 static void update_progress_nogui(gint id, gint count)
343 FILE *print = get_terminal_output();
345 if (print != NULL && need_refresh_progress(id == count - 1)) {
346 gint percent = id * 100 / count;
348 fprintf(print, " %d/%d: %d%%\r", id + 1, count, percent);
349 fflush(print);
353 static void update_progress(const gchar * filename, gint id, gint count)
355 const gchar *base;
356 time_t current_time, elapsed_time, total_time, remaining_time;
357 gchar *ratio_text;
359 if (progress_window == NULL) {
360 update_progress_nogui(id, count);
361 return;
364 if (filename == NULL) {
365 /* Last update. */
366 gchar *msg = _("Inserting files...");
368 need_refresh_progress(TRUE);
369 gtk_label_set_text(file_label, msg);
371 } else {
372 if (need_refresh_progress(FALSE) == FALSE)
373 return;
375 base = strrchr(filename, '/');
376 if (base == NULL)
377 base = filename;
378 else
379 base++;
381 gtk_label_set_text(file_label, base);
384 ratio_text = g_strdup_printf("%d / %d", id, count - 1);
385 gtk_label_set_text(ratio_label, ratio_text);
386 g_free(ratio_text);
388 if (count == 1)
389 gtk_progress_bar_set_fraction(progress, 1.0);
390 else
391 gtk_progress_bar_set_fraction(progress, (gdouble) id / (count - 1));
393 current_time = time(NULL);
394 elapsed_time = current_time - start_time;
395 if (id == 0)
396 total_time = 0;
397 else
398 total_time = elapsed_time * (count - 1) / id;
400 remaining_time = total_time - elapsed_time;
402 update_times(elapsed_time, remaining_time, total_time);
403 process_events();
406 static void destroy_progress_dialog(void)
408 if (progress_window != NULL) {
409 if (GTK_IS_WIDGET(progress_window))
410 gtk_widget_destroy(GTK_WIDGET(progress_window));
411 progress_window = NULL;
416 * In the serialization and deserialization,
417 * returning FALSE means aborting it.
420 /*** Serialization ***/
422 typedef struct {
423 FILE *file;
424 gint file_id;
425 gint count;
426 gchar *cwd; /* To avoid relative filenames. */
427 } serialization_data;
429 /* Attempts to put a string in the serialized file. */
430 #define WRITE(file, str) \
431 do { \
432 gchar * __str__ = (str); \
433 gint len = strlen(__str__) + 1; \
435 if (fwrite(__str__, 1, len, (file)) != len) \
436 return FALSE; \
437 } while (0)
439 /* Attempts to put a number (in string form) in the serialized file. */
440 #define WRITE_NB(file, nb) \
441 do { \
442 gchar *str = g_strdup_printf("%d%c", (nb), '\0'); \
443 gint len = strlen(str) + 1; \
445 if (fwrite(str, 1, len, (file)) != len) { \
446 g_free(str); \
447 return FALSE; \
450 g_free(str); \
451 } while (0)
453 static gboolean print_header(serialization_data * work)
455 WRITE(work->file, "GLiv <COLLECTION>");
456 WRITE(work->file, "1"); /* Version */
457 WRITE_NB(work->file, work->count); /* FileCount */
459 return TRUE;
462 static gboolean print_footer(FILE * file)
464 WRITE(file, "</COLLECTION>");
466 return TRUE;
470 * We will write the value number, then a '\0' and then the image
471 * data. The data should be aligned if we read it via mmap, as
472 * GdkPixbuf uses a rowstride value so that every line beginning are
473 * on a 32 bit boundary.
474 * The alignment is made by padding some '0'.
476 static gboolean align_next(FILE * file, gint value)
478 gint length, pos, align;
480 /* For the (extremly ...) rare case value == 0 */
481 value += !value;
483 length = (int) log10f(value) + 2;
484 pos = (int) ftell(file) + length; /* We don't need 64 bit precision */
485 align = (4 - pos % 4) % 4; /* We align on 32 bit boundaries */
487 while (align > 0) {
488 if (putc('0', file) < 0)
489 return FALSE;
491 align--;
494 return TRUE;
497 /* Write the MaybeThumb part of an entry. */
498 static gboolean write_thumb(const gchar * filename, FILE * file)
500 gint height, rowstride, len, bps;
501 gchar *thumb_key;
502 GdkPixbuf *thumb;
504 thumb = get_thumbnail(filename, &thumb_key);
505 if (thumb == NULL) {
506 WRITE(file, "0");
507 return TRUE;
510 height = gdk_pixbuf_get_height(thumb);
511 rowstride = gdk_pixbuf_get_rowstride(thumb);
512 len = pixels_size(thumb);
513 bps = gdk_pixbuf_get_bits_per_sample(thumb);
515 WRITE(file, "1");
516 WRITE_NB(file, (int) strlen(thumb_key)); /* HashKeyLength */
517 WRITE(file, thumb_key); /* HashKey */
518 WRITE_NB(file, gdk_pixbuf_get_colorspace(thumb)); /* GdkColorspace */
519 WRITE_NB(file, gdk_pixbuf_get_has_alpha(thumb)); /* AlphaChannel */
520 WRITE_NB(file, bps); /* BitsPerSample */
521 WRITE_NB(file, gdk_pixbuf_get_width(thumb)); /* ThumbWidth */
522 WRITE_NB(file, height); /* ThumbHeight */
523 WRITE_NB(file, rowstride); /* RowStride */
525 if (align_next(file, len) == FALSE)
526 return FALSE;
528 WRITE_NB(file, len); /* PixelDataLength */
530 /* PixelData */
531 return fwrite(gdk_pixbuf_get_pixels(thumb), 1, len, file) == len;
534 static gboolean serialize_file(const gchar * filename,
535 serialization_data * work)
537 FILE *file = work->file;
538 gchar *path;
540 update_progress(filename, work->file_id, work->count);
542 WRITE(file, "<FILE>");
544 path = get_absolute_filename(filename);
545 WRITE_NB(file, (int) strlen(path)); /* PathLength */
546 WRITE(file, path); /* Path */
547 g_free(path);
549 if (write_thumb(filename, file) == FALSE) /* MaybeThumb */
550 return FALSE;
552 WRITE(file, "</FILE>");
554 work->file_id++;
555 return TRUE;
558 static void file_error(const gchar * filename)
560 if (rt) {
561 gchar *message = g_strconcat(filename, ": ", g_strerror(errno), NULL);
562 DIALOG_MSG("%s", message);
563 g_free(message);
567 gint save_collection(FILE * file, gboolean * cancel)
569 GList *list;
570 serialization_data *work;
571 gboolean ok = FALSE;
573 list = get_list_head();
574 work = g_new(serialization_data, 1);
575 work->file = file;
576 work->file_id = 0;
577 work->count = get_list_length();
578 work->cwd = g_get_current_dir();
580 if (print_header(work) == FALSE)
581 goto error;
583 while (*cancel == FALSE && list != NULL) {
584 gboolean file_ok = serialize_file(list->data, work);
585 if (file_ok == FALSE)
586 goto error;
588 list = list->next;
591 if (*cancel || print_footer(work->file) == FALSE)
592 goto error;
594 ok = TRUE;
595 goto ok;
597 error:
598 ok = FALSE;
601 g_free(work->cwd);
602 g_free(work);
604 return ok ? 0 : 1;
607 gint serialize_collection_nogui(const gchar * filename)
609 FILE *tty;
610 FILE *file;
611 gint res;
612 gboolean cancel = FALSE;
614 if (get_list_head() == NULL) {
615 g_printerr(_("No images to put in a collection\n"));
616 return 1;
619 if (filename == NULL) {
620 file = stdout;
621 if (isatty(fileno(file))) {
622 g_printerr(_("GLiv won't write a collection to a terminal\n"));
623 return 1;
625 } else {
626 /* We remove the file, since we may have mmapped it */
627 remove(filename);
629 file = fopen(filename, "w");
630 if (file == NULL) {
631 perror(filename);
632 return 1;
636 tty = get_terminal_output();
637 if (tty != NULL) {
638 fprintf(tty, _("Saving collection: %s"), filename_to_utf8(filename));
639 putc('\n', tty);
642 res = save_collection(file, &cancel);
644 if (res)
645 perror(filename == NULL ? _("Standard output") : filename);
647 if (filename != NULL && fclose(file) < 0 && res == 0)
648 perror(filename);
650 return res;
653 gboolean serialize_collection_gui(void)
655 FILE *file;
656 gchar *filename;
657 gint res;
658 gboolean cancel = FALSE;
660 if (get_list_head() == NULL)
661 return FALSE;
663 filename = get_collection_destination();
664 if (filename == NULL)
665 return FALSE;
667 /* We remove the file, since we may have mmapped it */
668 remove(filename);
670 file = fopen(filename, "w");
671 if (file == NULL) {
672 file_error(filename);
673 return FALSE;
676 create_progress_dialog(filename, &cancel, TRUE);
678 res = save_collection(file, &cancel);
679 if (res) {
680 if (cancel == FALSE)
681 file_error(filename);
683 fclose(file);
684 remove(filename);
685 } else if (fclose(file) < 0)
686 file_error(filename);
688 destroy_progress_dialog();
689 g_free(filename);
691 return FALSE;
694 /*** Deserialization ***/
696 struct coll_src {
697 gboolean is_file;
698 union {
699 FILE *file;
700 struct {
701 gchar *base;
702 gchar *ptr;
703 gchar *end;
704 } mem;
705 } backend;
708 static void free_buffer(struct coll_src *source, gchar * buffer)
710 if (source->is_file)
711 g_free(buffer);
714 static gchar *read_buffer(struct coll_src *source, size_t length,
715 gboolean is_string)
717 gchar *data;
719 if (source->is_file) {
720 data = g_new(gchar, length);
721 if (fread(data, 1, length, source->backend.file) != length) {
722 g_free(data);
723 return NULL;
725 } else {
726 if (source->backend.mem.ptr + length > source->backend.mem.end)
727 return NULL;
729 data = source->backend.mem.ptr;
730 source->backend.mem.ptr += length;
733 if (is_string && data[length - 1] != '\0') {
734 free_buffer(source, data);
735 data = NULL;
738 return data;
741 static gint read_char(struct coll_src *source)
743 if (source->is_file)
744 return fgetc(source->backend.file);
746 if (source->backend.mem.ptr >= source->backend.mem.end)
747 return EOF;
749 return *(source->backend.mem.ptr++);
752 static GdkPixbufDestroyNotify destroy_func(struct coll_src *source)
754 return (GdkPixbufDestroyNotify) (source->is_file ? g_free : NULL);
757 /* Checks that file starts with magic. */
758 static gboolean check_magic(struct coll_src *source, const gchar * magic)
760 gboolean res;
761 gint size = strlen(magic) + 1;
762 gchar *buffer = read_buffer(source, size, TRUE);
764 if (buffer == NULL)
765 return FALSE;
767 res = g_str_equal(magic, buffer);
768 free_buffer(source, buffer);
770 return res;
775 /* Reads a number represented by a string. -1 => ERROR */
776 static gint read_number(struct coll_src *source)
778 gint number = 0;
779 gint next_char = read_char(source);
781 while (isdigit(next_char)) {
782 number = number * 10 + next_char - '0';
783 next_char = read_char(source);
786 if (next_char == '\0')
787 return number;
789 return -1;
792 #define READ_THUMB_NB(source, var) \
793 do { \
794 var = read_number(source); \
795 if (var < 0) \
796 return FALSE; \
797 } while (0)
799 static gboolean read_pixbuf(struct coll_src *source, GdkPixbuf ** thumb)
801 GdkColorspace colorspace;
802 gboolean has_alpha;
803 gint bits_per_sample, width, height, rowstride;
804 gint pixel_length;
805 gchar *pixel;
807 READ_THUMB_NB(source, colorspace);
808 READ_THUMB_NB(source, has_alpha);
809 if (has_alpha != FALSE && has_alpha != TRUE)
810 return FALSE;
812 READ_THUMB_NB(source, bits_per_sample);
813 READ_THUMB_NB(source, width);
814 READ_THUMB_NB(source, height);
815 READ_THUMB_NB(source, rowstride);
816 READ_THUMB_NB(source, pixel_length);
818 pixel = read_buffer(source, pixel_length, FALSE);
819 if (pixel == NULL)
820 return FALSE;
822 *thumb = gdk_pixbuf_new_from_data(pixel, colorspace, has_alpha,
823 bits_per_sample, width, height, rowstride,
824 destroy_func(source), NULL);
826 if (*thumb == NULL) {
827 free_buffer(source, pixel);
828 return FALSE;
831 return TRUE;
834 static gboolean read_thumb(struct coll_src *source, gchar ** hash_key,
835 GdkPixbuf ** thumb)
837 gint has_thumb, hash_key_length;
839 has_thumb = read_number(source);
840 if (has_thumb == 0)
841 return TRUE;
843 if (has_thumb != 1)
844 return FALSE;
846 hash_key_length = read_number(source);
847 if (hash_key_length <= 0)
848 return FALSE;
850 hash_key_length++;
851 *hash_key = read_buffer(source, hash_key_length, TRUE);
852 if (*hash_key == NULL)
853 return FALSE;
855 return read_pixbuf(source, thumb);
858 static tree_item *read_entry(struct coll_src *source)
860 tree_item *item;
861 gint len;
862 gchar *path, *hash_key = NULL;
863 GdkPixbuf *thumb = NULL;
865 if (check_magic(source, "<FILE>") == FALSE)
866 return NULL;
868 len = read_number(source);
869 if (len <= 0)
870 return NULL;
872 len++;
873 path = read_buffer(source, len, TRUE);
874 if (path == NULL)
875 return NULL;
877 if (read_thumb(source, &hash_key, &thumb) == FALSE) {
878 free_buffer(source, path);
879 return NULL;
882 if (check_magic(source, "</FILE>") == FALSE) {
883 free_buffer(source, path);
884 free_buffer(source, hash_key);
885 if (hash_key != NULL)
886 g_object_unref(thumb);
888 return NULL;
891 item = g_new(tree_item, 1);
892 item->name = NULL;
893 item->path = path;
894 item->thumb_key = hash_key;
895 item->thumb = thumb;
897 return item;
900 #define CHECK_MAGIC(source, str) \
901 do { \
902 gboolean res = check_magic(source, str); \
903 if (res == FALSE) \
904 goto error; \
905 } while (0)
907 static struct coll_src *build_source(FILE * file)
909 off_t length;
910 struct coll_src *source = g_new(struct coll_src, 1);
912 if (fseeko(file, 0, SEEK_END) < 0)
913 goto no_mmap;
915 length = ftello(file);
916 if (length < 0) {
917 perror("ftello");
918 goto no_mmap;
921 source->backend.mem.base =
922 mmap(NULL, length, PROT_READ, MAP_PRIVATE, fileno(file), 0);
923 if (source->backend.mem.base == MAP_FAILED) {
924 perror("mmap");
925 goto no_mmap;
928 source->is_file = FALSE;
929 source->backend.mem.ptr = source->backend.mem.base;
930 source->backend.mem.end = source->backend.mem.base + length;
931 goto ok;
933 no_mmap:
934 fseeko(file, 0, SEEK_SET);
936 source->is_file = TRUE;
937 source->backend.file = file;
940 return source;
943 static void destroy_source(struct coll_src *source, gboolean ok)
945 if (ok == FALSE && source->is_file == FALSE)
946 if (munmap(source->backend.mem.base,
947 source->backend.mem.end - source->backend.mem.base) < 0)
948 perror("munmap");
951 gint load_dot_gliv_from_file(const gchar * filename, FILE * file)
953 gint count, i = 0;
954 tree_item **items = NULL;
955 gchar **filenames = NULL;
956 gboolean ok = TRUE, cancel = FALSE;
957 gint nb_inserted = -1;
958 struct coll_src *source;
959 gboolean saved_force_load;
961 source = build_source(file);
963 CHECK_MAGIC(source, "GLiv <COLLECTION>");
964 CHECK_MAGIC(source, "1");
966 count = read_number(source);
967 if (count < 0)
968 goto error;
970 if (rt)
971 create_progress_dialog(filename, &cancel, FALSE);
973 items = g_new(tree_item *, count);
974 for (i = 0; i < count; i++) {
975 tree_item *item;
977 item = read_entry(source);
978 if (item == NULL)
979 goto error;
981 items[i] = item;
982 update_progress(item->path, i, count);
984 if (cancel) {
985 i++;
986 nb_inserted = 0;
987 goto error;
991 CHECK_MAGIC(source, "</COLLECTION>");
993 update_progress(NULL, count - 1, count);
994 nb_inserted = 0;
996 filenames = g_new(gchar *, count);
998 for (i = 0; i < count; i++) {
999 if (items[i]->thumb_key != NULL)
1000 collection_add_thumbnail(items[i]->thumb_key, items[i]->thumb);
1002 filenames[i] = items[i]->path;
1005 /* We don't want to stat() every file in the collection */
1006 saved_force_load = options->force;
1007 options->force = TRUE;
1009 nb_inserted = insert_after_current(filenames, count, TRUE, FALSE);
1011 options->force = saved_force_load;
1013 goto ok;
1015 error:
1016 ok = FALSE;
1020 while (i) {
1021 i--;
1022 free_buffer(source, items[i]->path);
1023 if (ok == FALSE && items[i]->thumb_key != NULL) {
1024 free_buffer(source, items[i]->thumb_key);
1025 g_object_unref(items[i]->thumb);
1027 g_free(items[i]);
1029 g_free(items);
1030 g_free(filenames);
1032 if (rt)
1033 destroy_progress_dialog();
1034 destroy_source(source, ok);
1035 return nb_inserted;
1038 gint load_dot_gliv(const gchar * filename)
1040 FILE *file;
1041 FILE *tty;
1042 gint nb_inserted;
1043 loader_t loader;
1045 tty = get_terminal_output();
1046 if (tty != NULL) {
1047 fprintf(tty, _("Loading collection: %s"), filename_to_utf8(filename));
1048 putc('\n', tty);
1051 loader = get_loader(filename);
1052 if (loader == LOADER_DECOMP_DOT_GLIV)
1053 nb_inserted = load_compressed_collection(filename);
1055 else {
1056 file = fopen(filename, "r");
1057 if (file == NULL) {
1058 file_error(filename);
1059 return 0;
1062 nb_inserted = load_dot_gliv_from_file(filename, file);
1063 fclose(file);
1066 if (nb_inserted < 0) {
1067 if (rt)
1068 DIALOG_MSG(_("%s is not a GLiv collection"),
1069 filename_to_utf8(filename));
1070 nb_inserted = 0;
1073 return nb_inserted;
1076 gboolean deserialize_collection(void)
1078 gchar *filename;
1079 gint nb_inserted;
1081 filename = get_filename(_("Choose a collection to load"), FALSE);
1082 if (filename == NULL)
1083 return FALSE;
1085 nb_inserted = load_dot_gliv(filename);
1086 g_free(filename);
1087 if (nb_inserted <= 0)
1088 return FALSE;
1090 new_images(nb_inserted);
1091 return FALSE;