Returning FALSE in a serialization or a deserialization aborts it.
[gliv.git] / src / collection.c
blob95f6edcdc37d7ac3b200b27127e102f3feba76ea
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@yahoo.fr>
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 <stdio.h> /* FILE, fopen(), fread(), fwrite(), fclose() */
81 #include <string.h> /* strlen() */
82 #include <errno.h> /* errno */
83 #include <ctype.h> /* isdigit() */
85 #include "gliv.h"
86 #include "collection.h"
87 #include "messages.h"
88 #include "tree.h"
89 #include "thumbnails.h"
90 #include "str_utils.h"
91 #include "files_list.h"
92 #include "next_image.h"
93 #include "gliv_image.h"
94 #include "formats.h"
95 #include "loading.h"
96 #include "decompression.h"
98 extern gliv_image *current_image;
100 static const gchar *get_a_file(const gchar * title)
102 GtkFileSelection *dialog;
103 gint response;
104 const gchar *filename;
106 dialog = GTK_FILE_SELECTION(gtk_file_selection_new(title));
108 gtk_file_selection_set_select_multiple(dialog, TRUE);
109 response = gtk_dialog_run(GTK_DIALOG(dialog));
111 if (response == GTK_RESPONSE_OK)
112 filename = gtk_file_selection_get_filename(dialog);
113 else
114 filename = NULL;
116 gtk_widget_destroy(GTK_WIDGET(dialog));
117 return filename;
120 /*** Progress dialog ***/
122 static GtkLabel *file_label;
123 static GtkProgressBar *progress;
124 static GtkWindow *progress_window;
126 static void create_progress_dialog(const gchar * filename)
128 GtkButton *cancel;
129 GtkTable *table;
130 gchar *title;
132 file_label = GTK_LABEL(gtk_label_new(NULL));
133 gtk_widget_show(GTK_WIDGET(file_label));
135 progress = GTK_PROGRESS_BAR(gtk_progress_bar_new());
136 gtk_widget_show(GTK_WIDGET(progress));
138 cancel = GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_CANCEL));
139 g_signal_connect(cancel, "clicked", G_CALLBACK(end_using_tree), NULL);
140 gtk_widget_show(GTK_WIDGET(cancel));
142 table = GTK_TABLE(gtk_table_new(3, 2, TRUE));
143 gtk_table_attach_defaults(table, GTK_WIDGET(file_label), 0, 2, 0, 1);
144 gtk_table_attach_defaults(table, GTK_WIDGET(progress), 0, 2, 1, 2);
145 gtk_table_attach_defaults(table, GTK_WIDGET(cancel), 1, 2, 2, 3);
147 gtk_widget_show(GTK_WIDGET(table));
149 progress_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
150 title = g_strdup_printf(_("Saving collection: %s"),
151 filename_to_utf8(filename));
152 gtk_container_add(GTK_CONTAINER(progress_window), GTK_WIDGET(table));
153 gtk_widget_show(GTK_WIDGET(progress_window));
157 * In the serialization and deserialization,
158 * returning FALSE means aborting it.
161 /*** Serialization ***/
163 typedef struct {
164 FILE *file;
165 gint file_id;
166 gint count;
167 } serialization_data;
169 /* Attempts to put a string in the serialized file. */
170 #define WRITE(str) \
171 do { \
172 gchar * __str__ = (str); \
173 gint len = strlen(__str__) + 1; \
175 if (fwrite(__str__, 1, len, work->file) != len) \
176 return FALSE; \
177 } while (0)
179 /* Attempts to put a number (in string form) in the serialized file. */
180 #define WRITE_NB(nb) \
181 do { \
182 gchar *str = g_strdup_printf("%d%c", (nb), '\0'); \
183 gint len = strlen(str) + 1; \
185 if (fwrite(str, 1, len, work->file) != len) { \
186 g_free(str); \
187 return FALSE; \
190 g_free(str); \
191 } while (0)
193 static gboolean print_header(serialization_data * work)
195 WRITE("GLiv <COLLECTION>");
196 WRITE("1"); /* Version */
197 WRITE_NB(work->count); /* FileCount */
199 return TRUE;
202 static gboolean print_footer(serialization_data * work)
204 WRITE("</COLLECTION>");
206 return TRUE;
209 /* Write the MaybeThumb part of an entry. */
210 static gboolean write_thumb(tree_item * item, serialization_data * work)
212 gint height, rowstride, len;
213 GdkPixbuf *thumb;
215 if (fill_thumbnail(item) == FALSE) {
216 WRITE("0");
217 return TRUE;
220 thumb = item->thumb;
221 height = gdk_pixbuf_get_height(thumb);
222 rowstride = gdk_pixbuf_get_rowstride(thumb);
223 len = height * rowstride;
225 WRITE("1");
226 WRITE_NB(strlen(item->thumb_key)); /* HashKeyLength */
227 WRITE(item->thumb_key); /* HashKey */
228 WRITE_NB(gdk_pixbuf_get_colorspace(thumb)); /* GdkColorspace */
229 WRITE_NB(gdk_pixbuf_get_has_alpha(thumb)); /* AlphaChannel */
230 WRITE_NB(gdk_pixbuf_get_bits_per_sample(thumb)); /* BitsPerSample */
231 WRITE_NB(gdk_pixbuf_get_width(thumb)); /* ThumbWidth */
232 WRITE_NB(height); /* ThumbHeight */
233 WRITE_NB(rowstride); /* RowStride */
234 WRITE_NB(len); /* PixelDataLength */
236 /* PixelData */
237 return fwrite(gdk_pixbuf_get_pixels(thumb), 1, len, work->file) == len;
240 static gboolean serialize_file(GNode * tree, serialization_data * work)
242 tree_item *item = tree->data;
244 gtk_label_set_text(file_label, item->path);
245 gtk_progress_bar_set_fraction(progress,
246 (gdouble) work->file_id / work->count);
247 WRITE("<FILE>");
248 WRITE_NB(strlen(item->path)); /* PathLength */
249 WRITE(item->path); /* Path */
251 if (write_thumb(item, work) == FALSE) /* MaybeThumb */
252 return FALSE;
254 WRITE("</FILE>");
256 work->file_id++;
257 return TRUE;
260 /* The tree is reversed, so we traverse it in the reverse direction. */
261 static gboolean reverse_traverse_leafs(GNode * tree,
262 GNodeTraverseFunc func, gpointer data)
264 GNode *child;
266 if (G_NODE_IS_LEAF(tree))
267 return func(tree, data);
269 child = g_node_last_child(tree);
270 while (child != NULL) {
271 if (reverse_traverse_leafs(child, func, data) == FALSE)
272 return FALSE;
274 child = child->prev;
277 return TRUE;
280 #define FILE_ERROR(filename) \
281 do { \
282 const gchar *message = g_strerror(errno); \
283 message = g_strconcat(filename, ": ", message, NULL); \
284 DIALOG_MSG(message); \
285 } while (0)
287 void serialize_collection(void)
289 FILE *file;
290 GNode *tree;
291 const gchar *filename;
292 serialization_data *work;
293 gboolean res;
295 tree = make_tree();
296 if (tree == NULL)
297 return;
299 filename = get_a_file(_("Choose a file to save the collection"));
300 if (filename == NULL) {
301 end_using_tree();
302 return;
305 file = fopen(filename, "w");
306 if (file == NULL) {
307 FILE_ERROR(filename);
308 end_using_tree();
309 return;
312 create_progress_dialog(filename);
314 work = g_new(serialization_data, 1);
315 work->file = file;
316 work->file_id = 0;
317 work->count = get_list_length();
319 if (print_header(work) == FALSE)
320 goto error;
322 res = reverse_traverse_leafs(tree,
323 (GNodeTraverseFunc) serialize_file, work);
325 if (res == FALSE || work->file_id != work->count)
326 goto error;
328 if (print_footer(work) == FALSE)
329 goto error;
331 if (fclose(work->file)) {
332 work->file = NULL;
333 goto error;
335 work->file = NULL;
337 goto ok;
339 error:
340 FILE_ERROR(filename);
343 if (work->file != NULL)
344 fclose(work->file);
346 g_free(work);
347 gtk_widget_destroy(GTK_WIDGET(progress_window));
348 end_using_tree();
351 /*** Deserialization ***/
353 /* Checks that file starts with magic. */
354 static gboolean check_magic(const gchar * magic, FILE * file)
356 gboolean res;
357 gint size = strlen(magic) + 1;
358 gchar *buffer = g_new(gchar, size);
360 if (fread(buffer, 1, size, file) != size) {
361 g_free(buffer);
362 return FALSE;
365 res = buffer[size - 1] == '\0' && g_str_equal(magic, buffer);
366 g_free(buffer);
368 return res;
371 /* Reads a number represented by a string. -1 => ERROR */
372 static gint read_number(FILE * file)
374 gint number = 0;
375 gint read_char = fgetc(file);
377 while (isdigit(read_char)) {
378 number = number * 10 + read_char - '0';
379 read_char = fgetc(file);
382 if (read_char == '\0')
383 return number;
385 return -1;
388 #define READ_THUMB_NB(var) \
389 do { \
390 var = read_number(file); \
391 if (var < 0) \
392 return FALSE; \
393 } while (0)
395 static gboolean read_pixbuf(FILE * file, GdkPixbuf ** thumb)
397 GdkColorspace colorspace;
398 gboolean has_alpha;
399 gint bits_per_sample, width, height, rowstride;
400 gint pixel_length;
401 gchar *pixel;
403 READ_THUMB_NB(colorspace);
404 READ_THUMB_NB(has_alpha);
405 if (has_alpha != FALSE && has_alpha != TRUE)
406 return FALSE;
408 READ_THUMB_NB(bits_per_sample);
409 READ_THUMB_NB(width);
410 READ_THUMB_NB(height);
411 READ_THUMB_NB(rowstride);
412 READ_THUMB_NB(pixel_length);
414 pixel = g_new(gchar, pixel_length);
415 if (fread(pixel, 1, pixel_length, file) != pixel_length) {
416 g_free(pixel);
417 return FALSE;
420 *thumb = gdk_pixbuf_new_from_data(pixel, colorspace, has_alpha,
421 bits_per_sample, width, height, rowstride,
422 (GdkPixbufDestroyNotify) g_free, NULL);
424 if (*thumb == NULL) {
425 g_free(pixel);
426 return FALSE;
429 return TRUE;
432 static gboolean read_thumb(FILE * file, gchar ** hash_key, GdkPixbuf ** thumb)
434 gint has_thumb, hash_key_length;
436 has_thumb = read_number(file);
437 if (has_thumb == 0)
438 return TRUE;
440 if (has_thumb != 1)
441 return FALSE;
443 hash_key_length = read_number(file);
444 if (hash_key_length <= 0)
445 return FALSE;
447 hash_key_length++;
448 *hash_key = g_new(gchar, hash_key_length);
450 if (fread(*hash_key, 1, hash_key_length, file) != hash_key_length ||
451 (*hash_key)[hash_key_length - 1] != '\0') {
453 g_free(*hash_key);
454 return FALSE;
457 return read_pixbuf(file, thumb);
460 static tree_item *read_entry(FILE * file)
462 tree_item *item;
463 gint len;
464 gchar *path, *hash_key = NULL;
465 GdkPixbuf *thumb = NULL;
467 if (check_magic("<FILE>", file) == FALSE)
468 return NULL;
470 len = read_number(file);
471 if (len <= 0)
472 return NULL;
474 len++;
475 path = g_new(gchar, len);
476 if (fread(path, 1, len, file) != len || path[len - 1] != '\0') {
477 g_free(path);
478 return NULL;
481 if (read_thumb(file, &hash_key, &thumb) == FALSE) {
482 g_free(path);
483 return NULL;
486 if (check_magic("</FILE>", file) == FALSE) {
487 g_free(path);
488 g_free(hash_key);
489 if (hash_key != NULL)
490 g_object_unref(thumb);
492 return NULL;
495 item = g_new(tree_item, 1);
496 item->name = NULL;
497 item->path = path;
498 item->thumb_key = hash_key;
499 item->thumb = thumb;
501 return item;
504 #define CHECK_MAGIC(str) \
505 do { \
506 gboolean res = check_magic((str), file); \
507 if (res == FALSE) \
508 goto error; \
509 } while (0)
511 gint load_dot_gliv_from_file(FILE * file, gboolean reverse)
513 gint count, i = 0;
514 tree_item **items = NULL;
515 gchar **filenames = NULL;
516 gboolean ok = TRUE;
517 gint nb_inserted = 0;
519 CHECK_MAGIC("GLiv <COLLECTION>");
520 CHECK_MAGIC("1");
522 count = read_number(file);
523 if (count < 0)
524 goto error;
526 items = g_new(tree_item *, count);
527 for (i = 0; i < count; i++) {
528 tree_item *item;
530 item = read_entry(file);
531 if (item == NULL)
532 goto error;
534 items[i] = item;
537 CHECK_MAGIC("</COLLECTION>");
539 filenames = g_new(gchar *, count);
540 for (i = 0; i < count; i++) {
541 if (reverse)
542 filenames[i] = items[count - 1 - i]->path;
543 else
544 filenames[i] = items[i]->path;
546 if (items[i]->thumb_key != NULL)
547 collection_add_thumbnail(items[i]->thumb_key, items[i]->thumb);
550 nb_inserted = insert_after_current(filenames, count, FALSE, FALSE);
552 goto ok;
554 error:
555 ok = FALSE;
558 while (i) {
559 i--;
560 g_free(items[i]->path);
561 if (ok == FALSE) {
562 g_free(items[i]->thumb_key);
563 g_object_unref(items[i]->thumb);
565 g_free(items[i]);
567 g_free(items);
568 g_free(filenames);
569 return nb_inserted;
572 gint load_dot_gliv(const gchar * filename, gboolean reverse)
574 FILE *file;
575 gint nb_inserted;
576 loader_t loader;
578 loader = get_loader(filename);
579 if (loader == LOADER_DECOMP_DOT_GLIV)
580 nb_inserted = load_compressed_collection(filename, reverse);
582 else {
583 file = fopen(filename, "r");
584 if (file == NULL) {
585 FILE_ERROR(filename);
586 return 0;
589 nb_inserted = load_dot_gliv_from_file(file, reverse);
590 fclose(file);
593 if (nb_inserted < 0) {
594 DIALOG_MSG(_("%s is not a GLiv collection"),
595 filename_to_utf8(filename));
596 nb_inserted = 0;
599 return nb_inserted;
602 void deserialize_collection(void)
604 const gchar *filename;
606 filename = get_a_file(_("Choose a collection to load"));
607 if (filename == NULL)
608 return;
610 if (load_dot_gliv(filename, FALSE) == 0)
611 return;
613 if (current_image == NULL)
614 load_first_image();