File Coverage

blib/lib/Mozilla/Backup/Plugin/Zip.pm
Criterion Covered Total %
statement 82 117 70.0
branch 13 36 36.1
condition 6 17 35.2
subroutine 16 18 88.8
pod 10 10 100.0
total 127 198 64.1


line stmt bran cond sub pod time code
1             =head1 NAME
2            
3             Mozilla::Backup::Plugin::Zip - A zip archive plugin for Mozilla::Backup
4            
5             =begin readme
6            
7             =head1 REQUIREMENTS
8            
9             The following non-core modules are required:
10            
11             Archive::Zip
12             Compress::Zlib
13             Log::Dispatch;
14             Mozilla::Backup
15             Params::Smart
16             Return::Value
17            
18             =end readme
19            
20             =head1 SYNOPSIS
21            
22             use Mozilla::Backup;
23            
24             my $moz = Mozilla::Backup->new(
25             plugin => 'Mozilla::Backup::Plugin::Zip'
26             );
27            
28             =head1 DESCRIPTION
29            
30             This is a plugin for Mozilla::Backup which allows backups to be saved
31             as zip files.
32            
33             Methods will return a true value on sucess, or false on failure. (The
34             "false" value is overloaded to return a string value with the error
35             code.)
36            
37             Methods are outlined below:
38            
39             =over
40            
41             =cut
42            
43             package Mozilla::Backup::Plugin::Zip;
44            
45 5     5   11263 use strict;
  5         10  
  5         241  
46            
47 5     5   6031 use Archive::Zip qw( :ERROR_CODES );
  5         317601  
  5         827  
48 5     5   55 use Carp;
  5         10  
  5         351  
49 5     5   36 use File::Spec;
  5         13  
  5         142  
50 5     5   30 use Log::Dispatch;
  5         14  
  5         151  
51 5     5   29 use Params::Smart 0.04;
  5         228  
  5         264  
52 5     5   29 use Return::Value;
  5         12  
  5         9515  
53            
54             # require Mozilla::Backup;
55            
56             # $Revision: 1.27 $
57            
58             our $VERSION = '0.03';
59            
60             =item new
61            
62             $plugin = Mozilla::Backup::Plugin::Zip->new( %options );
63            
64             The following C<%options> are supported:
65            
66             =over
67            
68             =item log
69            
70             The L objetc used by L. This is required.
71            
72             =item debug
73            
74             The debug flag from L. This is not used at the moment.
75            
76             =item compression
77            
78             The desired compression level to use when backing up files, between C<0>
79             and C<9>. C<0> means to store (not compress) files, C<1> is for the
80             fastest method with the lowest compression, and C<9> is for the slowest
81             method with the fastest compression. (The default is C<6>.)
82            
83             See the L documentation for more information on levels.
84            
85             =back
86            
87             =cut
88            
89             my @ALLOWED_OPTIONS = (
90             {
91             name => "log",
92             default => Log::Dispatch->new(),
93             callback => sub {
94             my ($self, $name, $log) = @_;
95             croak "invalid log sink"
96             unless ((ref $log) && $log->isa("Log::Dispatch"));
97             return $log;
98             },
99             name_only => 1,
100             required => 1,
101             },
102             {
103             name => "compression",
104             default => 6,
105             name_only => 1,
106             callback => sub {
107             my ($self, $name, $value) = @_;
108             # TODO - check if an integer?
109             croak "expected value between 0 and 9"
110             unless (($value >= 0) && ($value <= 9));
111             return $value;
112             },
113             },
114             {
115             name => "debug",
116             default => 0,
117             name_only => 1,
118             },
119             );
120            
121            
122             sub new {
123 4   50 4 1 13 my $class = shift || __PACKAGE__;
124 4         14 my %args = Params(@ALLOWED_OPTIONS)->args(@_);
125            
126 4         155 my $self = {
127             log => $args{log},
128             debug => $args{debug},
129             compression => $args{compression},
130             status => "closed",
131             };
132            
133 4         27 return bless $self, $class;
134             }
135            
136            
137             =item allowed_options
138            
139             @options = Mozilla::Backup::Plugin::Zip->allowed_options();
140            
141             if (Mozilla::Backup::Plugin::Zip->allowed_options('debug')) {
142             ...
143             }
144            
145             If no arguments are given, it returns a list of configuration parameters
146             that can be passed to the constructor. If arguments are given, it returns
147             true if all of the arguments are allowable options for the constructor.
148            
149             =cut
150            
151             sub allowed_options {
152 8   50 8 1 24 my $class = shift || __PACKAGE__;
153 8         29 my %args = Params(qw( ?*options ))->args(@_);
154            
155 8         710 my %allowed = map { $_->{name} => 1, } @ALLOWED_OPTIONS;
  24         59  
156            
157 8 50       30 my @opts = @{$args{options}}, if ($args{options});
  8         18  
158 8 50       20 if (@opts) {
159 8         11 my $allowed = 1;
160 8   66     50 while ($allowed && (my $opt = shift @opts)) {
161 8   33     57 $allowed = $allowed && $allowed{$opt};
162             }
163 8         59 return $allowed;
164             }
165             else {
166 0         0 return (keys %allowed);
167             }
168             }
169            
170             =item munge_location
171            
172             $filename = $plugin->munge_location( $filename );
173            
174             Munges the archive name by adding the "zip" extension to it, if it
175             does not already have it. If called with no arguments, just returns
176             ".zip".
177            
178             =cut
179            
180             sub munge_location {
181 1     1 1 4 my $self = shift;
182 1         4 my %args = Params(qw( file ))->args(@_);
183 1   50     123 my $file = $args{file} || "";
184 1 50       7 $file .= ".zip", unless ($file =~ /\.zip$/i);
185 1         14 return $file;
186             }
187            
188             =item open_for_backup
189            
190             if ($plugin->open_for_backup( $filename, %options )) {
191             ...
192             }
193            
194             Creates a new archive for backing the profile. C<$filename> is the
195             name of the archive file to be used. C<%options> are optional
196             configuration parameters.
197            
198             =cut
199            
200             sub open_for_backup {
201 1     1 1 2 my $self = shift;
202 1         5 my %args = Params(qw( path ?*options ))->args(@_);
203 1         185 my $path = $args{path};
204            
205 1 50       12 unless ($self->{status} eq "closed") {
206 0         0 return failure $self->_log(
207             "cannot create archive: status is \"$self->{status}\"" );
208             }
209            
210 1         3 $self->{path} = $path;
211 1         3 $self->{opts} = $args{options};
212            
213 1         6 $self->_log( level => "debug", message => "creating archive $path\n" );
214            
215 1 50       9 if ($self->{zip} = Archive::Zip->new()) {
216 1         72 $self->{status} = "open for backup";
217 1         6 return success;
218             }
219             else {
220 0         0 return failure $self->_log( "unable to create archive" );
221             }
222             }
223            
224             =item open_for_restore
225            
226             if ($plugin->open_for_restore( $filename, %options )) {
227             ...
228             }
229            
230             Opens an existing archive for restoring the profile.
231            
232             =cut
233            
234             sub open_for_restore {
235 1     1 1 3 my $self = shift;
236 1         6 my %args = Params(qw( path ?*options ))->args(@_);
237 1         208 my $path = $args{path};
238            
239 1 50       7 unless ($self->{status} eq "closed") {
240 0         0 return failure $self->_log(
241             "cannot open archive: status is \"$self->{status}\"" );
242             }
243            
244 1         3 $self->{path} = $path;
245 1         4 $self->{opts} = $args{options};
246            
247 1         8 $self->_log( level => "debug", message => "opening archive $path\n" );
248            
249 1 50       11 if ($self->{zip} = Archive::Zip->new( $path )) {
250 1         782 $self->{status} = "open for restore";
251 1         97 return success;
252             }
253             else {
254 0         0 return failure $self->_log( "unable to open archive" );
255             }
256             }
257            
258             =item get_contents
259            
260             @files = $plugin->get_contents;
261            
262             Returns a list of files in the archive. Assumes it has been opened for
263             restoring (may or may not work for archives opened for backup;
264             applications are expected to track files backed up separately).
265            
266             =cut
267            
268             sub get_contents {
269 1     1 1 2 my $self = shift;
270            
271 1 50       4 unless ($self->{status} ne "closed") {
272 0         0 return failure $self->_log(
273             "cannot get contents: status is \"$self->{status}\"" );
274             }
275            
276 1         6 return $self->{zip}->memberNames();
277             }
278            
279             =item backup_file
280            
281             $plugin->backup_file( $local_file, $internal_name );
282            
283             Backs up the file in the archive, using C<$internal_name> as the
284             name in the archive. Assumes it has been opened for backup.
285            
286             =cut
287            
288             sub backup_file {
289 0     0 1 0 my $self = shift;
290 0         0 my %args = Params(qw( file ?internal ))->args(@_);
291            
292 0 0       0 unless ($self->{status} eq "open for backup") {
293 0         0 return failure $self->_log(
294             "cannot backup file: status is \"$self->{status}\"" );
295             }
296            
297 0         0 my $file = $args{file}; # actual file
298 0   0     0 my $name = $args{internal} || $file; # name in archive
299            
300 0         0 $self->_log( level => "info", message => "backing up $name\n" );
301 0         0 my $member = $self->{zip}->addFileOrDirectory($file, $name);
302 0         0 $member->desiredCompressionLevel( $self->{compression} );
303 0         0 return $member;
304             }
305            
306             =item restore_file
307            
308             $plugin->restore_file( $internal_name, $local_file );
309            
310             Restores the file from the archive. Assumes it has been opened for
311             restoring.
312            
313             =cut
314            
315             sub restore_file {
316 0     0 1 0 my $self = shift;
317 0         0 my %args = Params(qw( internal file ))->args(@_);
318            
319 0 0       0 unless ($self->{status} eq "open for restore") {
320 0         0 return failure $self->_log(
321             "cannot restore file: status is \"$self->{status}\"" );
322             }
323            
324 0         0 my $file = $args{internal};
325 0   0     0 my $dest = $args{file} ||
326             return failure $self->_log( "no destination specified" );
327            
328 0 0       0 unless (-d $dest) {
329 0         0 return failure $self->_log( "destination does not exist" );
330             }
331            
332 0         0 my $path = File::Spec->catfile($dest, $file);
333 0 0       0 if (-e $path) {
334 0         0 $self->_log( level => "debug", message => "$path exists\n" );
335             # TODO: confirmation to overwrite?
336             }
337            
338 0         0 $self->_log( level => "info", message => "restoring $file\n" );
339 0         0 $self->{zip}->extractMember($file, $path);
340 0 0       0 unless (-e $path) {
341 0         0 return failure $self->_log( "extract failed" );
342             }
343 0         0 return success;
344             }
345            
346             =item close_backup
347            
348             $plugin->close_backup();
349            
350             Closes the backup.
351            
352             =cut
353            
354             sub close_backup {
355 1     1 1 2 my $self = shift;
356            
357 1 50       5 unless ($self->{status} eq "open for backup") {
358 0         0 return failure $self->_log(
359             "cannot close archive: status is \"$self->{status}\"" );
360             }
361            
362 1         3 my $path = $self->{path};
363 1         8 $self->_log( level => "debug", message => "saving archive: $path\n" );
364 1 50       7 if ($self->{zip}->writeToFileNamed( $path ) == AZ_OK) {
365 1         499 $self->{status} = "closed";
366 1         6 return success;
367             }
368             else {
369 0         0 return failure $self->_log( "writeToFileNamed $path failed" );
370             }
371             }
372            
373             =item close_restore
374            
375             $plugin->close_restore();
376            
377             Closes the restore.
378            
379             =cut
380            
381             sub close_restore {
382 1     1 1 1 my $self = shift;
383            
384 1 50       7 unless ($self->{status} eq "open for restore") {
385 0         0 return failure $self->_log(
386             "cannot close archive: status is \"$self->{status}\"" );
387             }
388            
389 1         3 $self->_log( level => "debug", message => "closing archive\n" );
390 1         2 $self->{status} = "closed";
391 1         4 return success;
392             }
393            
394             =begin internal
395            
396             =item _log
397            
398             $moz->_log( $message, $level );
399            
400             $moz->_log( $message => $message, level => $level );
401            
402             Logs an event to the dispatcher. If C<$level> is unspecified, "error"
403             is assumed.
404            
405             =end internal
406            
407             =cut
408            
409             sub _log {
410 4     4   9 my $self = shift;
411 4         12 my %args = Params(qw( message ?level="error" ))->args(@_);
412 4         556 my $msg = $args{message};
413            
414             # we want log messages to always have a newline, but not necessarily
415             # the returned value that we pass to carp/croak/return value
416            
417 4 50       25 $args{message} .= "\n" unless ($args{message} =~ /\n$/);
418 4 50       31 $self->{log}->log(%args) if ($self->{log});
419 4         83 return $msg; # when used by carp/croak/return value
420             }
421            
422            
423             1;
424            
425             =back
426            
427             =head1 KNOWN ISSUES
428            
429             =head2 MozBackup Compatability
430            
431             The "MozBackup" utility (L) produces zip archives
432             (with the F extension) which should be compatible with this module,
433             although support for handling the F has not been added (it
434             should probably be exluded in a restore).
435            
436             =head1 AUTHOR
437            
438             Robert Rothenberg
439            
440             =head1 LICENSE
441            
442             Copyright (c) 2005 Robert Rothenberg. All rights reserved.
443             This program is free software; you can redistribute it and/or
444             modify it under the same terms as Perl itself.
445            
446             =cut
447