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