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>
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 ::=
45 * "1" (Currently there is only one version)
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() */
86 #include "collection.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"
96 #include "decompression.h"
98 extern gliv_image
*current_image
;
100 static const gchar
*get_a_file(const gchar
* title
)
102 GtkFileSelection
*dialog
;
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
);
116 gtk_widget_destroy(GTK_WIDGET(dialog
));
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
)
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 ***/
167 } serialization_data
;
169 /* Attempts to put a string in the serialized file. */
172 gchar * __str__ = (str); \
173 gint len = strlen(__str__) + 1; \
175 if (fwrite(__str__, 1, len, work->file) != len) \
179 /* Attempts to put a number (in string form) in the serialized file. */
180 #define WRITE_NB(nb) \
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) { \
193 static gboolean
print_header(serialization_data
* work
)
195 WRITE("GLiv <COLLECTION>");
196 WRITE("1"); /* Version */
197 WRITE_NB(work
->count
); /* FileCount */
202 static gboolean
print_footer(serialization_data
* work
)
204 WRITE("</COLLECTION>");
209 /* Write the MaybeThumb part of an entry. */
210 static gboolean
write_thumb(tree_item
* item
, serialization_data
* work
)
212 gint height
, rowstride
, len
;
215 if (fill_thumbnail(item
) == FALSE
) {
221 height
= gdk_pixbuf_get_height(thumb
);
222 rowstride
= gdk_pixbuf_get_rowstride(thumb
);
223 len
= height
* rowstride
;
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 */
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
);
248 WRITE_NB(strlen(item
->path
)); /* PathLength */
249 WRITE(item
->path
); /* Path */
251 if (write_thumb(item
, work
) == FALSE
) /* MaybeThumb */
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
)
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
)
280 #define FILE_ERROR(filename) \
282 const gchar *message = g_strerror(errno); \
283 message = g_strconcat(filename, ": ", message, NULL); \
284 DIALOG_MSG(message); \
287 void serialize_collection(void)
291 const gchar
*filename
;
292 serialization_data
*work
;
299 filename
= get_a_file(_("Choose a file to save the collection"));
300 if (filename
== NULL
) {
305 file
= fopen(filename
, "w");
307 FILE_ERROR(filename
);
312 create_progress_dialog(filename
);
314 work
= g_new(serialization_data
, 1);
317 work
->count
= get_list_length();
319 if (print_header(work
) == FALSE
)
322 res
= reverse_traverse_leafs(tree
,
323 (GNodeTraverseFunc
) serialize_file
, work
);
325 if (res
== FALSE
|| work
->file_id
!= work
->count
)
328 if (print_footer(work
) == FALSE
)
331 if (fclose(work
->file
)) {
340 FILE_ERROR(filename
);
343 if (work
->file
!= NULL
)
347 gtk_widget_destroy(GTK_WIDGET(progress_window
));
351 /*** Deserialization ***/
353 /* Checks that file starts with magic. */
354 static gboolean
check_magic(const gchar
* magic
, FILE * file
)
357 gint size
= strlen(magic
) + 1;
358 gchar
*buffer
= g_new(gchar
, size
);
360 if (fread(buffer
, 1, size
, file
) != size
) {
365 res
= buffer
[size
- 1] == '\0' && g_str_equal(magic
, buffer
);
371 /* Reads a number represented by a string. -1 => ERROR */
372 static gint
read_number(FILE * file
)
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')
388 #define READ_THUMB_NB(var) \
390 var = read_number(file); \
395 static gboolean
read_pixbuf(FILE * file
, GdkPixbuf
** thumb
)
397 GdkColorspace colorspace
;
399 gint bits_per_sample
, width
, height
, rowstride
;
403 READ_THUMB_NB(colorspace
);
404 READ_THUMB_NB(has_alpha
);
405 if (has_alpha
!= FALSE
&& has_alpha
!= TRUE
)
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
) {
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
) {
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
);
443 hash_key_length
= read_number(file
);
444 if (hash_key_length
<= 0)
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') {
457 return read_pixbuf(file
, thumb
);
460 static tree_item
*read_entry(FILE * file
)
464 gchar
*path
, *hash_key
= NULL
;
465 GdkPixbuf
*thumb
= NULL
;
467 if (check_magic("<FILE>", file
) == FALSE
)
470 len
= read_number(file
);
475 path
= g_new(gchar
, len
);
476 if (fread(path
, 1, len
, file
) != len
|| path
[len
- 1] != '\0') {
481 if (read_thumb(file
, &hash_key
, &thumb
) == FALSE
) {
486 if (check_magic("</FILE>", file
) == FALSE
) {
489 if (hash_key
!= NULL
)
490 g_object_unref(thumb
);
495 item
= g_new(tree_item
, 1);
498 item
->thumb_key
= hash_key
;
504 #define CHECK_MAGIC(str) \
506 gboolean res = check_magic((str), file); \
511 gint
load_dot_gliv_from_file(FILE * file
, gboolean reverse
)
514 tree_item
**items
= NULL
;
515 gchar
**filenames
= NULL
;
517 gint nb_inserted
= 0;
519 CHECK_MAGIC("GLiv <COLLECTION>");
522 count
= read_number(file
);
526 items
= g_new(tree_item
*, count
);
527 for (i
= 0; i
< count
; i
++) {
530 item
= read_entry(file
);
537 CHECK_MAGIC("</COLLECTION>");
539 filenames
= g_new(gchar
*, count
);
540 for (i
= 0; i
< count
; i
++) {
542 filenames
[i
] = items
[count
- 1 - i
]->path
;
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
);
560 g_free(items
[i
]->path
);
562 g_free(items
[i
]->thumb_key
);
563 g_object_unref(items
[i
]->thumb
);
572 gint
load_dot_gliv(const gchar
* filename
, gboolean reverse
)
578 loader
= get_loader(filename
);
579 if (loader
== LOADER_DECOMP_DOT_GLIV
)
580 nb_inserted
= load_compressed_collection(filename
, reverse
);
583 file
= fopen(filename
, "r");
585 FILE_ERROR(filename
);
589 nb_inserted
= load_dot_gliv_from_file(file
, reverse
);
593 if (nb_inserted
< 0) {
594 DIALOG_MSG(_("%s is not a GLiv collection"),
595 filename_to_utf8(filename
));
602 void deserialize_collection(void)
604 const gchar
*filename
;
606 filename
= get_a_file(_("Choose a collection to load"));
607 if (filename
== NULL
)
610 if (load_dot_gliv(filename
, FALSE
) == 0)
613 if (current_image
== NULL
)