File Coverage

blib/lib/Mozilla/Backup.pm
Criterion Covered Total %
statement 179 276 64.8
branch 33 110 30.0
condition 10 40 25.0
subroutine 33 37 89.1
pod 7 7 100.0
total 262 470 55.7


line stmt bran cond sub pod time code
1             =head1 NAME
2            
3             Mozilla::Backup - Backup utility for Mozilla profiles
4            
5             =begin readme
6            
7             =head1 REQUIREMENTS
8            
9             The following non-core modules are required:
10            
11             Archive::Tar
12             Archive::Zip
13             Compress::Zlib
14             Config::IniFiles
15             File::Temp
16             IO::Zlib
17             Log::Dispatch
18             Module::Pluggable
19             Params::Smart
20             Regexp::Assemble;
21             Regexp::Common
22             Return::Value
23             Test::More
24            
25             The Archive::* and *::Zlib modules are used by their respective plugins.
26            
27             =head1 INSTALLATION
28            
29             Installation can be done using the traditional Makefile.PL or the newer
30             Build.PL methods.
31            
32             Using Makefile.PL:
33            
34             perl Makefile.PL
35             make test
36             make install
37            
38             (On Windows platforms you should use C instead.)
39            
40             Using Build.PL (if you have Module::Build installed):
41            
42             perl Build.PL
43             perl Build test
44             perl Build install
45            
46             =end readme
47            
48             =head1 SYNOPSIS
49            
50             $moz = Mozilla::Backup->new();
51             $file = $moz->backup_profile("firefox", "default");
52            
53             =head1 DESCRIPTION
54            
55             This package provides a simple interface to back up and restore the
56             profiles of Mozilla-related applications such as Firefox or Thunderbird.
57            
58             =begin readme
59            
60             More details are available in the module documentation.
61            
62             =end readme
63            
64             =for readme stop
65            
66             Method calls may use named or positional parameters (named calls are
67             recommended). Methods are outlined below:
68            
69             =cut
70            
71             package Mozilla::Backup;
72            
73 5     5   19565 use 5.006;
  5         18  
  5         799  
74 5     5   31 use strict;
  5         9  
  5         182  
75 5     5   36 use warnings;
  5         9  
  5         172  
76            
77 5     5   35 use Carp;
  5         8  
  5         438  
78             # use Config::IniFiles;
79 5     5   4775 use File::Copy qw( copy );
  5         13141  
  5         361  
80 5     5   36 use File::Find;
  5         9  
  5         339  
81 5     5   26 use File::Spec;
  5         10  
  5         89  
82 5     5   3620 use IO::File;
  5         22838  
  5         904  
83 5     5   5155 use Log::Dispatch 1.6;
  5         84424  
  5         172  
84 5     5   4754 use Module::Pluggable;
  5         54508  
  5         33  
85 5     5   3921 use Mozilla::ProfilesIni;
  5         17  
  5         188  
86 5     5   28 use Params::Smart 0.04;
  5         86  
  5         251  
87 5     5   11098 use Regexp::Assemble;
  5         118607  
  5         224  
88 5     5   48 use Regexp::Common 1.8 qw( comment balanced delimited );
  5         171  
  5         42  
89 5     5   66190 use Return::Value;
  5         11  
  5         18195  
90            
91             # $Revision: 1.64 $
92            
93             our $VERSION = '0.06';
94            
95             # Note: the 'pseudo' profile type is deliberately left out.
96             # 'minotaur' is obsolete, and so omitted; what about 'phoenix'?
97            
98             # TODO: add support for Epiphany, SkipStone, and DocZilla, if relevant
99            
100             my @PROFILE_TYPES = qw(
101             beonex camino firefox galeon k-meleon mozilla netscape phoenix
102             sunbird thunderbird
103             );
104            
105             sub profile_types {
106 6     6 1 758 return @PROFILE_TYPES;
107             }
108            
109             sub _catdir {
110 6     6   3314 goto \&Mozilla::ProfilesIni::_catdir;
111             }
112            
113             sub _catfile {
114 3     3   1007 goto \&Mozilla::ProfilesIni::_catfile;
115             }
116            
117             =begin internal
118            
119             =item _find_all_profiles
120            
121             $moz->_find_all_profiles()
122            
123             Attempts to locale the profiles for all known L).
124            
125             =end internal
126            
127             =cut
128            
129             sub _find_all_profiles {
130 4     4   9 my $self = shift;
131            
132 4         16 my $home = $ENV{HOME};
133 4 50       20 if ($^O eq "MSWin32") {
134 0   0     0 $home = $ENV{APPDATA} ||
135             _catdir($ENV{USERPROFILE}, "Application Data") ||
136             _catdir($ENV{WINDIR}, "Profiles", "Application Data") ||
137             _catdir($ENV{WINDIR}, "Application Data");
138            
139             # Question: is WinDir set for all Windows 9x/WinNT machines? Where
140             # is the code that Mozilla uses to determine where the profile
141             # should be?
142            
143             }
144            
145 4         17 foreach my $type (profile_types) {
146 40 50       106 if (my $path = Mozilla::ProfilesIni::_find_profile_path(
147             home => $home, type => $type)) {
148 0 0       0 if (_catfile($path, "profiles.ini")) {
149 0         0 $self->{profiles}->{$type} =
150             Mozilla::ProfilesIni->new( path => $path, debug => $self->{debug} );
151             }
152             } else {
153             }
154             }
155 4 100       25 if ($self->{pseudo}) {
156 2         20 my $pseudo =
157             Mozilla::ProfilesIni->new( path => $self->{pseudo}, create => 1,
158             debug => $self->{debug} );
159 2 50       16 $pseudo->create_profile( name => "default", is_default => 1 ),
160             unless ($pseudo->profile_exists( name => "default" ));
161 2         78 $self->{profiles}->{pseudo} = $pseudo;
162             }
163             }
164            
165            
166             =begin internal
167            
168             =item _load_plugin
169            
170             $moz->_load_plugin( plugin => $plugin, options => \%options );
171            
172             $moz->_load_plugin( $plugin, %options );
173            
174             Loads a plugin module. It assumes that C<$plugin> contains the full
175             module name. C<%options> are passed to the plugin constructor.
176            
177             =end internal
178            
179             =cut
180            
181             sub _load_plugin {
182 4     4   8 my $self = shift;
183 4         17 my %args = Params(qw( plugin *?options ))->args(@_);
184 4         614 my $plugin = $args{plugin};
185 4   50     16 my $opts = $args{options} || { };
186            
187 4         9 local ($_);
188            
189             # TODO - check if plugin already loaded
190            
191 4         284 eval "CORE::require $plugin";
192 4 50       27 if ($@) {
193 0         0 croak $self->_log( "Unable to load plugin plugin" );
194             }
195             else {
196             # We check to see if the plugin supports the methods we
197             # need. Would it make more sense to have a base class and test
198             # isa() instead?
199            
200 4         15 foreach (qw(
201             allowed_options new munge_location open_for_backup open_for_restore
202             get_contents backup_file restore_file close_backup close_restore
203             )) {
204 40 50       235 croak $self->_log( "Plugin does not support $_ method" )
205             unless ($plugin->can($_));
206             }
207            
208             # We check to see if the plugin accepts certain options
209            
210 4         12 my %copts = ( );
211 4         9 foreach (qw( log debug )) {
212 8 50       28 $copts{$_} = $self->{$_} if ($plugin->allowed_options($_));
213             }
214 4         28 $self->{plugin} = $plugin->new(%copts,%$opts);
215             }
216 4         19 return $self->{plugin};
217             }
218            
219             =over
220            
221             =item new
222            
223             $moz = Mozilla::Backup->new( %options );
224            
225             Creates a new Mozilla::Backup object. The options are as follows:
226            
227             =over
228            
229             =item log
230            
231             A L object for receiving log messages.
232            
233             This value is passed to plugins if they accept it.
234            
235             =item plugin
236            
237             A plugin to use for archiving. Plugins included are:
238            
239             =over
240            
241             =item Mozilla::Backup::Plugin::Zip
242            
243             Saves the profile in a zip archive. This is the default plugin.
244            
245             =item Mozilla::Backup::Plugin::FileCopy
246            
247             Copies the files in the profile into another directory.
248            
249             =item Mozilla::Backup::Plugin::Tar
250            
251             Saves the profile in a tar or tar.gz archive.
252            
253             =back
254            
255             You may pass options to the plugin in the following manner:
256            
257             $moz = Mozilla::Backup->new(
258             plugin => [ 'Mozilla::Backup::Plugin::Tar', compress => 1 ],
259             );
260            
261             =item exclude
262            
263             An array reference of regular expressions for files to exclude from
264             the backup. For example,
265            
266             $moz = Mozilla::Backup->new(
267             exclude => [ '^history', '^Cache' ],
268             );
269            
270             Regular expressions can be strings or compiled Regexps.
271            
272             By default the F, folders, XUL cache, mail folders
273             cache and lock files are excluded.
274            
275             =begin internal
276            
277             =item pseudo
278            
279             Specifies the directory of a special C profile type used for debugging
280             and testing. This does not appear in the L.
281            
282             =item debug
283            
284             Sets an internal debug flag, which adds a "debug"-level screen output
285             sink to the log dispatcher. This value is passed to plugins if they
286             accept it.
287            
288             =end internal
289            
290             =back
291            
292             =cut
293            
294             sub new {
295 4   50 4 1 2604 my $class = shift || __PACKAGE__;
296            
297             my %args = Params(
298             {
299             name => "plugin",
300             default => "Mozilla::Backup::Plugin::Zip",
301             callback => sub {
302 1     1   25 my ($self, $name, $value) = @_;
303 1 50 33     7 croak "expected scalar or array reference"
304            
305             unless ((!ref $value) || (ref($value) eq "ARRAY"));
306 1         4 return $value;
307             },
308             name_only => 0,
309             },
310             {
311             name => "log",
312             default => Log::Dispatch->new(),
313             callback => sub {
314 0     0   0 my ($self, $name, $log) = @_;
315 0 0 0     0 croak "invalid log sink"
316             unless ((ref $log) && $log->isa("Log::Dispatch"));
317 0         0 return $log;
318             },
319             name_only => 1,
320             },
321             {
322             name => "pseudo",
323             default => "",
324             callback => sub {
325 2     2   559 my ($self, $name, $value) = @_;
326 2   50     8 $value ||= "";
327 2 50 33     14 croak "invalid pseudo directory"
328             unless (($value eq "") || _catdir($value));
329 2         9 return $value;
330             },
331             name_only => 1,
332             },
333             {
334             name => "debug",
335             default => 0,
336             name_only => 1,
337             },
338             {
339             name => "exclude",
340             default => [
341             '^Cache(.Trash)?\/', # web cache
342             'XUL\.(mfl|mfasl)', # XUL cache
343             'XUL FastLoad File', # XUL cache
344             '(Invalid|Aborted)\.mfasl', # Invalidated XUL
345             'panacea.dat', # mail folder cache
346             '(\.parentlock|parent\.lock|lock)', # lock file
347             ],
348             callback => sub {
349 0     0   0 my ($self, $name, $value) = @_;
350 0 0       0 $value = [ $value ] unless (ref $value);
351 0 0       0 croak "expected scalar or array reference"
352             unless (ref($value) eq "ARRAY");
353 0         0 local ($_);
354 0         0 foreach (@$value) {
355 0 0 0     0 croak "expected regular expression"
356             unless ((!ref $value) || (ref($value) eq "Regexp"));
357             }
358 0         0 return $value;
359             },
360 4         69 name_only => 0,
361             slurp => 1,
362             },
363             )->args(@_);
364            
365 4         970 my $self = {
366             profiles => { },
367             };
368            
369 4         8 local ($_);
370            
371 4         9 foreach (qw( log debug pseudo exclude )) {
372 16         34 $self->{$_} = $args{$_};
373             }
374            
375 4         13 bless $self, $class;
376            
377 4 100       28 if ($self->{debug}) {
378 1         1038 require Log::Dispatch::Screen;
379 1         2473 $self->{log}->add( Log::Dispatch::Screen->new(
380             name => __PACKAGE__,
381             min_level => "debug",
382             stderr => 1,
383             ));
384             }
385            
386             {
387 4         154 my $plugin = $args{plugin};
  4         10  
388 4         6 my $opts = [ ];
389 4 100       17 if (ref($plugin) eq 'ARRAY') {
390 1         3 $opts = $plugin;
391 1         2 $plugin = shift @{$opts};
  1         2  
392             }
393 4         21 $self->_load_plugin( plugin => $plugin, options => { @$opts } );
394             }
395 4         24 $self->_find_all_profiles();
396            
397 4         31 return $self;
398             }
399            
400             =item profile_types
401            
402             @types = $moz->profile_types;
403            
404             Returns a list of all profile types that are supported by this version
405             of the module.
406            
407             Supported profile types:
408            
409             beonex
410             camino
411             firefox
412             galeon
413             kmeleon
414             mozilla
415             phoenix
416             netscape
417             sunbird
418             thunderbird
419            
420             Some of these profile types are for platform-specific or obsolete
421             applications, so you may never run into them.
422            
423             =item found_profile_types
424            
425             @types = $moz->found_profile_types();
426            
427             Returns a list of applications for which profiles were found. (This
428             does not mean that the applications are installed on the machine, only
429             that profiles were found where they were expected.)
430            
431             =cut
432            
433             sub found_profile_types {
434 2     2 1 1217 my $self = shift;
435 2         5 return (keys %{$self->{profiles}});
  2         11  
436             }
437            
438             =item type
439            
440             $ini = $moz->type( type => $type );
441            
442             $ini = $moz->type( $type );
443            
444             if ($moz->type( $type )->profile_exists( $name )) { ... }
445            
446             Returns the L object for the corresponding C<$type>,
447             or an error if it is invalid.
448            
449             =cut
450            
451             sub type {
452 13     13 1 3567 my $self = shift;
453 13         43 my %args = Params(qw( type ))->args(@_);
454 13         745 my $type = $args{type};
455 13   33     112 return $self->{profiles}->{$type} ||
456             croak $self->_log(
457             "invalid profile type: $type"
458             );
459             }
460            
461             =item type_exists
462            
463             if ($moz->type_exists( type => $type)) { ... }
464            
465             if ($moz->type_exists($type)) { ... }
466            
467             Returns true if a profile type exists on the machine.
468            
469             =cut
470            
471             sub type_exists {
472 6     6 1 2079 my $self = shift;
473 6         30 my %args = Params(qw( type ))->args(@_);
474 6         616 my $type = $args{type};
475 6         37 return (exists $self->{profiles}->{$type});
476             }
477            
478             =begin internal
479            
480             =item _backup_path
481            
482             An internal routine used by L.
483            
484             =end internal
485            
486             =cut
487            
488             sub _backup_path {
489 1     1   3 my $self = shift;
490 1         4 my %args = Params(qw( profile_path destination relative ))->args(@_);
491 1         281 my $path = $args{profile_path};
492 1         3 my $dest = $args{destination};
493 1         2 my $relative = $args{relative};
494            
495             # TODO - an option for overwriting existing files?
496            
497 1 50       34 if (-e $dest) {
498 0         0 return failure
499             $self->_log( "$dest exists already" );
500             }
501            
502 1         8 $self->_log( level => "notice", message => "backing up $path\n" );
503            
504 1 50       8 unless ($self->{plugin}->open_for_backup( path => $dest)) {
505 0         0 return failure
506             $self->_log( "error creating archive" );
507             }
508            
509            
510 1         82 my $exclude = Regexp::Assemble->new( debug => $self->{debug} );
511 1         138 $exclude->add( @{$self->{exclude}} );
  1         10  
512            
513             find({
514             bydepth => 1,
515             wanted => sub {
516 1     1   3 my $file = $File::Find::name;
517 1 50       6 my $name = $relative ? substr($file, length($path)) : $file;
518 1 50       36 if ($name) {
519 0         0 $name = substr($name,1); # remove initial '/'
520            
521 0 0       0 unless ($name =~ $exclude->re) {
522 0 0       0 $name .= '/' if (-d $file);
523 0         0 my $r = $self->{plugin}->backup_file($file, $name);
524 0 0       0 return failure $self->_log(
525             "error backing up $file: $r" ) unless ($r);
526             }
527             }
528            
529             },
530 1         1520 }, $path
531             );
532            
533             # TODO: check for errors here
534 1 50       17 unless ($self->{plugin}->close_backup()) {
535 0         0 return failure "close_backup failed";
536             }
537            
538 1         58 return success;
539             }
540            
541             =item backup_profile
542            
543             $file = $moz->backup_profile(
544             type => $type,
545             name => $name,
546             destination => $dest,
547             archive_name => $arch,
548             relative => $rel,
549             );
550            
551             $file = $moz->backup_profile($type,$name,$dest,$arch,$rel);
552            
553             Backs up the profile as a zip archive to the path specified in C<$dest>.
554             (If none is given, the current directory is assumed.)
555            
556             C<$arch> is an optional name for the archive file. If none is given, it
557             assumes F (for example,
558             F if the Zip plugin is used.)
559            
560             C<$rel> is an optional flag to backup files with relative paths instead
561             of absolute pathnames. It defaults to the value of L
562             for that profile. (Non-relative profiles will show a warning message.)
563            
564             If the profile is currently in use, it may not be backed up properly.
565            
566             This version does no munging of the profile data, nor does it store any
567             meta information. See L below.
568            
569             =cut
570            
571             sub backup_profile {
572 1     1 1 3 my $self = shift;
573 1         17 my %args = Params(qw( type name ?destination ?archive_name ?relative ))
574             ->args(@_);
575 1         371 my $type = $args{type};
576 1         4 my $name = $args{name};
577            
578 1         4 my $prof = $self->type( type => $type );
579            
580 1   50     6 my $dest = $args{destination} || '.';
581 1   33     4 my $arch = $args{archive_name} ||
582             $self->_archive_name( type => $type, name => $name);
583            
584             # TODO - if destination includes a file name, use it. The plugin
585             # should have methods for parsing destination appropriate to the
586             # backup method.
587            
588 1         11 my $back = File::Spec->catfile($dest, $arch);
589            
590             # This needs to be rethought here. IsRelative refers to the Path in
591             # the .ini file being relative, but does it also refer to locations
592             # from within the profile being stored relatively? Not sure here.
593            
594 1         3 my $relative = $args{relative};
595            
596 1 50       8 $relative = $prof->profile_is_relative( name => $name )
597             unless (defined $relative);
598            
599 1 50       4 unless ($relative) {
600 0         0 $self->_log( level => "notice",
601             message => "backup will not use relative pathnames\n" );
602             }
603            
604 1 50       4 if ($prof->profile_is_locked( name => $name )) {
605 0         0 return failure $self->_log(
606             "cannot backup locked profile" );
607             }
608            
609 1         4 my $r = $self->_backup_path(
610             profile_path => $prof->profile_path( name => $name ),
611             destination => $back,
612             relative => $relative
613             );
614 1 50       41 return failure $r unless ($r);
615            
616 1         32 return $back;
617             }
618            
619             =begin internal
620            
621             =item _archive_name
622            
623             Returns a default "archive name" appropriate to the plugin type.
624            
625             =end internal
626            
627             =cut
628            
629             sub _archive_name {
630 1     1   3 my $self = shift;
631 1         4 my %args = Params(qw( type name ))->args(@_);
632 1         254 my $type = $args{type};
633 1         2 my $name = $args{name};
634            
635             # We don't really care about validating profile types and names
636             # here. If it's invalid, so what. We just have a name that doesn't
637             # refer to any actual profiles.
638            
639 1         78 my $timestamp = sprintf("%04d%02d%02d-%02d%02d%02d",
640             (localtime)[5]+1900, (localtime)[4]+1,
641             reverse((localtime)[0..3]),
642             );
643 1         4 my $arch = join("-", $type, $name, $timestamp);
644 1         9 return $self->{plugin}->munge_location($arch);
645             }
646            
647             =begin internal
648            
649             =item _log
650            
651             $moz->_log( $message, $level );
652            
653             $moz->_log( $message => $message, level => $level );
654            
655             Logs an event to the dispatcher. If C<$level> is unspecified, "error"
656             is assumed.
657            
658             =end internal
659            
660             =cut
661            
662             sub _log {
663 3     3   1076 my $self = shift;
664 3         20 my %args = Params(qw( message ?level="error" ))->args(@_);
665 3         1177 my $msg = $args{message};
666            
667             # we want log messages to always have a newline, but not necessarily
668             # the returned value that we pass to carp/croak/return value
669            
670 3 100       74 $args{message} .= "\n" unless ($args{message} =~ /\n$/);
671 3 50       40 $self->{log}->log(%args) if ($self->{log});
672 2         51 return $msg; # when used by carp/croak/return value
673             }
674            
675             =begin internal
676            
677             =item _munge_prefs_js
678            
679             $moz->_munge_prefs_js( $profile_path, $prefs_file );
680            
681             =end internal
682            
683             =cut
684            
685             # TODO - test if we really need this. Thunderbird saves the relative
686             # path info, which we use for munging. But we need to check the
687             # behavior, since in the case where we copy profiles, we don't want it
688             # using a valid path but for a different profile.
689            
690             sub _munge_prefs_js {
691 0     0   0 my $self = shift;
692 0         0 my %args = Params(qw( profile_path ?prefs_file ))->args(@_);
693 0         0 my $profd = $args{profile_path};
694 0   0     0 my $filename = $args{prefs_file} || _catfile($profd, "prefs.js");
695            
696 0 0       0 unless (-d $profd) {
697 0         0 return failure $self->_log( "Invalid profile path: $profd" );
698             }
699            
700 0 0       0 unless (-r $filename) {
701 0         0 return failure $self->_log( "Invalid prefs file: $filename" );
702             }
703            
704 0   0     0 my $fh = IO::File->new("<$filename")
705             || return failure $self->_log( "Unable to open file: $filename" );
706            
707 0         0 my $buffer = join("", <$fh>);
708            
709 0 0       0 close $fh ||
710             return failure $self->_log( "Unable to close file: $filename" );
711            
712 0         0 $buffer =~ s/$RE{comment}{Perl}//g;
713 0         0 $buffer =~ s/$RE{comment}{JavaScript}//g;
714            
715 0         0 my %prefs = ( );
716            
717 0         0 local ($_);
718 0         0 foreach (split /\n/, $buffer) {
719 0 0       0 if ($_ =~ /user_pref($RE{balanced}{-parens=>'()'})\;/) {
720 0         0 my $args = $1;
721 0 0       0 if ($args =~ /\(\s*($RE{delimited}{-delim=>'"'}{-esc})\,\s*(.+)\s*\)/) {
722 0         0 my ($pref, $val) = ($1, $2);
723 0         0 $pref = substr($pref,1,-1);
724 0         0 $prefs{$pref} = $val;
725             # print "user_pref(\"$pref\", $val);\n";
726             }
727             else {
728 0         0 return failure $self->_log( "Don\'t know how to handle line: $args" );
729             }
730            
731             }
732             }
733            
734 0         0 my $re = Regexp::Assemble->new();
735 0         0 $re->add(
736             qr/^mail\.root\.pop3$/,
737             qr/^mail\.server\.server\d+\.(directory|newsrc\.file)$/,
738             );
739            
740 0         0 foreach my $pref (keys %prefs) {
741 0 0       0 if ($pref =~ $re->re) {
    0          
742 0 0       0 if (exists $prefs{$pref."-rel"}) {
743 0 0       0 if ($prefs{$pref."-rel"} =~ /\"\[ProfD\](.+)\"/) {
744 0         0 my $path = File::Spec->catdir($profd, $1);
745 0         0 $path =~ s/\\{2,}/\\/g; # unescape multiple slashes
746 0 0       0 unless (-e $path) {
747 0         0 $self->_log( level => "warn",
748             message => "Path does not exist: $path", );
749             }
750 0         0 $path =~ s/\\/\\\\/g; # escape single slashes
751 0         0 $prefs{$pref} = "\"$path\"";
752             }
753             else {
754 0         0 $self->_log( level => "warn",
755             message => "Cannot handle $pref-rel key", );
756             }
757             }
758             else {
759 0         0 $self->_log( level => "warn",
760             message => "Cannot find $pref-rel key", );
761             }
762             }
763             elsif ($pref =~ /\.dir$/) {
764             # TODO - check if directory exists, and if not, give a warning
765             }
766             }
767            
768 0 0       0 if (keys %prefs) {
769 0         0 copy($filename, $filename.".backup");
770 0         0 chmod 0600, $filename.".backup";
771            
772 0   0     0 $fh = IO::File->new(">$filename")
773             || return failure $self->_log ( "Unable to write to $filename" );
774            
775 0         0 print $fh "
776             # Mozilla User Preferences
777            
778             /* Do not edit this file.
779             *
780             * This file was modified by Mozilla::Backup.
781             *
782             * The original is at $filename.backup
783             */
784            
785             ";
786            
787 0         0 foreach my $pref (sort keys %prefs) {
788 0         0 print $fh "user_pref(\"$pref\", $prefs{$pref});\n";
789             }
790            
791 0 0       0 close $fh || return failure $self->_log( "Unable to close $filename" );
792             } else {
793 0         0 return failure $self->_log( "No preferences to save" );
794             }
795            
796 0         0 return success;
797             }
798            
799            
800             =item restore_profile
801            
802             $res = $moz->restore_profile(
803             archive_path => $backup,
804             type => $type,
805             name => $name,
806             is_default => $is_default,
807             munge_prefs => $munge_prefs, # update prefs.js file
808             );
809            
810             $res = $moz->restore_profile($backup,$type,$name,$is_default);
811            
812             Restores the profile at C<$backup>. Returns true if successful,
813             false otherwise.
814            
815             C<$munge_prefs> can only be specified using named parameter calls. If
816             C<$munge_prefs> is true, then it will attempt to correct any absolute
817             paths specified in the F file.
818            
819             Warning: this does not check that it is the correct profile type. It will
820             allow you to restore a profile of a different (and possibly incompatible)
821             type.
822            
823             Potential incompatabilities with extensions are also not handled.
824             See L below.
825            
826             =cut
827            
828             sub restore_profile {
829 1     1 1 3 my $self = shift;
830 1         6 my %args =
831             Params(qw( archive_path type name ?is_default ?+munge_prefs ))->args(@_);
832 1         391 my $path = $args{archive_path};
833 1         3 my $type = $args{type};
834 1         2 my $name = $args{name};
835 1   50     12 my $def = $args{is_default} || 0;
836 1   50     8 my $munge = $args{munge_prefs} || 0;
837            
838 1         4 my $prof = $self->type( type => $type );
839            
840 1 50       5 unless ($prof->profile_exists( name => $name)) {
841 1         7 $self->_log( level => "info",
842             message => "creating new profile: $name\n" );
843            
844 1 50       7 unless ($prof->create_profile(
845             name => $name,
846             is_default => $def )) {
847 0         0 return failure $self->_log( "unable to create profile: $name" );
848             }
849             }
850 1 50       80 unless ($prof->profile_exists( name => $name )) {
851 0         0 return failure $self->_log(
852             "unable to create profile: $name"
853             );
854             }
855            
856 1         8 my $dest = $prof->profile_path( name => $name );
857 1 50       27 unless (-d $dest) {
858 0         0 return failure $self->_log( "invalid profile path$ path" );
859             }
860            
861 1 50       7 if ($prof->profile_is_locked( name => $name )) {
862 0         0 return failure $self->_log( "cannot restore locked profile" );
863             }
864            
865             # Note: the guts of this should be moved to a _restore_profile method
866            
867 1         13 my $exclude = Regexp::Assemble->new( debug => $self->{debug} );
868 1         150 $exclude->add( @{$self->{exclude}} );
  1         9  
869            
870 1 50       893 if ($self->{plugin}->open_for_restore($path)) {
871 1         61 foreach my $file ($self->{plugin}->get_contents) {
872             # TODO:
873             # - an option for overwriting existing files?
874             # - handle relative profile issues!
875            
876 0 0       0 unless ($file =~ $exclude->re) {
877 0 0       0 unless ($self->{plugin}->restore_file($file, $dest)) {
878 0         0 return failure $self->_log( "unable to restore files $file" );
879             }
880             }
881             }
882 1         16 $self->{plugin}->close_restore;
883            
884 1 50       23 if ($munge) {
885 0 0       0 if (my $filename = _catfile($dest, "prefs.js")) {
886 0         0 my $r = $self->_munge_prefs_js(
887             profile_path => $dest,
888             prefs_file => $filename,
889             );
890 0 0       0 return failure $r unless ($r);
891             } else {
892 0         0 $self->_log( level => "warn", message => "Cannot find prefs.js" );
893             }
894             }
895            
896             }
897             else {
898 0         0 return failure $self->_log( "unable to open backup: $path" );
899             }
900            
901 1         17 return success;
902             }
903            
904             # TODO - a separate copy_profile method that copies a profile into another
905             # one for the same application
906            
907             our $AUTOLOAD;
908            
909             sub AUTOLOAD {
910 0     0   0 my $self = shift;
911 0         0 $AUTOLOAD =~ /.*::(\w+)/;
912 0         0 my $meth = $1;
913 0 0       0 if (Mozilla::ProfilesIni->can($meth)) {
914 0         0 carp $self->_log(
915             level => "warn",
916             message => "Warning: deprecated method \"$meth\"",
917             );
918 0 0       0 if ($_[0] eq "type") {
919 0         0 my %args = @_;
920 0         0 my $type = $args{type}; delete $args{type};
  0         0  
921 0         0 return $self->type(type => $type)->$meth(%args);
922             }
923             else {
924 0         0 my @args = @_;
925 0         0 my $type = shift @args;
926 0         0 return $self->type(type => $type)->$meth(@args);
927             }
928             }
929             else {
930 0         0 croak $self->_log(
931             "Unrecognized object method \"$meth\" in \"".__PACKAGE__."\"",
932             );
933             }
934             }
935            
936             # Otherwise AUTOLOAD looks for a DESTROY method
937            
938             sub DESTROY {
939 4     4   1566 my $self = shift;
940 4         393 undef $self;
941             }
942            
943             1;
944            
945             =back
946            
947             =head2 Compatabilty with Earlier Versions
948            
949             The interface has been changed from version 0.04. Various methods for
950             querying profile information were moved into the L
951             module. Code that was of the form
952            
953             $moz->method($type,$name);
954            
955             should be changed to
956            
957             $moz->type($type)->method($name);
958            
959             The older method calls should still work, but are deprecated and will
960             issue warnings. (Support for them will be removed in some future
961             version.)
962            
963             See the L method for more information.
964            
965             =for readme continue
966            
967             =begin readme
968            
969             =head1 REVISION HISTORY
970            
971             The following changes have been made since the last release:
972            
973             =for readme include type=text file=Changes start=0.06 stop=0.05
974            
975             Details can be found in the Changes file.
976            
977             =end readme
978            
979             =head1 KNOWN ISSUES
980            
981             This module is a prototype. Use at your own risk!
982            
983             Not all of the profile types have been tested, and are implemented
984             based on information gleaned from sources which may or may not be
985             accurate.
986            
987             The current version of this module only copies files and does little
988             manipulation of any files, except for the F and F
989             to update some pathnames. This means that information specific to a
990             profile on a machine such as extensions and themes is kept as-is, which
991             may not be a good thing if a profile is restored to a different location
992             or machine, or even application.
993            
994             (By default cache files are excluded from backups; there may be problems
995             if cache files are restored to incompatible applications or machines.)
996            
997             =for readme stop
998            
999             =head2 To Do List
1000            
1001             A list of to-do items, in no particular order:
1002            
1003             =over
1004            
1005             =item Meta-data
1006            
1007             Save meta-data about backups (such as profile type, file locations, platform)
1008             so that file-restoration can make the appropriate conversions.
1009            
1010             =item Improved Parameter Checking
1011            
1012             Improve parameter type and value checking.
1013            
1014             =item Tests
1015            
1016             The test suite needs improved coverage. Sample profiles should be included
1017             for more thorough testing.
1018            
1019             =item User-friendly Exclusion Lists
1020            
1021             User-friendly exclusion lists (via another module?). Exclusion by categories
1022             (privacy protection, E-mail, Bookmarks, etc.).
1023            
1024             =item Standardize Log Messages
1025            
1026             Have a standard format (case, puntuation etc.) for log messages. Also
1027             standardize error levels (error, alert, critical, etc.).
1028            
1029             Possiblly add hooks for internationalisation of messages.
1030            
1031             =item Other
1032            
1033             Other "TODO" items marked in source code.
1034            
1035             =back
1036            
1037             =for readme continue
1038            
1039             =head1 SEE ALSO
1040            
1041             Mozilla web site at L.
1042            
1043             =for readme stop
1044            
1045             MozillaZine KnowledgeBase article on Profiles at
1046             L.
1047            
1048             Mozilla Profile Service source code at
1049             L.
1050            
1051             =for readme continue
1052            
1053             =head1 AUTHOR
1054            
1055             Robert Rothenberg
1056            
1057             =head2 Suggestions and Bug Reporting
1058            
1059             Feedback is always welcome. Please use the CPAN Request Tracker at
1060             L to submit bug reports.
1061            
1062             There is now a SourceForge project for this module at
1063             L.
1064            
1065             =head1 LICENSE
1066            
1067             Copyright (c) 2005 Robert Rothenberg. All rights reserved.
1068             This program is free software; you can redistribute it and/or
1069             modify it under the same terms as Perl itself.
1070            
1071             =cut
1072            
1073            
1074            
1075