File Coverage

blib/lib/Mozilla/Backup/Plugin/FileCopy.pm
Criterion Covered Total %
statement 27 163 16.5
branch 0 64 0.0
condition 0 20 0.0
subroutine 9 24 37.5
pod 10 10 100.0
total 46 281 16.3


line stmt bran cond sub pod time code
1             =head1 NAME
2            
3             Mozilla::Backup::Plugin::FileCopy - A file copy plugin for Mozilla::Backup
4            
5             =begin readme
6            
7             =head1 REQUIREMENTS
8            
9             The following non-core modules are required:
10            
11             File::Copy;
12             Log::Dispatch;
13             Mozilla::Backup
14             Mozilla::ProfilesIni;
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::FileCopy'
26             );
27            
28            
29             =head1 DESCRIPTION
30            
31             This is a plugin for Mozilla::Backup which copies profiles to another
32             directory.
33            
34             =over
35            
36             =cut
37            
38             package Mozilla::Backup::Plugin::FileCopy;
39            
40 2     2   9246 use strict;
  2         6  
  2         68  
41            
42 2     2   9 use Carp;
  2         5  
  2         128  
43 2     2   10 use File::Copy;
  2         3  
  2         89  
44 2     2   11 use File::Find;
  2         4  
  2         89  
45 2     2   10 use File::Spec;
  2         3  
  2         37  
46 2     2   9 use Log::Dispatch;
  2         3  
  2         38  
47 2     2   12 use Mozilla::ProfilesIni;
  2         3  
  2         57  
48 2     2   10 use Params::Smart 0.04;
  2         58  
  2         79  
49 2     2   8 use Return::Value;
  2         2  
  2         4412  
50            
51             # require Mozilla::Backup;
52            
53             # $Revision: 1.16 $
54            
55             our $VERSION = '0.03';
56            
57             =item new
58            
59             $plugin = Mozilla::Backup::Plugin::FileCopy->new( %options );
60            
61             The following C<%options> are supported:
62            
63             =over
64            
65             =item log
66            
67             The L objetc used by L. This is required.
68            
69             =item debug
70            
71             The debug flag from L. This is not used at the moment.
72            
73             =back
74            
75             =cut
76            
77             # TODO - option to preserve file perms/ownership, which should be
78             # enabled by default. Possibly specify a callback to run on each
79             # copied file?
80            
81             my @ALLOWED_OPTIONS = (
82             {
83             name => "log",
84             default => Log::Dispatch->new(),
85             callback => sub {
86             my ($self, $name, $log) = @_;
87             croak "invalid log sink"
88             unless ((ref $log) && $log->isa("Log::Dispatch"));
89             return $log;
90             },
91             name_only => 1,
92             required => 1,
93             },
94             {
95             name => "debug",
96             default => 0,
97             name_only => 1,
98             },
99             );
100            
101             sub new {
102 0   0 0 1   my $class = shift || __PACKAGE__;
103 0           my %args = Params(@ALLOWED_OPTIONS)->args(@_);
104            
105 0           my $self = {
106             log => $args{log},
107             debug => $args{debug},
108             status => "closed",
109             };
110            
111 0           return bless $self, $class;
112             }
113            
114             =item allowed_options
115            
116             @options = Mozilla::Backup::Plugin::FileCopy->allowed_options();
117            
118             if (Mozilla::Backup::Plugin::FileCopy->allowed_options('debug')) {
119             ...
120             }
121            
122             If no arguments are given, it returns a list of configuration parameters
123             that can be passed to the constructor. If arguments are given, it returns
124             true if all of the arguments are allowable options for the constructor.
125            
126             =cut
127            
128             sub allowed_options {
129 0   0 0 1   my $class = shift || __PACKAGE__;
130 0           my %args = Params(qw( ?*options ))->args(@_);
131            
132 0           my %allowed = map { $_->{name} => 1, } @ALLOWED_OPTIONS;
  0            
133            
134 0 0         my @opts = @{$args{options}}, if ($args{options});
  0            
135 0 0         if (@opts) {
136 0           my $allowed = 1;
137 0   0       while ($allowed && (my $opt = shift @opts)) {
138 0   0       $allowed = $allowed && $allowed{$opt};
139             }
140 0           return $allowed;
141             }
142             else {
143 0           return (keys %allowed);
144             }
145             }
146            
147             =item munge_location
148            
149             $directory = $plugin->munge_location( $directory );
150            
151             Munges the backup location name for use by this plugin. (Currently
152             has no effect.)
153            
154             =cut
155            
156             sub munge_location {
157 0     0 1   my $self = shift;
158 0           my %args = Params(qw( file ))->args(@_);
159 0   0       my $file = $args{file} || "";
160 0           return $file;
161             }
162            
163             =item open_for_backup
164            
165             if ($plugin->open_for_backup( $filename, %options )) {
166             ...
167             }
168            
169             Creates a new archive for backing the profile. C<$filename> is the
170             name of the archive file to be used. C<%options> are optional
171             configuration parameters.
172            
173             =cut
174            
175             sub open_for_backup {
176 0     0 1   my $self = shift;
177 0           my %args = Params(qw( path ?*options ))->args(@_);
178            
179 0 0         unless ($self->{status} eq "closed") {
180 0           return failure $self->_log(
181             "cannot create archive: status is \"$self->{status}\"" );
182             }
183            
184 0           my $path = File::Spec->rel2abs($args{path});
185            
186 0           $self->{opts} = $args{options};
187            
188 0           $self->_log( level => "debug", message => "creating archive $path\n" );
189            
190 0           mkdir $path;
191 0           chmod 0700, $path;
192 0 0         if ($self->{path} = _catdir($path)) {
193 0           $self->{status} = "open for backup";
194 0           return success;
195             }
196             else {
197 0           return failure $self->_log(
198             "unable to create path: \"$path\"", );
199             }
200            
201             }
202            
203             =item open_for_restore
204            
205             if ($plugin->open_for_restore( $filename, %options )) {
206             ...
207             }
208            
209             Opens an existing archive for restoring the profile.
210            
211             =cut
212            
213             sub open_for_restore {
214 0     0 1   my $self = shift;
215 0           my %args = Params(qw( path ?*options ))->args(@_);
216            
217 0 0         unless ($self->{status} eq "closed") {
218 0           return failure $self->_log(
219             "cannot open archive: status is \"$self->{status}\"" );
220             }
221            
222 0           my $path = File::Spec->rel2abs($args{path});
223            
224 0 0         if ($self->{path} = _catdir($path)) {
225 0           $self->{status} = "open for restore";
226 0           return success;
227             }
228             else {
229 0           return failure $self->_log( "cannot find archive: \"$path\"" );
230             }
231             }
232            
233             =item get_contents
234            
235             @files = $plugin->get_contents;
236            
237             Returns a list of files in the archive.
238            
239             =cut
240            
241             sub get_contents {
242 0     0 1   my $self = shift;
243            
244 0 0         unless ($self->{status} ne "closed") {
245 0           return failure $self->_log(
246             "cannot get contents: status is \"$self->{status}\"" );
247             }
248            
249 0           my $path = $self->{path};
250 0           my @files = ( );
251            
252             find({
253             bydepth => 1,
254             wanted => sub {
255 0     0     my $file = $File::Find::name;
256 0           my $name = substr($file, length($path));
257 0 0         if ($name) {
258 0           $name = substr($name,1); # remove initial '/'
259             {
260 0 0         $name .= '/' if (-d $file);
  0            
261 0           push @files, $name;
262             }
263             }
264            
265             },
266 0           }, $path
267             );
268            
269 0 0         unless (@files) {
270 0           carp $self->_log( level => "warn",
271             message => "no files in backup" );
272             }
273            
274 0           return @files;
275             }
276            
277             =item backup_file
278            
279             $plugin->backup_file( $local_file, $internal_name );
280            
281             Backs up the file in the archive, using C<$internal_name> as the
282             name in the archive.
283            
284             =cut
285            
286             sub backup_file {
287 0     0 1   my $self = shift;
288 0           my %args = Params(qw( file ?internal ))->args(@_);
289            
290 0 0         unless ($self->{status} eq "open for backup") {
291 0           return failure $self->_log(
292             "cannot backup file: status is \"$self->{status}\"" );
293             }
294            
295 0           my $file = File::Spec->canonpath($args{file}); # actual file
296 0   0       my $name = $args{internal} || $file; # name in archive
297            
298 0           $self->_log( level => "info", message => "backing up $name\n" );
299            
300 0 0         if (-d $file) {
    0          
301 0           my $dest = File::Spec->catdir($self->{path}, $name);
302 0 0         if ($self->_create_dir($name)) {
303 0           $self->_log( level => "debug", message => "creating $dest\n" );
304 0           mkdir $dest;
305 0           chmod 0700, $dest;
306             }
307 0 0         return failure "directory $dest not found" unless (_catdir($dest));
308 0           return success;
309             } elsif (-r $file) {
310 0           my $dest = File::Spec->catfile($self->{path}, $name);
311 0 0         if ($self->_create_dir($name)) {
312 0           $self->_log( level => "debug",
313             message => "copying $file to $dest\n" );
314            
315             # TODO - options to copy permissions
316            
317 0 0         copy($file, $dest)
318             || return failure $self->_log( "copying failed: $!" );
319             }
320 0 0         return failure "file $dest not found" unless (_catfile($dest));
321 0           return success;
322             } else {
323 0           return failure $self->_log( "cannot find file $file" );
324             }
325             }
326            
327             =begin internal
328            
329             =item _create_dir
330            
331             if ($plugin->_create_dir($name, $root)) {
332             ...
333             }
334            
335             Creates deep directories. (This may be removed in future versions.)
336            
337             =end internal
338            
339             =cut
340            
341             sub _create_dir {
342 0     0     my $self = shift;
343 0           my $name = shift;
344 0   0       my $root = shift || $self->{path};
345            
346 0           my @dirs = File::Spec->splitdir($name);
347 0           my $file = pop @dirs;
348            
349 0           foreach my $dir ("", @dirs) {
350 0           $root = File::Spec->catdir($root, $dir);
351 0 0         unless (-d $root) {
352 0           $self->_log( level => "debug", message => "creating $root\n" );
353 0           mkdir $root;
354 0           chmod 0700, $root;
355             }
356             }
357 0 0         return _catdir($root) ? $file : undef;
358             }
359            
360            
361             =item restore_file
362            
363             $plugin->restore_file( $internal_name, $local_file );
364            
365             Restores the file from the archive.
366            
367             =cut
368            
369             sub restore_file {
370 0     0 1   my $self = shift;
371 0           my %args = Params(qw( internal file ))->args(@_);
372            
373 0 0         unless ($self->{status} eq "open for restore") {
374 0           return failure $self->_log(
375             "cannot restore file: status is \"$self->{status}\"" );
376             }
377            
378 0           my $file = $args{internal};
379 0   0       my $dest = $args{file} ||
380             return failure $self->_log( "no destination specified" );
381            
382 0 0         unless (-d $dest) {
383 0           return failure $self->_log( "destination does not exist" );
384             }
385            
386 0           my $path = File::Spec->catfile($dest, $file);
387 0 0         if (-e $path) {
388 0           $self->_log( level => "debug", message => "$path exists\n" );
389             # TODO: confirmation to overwrite?
390             }
391            
392 0           $self->_log( level => "info", message => "restoring $file\n" );
393            
394 0           my $src = File::Spec->catfile($self->{path}, $file);
395            
396 0 0         if (-d $src) {
    0          
397 0 0         if ($self->_create_dir($file, $dest)) {
398 0           $self->_log( level => "debug", message => "creating $file\n" );
399 0           mkdir $path;
400 0           chmod 0700, $path;
401             }
402 0 0         return failure "directory $path not found" unless (_catdir($path));
403 0           return success;
404             } elsif (-r $src) {
405 0 0         if ($self->_create_dir($file, $dest)) {
406 0           $self->_log( level => "debug", message => "copying $file\n" );
407            
408             # TODO - options to copy permissions
409            
410 0 0         copy($src, $path)
411             || return failure $self->_log( "copying failed: $!" );
412 0           chmod 0600, $path;
413             }
414 0 0         return failure "file $path not found" unless (_catfile($path));
415 0           return success;
416             } else {
417 0           return failure $self->_log( "cannot find file $src" );
418             }
419             }
420            
421             =item close_backup
422            
423             $plugin->close_backup();
424            
425             Closes the backup.
426            
427             =cut
428            
429             sub close_backup {
430 0     0 1   my $self = shift;
431 0           my $path = $self->{path};
432 0           $self->_log( level => "debug", message => "closing archive\n" );
433 0           $self->{status} = "closed";
434 0           return success;
435             }
436            
437            
438             =item close_restore
439            
440             $plugin->close_restore();
441            
442             Closes the restore.
443            
444             =cut
445            
446             sub close_restore {
447 0     0 1   my $self = shift;
448 0           $self->_log( level => "debug", message => "closing archive\n" );
449 0           $self->{status} = "closed";
450 0           return success;
451             }
452            
453            
454             =begin internal
455            
456             =item _log
457            
458             $moz->_log( $message, $level );
459            
460             $moz->_log( $message => $message, level => $level );
461            
462             Logs an event to the dispatcher. If C<$level> is unspecified, "error"
463             is assumed.
464            
465             =end internal
466            
467             =cut
468            
469             sub _log {
470 0     0     my $self = shift;
471 0           my %args = Params(qw( message ?level="error" ))->args(@_);
472 0           my $msg = $args{message};
473            
474             # we want log messages to always have a newline, but not necessarily
475             # the returned value that we pass to carp/croak/return value
476            
477 0 0         $args{message} .= "\n" unless ($args{message} =~ /\n$/);
478 0 0         $self->{log}->log(%args) if ($self->{log});
479 0           return $msg; # when used by carp/croak/return value
480             }
481            
482             =begin internal
483            
484             =item _catdir
485            
486             =item _catfile
487            
488             =end internal
489            
490             =cut
491            
492             sub _catdir {
493 0     0     goto \&Mozilla::ProfilesIni::_catdir;
494             }
495            
496             sub _catfile {
497 0     0     goto \&Mozilla::ProfilesIni::_catfile;
498             }
499            
500             1;
501            
502             =back
503            
504             =head1 EXAMPLES
505            
506             =head2 Creating archvies other than zip or tar.gz
507            
508             If you would like to create backups in a format for which no plugin
509             is available, you can use Mozilla::Backup::Plugin::FileCopy with a
510             system call to the appropriate archiver. For example,
511            
512             $moz = Mozilla::backup->new(
513             plugin => "Mozilla::Backup::Plugin::FileCopy",
514             );
515            
516             $dest = $moz->backup_profile(
517             type => "firefox",
518             name => "default",
519             );
520            
521             system("tar cf - $dest |bzip2 - > firefox-default-profile.tar.bz2");
522            
523             =head1 AUTHOR
524            
525             Robert Rothenberg
526            
527             =head1 LICENSE
528            
529             Copyright (c) 2005 Robert Rothenberg. All rights reserved.
530             This program is free software; you can redistribute it and/or
531             modify it under the same terms as Perl itself.
532            
533             =cut
534