File Coverage

blib/lib/Mozilla/Backup/Plugin/Tar.pm
Criterion Covered Total %
statement 21 124 16.9
branch 0 44 0.0
condition 0 17 0.0
subroutine 7 18 38.8
pod 10 10 100.0
total 38 213 17.8


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