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(), strrchr() */
82 #include <errno.h> /* errno */
83 #include <ctype.h> /* isdigit() */
84 #include <time.h> /* time_t, time() */
87 #include "collection.h"
90 #include "thumbnails.h"
91 #include "str_utils.h"
92 #include "files_list.h"
93 #include "next_image.h"
94 #include "gliv-image.h"
97 #include "decompression.h"
98 #include "callbacks.h"
101 /* Max filename length in the progress window. */
102 #define MAX_DISPLAYED_NAME_LENGTH 30
104 extern GlivImage
*current_image
;
106 /* Let the user choose a filename using a file selection dialog. */
107 static const gchar
*get_a_file(const gchar
* title
)
109 GtkFileSelection
*dialog
;
111 const gchar
*filename
;
113 dialog
= GTK_FILE_SELECTION(gtk_file_selection_new(title
));
115 gtk_file_selection_set_select_multiple(dialog
, TRUE
);
116 response
= run_modal_dialog(GTK_DIALOG(dialog
));
118 if (response
== GTK_RESPONSE_OK
)
119 filename
= gtk_file_selection_get_filename(dialog
);
123 gtk_widget_destroy(GTK_WIDGET(dialog
));
127 /*** Progress dialog ***/
129 static GtkLabel
*file_label
;
130 static GtkLabel
*ratio_label
;
131 static GtkProgressBar
*progress
;
132 static GtkWindow
*progress_window
;
133 static GtkLabel
*elapsed_label
, *total_label
, *remaining_label
;
134 static time_t start_time
;
136 static gboolean
set_true(gboolean
* ptr
)
142 static GtkLabel
*add_time_display(GtkTable
* table
, const gchar
* text
,
145 GtkLabel
*name_label
, *time_label
;
147 name_label
= GTK_LABEL(gtk_label_new(text
));
148 gtk_widget_show(GTK_WIDGET(name_label
));
150 time_label
= GTK_LABEL(gtk_label_new(NULL
));
151 gtk_widget_show(GTK_WIDGET(time_label
));
153 gtk_table_attach_defaults(table
, GTK_WIDGET(name_label
),
156 gtk_table_attach_defaults(table
, GTK_WIDGET(time_label
),
162 static void create_progress_dialog(const gchar
* filename
, gboolean serialize
,
169 file_label
= GTK_LABEL(gtk_label_new(NULL
));
170 gtk_widget_show(GTK_WIDGET(file_label
));
172 ratio_label
= GTK_LABEL(gtk_label_new(NULL
));
173 gtk_widget_show(GTK_WIDGET(ratio_label
));
176 progress
= GTK_PROGRESS_BAR(gtk_progress_bar_new());
177 gtk_widget_show(GTK_WIDGET(progress
));
179 cancel
= GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_CANCEL
));
182 g_signal_connect(cancel
, "clicked",
183 G_CALLBACK(cancel_using_tree
), data
);
185 g_signal_connect_swapped(cancel
, "clicked", G_CALLBACK(set_true
), data
);
187 gtk_widget_show(GTK_WIDGET(cancel
));
189 table
= GTK_TABLE(gtk_table_new(7, 2, TRUE
));
190 gtk_table_attach_defaults(table
, GTK_WIDGET(file_label
), 0, 2, 0, 1);
191 gtk_table_attach_defaults(table
, GTK_WIDGET(ratio_label
), 0, 2, 1, 2);
192 gtk_table_attach_defaults(table
, GTK_WIDGET(progress
), 0, 2, 2, 3);
194 elapsed_label
= add_time_display(table
, _("Elapsed time"), 3);
195 remaining_label
= add_time_display(table
, _("Remaining time"), 4);
196 total_label
= add_time_display(table
, _("Total time"), 5);
198 gtk_table_attach_defaults(table
, GTK_WIDGET(cancel
), 1, 2, 6, 7);
200 gtk_widget_show(GTK_WIDGET(table
));
202 progress_window
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
205 g_signal_connect(progress_window
, "delete-event",
206 G_CALLBACK(cancel_using_tree
), NULL
);
208 g_signal_connect_swapped(progress_window
, "delete-event",
209 G_CALLBACK(set_true
), data
);
212 title
= g_strdup_printf(_("Saving collection: %s"),
213 filename_to_utf8(filename
));
214 gtk_window_set_title(progress_window
, title
);
217 gtk_window_set_title(progress_window
, _("Loading GLiv collection"));
219 gtk_container_add(GTK_CONTAINER(progress_window
), GTK_WIDGET(table
));
220 gtk_widget_show(GTK_WIDGET(progress_window
));
221 start_time
= time(NULL
);
224 static gchar
*time2str(time_t t
)
226 gint hours
= t
/ 3600;
227 gint minutes
= (t
- hours
* 3600) / 60;
228 gint seconds
= t
% 60;
230 return g_strdup_printf("%02d:%02d:%02d", hours
, minutes
, seconds
);
233 static void update_times(time_t elapsed_time
,
234 time_t remaining_time
, time_t total_time
)
238 text
= time2str(elapsed_time
);
239 gtk_label_set_text(elapsed_label
, text
);
242 text
= time2str(remaining_time
);
243 gtk_label_set_text(remaining_label
, text
);
246 text
= time2str(total_time
);
247 gtk_label_set_text(total_label
, text
);
251 static void update_progress(const gchar
* filename
, gint id
, gint count
)
254 static GTimeVal last
= { 0, 0 };
255 time_t current_time
, elapsed_time
, total_time
, remaining_time
;
259 if (filename
== NULL
) {
261 gchar
*msg
= _("Inserting files...");
263 last
.tv_sec
= last
.tv_usec
= 0;
264 gtk_label_set_text(file_label
, msg
);
268 g_get_current_time(&now
);
270 if (last
.tv_sec
!= 0 || last
.tv_usec
!= 0) {
271 /* Not the first time. */
274 g_get_current_time(&now
);
275 usec_diff
= (now
.tv_sec
- last
.tv_sec
) * G_USEC_PER_SEC
+
276 now
.tv_usec
- last
.tv_usec
;
278 if (usec_diff
< G_USEC_PER_SEC
/ 10)
279 /* Lower the redrawing load. */
285 base
= strrchr(filename
, '/');
292 if (len
> MAX_DISPLAYED_NAME_LENGTH
) {
296 begin
= g_strndup(base
, MAX_DISPLAYED_NAME_LENGTH
/ 2 - 3);
297 end
= base
+ len
- MAX_DISPLAYED_NAME_LENGTH
/ 2 + 3;
298 text
= g_strconcat(begin
, " ... ", end
, NULL
);
301 gtk_label_set_text(file_label
, text
);
304 gtk_label_set_text(file_label
, base
);
307 ratio_text
= g_strdup_printf("%d / %d", id
, count
- 1);
308 gtk_label_set_text(ratio_label
, ratio_text
);
312 gtk_progress_bar_set_fraction(progress
, 1.0);
314 gtk_progress_bar_set_fraction(progress
, (gdouble
) id
/ (count
- 1));
316 current_time
= time(NULL
);
317 elapsed_time
= current_time
- start_time
;
321 total_time
= elapsed_time
* (count
- 1) / id
;
323 remaining_time
= total_time
- elapsed_time
;
325 update_times(elapsed_time
, remaining_time
, total_time
);
329 static void destroy_progress_dialog(void)
331 if (progress_window
!= NULL
) {
332 if (GTK_IS_WIDGET(progress_window
))
333 gtk_widget_destroy(GTK_WIDGET(progress_window
));
334 progress_window
= NULL
;
339 * In the serialization and deserialization,
340 * returning FALSE means aborting it.
343 /*** Serialization ***/
349 gchar
*cwd
; /* To avoid relative filenames. */
350 } serialization_data
;
352 /* Attempts to put a string in the serialized file. */
353 #define WRITE(file, str) \
355 gchar * __str__ = (str); \
356 gint len = strlen(__str__) + 1; \
358 if (fwrite(__str__, 1, len, (file)) != len) \
362 /* Attempts to put a number (in string form) in the serialized file. */
363 #define WRITE_NB(file, nb) \
365 gchar *str = g_strdup_printf("%d%c", (nb), '\0'); \
366 gint len = strlen(str) + 1; \
368 if (fwrite(str, 1, len, (file)) != len) { \
376 static gboolean
print_header(serialization_data
* work
)
378 WRITE(work
->file
, "GLiv <COLLECTION>");
379 WRITE(work
->file
, "1"); /* Version */
380 WRITE_NB(work
->file
, work
->count
); /* FileCount */
385 static gboolean
print_footer(FILE * file
)
387 WRITE(file
, "</COLLECTION>");
392 /* Write the MaybeThumb part of an entry. */
393 static gboolean
write_thumb(tree_item
* item
, FILE * file
)
395 gint height
, rowstride
, len
, bps
;
398 if (fill_thumbnail(item
) == FALSE
) {
404 height
= gdk_pixbuf_get_height(thumb
);
405 rowstride
= gdk_pixbuf_get_rowstride(thumb
);
406 len
= pixels_size(thumb
);
407 bps
= gdk_pixbuf_get_bits_per_sample(thumb
);
410 WRITE_NB(file
, strlen(item
->thumb_key
)); /* HashKeyLength */
411 WRITE(file
, item
->thumb_key
); /* HashKey */
412 WRITE_NB(file
, gdk_pixbuf_get_colorspace(thumb
)); /* GdkColorspace */
413 WRITE_NB(file
, gdk_pixbuf_get_has_alpha(thumb
)); /* AlphaChannel */
414 WRITE_NB(file
, bps
); /* BitsPerSample */
415 WRITE_NB(file
, gdk_pixbuf_get_width(thumb
)); /* ThumbWidth */
416 WRITE_NB(file
, height
); /* ThumbHeight */
417 WRITE_NB(file
, rowstride
); /* RowStride */
418 WRITE_NB(file
, len
); /* PixelDataLength */
421 return fwrite(gdk_pixbuf_get_pixels(thumb
), 1, len
, file
) == len
;
424 static gboolean
serialize_file(GNode
* tree
, serialization_data
* work
)
426 tree_item
*item
= tree
->data
;
427 FILE *file
= work
->file
;
430 update_progress(item
->path
, work
->file_id
, work
->count
);
432 WRITE(file
, "<FILE>");
434 path
= get_absolute_filename(item
->path
);
435 WRITE_NB(file
, strlen(path
)); /* PathLength */
436 WRITE(file
, path
); /* Path */
439 if (write_thumb(item
, file
) == FALSE
) /* MaybeThumb */
442 WRITE(file
, "</FILE>");
448 /* The tree is reversed, so we traverse it in the reverse direction. */
449 static gboolean
reverse_traverse_leafs(GNode
* tree
,
450 GNodeTraverseFunc func
, gpointer data
)
454 if (G_NODE_IS_LEAF(tree
))
455 return func(tree
, data
);
457 child
= g_node_last_child(tree
);
458 while (child
!= NULL
) {
459 if (canceled_using_tree())
462 if (reverse_traverse_leafs(child
, func
, data
) == FALSE
)
471 #define FILE_ERROR(filename) \
473 const gchar *message = g_strerror(errno); \
474 message = g_strconcat((filename), ": ", message, NULL); \
475 DIALOG_MSG(message); \
478 void serialize_collection(void)
482 const gchar
*filename
;
483 serialization_data
*work
;
490 filename
= get_a_file(_("Choose a file to save the collection"));
491 if (filename
== NULL
) {
496 file
= fopen(filename
, "w");
498 FILE_ERROR(filename
);
503 create_progress_dialog(filename
, TRUE
, NULL
);
505 work
= g_new(serialization_data
, 1);
508 work
->count
= tree_count_files(tree
);
509 work
->cwd
= g_get_current_dir();
511 if (print_header(work
) == FALSE
)
514 res
= reverse_traverse_leafs(tree
,
515 (GNodeTraverseFunc
) serialize_file
, work
);
517 if (res
== FALSE
|| work
->file_id
!= work
->count
)
520 if (print_footer(work
->file
) == FALSE
)
523 if (fclose(work
->file
)) {
533 if (canceled_using_tree() == FALSE
|| remove(filename
) < 0)
534 FILE_ERROR(filename
);
537 if (work
->file
!= NULL
)
543 destroy_progress_dialog();
546 /*** Deserialization ***/
548 /* Checks that file starts with magic. */
549 static gboolean
check_magic(const gchar
* magic
, FILE * file
)
552 gint size
= strlen(magic
) + 1;
553 gchar
*buffer
= g_new(gchar
, size
);
555 if (fread(buffer
, 1, size
, file
) != size
) {
560 res
= buffer
[size
- 1] == '\0' && g_str_equal(magic
, buffer
);
566 /* Reads a number represented by a string. -1 => ERROR */
567 static gint
read_number(FILE * file
)
570 gint read_char
= fgetc(file
);
572 while (isdigit(read_char
)) {
573 number
= number
* 10 + read_char
- '0';
574 read_char
= fgetc(file
);
577 if (read_char
== '\0')
583 #define READ_THUMB_NB(file, var) \
585 var = read_number(file); \
590 static gboolean
read_pixbuf(FILE * file
, GdkPixbuf
** thumb
)
592 GdkColorspace colorspace
;
594 gint bits_per_sample
, width
, height
, rowstride
;
598 READ_THUMB_NB(file
, colorspace
);
599 READ_THUMB_NB(file
, has_alpha
);
600 if (has_alpha
!= FALSE
&& has_alpha
!= TRUE
)
603 READ_THUMB_NB(file
, bits_per_sample
);
604 READ_THUMB_NB(file
, width
);
605 READ_THUMB_NB(file
, height
);
606 READ_THUMB_NB(file
, rowstride
);
607 READ_THUMB_NB(file
, pixel_length
);
609 pixel
= g_new(guchar
, pixel_length
);
610 if (fread(pixel
, 1, pixel_length
, file
) != pixel_length
) {
615 *thumb
= gdk_pixbuf_new_from_data(pixel
, colorspace
, has_alpha
,
616 bits_per_sample
, width
, height
, rowstride
,
617 (GdkPixbufDestroyNotify
) g_free
, NULL
);
619 if (*thumb
== NULL
) {
627 static gboolean
read_thumb(FILE * file
, gchar
** hash_key
, GdkPixbuf
** thumb
)
629 gint has_thumb
, hash_key_length
;
631 has_thumb
= read_number(file
);
638 hash_key_length
= read_number(file
);
639 if (hash_key_length
<= 0)
643 *hash_key
= g_new(gchar
, hash_key_length
);
645 if (fread(*hash_key
, 1, hash_key_length
, file
) != hash_key_length
||
646 (*hash_key
)[hash_key_length
- 1] != '\0') {
652 return read_pixbuf(file
, thumb
);
655 static tree_item
*read_entry(FILE * file
)
659 gchar
*path
, *hash_key
= NULL
;
660 GdkPixbuf
*thumb
= NULL
;
662 if (check_magic("<FILE>", file
) == FALSE
)
665 len
= read_number(file
);
670 path
= g_new(gchar
, len
);
671 if (fread(path
, 1, len
, file
) != len
|| path
[len
- 1] != '\0') {
676 if (read_thumb(file
, &hash_key
, &thumb
) == FALSE
) {
681 if (check_magic("</FILE>", file
) == FALSE
) {
684 if (hash_key
!= NULL
)
685 g_object_unref(thumb
);
690 item
= g_new(tree_item
, 1);
693 item
->thumb_key
= hash_key
;
699 #define CHECK_MAGIC(file, str) \
701 gboolean res = check_magic((str), (file)); \
706 gint
load_dot_gliv_from_file(FILE * file
, gboolean in_the_head
)
709 tree_item
**items
= NULL
;
710 gchar
**filenames
= NULL
;
711 gboolean ok
= TRUE
, cancel
= FALSE
;
712 gint nb_inserted
= -1;
714 CHECK_MAGIC(file
, "GLiv <COLLECTION>");
715 CHECK_MAGIC(file
, "1");
717 count
= read_number(file
);
721 create_progress_dialog(NULL
, FALSE
, &cancel
);
723 items
= g_new(tree_item
*, count
);
724 for (i
= 0; i
< count
; i
++) {
727 item
= read_entry(file
);
732 update_progress(item
->path
, i
, count
);
741 CHECK_MAGIC(file
, "</COLLECTION>");
743 update_progress(NULL
, count
- 1, count
);
746 if (in_the_head
== FALSE
)
747 filenames
= g_new(gchar
*, count
);
749 for (i
= 0; i
< count
; i
++) {
750 if (items
[i
]->thumb_key
!= NULL
)
751 collection_add_thumbnail(items
[i
]->thumb_key
, items
[i
]->thumb
);
754 nb_inserted
+= add_file_to_list(items
[i
]->path
);
756 filenames
[i
] = items
[i
]->path
;
759 if (in_the_head
== FALSE
)
760 nb_inserted
= insert_after_current(filenames
, count
,
761 FALSE
, FALSE
, FALSE
, FALSE
);
771 g_free(items
[i
]->path
);
772 if (ok
== FALSE
&& items
[i
]->thumb_key
!= NULL
) {
773 g_free(items
[i
]->thumb_key
);
774 g_object_unref(items
[i
]->thumb
);
780 destroy_progress_dialog();
784 gint
load_dot_gliv(const gchar
* filename
, gboolean in_the_head
)
790 loader
= get_loader(filename
);
791 if (loader
== LOADER_DECOMP_DOT_GLIV
)
792 nb_inserted
= load_compressed_collection(filename
, in_the_head
);
795 file
= fopen(filename
, "r");
797 FILE_ERROR(filename
);
801 nb_inserted
= load_dot_gliv_from_file(file
, in_the_head
);
805 if (nb_inserted
< 0) {
806 DIALOG_MSG(_("%s is not a GLiv collection"),
807 filename_to_utf8(filename
));
814 void deserialize_collection(void)
816 const gchar
*filename
;
819 filename
= get_a_file(_("Choose a collection to load"));
820 if (filename
== NULL
)
823 nb_inserted
= load_dot_gliv(filename
, FALSE
);
824 if (nb_inserted
<= 0)
827 open_next_image(nb_inserted
== 1);