1 /* vim: set sw=2 ts=2 sts=2 et: */
2 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /*
4 * autoar-compressor.c
5 * Automatically create archives in some GNOME programs
6 *
7 * Copyright (C) 2013 Ting-Wei Lan
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this program; if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25
26 #include "config.h"
27 #include "autoar-compressor.h"
28
29 #include "autoar-misc.h"
30 #include "autoar-private.h"
31 #include "autoar-format-filter.h"
32 #include "autoar-enum-types.h"
33
34 #include <archive.h>
35 #include <archive_entry.h>
36 #include <gio/gio.h>
37 #include <glib.h>
38 #include <stdarg.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42
43 /**
44 * SECTION:autoar-compressor
45 * @Short_description: Automatically compress files
46 * @Title: AutoarCompressor
47 * @Include: gnome-autoar/autoar.h
48 *
49 * The #AutoarCompressor object is used to automatically compress files and
50 * directories into an archive. The new archive can contain a top-level directory.
51 * Applying multiple filters is currently not supported because most
52 * applications do not need this function. GIO is used for both read and write
53 * operations. A few POSIX functions are also used to get more information from
54 * files if GIO does not provide relevant functions.
55 *
56 * When #AutoarCompressor stop all work, it will emit one of the three signals:
57 * #AutoarCompressor::cancelled, #AutoarCompressor::error, and
58 * #AutoarCompressor::completed. After one of these signals is received, the
59 * #AutoarCompressor object should be destroyed because it cannot be used to
60 * start another archive operation. An #AutoarCompressor object can only be
61 * used once and create one archive.
62 **/
63
64 /**
65 * autoar_compressor_quark:
66 *
67 * Gets the #AutoarCompressor Error Quark.
68 *
69 * Returns: a #GQuark.
70 **/
71 G_DEFINE_QUARK (autoar-compressor, autoar_compressor)
72
73 #define BUFFER_SIZE (64 * 1024)
74 #define ARCHIVE_WRITE_RETRY_TIMES 5
75
76 #define INVALID_FORMAT 1
77 #define INVALID_FILTER 2
78
79 struct _AutoarCompressor
80 {
81 GObject parent_instance;
82
83 GList *source_files;
84 GFile *output_file;
85 AutoarFormat format;
86 AutoarFilter filter;
87
88 int output_is_dest : 1;
89
90 guint64 size; /* This field is currently unused */
91 guint64 completed_size;
92
93 guint files;
94 guint completed_files;
95
96 gint64 notify_last;
97 gint64 notify_interval;
98
99 GOutputStream *ostream;
100 void *buffer;
101 gssize buffer_size;
102 GError *error;
103
104 GCancellable *cancellable;
105
106 struct archive *a;
107 struct archive_entry *entry;
108 struct archive_entry_linkresolver *resolver;
109 GFile *dest;
110 GHashTable *pathname_to_g_file;
111 char *source_basename_noext;
112 char *extension;
113
114 int in_thread : 1;
115 gboolean create_top_level_directory;
116
117 gchar *passphrase;
118 };
119
120 G_DEFINE_TYPE (AutoarCompressor, autoar_compressor, G_TYPE_OBJECT)
121
122 enum
123 {
124 DECIDE_DEST,
125 PROGRESS,
126 CANCELLED,
127 COMPLETED,
128 AR_ERROR,
129 LAST_SIGNAL
130 };
131
132 enum
133 {
134 PROP_0,
135 PROP_SOURCE_FILES,
136 PROP_OUTPUT_FILE,
137 PROP_FORMAT,
138 PROP_FILTER,
139 PROP_CREATE_TOP_LEVEL_DIRECTORY,
140 PROP_SIZE, /* This property is currently unused */
141 PROP_COMPLETED_SIZE,
142 PROP_FILES,
143 PROP_COMPLETED_FILES,
144 PROP_OUTPUT_IS_DEST,
145 PROP_NOTIFY_INTERVAL
146 };
147
148 static guint autoar_compressor_signals[LAST_SIGNAL] = { 0 };
149
150 static void
autoar_compressor_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)151 autoar_compressor_get_property (GObject *object,
152 guint property_id,
153 GValue *value,
154 GParamSpec *pspec)
155 {
156 AutoarCompressor *self;
157
158 self = AUTOAR_COMPRESSOR (object);
159
160 switch (property_id) {
161 case PROP_SOURCE_FILES:
162 g_value_set_pointer (value, self->source_files);
163 break;
164 case PROP_OUTPUT_FILE:
165 g_value_set_object (value, self->output_file);
166 break;
167 case PROP_FORMAT:
168 g_value_set_enum (value, self->format);
169 break;
170 case PROP_FILTER:
171 g_value_set_enum (value, self->filter);
172 break;
173 case PROP_CREATE_TOP_LEVEL_DIRECTORY:
174 g_value_set_boolean (value, self->create_top_level_directory);
175 break;
176 case PROP_SIZE:
177 g_value_set_uint64 (value, self->size);
178 break;
179 case PROP_COMPLETED_SIZE:
180 g_value_set_uint64 (value, self->completed_size);
181 break;
182 case PROP_FILES:
183 g_value_set_uint (value, self->files);
184 break;
185 case PROP_COMPLETED_FILES:
186 g_value_set_uint (value, self->completed_files);
187 break;
188 case PROP_OUTPUT_IS_DEST:
189 g_value_set_boolean (value, self->output_is_dest);
190 break;
191 case PROP_NOTIFY_INTERVAL:
192 g_value_set_int64 (value, self->notify_interval);
193 break;
194 default:
195 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
196 break;
197 }
198 }
199
200 static void
autoar_compressor_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)201 autoar_compressor_set_property (GObject *object,
202 guint property_id,
203 const GValue *value,
204 GParamSpec *pspec)
205 {
206 AutoarCompressor *self;
207
208 self = AUTOAR_COMPRESSOR (object);
209
210 switch (property_id) {
211 case PROP_SOURCE_FILES:
212 if (self->source_files != NULL)
213 g_list_free_full (self->source_files, g_object_unref);
214 self->source_files = g_list_copy_deep (g_value_get_pointer (value),
215 (GCopyFunc)g_object_ref,
216 NULL);
217 break;
218 case PROP_OUTPUT_FILE:
219 autoar_common_g_object_unref (self->output_file);
220 self->output_file = g_object_ref (g_value_get_object (value));
221 break;
222 case PROP_FORMAT:
223 self->format = g_value_get_enum (value);
224 break;
225 case PROP_FILTER:
226 self->filter = g_value_get_enum (value);
227 break;
228 case PROP_CREATE_TOP_LEVEL_DIRECTORY:
229 self->create_top_level_directory = g_value_get_boolean (value);
230 break;
231 case PROP_OUTPUT_IS_DEST:
232 self->output_is_dest = g_value_get_boolean (value);
233 break;
234 case PROP_NOTIFY_INTERVAL:
235 self->notify_interval = g_value_get_int64 (value);
236 break;
237 default:
238 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
239 break;
240 }
241 }
242
243 /**
244 * autoar_compressor_get_source_files:
245 * @self: an #AutoarCompressor
246 *
247 * Gets the list of source files.
248 *
249 * Returns: (transfer none) (element-type GFile): a #GList with the source files
250 **/
251 GList*
autoar_compressor_get_source_files(AutoarCompressor * self)252 autoar_compressor_get_source_files (AutoarCompressor *self)
253 {
254 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), NULL);
255 return self->source_files;
256 }
257
258 /**
259 * autoar_compressor_get_output_file:
260 * @self: an #AutoarCompressor
261 *
262 * If #AutoarCompressor:output_is_dest is %FALSE, gets the directory which
263 * contains the new archive. Otherwise, gets the the new archive. See
264 * autoar_compressor_set_output_is_dest().
265 *
266 * Returns: (transfer none): a #GFile
267 **/
268 GFile*
autoar_compressor_get_output_file(AutoarCompressor * self)269 autoar_compressor_get_output_file (AutoarCompressor *self)
270 {
271 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), NULL);
272 return self->output_file;
273 }
274
275 /**
276 * autoar_compressor_get_format:
277 * @self: an #AutoarCompressor
278 *
279 * Gets the compression format
280 *
281 * Returns: the compression format
282 **/
283 AutoarFormat
autoar_compressor_get_format(AutoarCompressor * self)284 autoar_compressor_get_format (AutoarCompressor *self)
285 {
286 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), AUTOAR_FORMAT_0);
287 return self->format;
288 }
289
290 /**
291 * autoar_compressor_get_filter:
292 * @self: an #AutoarCompressor
293 *
294 * Gets the compression filter
295 *
296 * Returns: the compression filter
297 **/
298 AutoarFilter
autoar_compressor_get_filter(AutoarCompressor * self)299 autoar_compressor_get_filter (AutoarCompressor *self)
300 {
301 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), AUTOAR_FILTER_0);
302 return self->filter;
303 }
304
305 /**
306 * autoar_compressor_get_create_top_level_directory:
307 * @self: an #AutoarCompressor
308 *
309 * Gets whether a top level directory will be created in the new archive.
310 *
311 * Returns: whether a top level directory will be created
312 **/
313 gboolean
autoar_compressor_get_create_top_level_directory(AutoarCompressor * self)314 autoar_compressor_get_create_top_level_directory (AutoarCompressor *self)
315 {
316 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), FALSE);
317 return self->create_top_level_directory;
318 }
319
320 /**
321 * autoar_compressor_get_size:
322 * @self: an #AutoarCompressor
323 *
324 * Gets the size in bytes will be read when the operation is completed. This
325 * value is currently unset, so calling this function is useless.
326 *
327 * Returns: total file size in bytes
328 **/
329 guint64
autoar_compressor_get_size(AutoarCompressor * self)330 autoar_compressor_get_size (AutoarCompressor *self)
331 {
332 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), 0);
333 return self->size;
334 }
335
336 /**
337 * autoar_compressor_get_completed_size:
338 * @self: an #AutoarCompressor
339 *
340 * Gets the size in bytes has been read from the source files and directories.
341 *
342 * Returns: file size in bytes has been read
343 **/
344 guint64
autoar_compressor_get_completed_size(AutoarCompressor * self)345 autoar_compressor_get_completed_size (AutoarCompressor *self)
346 {
347 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), 0);
348 return self->completed_size;
349 }
350
351 /**
352 * autoar_compressor_get_files:
353 * @self: an #AutoarCompressor
354 *
355 * Gets the number of files will be read when the operation is completed. This
356 * value is currently unset, so calling this function is useless.
357 *
358 * Returns: total number of files
359 **/
360 guint
autoar_compressor_get_files(AutoarCompressor * self)361 autoar_compressor_get_files (AutoarCompressor *self)
362 {
363 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), 0);
364 return self->files;
365 }
366
367 /**
368 * autoar_compressor_get_completed_files:
369 * @self: an #AutoarCompressor
370 *
371 * Gets the number of files has been read
372 *
373 * Returns: number of files has been read
374 **/
375 guint
autoar_compressor_get_completed_files(AutoarCompressor * self)376 autoar_compressor_get_completed_files (AutoarCompressor *self)
377 {
378 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), 0);
379 return self->completed_files;
380 }
381
382 /**
383 * autoar_compressor_get_output_is_dest:
384 * @self: an #AutoarCompressor
385 *
386 * See autoar_compressor_set_output_is_dest().
387 *
388 * Returns: %TRUE if #AutoarCompressor:output is the location of the new
389 * archive.
390 **/
391 gboolean
autoar_compressor_get_output_is_dest(AutoarCompressor * self)392 autoar_compressor_get_output_is_dest (AutoarCompressor *self)
393 {
394 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), 0);
395 return self->output_is_dest;
396 }
397
398 /**
399 * autoar_compressor_get_notify_interval:
400 * @self: an #AutoarCompressor
401 *
402 * See autoar_compressor_set_notify_interval().
403 *
404 * Returns: the minimal interval in microseconds between the emission of the
405 * #AutoarCompressor::progress signal.
406 **/
407 gint64
autoar_compressor_get_notify_interval(AutoarCompressor * self)408 autoar_compressor_get_notify_interval (AutoarCompressor *self)
409 {
410 g_return_val_if_fail (AUTOAR_IS_COMPRESSOR (self), 0);
411 return self->notify_interval;
412 }
413
414 /**
415 * autoar_compressor_set_output_is_dest:
416 * @self: an #AutoarCompressor
417 * @output_is_dest: %TRUE if the location of the new archive has been already
418 * decided
419 *
420 * By default #AutoarCompressor:output-is-dest is set to %FALSE, which means
421 * the new archive will be created as a regular file under
422 * #AutoarCompressor:output directory. The name of the new archive will be
423 * automatically generated and you will be notified via
424 * #AutoarCompressor::decide-dest when the name is decided. If you have already
425 * decided the location of the new archive, and you do not want
426 * #AutoarCompressor to decide it for you, you can set
427 * #AutoarCompressor:output-is-dest to %TRUE. #AutoarCompressor will use
428 * #AutoarCompressor:output as the location of the new archive, and it will
429 * neither check whether the file exists nor create the necessary
430 * directories for you. This function should only be called before calling
431 * autoar_compressor_start() or autoar_compressor_start_async().
432 **/
433 void
autoar_compressor_set_output_is_dest(AutoarCompressor * self,gboolean output_is_dest)434 autoar_compressor_set_output_is_dest (AutoarCompressor *self,
435 gboolean output_is_dest)
436 {
437 g_return_if_fail (AUTOAR_IS_COMPRESSOR (self));
438 self->output_is_dest = output_is_dest;
439 }
440
441 /**
442 * autoar_compressor_set_notify_interval:
443 * @self: an #AutoarCompressor
444 * @notify_interval: the minimal interval in microseconds
445 *
446 * Sets the minimal interval between emission of #AutoarCompressor::progress
447 * signal. This prevent too frequent signal emission, which may cause
448 * performance impact. If you do not want this feature, you can set the
449 * interval to 0, so you will receive every progress update.
450 **/
451 void
autoar_compressor_set_notify_interval(AutoarCompressor * self,gint64 notify_interval)452 autoar_compressor_set_notify_interval (AutoarCompressor *self,
453 gint64 notify_interval)
454 {
455 g_return_if_fail (AUTOAR_IS_COMPRESSOR (self));
456 g_return_if_fail (notify_interval >= 0);
457 self->notify_interval = notify_interval;
458 }
459
460 /**
461 * autoar_compressor_set_passphrase:
462 * @self: an #AutoarCompressor
463 * @passphrase: the archive passphrase
464 *
465 * Sets the archive passphrase. It works only with %ARCHIVE_FORMAT_ZIP.
466 **/
467 void
autoar_compressor_set_passphrase(AutoarCompressor * self,const gchar * passphrase)468 autoar_compressor_set_passphrase (AutoarCompressor *self,
469 const gchar *passphrase)
470 {
471 g_return_if_fail (AUTOAR_IS_COMPRESSOR (self));
472 g_return_if_fail (self->format == AUTOAR_FORMAT_ZIP);
473
474 self->passphrase = g_strdup (passphrase);
475 }
476
477 static void
autoar_compressor_dispose(GObject * object)478 autoar_compressor_dispose (GObject *object)
479 {
480 AutoarCompressor *self;
481
482 self = AUTOAR_COMPRESSOR (object);
483
484 g_debug ("AutoarCompressor: dispose");
485
486 if (self->ostream != NULL) {
487 if (!g_output_stream_is_closed (self->ostream)) {
488 g_output_stream_close (self->ostream,
489 self->cancellable,
490 NULL);
491 }
492 g_object_unref (self->ostream);
493 self->ostream = NULL;
494 }
495
496 g_clear_object (&(self->dest));
497 g_clear_object (&(self->cancellable));
498 g_clear_object (&(self->output_file));
499
500 if (self->pathname_to_g_file != NULL) {
501 g_hash_table_unref (self->pathname_to_g_file);
502 self->pathname_to_g_file = NULL;
503 }
504
505 if (self->source_files != NULL) {
506 g_list_free_full (self->source_files, g_object_unref);
507 self->source_files = NULL;
508 }
509
510 G_OBJECT_CLASS (autoar_compressor_parent_class)->dispose (object);
511 }
512
513 static void
autoar_compressor_finalize(GObject * object)514 autoar_compressor_finalize (GObject *object)
515 {
516 AutoarCompressor *self;
517
518 self = AUTOAR_COMPRESSOR (object);
519
520 g_debug ("AutoarCompressor: finalize");
521
522 g_free (self->buffer);
523 self->buffer = NULL;
524
525 /* If self->error == NULL, no errors occurs. Therefore, we can safely
526 * free libarchive objects because it will not call the callbacks during the
527 * the process of freeing.
528 * If self->error != NULL, we must free libarchive objects beforce
529 * freeing self->error in order to prevent libarchive callbacks from
530 * accessing freed private objects and buffers.
531 */
532 if (self->a != NULL) {
533 archive_write_free (self->a);
534 self->a = NULL;
535 }
536
537 if (self->entry != NULL) {
538 archive_entry_free (self->entry);
539 self->entry = NULL;
540 }
541
542 if (self->resolver != NULL) {
543 archive_entry_linkresolver_free (self->resolver);
544 self->resolver = NULL;
545 }
546
547 if (self->error != NULL) {
548 g_error_free (self->error);
549 self->error = NULL;
550 }
551
552 g_free (self->source_basename_noext);
553 self->source_basename_noext = NULL;
554
555 g_free (self->extension);
556 self->extension = NULL;
557
558 g_clear_pointer (&self->passphrase, g_free);
559
560 G_OBJECT_CLASS (autoar_compressor_parent_class)->finalize (object);
561 }
562
563 static int
libarchive_write_open_cb(struct archive * ar_write,void * client_data)564 libarchive_write_open_cb (struct archive *ar_write,
565 void *client_data)
566 {
567 AutoarCompressor *self;
568
569 g_debug ("libarchive_write_open_cb: called");
570
571 self = AUTOAR_COMPRESSOR (client_data);
572 if (self->error != NULL) {
573 return ARCHIVE_FATAL;
574 }
575
576 self->ostream = (GOutputStream*)g_file_create (self->dest,
577 G_FILE_CREATE_NONE,
578 self->cancellable,
579 &(self->error));
580 if (self->error != NULL) {
581 g_debug ("libarchive_write_open_cb: ARCHIVE_FATAL");
582 return ARCHIVE_FATAL;
583 }
584
585 g_debug ("libarchive_write_open_cb: ARCHIVE_OK");
586 return ARCHIVE_OK;
587 }
588
589 static int
libarchive_write_close_cb(struct archive * ar_write,void * client_data)590 libarchive_write_close_cb (struct archive *ar_write,
591 void *client_data)
592 {
593 AutoarCompressor *self;
594
595 g_debug ("libarchive_write_close_cb: called");
596
597 self = AUTOAR_COMPRESSOR (client_data);
598 if (self->error != NULL) {
599 return ARCHIVE_FATAL;
600 }
601
602 if (self->ostream != NULL) {
603 g_output_stream_close (self->ostream,
604 self->cancellable, &(self->error));
605 g_object_unref (self->ostream);
606 self->ostream = NULL;
607 }
608
609 if (self->error != NULL) {
610 g_debug ("libarchive_write_close_cb: ARCHIVE_FATAL");
611 return ARCHIVE_FATAL;
612 }
613
614 g_debug ("libarchive_write_close_cb: ARCHIVE_OK");
615 return ARCHIVE_OK;
616 }
617
618 static ssize_t
libarchive_write_write_cb(struct archive * ar_write,void * client_data,const void * buffer,size_t length)619 libarchive_write_write_cb (struct archive *ar_write,
620 void *client_data,
621 const void *buffer,
622 size_t length)
623 {
624 AutoarCompressor *self;
625 gssize write_size;
626
627 g_debug ("libarchive_write_write_cb: called");
628
629 self = AUTOAR_COMPRESSOR (client_data);
630 if (self->error != NULL || self->ostream == NULL) {
631 return -1;
632 }
633
634 write_size = g_output_stream_write (self->ostream,
635 buffer,
636 length,
637 self->cancellable,
638 &(self->error));
639 if (self->error != NULL)
640 return -1;
641
642 g_debug ("libarchive_write_write_cb: %" G_GSSIZE_FORMAT, write_size);
643 return write_size;
644 }
645
646 static inline void
autoar_compressor_signal_decide_dest(AutoarCompressor * self)647 autoar_compressor_signal_decide_dest (AutoarCompressor *self)
648 {
649 autoar_common_g_signal_emit (self, self->in_thread,
650 autoar_compressor_signals[DECIDE_DEST], 0,
651 self->dest);
652 }
653
654 static inline void
autoar_compressor_signal_progress(AutoarCompressor * self)655 autoar_compressor_signal_progress (AutoarCompressor *self)
656 {
657 gint64 mtime;
658 mtime = g_get_monotonic_time ();
659 if (mtime - self->notify_last >= self->notify_interval) {
660 autoar_common_g_signal_emit (self, self->in_thread,
661 autoar_compressor_signals[PROGRESS], 0,
662 self->completed_size,
663 self->completed_files);
664 self->notify_last = mtime;
665 }
666 }
667
668 static inline void
autoar_compressor_signal_cancelled(AutoarCompressor * self)669 autoar_compressor_signal_cancelled (AutoarCompressor *self)
670 {
671 autoar_common_g_signal_emit (self, self->in_thread,
672 autoar_compressor_signals[CANCELLED], 0);
673
674 }
675
676 static inline void
autoar_compressor_signal_completed(AutoarCompressor * self)677 autoar_compressor_signal_completed (AutoarCompressor *self)
678 {
679 autoar_common_g_signal_emit (self, self->in_thread,
680 autoar_compressor_signals[COMPLETED], 0);
681
682 }
683
684 static inline void
autoar_compressor_signal_error(AutoarCompressor * self)685 autoar_compressor_signal_error (AutoarCompressor *self)
686 {
687 if (self->error != NULL) {
688 if (self->error->domain == G_IO_ERROR &&
689 self->error->code == G_IO_ERROR_CANCELLED) {
690 g_error_free (self->error);
691 self->error = NULL;
692 autoar_compressor_signal_cancelled (self);
693 } else {
694 autoar_common_g_signal_emit (self, self->in_thread,
695 autoar_compressor_signals[AR_ERROR], 0,
696 self->error);
697 }
698 }
699 }
700
701 static void
autoar_compressor_do_write_data(AutoarCompressor * self,struct archive_entry * entry,GFile * file)702 autoar_compressor_do_write_data (AutoarCompressor *self,
703 struct archive_entry *entry,
704 GFile *file)
705 {
706 int r;
707
708 g_debug ("autoar_compressor_do_write_data: called");
709
710 if (self->error != NULL)
711 return;
712
713 if (g_cancellable_is_cancelled (self->cancellable))
714 return;
715
716 while ((r = archive_write_header (self->a, entry)) == ARCHIVE_RETRY);
717 if (r == ARCHIVE_FATAL) {
718 if (self->error == NULL)
719 self->error =
720 autoar_common_g_error_new_a_entry (self->a, entry);
721 return;
722 }
723
724 g_debug ("autoar_compressor_do_write_data: write header OK");
725
726 /* Non-regular files have no content to write */
727 if (archive_entry_size (entry) > 0 && archive_entry_filetype (entry) == AE_IFREG) {
728 GInputStream *istream;
729 ssize_t read_actual, written_actual, written_acc;
730 int written_try;
731
732 g_debug ("autoar_compressor_do_write_data: entry size is %"G_GUINT64_FORMAT,
733 archive_entry_size (entry));
734
735 written_actual = 0;
736 written_try = 0;
737
738 istream = (GInputStream*)g_file_read (file,
739 self->cancellable,
740 &(self->error));
741 if (istream == NULL)
742 return;
743
744 do {
745 read_actual = g_input_stream_read (istream,
746 self->buffer,
747 self->buffer_size,
748 self->cancellable,
749 &(self->error));
750 self->completed_size += read_actual > 0 ? read_actual : 0;
751 autoar_compressor_signal_progress (self);
752 if (read_actual > 0) {
753 written_acc = 0;
754 written_try = 0;
755 do {
756 written_actual =
757 archive_write_data (self->a,
758 (const char*)(self->buffer) + written_acc,
759 read_actual);
760 written_acc += written_actual > 0 ? written_actual : 0;
761 written_try = written_actual ? 0 : written_try + 1;
762 /* archive_write_data may return zero, so we have to limit the
763 * retry times to prevent infinite loop */
764 } while (written_acc < read_actual && written_actual >= 0 && written_try < ARCHIVE_WRITE_RETRY_TIMES);
765 }
766 } while (read_actual > 0 && written_actual >= 0);
767
768 self->completed_files++;
769
770 g_input_stream_close (istream, self->cancellable, NULL);
771 g_object_unref (istream);
772
773 if (read_actual < 0)
774 return;
775
776 if (written_actual < 0 || written_try >= ARCHIVE_WRITE_RETRY_TIMES) {
777 if (self->error == NULL)
778 self->error =
779 autoar_common_g_error_new_a_entry (self->a, entry);
780 return;
781 }
782 g_debug ("autoar_compressor_do_write_data: write data OK");
783 } else {
784 g_debug ("autoar_compressor_do_write_data: no data, return now!");
785 self->completed_files++;
786 autoar_compressor_signal_progress (self);
787 }
788 }
789
790 static void
autoar_compressor_do_add_to_archive(AutoarCompressor * self,GFile * root,GFile * file)791 autoar_compressor_do_add_to_archive (AutoarCompressor *self,
792 GFile *root,
793 GFile *file)
794 {
795 GFileInfo *info;
796 GFileType filetype;
797
798 if (self->error != NULL)
799 return;
800
801 if (g_cancellable_is_cancelled (self->cancellable))
802 return;
803
804 archive_entry_clear (self->entry);
805 info = g_file_query_info (file, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
806 self->cancellable, &(self->error));
807 if (info == NULL)
808 return;
809
810 filetype = g_file_info_get_file_type (info);
811 switch (archive_format (self->a)) {
812 case ARCHIVE_FORMAT_AR:
813 case ARCHIVE_FORMAT_AR_GNU:
814 case ARCHIVE_FORMAT_AR_BSD:
815 if (filetype == G_FILE_TYPE_DIRECTORY ||
816 filetype == G_FILE_TYPE_SYMBOLIC_LINK ||
817 filetype == G_FILE_TYPE_SPECIAL) {
818 /* ar only support regular files, so we abort this operation to
819 * prevent producing a malformed archive. */
820 g_object_unref (info);
821 return;
822 }
823 break;
824
825 case ARCHIVE_FORMAT_ZIP:
826 if (filetype == G_FILE_TYPE_SPECIAL) {
827 /* Add special files to zip archives cause unknown fatal error
828 * in libarchive. */
829 g_object_unref (info);
830 return;
831 }
832 break;
833 }
834
835 {
836 char *root_basename;
837 char *pathname_relative;
838 char *pathname;
839
840 switch (archive_format (self->a)) {
841 /* ar format does not support directories */
842 case ARCHIVE_FORMAT_AR:
843 case ARCHIVE_FORMAT_AR_GNU:
844 case ARCHIVE_FORMAT_AR_BSD:
845 pathname = g_file_get_basename (file);
846 archive_entry_set_pathname (self->entry, pathname);
847 g_free (pathname);
848 break;
849
850 default:
851 root_basename = g_file_get_basename (root);
852 pathname_relative = g_file_get_relative_path (root, file);
853 pathname =
854 g_strconcat (self->create_top_level_directory ?
855 self->source_basename_noext : "",
856 self->create_top_level_directory ? "/" : "",
857 root_basename,
858 pathname_relative != NULL ? "/" : "",
859 pathname_relative != NULL ? pathname_relative : "",
860 NULL);
861 archive_entry_set_pathname (self->entry, pathname);
862 g_free (root_basename);
863 g_free (pathname_relative);
864 g_free (pathname);
865 }
866 }
867
868 g_debug ("autoar_compressor_do_add_to_archive: %s",
869 archive_entry_pathname (self->entry));
870
871 {
872 time_t atime, btime, ctime, mtime;
873 long atimeu, btimeu, ctimeu, mtimeu;
874
875 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS)) {
876 atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
877 atimeu = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC);
878 archive_entry_set_atime (self->entry, atime, atimeu * 1000);
879 }
880
881 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_CREATED)) {
882 btime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED);
883 btimeu = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CREATED_USEC);
884 archive_entry_set_birthtime (self->entry, btime, btimeu * 1000);
885 }
886
887 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_CHANGED)) {
888 ctime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED);
889 ctimeu = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC);
890 archive_entry_set_ctime (self->entry, ctime, ctimeu * 1000);
891 }
892
893 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) {
894 mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
895 mtimeu = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC);
896 archive_entry_set_mtime (self->entry, mtime, mtimeu * 1000);
897 }
898
899 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID))
900 archive_entry_set_uid (self->entry, g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID));
901 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID))
902 archive_entry_set_gid (self->entry, g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID));
903 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_OWNER_USER))
904 archive_entry_set_uname (self->entry, g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER));
905 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_OWNER_GROUP))
906 archive_entry_set_gname (self->entry, g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP));
907 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE))
908 archive_entry_set_mode (self->entry, g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE));
909 }
910
911 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
912 archive_entry_set_size (self->entry, g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE));
913
914 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_DEVICE))
915 archive_entry_set_dev (self->entry, g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE));
916 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_INODE))
917 archive_entry_set_ino64 (self->entry, g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE));
918 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_NLINK))
919 archive_entry_set_nlink (self->entry, g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK));
920 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_RDEV))
921 archive_entry_set_rdev (self->entry, g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV));
922
923 switch (filetype) {
924 case G_FILE_TYPE_DIRECTORY:
925 g_debug ("autoar_compressor_do_add_to_archive: file type set to DIR");
926 archive_entry_set_filetype (self->entry, AE_IFDIR);
927 break;
928
929 case G_FILE_TYPE_SYMBOLIC_LINK:
930 g_debug ("autoar_compressor_do_add_to_archive: file type set to SYMLINK");
931 archive_entry_set_filetype (self->entry, AE_IFLNK);
932 archive_entry_set_symlink (self->entry,
933 g_file_info_get_attribute_byte_string (info,
934 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET));
935 break;
936
937 case G_FILE_TYPE_SPECIAL:
938 #if (defined HAVE_STAT) && \
939 (defined S_ISBLK) && (defined S_ISSOCK) && \
940 (defined S_ISCHR) && (defined S_ISFIFO)
941 {
942 struct stat filestat;
943 char *local_pathname;
944
945 local_pathname = g_file_get_path (file);
946 if (local_pathname != NULL && stat (local_pathname, &filestat) >= 0) {
947 if (S_ISBLK (filestat.st_mode)) {
948 g_debug ("autoar_compressor_do_add_to_archive: file type set to BLOCK");
949 archive_entry_set_filetype (self->entry, AE_IFBLK);
950 } else if (S_ISSOCK (filestat.st_mode)) {
951 g_debug ("autoar_compressor_do_add_to_archive: file type set to SOCKET");
952 archive_entry_set_filetype (self->entry, AE_IFSOCK);
953 } else if (S_ISCHR (filestat.st_mode)) {
954 g_debug ("autoar_compressor_do_add_to_archive: file type set to CHAR");
955 archive_entry_set_filetype (self->entry, AE_IFCHR);
956 } else if (S_ISFIFO (filestat.st_mode)) {
957 g_debug ("autoar_compressor_do_add_to_archive: file type set to FIFO");
958 archive_entry_set_filetype (self->entry, AE_IFIFO);
959 } else {
960 g_debug ("autoar_compressor_do_add_to_archive: file type set to REGULAR");
961 archive_entry_set_filetype (self->entry, AE_IFREG);
962 }
963 g_free (local_pathname);
964 } else {
965 g_debug ("autoar_compressor_do_add_to_archive: file type set to REGULAR");
966 archive_entry_set_filetype (self->entry, AE_IFREG);
967 }
968 }
969 break;
970
971 #endif
972 case G_FILE_TYPE_UNKNOWN:
973 case G_FILE_TYPE_SHORTCUT:
974 case G_FILE_TYPE_MOUNTABLE:
975 case G_FILE_TYPE_REGULAR:
976 default:
977 g_debug ("autoar_compressor_do_add_to_archive: file type set to REGULAR");
978 archive_entry_set_filetype (self->entry, AE_IFREG);
979 break;
980 }
981
982 g_hash_table_insert (self->pathname_to_g_file,
983 g_strdup (archive_entry_pathname (self->entry)),
984 g_object_ref (file));
985
986 {
987 struct archive_entry *sparse = NULL;
988
989 /* Hardlinks are handled in different ways by the archive formats. The
990 * archive_entry_linkify function is a unified interface, which handling
991 * the complexity behind the scene. It assumes that archive_entry instances
992 * have valid nlinks, inode and device values. The inode and device value
993 * is used to match entries. The nlinks value is used to determined if all
994 * references have been found and if the internal references can be
995 * recycled. */
996 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_DEVICE) &&
997 g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_INODE) &&
998 g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_NLINK))
999 archive_entry_linkify (self->resolver, &self->entry, &sparse);
1000
1001 if (self->entry != NULL) {
1002 GFile *file_to_read;
1003 const char *pathname_in_entry;
1004 pathname_in_entry = archive_entry_pathname (self->entry);
1005 file_to_read = g_hash_table_lookup (self->pathname_to_g_file,
1006 pathname_in_entry);
1007 autoar_compressor_do_write_data (self, self->entry, file_to_read);
1008 /* Entries for non-regular files might have their size attribute
1009 * different to their actual size on the disk
1010 */
1011 if (archive_entry_filetype (self->entry) != AE_IFREG &&
1012 archive_entry_size (self->entry) != g_file_info_get_size (info)) {
1013 self->completed_size += g_file_info_get_size (info);
1014 autoar_compressor_signal_progress (self);
1015 }
1016
1017 g_hash_table_remove (self->pathname_to_g_file, pathname_in_entry);
1018 /* We have registered g_object_unref function to free the GFile object,
1019 * so we do not have to unref it here. */
1020 } else {
1021 /* The archive_entry_linkify function stole our entry, so new one has to
1022 * be allocated here to not crash on the next file. */
1023 self->entry = archive_entry_new ();
1024 }
1025
1026 if (sparse != NULL) {
1027 GFile *file_to_read;
1028 const char *pathname_in_entry;
1029 pathname_in_entry = archive_entry_pathname (self->entry);
1030 file_to_read = g_hash_table_lookup (self->pathname_to_g_file,
1031 pathname_in_entry);
1032 autoar_compressor_do_write_data (self, sparse, file_to_read);
1033 g_hash_table_remove (self->pathname_to_g_file, pathname_in_entry);
1034 }
1035 }
1036
1037 g_object_unref (info);
1038 };
1039
1040 static void
autoar_compressor_do_recursive_read(AutoarCompressor * self,GFile * root,GFile * file)1041 autoar_compressor_do_recursive_read (AutoarCompressor *self,
1042 GFile *root,
1043 GFile *file)
1044 {
1045 GFileEnumerator *enumerator;
1046 GFileInfo *info;
1047 GFile *thisfile;
1048 const char *thisname;
1049
1050 enumerator = g_file_enumerate_children (file,
1051 "standard::*",
1052 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1053 self->cancellable,
1054 &(self->error));
1055 if (enumerator == NULL)
1056 return;
1057
1058 while ((info = g_file_enumerator_next_file (enumerator,
1059 self->cancellable,
1060 &(self->error))) != NULL) {
1061 thisname = g_file_info_get_name (info);
1062 thisfile = g_file_get_child (file, thisname);
1063 autoar_compressor_do_add_to_archive (self, root, thisfile);
1064 if (self->error != NULL) {
1065 g_object_unref (thisfile);
1066 g_object_unref (info);
1067 break;
1068 }
1069
1070 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
1071 autoar_compressor_do_recursive_read (self, root, thisfile);
1072 g_object_unref (thisfile);
1073 g_object_unref (info);
1074
1075 if (self->error != NULL)
1076 break;
1077 if (g_cancellable_is_cancelled (self->cancellable))
1078 break;
1079 }
1080
1081 g_object_unref (enumerator);
1082 }
1083
1084 static void
autoar_compressor_class_init(AutoarCompressorClass * klass)1085 autoar_compressor_class_init (AutoarCompressorClass *klass)
1086 {
1087 GObjectClass *object_class;
1088 GType type;
1089
1090 object_class = G_OBJECT_CLASS (klass);
1091 type = G_TYPE_FROM_CLASS (klass);
1092
1093 object_class->get_property = autoar_compressor_get_property;
1094 object_class->set_property = autoar_compressor_set_property;
1095 object_class->dispose = autoar_compressor_dispose;
1096 object_class->finalize = autoar_compressor_finalize;
1097
1098 g_object_class_install_property (object_class, PROP_SOURCE_FILES,
1099 g_param_spec_pointer ("source-files",
1100 "Source files list",
1101 "The list of GFiles to be archived",
1102 G_PARAM_READWRITE |
1103 G_PARAM_CONSTRUCT_ONLY |
1104 G_PARAM_STATIC_STRINGS));
1105
1106 g_object_class_install_property (object_class, PROP_OUTPUT_FILE,
1107 g_param_spec_object ("output-file",
1108 "Output directory GFile",
1109 "Output directory (GFile) of created archive",
1110 G_TYPE_FILE,
1111 G_PARAM_READWRITE |
1112 G_PARAM_CONSTRUCT_ONLY |
1113 G_PARAM_STATIC_STRINGS));
1114
1115 g_object_class_install_property (object_class, PROP_FORMAT,
1116 g_param_spec_enum ("format",
1117 "Compression format",
1118 "The compression format that will be used",
1119 AUTOAR_TYPE_FORMAT,
1120 AUTOAR_FORMAT_ZIP,
1121 G_PARAM_READWRITE |
1122 G_PARAM_CONSTRUCT_ONLY |
1123 G_PARAM_STATIC_STRINGS));
1124
1125 g_object_class_install_property (object_class, PROP_FILTER,
1126 g_param_spec_enum ("filter",
1127 "Compression filter",
1128 "The compression filter that will be used",
1129 AUTOAR_TYPE_FILTER,
1130 AUTOAR_FILTER_NONE,
1131 G_PARAM_READWRITE |
1132 G_PARAM_CONSTRUCT_ONLY |
1133 G_PARAM_STATIC_STRINGS));
1134
1135 g_object_class_install_property (object_class, PROP_CREATE_TOP_LEVEL_DIRECTORY,
1136 g_param_spec_boolean ("create-top-level-directory",
1137 "Create top level directory",
1138 "Whether to create a top level directory",
1139 FALSE,
1140 G_PARAM_READWRITE |
1141 G_PARAM_CONSTRUCT |
1142 G_PARAM_STATIC_STRINGS));
1143
1144
1145 g_object_class_install_property (object_class, PROP_SIZE, /* This propery is unused! */
1146 g_param_spec_uint64 ("size",
1147 "Size",
1148 "Total bytes will be read from disk",
1149 0, G_MAXUINT64, 0,
1150 G_PARAM_READABLE |
1151 G_PARAM_STATIC_STRINGS));
1152
1153 g_object_class_install_property (object_class, PROP_COMPLETED_SIZE,
1154 g_param_spec_uint64 ("completed-size",
1155 "Read file size",
1156 "Bytes has read from disk",
1157 0, G_MAXUINT64, 0,
1158 G_PARAM_READABLE |
1159 G_PARAM_STATIC_STRINGS));
1160
1161 g_object_class_install_property (object_class, PROP_FILES,
1162 g_param_spec_uint ("files",
1163 "Files",
1164 "Number of files will be compressed",
1165 0, G_MAXUINT32, 0,
1166 G_PARAM_READABLE |
1167 G_PARAM_STATIC_STRINGS));
1168
1169 g_object_class_install_property (object_class, PROP_COMPLETED_FILES,
1170 g_param_spec_uint ("completed-files",
1171 "Read files",
1172 "Number of files has been read",
1173 0, G_MAXUINT32, 0,
1174 G_PARAM_READABLE |
1175 G_PARAM_STATIC_STRINGS));
1176
1177 g_object_class_install_property (object_class, PROP_OUTPUT_IS_DEST,
1178 g_param_spec_boolean ("output-is-dest",
1179 "Output is destination",
1180 "Whether output file is used as destination",
1181 FALSE,
1182 G_PARAM_READWRITE |
1183 G_PARAM_CONSTRUCT |
1184 G_PARAM_STATIC_STRINGS));
1185
1186 g_object_class_install_property (object_class, PROP_NOTIFY_INTERVAL,
1187 g_param_spec_int64 ("notify-interval",
1188 "Notify interval",
1189 "Minimal time interval between progress signal",
1190 0, G_MAXINT64, 100000,
1191 G_PARAM_READWRITE |
1192 G_PARAM_CONSTRUCT |
1193 G_PARAM_STATIC_STRINGS));
1194
1195 /**
1196 * AutoarCompressor::decide-dest:
1197 * @self: the #AutoarCompressor
1198 * @destination: the location of the new archive
1199 *
1200 * This signal is emitted when the location of the new archive is determined.
1201 **/
1202 autoar_compressor_signals[DECIDE_DEST] =
1203 g_signal_new ("decide-dest",
1204 type,
1205 G_SIGNAL_RUN_LAST,
1206 0, NULL, NULL,
1207 g_cclosure_marshal_generic,
1208 G_TYPE_NONE,
1209 1,
1210 G_TYPE_FILE);
1211
1212 /**
1213 * AutoarCompressor::progress:
1214 * @self: the #AutoarCompressor
1215 * @completed_size: bytes has been read from source files and directories
1216 * @completed_files: number of files and directories has been read
1217 *
1218 * This signal is used to report progress of creating archives. The value of
1219 * @completed_size and @completed_files are the same as the
1220 * #AutoarCompressor:completed_size and #AutoarCompressor:completed_files properties,
1221 * respectively.
1222 **/
1223 autoar_compressor_signals[PROGRESS] =
1224 g_signal_new ("progress",
1225 type,
1226 G_SIGNAL_RUN_LAST,
1227 0, NULL, NULL,
1228 g_cclosure_marshal_generic,
1229 G_TYPE_NONE,
1230 2,
1231 G_TYPE_UINT64,
1232 G_TYPE_UINT);
1233
1234 /**
1235 * AutoarCompressor::cancelled:
1236 * @self: the #AutoarCompressor
1237 *
1238 * This signal is emitted after archive creating job is cancelled by the
1239 * #GCancellable.
1240 **/
1241 autoar_compressor_signals[CANCELLED] =
1242 g_signal_new ("cancelled",
1243 type,
1244 G_SIGNAL_RUN_LAST,
1245 0, NULL, NULL,
1246 g_cclosure_marshal_VOID__VOID,
1247 G_TYPE_NONE,
1248 0);
1249
1250 /**
1251 * AutoarCompressor::completed:
1252 * @self: the #AutoarCompressor
1253 *
1254 * This signal is emitted after the archive creating job is successfully
1255 * completed.
1256 **/
1257 autoar_compressor_signals[COMPLETED] =
1258 g_signal_new ("completed",
1259 type,
1260 G_SIGNAL_RUN_LAST,
1261 0, NULL, NULL,
1262 g_cclosure_marshal_VOID__VOID,
1263 G_TYPE_NONE,
1264 0);
1265
1266 /**
1267 * AutoarCompressor::error:
1268 * @self: the #AutoarCompressor
1269 * @error: the #GError
1270 *
1271 * This signal is emitted when error occurs and all jobs should be terminated.
1272 * Possible error domains are %AUTOAR_COMPRESSOR_ERROR, %G_IO_ERROR, and
1273 * %AUTOAR_LIBARCHIVE_ERROR, which represent error occurs in #AutoarCompressor,
1274 * GIO, and libarchive, respectively. The #GError is owned by #AutoarCompressor
1275 * and should not be freed.
1276 **/
1277 autoar_compressor_signals[AR_ERROR] =
1278 g_signal_new ("error",
1279 type,
1280 G_SIGNAL_RUN_LAST,
1281 0, NULL, NULL,
1282 g_cclosure_marshal_generic,
1283 G_TYPE_NONE,
1284 1,
1285 G_TYPE_ERROR);
1286 }
1287
1288 static void
autoar_compressor_init(AutoarCompressor * self)1289 autoar_compressor_init (AutoarCompressor *self)
1290 {
1291 self->size = 0;
1292 self->completed_size = 0;
1293 self->files = 0;
1294 self->completed_files = 0;
1295
1296 self->notify_last = 0;
1297
1298 self->ostream = NULL;
1299 self->buffer_size = BUFFER_SIZE;
1300 self->buffer = g_new (char, self->buffer_size);
1301 self->error = NULL;
1302
1303 self->cancellable = NULL;
1304
1305 self->a = archive_write_new ();
1306 self->entry = archive_entry_new ();
1307 self->resolver = archive_entry_linkresolver_new ();
1308 self->pathname_to_g_file = g_hash_table_new_full (g_str_hash,
1309 g_str_equal,
1310 g_free,
1311 g_object_unref);
1312 self->source_basename_noext = NULL;
1313 self->extension = NULL;
1314
1315 self->in_thread = FALSE;
1316 self->passphrase = NULL;
1317 }
1318
1319 /**
1320 * autoar_compressor_new:
1321 * @source_files: (element-type GFile): a #GList of source #GFiles to be archived
1322 * @output_file: output directory of the new archive, or the file name of the
1323 * new archive if you set #AutoarCompressor:output-is-dest on the returned object
1324 * @format: the compression format
1325 * @filter: the compression filter
1326 *
1327 * Create a new #AutoarCompressor object.
1328 *
1329 * Returns: (transfer full): a new #AutoarCompressor object
1330 **/
1331 AutoarCompressor*
autoar_compressor_new(GList * source_files,GFile * output_file,AutoarFormat format,AutoarFilter filter,gboolean create_top_level_directory)1332 autoar_compressor_new (GList *source_files,
1333 GFile *output_file,
1334 AutoarFormat format,
1335 AutoarFilter filter,
1336 gboolean create_top_level_directory)
1337 {
1338 AutoarCompressor *self;
1339
1340 self =
1341 g_object_new (AUTOAR_TYPE_COMPRESSOR,
1342 "source-files", source_files,
1343 "output-file", output_file,
1344 "format", format,
1345 "filter", filter,
1346 "create-top-level-directory", create_top_level_directory,
1347 NULL);
1348
1349 return self;
1350 }
1351
1352 static void
autoar_compressor_step_initialize_object(AutoarCompressor * self)1353 autoar_compressor_step_initialize_object (AutoarCompressor *self)
1354 {
1355 /* Step 0: Setup the libarchive object and the file name extension */
1356
1357 AutoarFormatFunc format_func;
1358 AutoarFilterFunc filter_func;
1359
1360 int r;
1361
1362 if (!autoar_format_is_valid (self->format)) {
1363 self->error = g_error_new (AUTOAR_COMPRESSOR_ERROR, INVALID_FORMAT,
1364 "Format %d is invalid", self->format);
1365 return;
1366 }
1367
1368 if (!autoar_filter_is_valid (self->filter)) {
1369 self->error = g_error_new (AUTOAR_COMPRESSOR_ERROR, INVALID_FILTER,
1370 "Filter %d is invalid", self->filter);
1371 return;
1372 }
1373
1374 self->extension = autoar_format_filter_get_extension (self->format,
1375 self->filter);
1376
1377 r = archive_write_set_bytes_in_last_block (self->a, 1);
1378 if (r != ARCHIVE_OK) {
1379 self->error = autoar_common_g_error_new_a (self->a, NULL);
1380 return;
1381 }
1382
1383 format_func = autoar_format_get_libarchive_write (self->format);
1384 r = (*format_func)(self->a);
1385 if (r != ARCHIVE_OK) {
1386 self->error = autoar_common_g_error_new_a (self->a, NULL);
1387 return;
1388 }
1389
1390 filter_func = autoar_filter_get_libarchive_write (self->filter);
1391 r = (*filter_func)(self->a);
1392 if (r != ARCHIVE_OK) {
1393 self->error = autoar_common_g_error_new_a (self->a, NULL);
1394 return;
1395 }
1396
1397 if (self->passphrase != NULL && self->format == AUTOAR_FORMAT_ZIP) {
1398 r = archive_write_set_options (self->a, "zip:encryption=aes256");
1399 if (r != ARCHIVE_OK) {
1400 self->error = autoar_common_g_error_new_a (self->a, NULL);
1401 return;
1402 }
1403
1404 r = archive_write_set_passphrase (self->a, self->passphrase);
1405 if (r != ARCHIVE_OK) {
1406 self->error = autoar_common_g_error_new_a (self->a, NULL);
1407 return;
1408 }
1409 }
1410 }
1411
1412 static void
autoar_compressor_step_decide_dest(AutoarCompressor * self)1413 autoar_compressor_step_decide_dest (AutoarCompressor *self)
1414 {
1415 /* Step 1: Set the destination file name
1416 * Use the first source file name */
1417
1418 g_debug ("autoar_compressor_step_decide_dest: called");
1419
1420 {
1421 GFile *file_source; /* Do not unref */
1422 GFileInfo *source_info;
1423 char *source_basename;
1424
1425 file_source = self->source_files->data;
1426 source_info = g_file_query_info (file_source,
1427 G_FILE_ATTRIBUTE_STANDARD_TYPE,
1428 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1429 self->cancellable,
1430 &(self->error));
1431 if (source_info == NULL)
1432 return;
1433
1434 source_basename = g_file_get_basename (file_source);
1435 if (g_file_info_get_file_type (source_info) == G_FILE_TYPE_REGULAR)
1436 self->source_basename_noext =
1437 autoar_common_get_basename_remove_extension (source_basename);
1438 else
1439 self->source_basename_noext = g_strdup (source_basename);
1440
1441 g_object_unref (source_info);
1442 g_free (source_basename);
1443 }
1444
1445 {
1446 char *dest_basename;
1447 int i;
1448
1449 dest_basename = g_strconcat (self->source_basename_noext,
1450 self->extension, NULL);
1451 self->dest = g_file_get_child (self->output_file, dest_basename);
1452
1453 for (i = 1;
1454 g_file_query_exists (self->dest, self->cancellable);
1455 i++) {
1456 g_free (dest_basename);
1457 g_object_unref (self->dest);
1458
1459 if (g_cancellable_is_cancelled (self->cancellable))
1460 return;
1461
1462 dest_basename = g_strdup_printf ("%s(%d)%s",
1463 self->source_basename_noext,
1464 i, self->extension);
1465 self->dest = g_file_get_child (self->output_file,
1466 dest_basename);
1467 }
1468
1469 g_free (dest_basename);
1470 }
1471
1472 if (!g_file_query_exists (self->output_file, self->cancellable)) {
1473 g_file_make_directory_with_parents (self->output_file,
1474 self->cancellable,
1475 &(self->error));
1476 if (self->error != NULL)
1477 return;
1478 }
1479
1480 autoar_compressor_signal_decide_dest (self);
1481 }
1482
1483 static void
autoar_compressor_step_decide_dest_already(AutoarCompressor * self)1484 autoar_compressor_step_decide_dest_already (AutoarCompressor *self)
1485 {
1486 /* Alternative step 1: Output is destination */
1487
1488 char *output_basename;
1489 self->dest = g_object_ref (self->output_file);
1490 output_basename = g_file_get_basename (self->output_file);
1491 self->source_basename_noext =
1492 autoar_common_get_basename_remove_extension (output_basename);
1493 g_free (output_basename);
1494
1495 autoar_compressor_signal_decide_dest (self);
1496 }
1497
1498 static void
autoar_compressor_step_create(AutoarCompressor * self)1499 autoar_compressor_step_create (AutoarCompressor *self)
1500 {
1501 /* Step 2: Create and open the new archive file */
1502 GList *l;
1503 int r;
1504
1505 g_debug ("autoar_compressor_step_create: called");
1506
1507 r = archive_write_open (self->a, self,
1508 libarchive_write_open_cb,
1509 libarchive_write_write_cb,
1510 libarchive_write_close_cb);
1511 if (r != ARCHIVE_OK) {
1512 if (self->error == NULL)
1513 self->error = autoar_common_g_error_new_a (self->a, NULL);
1514 return;
1515 }
1516
1517 archive_entry_linkresolver_set_strategy (self->resolver,
1518 archive_format (self->a));
1519
1520 for (l = self->source_files; l != NULL; l = l->next) {
1521 GFile *file; /* Do not unref */
1522 GFileType filetype;
1523 GFileInfo *fileinfo;
1524 g_autofree gchar *pathname;
1525
1526 file = l->data;
1527
1528 pathname = g_file_get_path (file);
1529 g_debug ("autoar_compressor_step_create: %s", pathname);
1530
1531 fileinfo = g_file_query_info (file,
1532 G_FILE_ATTRIBUTE_STANDARD_TYPE,
1533 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1534 self->cancellable,
1535 &(self->error));
1536 if (self->error != NULL)
1537 return;
1538
1539 filetype = g_file_info_get_file_type (fileinfo);
1540 g_object_unref (fileinfo);
1541
1542 autoar_compressor_do_add_to_archive (self, file, file);
1543
1544 if (filetype == G_FILE_TYPE_DIRECTORY)
1545 autoar_compressor_do_recursive_read (self, file, file);
1546
1547 if (self->error != NULL)
1548 return;
1549
1550 if (g_cancellable_is_cancelled (self->cancellable))
1551 return;
1552 }
1553
1554 /* Flush deferred entries, if any, by calling linkify with entry unset. */
1555 {
1556 struct archive_entry *entry, *sparse;
1557 GFile *file_to_read;
1558 const char *pathname_in_entry;
1559
1560 while (TRUE) {
1561 /* The archive_entry is freed by the archive_entry_linkify function. */
1562 entry = NULL;
1563 archive_entry_linkify (self->resolver, &entry, &sparse);
1564 if (entry == NULL)
1565 break;
1566
1567 pathname_in_entry = archive_entry_pathname (entry);
1568 file_to_read = g_hash_table_lookup (self->pathname_to_g_file,
1569 pathname_in_entry);
1570 autoar_compressor_do_write_data (self, entry, file_to_read);
1571 /* I think we do not have to remove the entry in the hash table now
1572 * because we are going to free the entire hash table. */
1573 }
1574 }
1575 }
1576
1577 static void
autoar_compressor_step_cleanup(AutoarCompressor * self)1578 autoar_compressor_step_cleanup (AutoarCompressor *self)
1579 {
1580 /* Step 3: Close the libarchive object and force progress to be updated.
1581 * We do not have to do other cleanup because they are handled in dispose
1582 * and finalize functions. */
1583 self->notify_last = 0;
1584 autoar_compressor_signal_progress (self);
1585 if (archive_write_close (self->a) != ARCHIVE_OK) {
1586 g_autofree gchar *output_name;
1587
1588 output_name = autoar_common_g_file_get_name (self->output_file);
1589
1590 if (self->error == NULL)
1591 self->error =
1592 autoar_common_g_error_new_a (self->a, output_name);
1593 return;
1594 }
1595 }
1596
1597 static void
autoar_compressor_run(AutoarCompressor * self)1598 autoar_compressor_run (AutoarCompressor *self)
1599 {
1600 /* Numbers of steps.
1601 * The array size must be modified if more steps are added. */
1602 void (*steps[5])(AutoarCompressor*);
1603
1604 int i;
1605
1606 g_return_if_fail (AUTOAR_IS_COMPRESSOR (self));
1607
1608 g_return_if_fail (self->source_files != NULL);
1609 g_return_if_fail (self->output_file != NULL);
1610
1611 /* A GFile* list without a GFile* is not allowed */
1612 g_return_if_fail (self->source_files->data != NULL);
1613
1614 if (g_cancellable_is_cancelled (self->cancellable)) {
1615 autoar_compressor_signal_cancelled (self);
1616 return;
1617 }
1618
1619 i = 0;
1620 steps[i++] = autoar_compressor_step_initialize_object;
1621 steps[i++] = self->output_is_dest ?
1622 autoar_compressor_step_decide_dest_already :
1623 autoar_compressor_step_decide_dest;
1624 steps[i++] = autoar_compressor_step_create;
1625 steps[i++] = autoar_compressor_step_cleanup;
1626 steps[i++] = NULL;
1627
1628 for (i = 0; steps[i] != NULL; i++) {
1629 g_debug ("autoar_compressor_run: Step %d Begin", i);
1630 (*steps[i])(self);
1631 g_debug ("autoar_compressor_run: Step %d End", i);
1632 if (self->error != NULL) {
1633 autoar_compressor_signal_error (self);
1634 return;
1635 }
1636 if (g_cancellable_is_cancelled (self->cancellable)) {
1637 autoar_compressor_signal_cancelled (self);
1638 return;
1639 }
1640 }
1641
1642 autoar_compressor_signal_completed (self);
1643 }
1644
1645 /**
1646 * autoar_compressor_start:
1647 * @self: an #AutoarCompressor object
1648 * @cancellable: optional #GCancellable object, or %NULL to ignore
1649 *
1650 * Runs the archive creating work. All callbacks will be called in the same
1651 * thread as the caller of this functions.
1652 **/
1653 void
autoar_compressor_start(AutoarCompressor * self,GCancellable * cancellable)1654 autoar_compressor_start (AutoarCompressor *self,
1655 GCancellable *cancellable)
1656 {
1657 if (cancellable != NULL)
1658 g_object_ref (cancellable);
1659 self->cancellable = cancellable;
1660 self->in_thread = FALSE;
1661 autoar_compressor_run (self);
1662 }
1663
1664 static void
autoar_compressor_start_async_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1665 autoar_compressor_start_async_thread (GTask *task,
1666 gpointer source_object,
1667 gpointer task_data,
1668 GCancellable *cancellable)
1669 {
1670 AutoarCompressor *self = source_object;
1671 autoar_compressor_run (self);
1672 g_task_return_pointer (task, NULL, g_free);
1673 g_object_unref (self);
1674 g_object_unref (task);
1675 }
1676
1677 /**
1678 * autoar_compressor_start_async:
1679 * @self: an #AutoarCompressor object
1680 * @cancellable: optional #GCancellable object, or %NULL to ignore
1681 *
1682 * Asynchronously runs the archive creating work. You should connect to
1683 * #AutoarCompressor::cancelled, #AutoarCompressor::error, and
1684 * #AutoarCompressor::completed signal to get notification when the work is
1685 * terminated. All callbacks will be called in the main thread, so you can
1686 * safely manipulate GTK+ widgets in the callbacks.
1687 **/
1688 void
autoar_compressor_start_async(AutoarCompressor * self,GCancellable * cancellable)1689 autoar_compressor_start_async (AutoarCompressor *self,
1690 GCancellable *cancellable)
1691 {
1692 GTask *task;
1693
1694 g_object_ref (self);
1695 if (cancellable != NULL)
1696 g_object_ref (cancellable);
1697 self->cancellable = cancellable;
1698 self->in_thread = TRUE;
1699
1700 task = g_task_new (self, NULL, NULL, NULL);
1701 g_task_set_task_data (task, NULL, NULL);
1702 g_task_run_in_thread (task, autoar_compressor_start_async_thread);
1703 }
1704