File Coverage

blib/lib/Mozilla/ProfilesIni.pm
Criterion Covered Total %
statement 186 228 81.5
branch 54 102 52.9
condition 6 13 46.1
subroutine 25 26 96.1
pod 10 10 100.0
total 281 379 74.1


line stmt bran cond sub pod time code
1             =head1 NAME
2            
3             Mozilla::ProfilesIni - Manipulation of Mozilla F files
4            
5             =begin readme
6            
7             =head1 REQUIREMENTS
8            
9             The following non-core modules are required:
10            
11             Config::IniFiles
12             Log::Dispatch
13             Params::Smart
14             Return::Value
15            
16             =end readme
17            
18             =head1 SYNOPSIS
19            
20             $path = Mozilla::ProfilesIni::_find_profile_path(
21             home => $ENV{HOME},
22             type => "firefox"
23             );
24            
25             $ini = Mozilla::ProfilesIni->( path => $path );
26            
27            
28             =head1 DESCRIPTION
29            
30             This module provides routines for parsing and manipulating Mozilla
31             F files.
32            
33             The following methods are implemented:
34            
35             =cut
36            
37             package Mozilla::ProfilesIni;
38            
39             require 5.006;
40            
41 6     6   1802 use strict;
  6         13  
  6         203  
42 6     6   33 use warnings;
  6         12  
  6         176  
43            
44 6     6   29 use Carp;
  6         10  
  6         429  
45 6     6   7521 use Config::IniFiles;
  6         120411  
  6         229  
46 6     6   74 use File::Find;
  6         12  
  6         342  
47 6     6   36 use File::Spec;
  6         12  
  6         120  
48 6     6   1047 use Log::Dispatch;
  6         18950  
  6         162  
49 6     6   6446 use Params::Smart 0.04;
  6         58636  
  6         497  
50 6     6   5683 use Return::Value;
  6         7695  
  6         17050  
51            
52             # $Revision: 1.14 $
53            
54             our $VERSION = '0.02';
55            
56             =over
57            
58             =item new
59            
60             $ini = Mozilla::ProfilesIni->new( $path );
61            
62             $ini = Mozilla::ProfilesIni->new( path => $path, %options );
63            
64             The following options are supported:
65            
66             =over
67            
68             =item path
69            
70             Path to where the F file is (excluding the actual
71             F file). This is a required option.
72            
73             =item log
74            
75             A L object for receiving log messages.
76            
77             =item debug
78            
79             Sets an internal debug flag. (Not implemented at this time.)
80            
81             =item create
82            
83             Create a new F file in L.
84            
85             =back
86            
87             =cut
88            
89             sub new {
90 5   50 5 1 1418 my $class = shift || __PACKAGE__;
91            
92             my %args = Params(
93             {
94             name => "path",
95             required => 1,
96             name_only => 1,
97             },
98             {
99             name => "log",
100             default => Log::Dispatch->new(),
101             callback => sub {
102 0     0   0 my ($self, $name, $log) = @_;
103 0 0 0     0 croak "invalid log sink"
104             unless ((ref $log) && $log->isa("Log::Dispatch"));
105 0         0 return $log;
106             },
107 5         55 name_only => 1,
108             required => 0,
109             },
110             {
111             name => "debug",
112             default => 0,
113             name_only => 1,
114             },
115             {
116             name => "create",
117             default => 0,
118             name_only => 1,
119             },
120             )->args(@_);
121            
122 5         1408 my $self = {
123             profiles => { },
124             };
125            
126 5         8 local ($_);
127            
128 5         68 foreach (qw( log debug create )) {
129 15         40 $self->{$_} = $args{$_};
130             }
131            
132 5         15 bless $self, $class;
133            
134 5 100       33 if ($self->{create}) {
135 3         19 my $r = $self->_create_profile_ini( path => $args{path}, ignore => 1 );
136 3 50       224 croak $r unless ($r);
137             }
138            
139 5         302 my $r = $self->_read_profile_ini( path => $args{path} );
140 5 100       196 croak $r unless ($r);
141            
142 4         134 return $self;
143             }
144            
145             =begin internal
146            
147             =item _create_profile_ini
148            
149             $ini->_create_profile_ini( path => $path, ignore => $ignore );
150            
151             $ini->_create_profile_ini( $path );
152            
153             Creates a new F file in C<$path>.
154            
155             By default it will die if a profiles file already exists, unless the
156             ignore flag is specified explicitly, in which case it will return
157             without creating a profile (or complaining).
158            
159             =end internal
160            
161             =cut
162            
163             sub _create_profile_ini {
164 3     3   4 my $self = shift;
165 3         13 my %args = Params(qw( path ?+ignore ))->args(@_);
166 3         566 my $path = File::Spec->rel2abs($args{path});
167            
168 3 50       91 unless (-d $path) {
169 0         0 return failure $self->_log(
170             "cannot access psuedo profile directory: $path" );
171             }
172            
173 3         44 my $ini_file = File::Spec->catfile($path, "profiles.ini" );
174 3 50       113 if (-e $ini_file) {
175 0 0       0 if ($args{ignore}) {
176 0         0 return success;
177             } else {
178 0         0 return failure $self->_log(
179             "a profile exists already at $path" );
180             }
181             } else {
182 3         180 my $cfg = Config::IniFiles->new();
183 3         493 $cfg->AddSection("General");
184 3         697 $cfg->newval("General", "StartWithLastProfile", "");
185            
186 3 50       476 unless ($cfg->WriteConfig( $ini_file )) {
187 0         0 return failure $self->_log(
188             "unable to create pseudo configuration" );
189             }
190            
191 3 50       4125 unless (-e $ini_file) {
192 0         0 return failure $self->_log(
193             "unexpected error in creating pseudo configuration" );
194             }
195             }
196 3         22 return success;
197             }
198            
199             =begin internal
200            
201             =item _read_profile_ini
202            
203             $ini->_read_profile_ini( path => $path );
204            
205             $ini->_read_profile_ini( $path );
206            
207             Parses the F in C<$path>.
208            
209             This is called automatically by L.
210            
211             =end internal
212            
213             =cut
214            
215             sub _read_profile_ini {
216 5     5   11 my $self = shift;
217 5         21 my %args = Params(qw( path ))->args(@_);
218 5         809 my $path = File::Spec->rel2abs($args{path});
219            
220 5         11 local ($_);
221            
222 5         13 $self->{profiles} = { };
223            
224 5 100       20 if (my $ini_file = _catfile($path, "profiles.ini")) {
225 4         12 $self->{ini_file} = $ini_file;
226 4         44 my $cfg = Config::IniFiles->new( -file => $ini_file );
227 4 50       4736 if ($cfg) {
228 4         20 my $start_with_last = $cfg->val("General","StartWithLastProfile","");
229            
230 4         96 my $name = "";
231 4         9 my $i = 0;
232 4         22 while (my $profile = $cfg->val("Profile$i","Path")) {
233            
234 1         23 my $profile_path = _catdir($path, $profile);
235 1 50       6 $profile_path = _catdir($profile) unless ($profile_path);
236            
237 1 50       3 if ($profile_path) {
238 1         5 my $data = {
239             ProfileId => "Profile$i",
240             Path => $profile_path,
241             };
242            
243 1 50       6 unless ($name = $cfg->val("Profile$i", "Name")) {
244 0         0 return failure $self->_log(
245             "No name is defined for Profile$i");
246             }
247            
248             # In nsToolkitProfileService.cpp, flags are "1" or ""
249            
250 1         23 foreach (qw( Name IsRelative Default )) {
251 3         79 $data->{$_} = $cfg->val("Profile$i",$_, "");
252             }
253            
254 1         21 $self->{profiles}->{$name} = $data;
255            
256             } else {
257             # Do we warn instead of exit if there's bad data in profiles.ini?
258 0         0 return failure $self->_log(
259             "Bad Path: $profile_path not a directory");
260             }
261 1         5 $i++;
262             }
263 4 100 66     151 if ($start_with_last && $name) {
264 1         16 $self->{profiles}->{$name}->{Default} = "1";
265             }
266             } else {
267 0         0 return failure $self->_log(
268             "Bad INI file: $ini_file");
269             }
270             } else {
271 1         6 return failure $self->_log(
272             "Cannot find profiles.ini in $path" );
273             }
274 4         20 return success;
275             }
276            
277             =item create_profile
278            
279             $ini->create_profile( name => $name, is_default => $def, path => $path );
280            
281             $ini->create_profile( $name, $def, $path );
282            
283             Creates a profile named C<$name> in C<$path>. If C<$path> is not
284             specified, it creates a relative profile in the F
285             subdirectory below the F file.
286            
287             =cut
288            
289             sub create_profile {
290 4     4 1 9 my $self = shift;
291 4         17 my %args = Params(qw( name ?is_default ?path ))->args(@_);
292 4         1005 my $name = $args{name};
293 4 100       21 my $def = $args{is_default} ? "1" : "";
294 4         8 my $path = $args{path};
295            
296 4         10 local ($_);
297            
298 4 100 100     22 unless ($def || (keys %{$self->{profiles}})) {
  2         15  
299 1         2 $def = "1";
300 1         5 $self->_log( level => "info",
301             message => "the only profile must be default" );
302             }
303            
304            
305 4         17 my $ini_file = $self->ini_file();
306 4 50       138 if (-r $ini_file) {
307 4         53 my @dirs = File::Spec->splitdir($ini_file);
308 4         54 my $prof = File::Spec->catdir( @dirs[0..$#dirs-1], "Profiles" );
309 4 100       123 unless (-d $prof) {
310 3         25 $self->_log( level => "info",
311             message => "creating directory $prof\n" );
312 3 50       361 unless (mkdir $prof) {
313 0         0 return failure $self->_log(
314             "unable to create directory $prof" );
315             }
316            
317             # TODO - option whether to set perms; also a portable chmod
318            
319 3         121 chmod 0700, $prof;
320             }
321            
322             # create a unique name
323            
324             # Note: Mozilla-style is to use "Profiles/$name/$random.slt" rather
325             # than "Profiles/$random.$name"
326            
327 4         9 my $dir;
328 4 50       25 unless ($path) {
329 4         7 do {
330 4         11 $dir = "";
331 4         15 for (1..8) { $dir .= ("a".."z","0".."9")[int(rand(36))]; }
  32         76  
332 4         12 $dir .= "." . $name;
333 4         180 $path = File::Spec->catdir($prof, $dir);
334             } while (-d $path);
335             }
336            
337 4         35 $self->_log( level => "info",
338             message => "creating directory $path\n" );
339 4 50       392 unless (mkdir $path) {
340 0         0 return failure $self->_log(
341             "unable to create directory $path" );
342             }
343 4         113 chmod 0700, $path;
344            
345             # BUG/TODO - We need to check how Mozilla etc. handles profile ids
346            
347 4         9 my $id = "Profile" . scalar( keys %{$self->{profiles}} );
  4         20  
348            
349 4         10 foreach (keys %{$self->{profiles}}) {
  4         17  
350 1 50       8 if ($self->{profiles}->{$_}->{ProfileId} eq $id) {
351 0         0 return failure $self->_log(
352             "Profile Id conflict" );
353             }
354             }
355            
356 4         447 my $cfg = Config::IniFiles->new( -file => $ini_file );
357            
358             # update profile default flags
359            
360 4         4934 foreach (keys %{$self->{profiles}}) {
  4         24  
361 1         4 my $data = $self->{profiles}->{$_};
362 1 50       5 $data->{Default} = "", if ($def);
363 1 50       7 if (defined $cfg->val($data->{ProfileId}, "Default")) {
364 1         28 $cfg->setval($data->{ProfileId}, "Default", $data->{Default});
365             } else {
366 0         0 $cfg->newval($data->{ProfileId}, "Default", $data->{Default});
367             }
368             }
369            
370 4 100       326 if ($def) {
371 3         18 $cfg->setval("General", "StartWithLastProfile", "1");
372             }
373             else {
374 1         6 $cfg->setval("General", "StartWithLastProfile", "");
375             }
376            
377 4 50       240 my $data = {
    50          
378             ProfileId => $id,
379             Name => $name,
380             IsRelative => (($dir) ? "1" : ""),
381             Default => $def,
382             Path => (($dir) ? ("Profiles/" . $dir) : $path),
383             };
384            
385 4         17 $cfg->AddSection($id);
386 4         311 foreach (qw( Name IsRelative Path Default )) {
387 16         953 $cfg->newval($id, $_, $data->{$_});
388             }
389            
390 4         305 $data->{Path} = $path;
391 4         12 $self->{profiles}->{$name} = $data;
392            
393             # TODO/BUG? - Make sure IsRelative paths are not changed to
394             # absolute paths when rewritten!
395            
396 4 50       22 unless ($cfg->RewriteConfig) {
397 0         0 return failure $self->_log(
398             "Unable to update INI file" );
399             }
400             }
401             else {
402 0         0 return failure $self->_log(
403             "cannot find INI file $ini_file" );
404             }
405 4         4665 return success;
406             }
407            
408             =item ini_file
409            
410             $path = $ini->ini_file();
411            
412             Returns the path to the F file.
413            
414             =cut
415            
416             sub ini_file {
417 8     8 1 1079 my $self = shift;
418 8         144 return $self->{ini_file};
419             }
420            
421             =item profile_names
422            
423             @names = $ini->profile_names($type);
424            
425             Returns the names of profiles associated with the type.
426            
427             =cut
428            
429             sub profile_names {
430 4     4 1 9 my $self = shift;
431 4         8 return (keys %{$self->{profiles}});
  4         26  
432             }
433            
434             =item profile_exists
435            
436             if ($ini->profile_exists($name)) { ... }
437            
438             Returns true if a profile exists.
439            
440             =cut
441            
442             sub profile_exists {
443 12     12 1 26 my $self = shift;
444 12         48 my %args = Params(qw( name ))->args(@_);
445 12         889 my $name = $args{name};
446 12         79 return (exists $self->{profiles}->{$name});
447             }
448            
449             =item profile_is_relative
450            
451             if ($ini->profile_is_relative($name)) { ... }
452            
453             Returns the "IsRelative" flag for the profile.
454            
455             =cut
456            
457             sub profile_is_relative {
458 6     6 1 16 my $self = shift;
459 6         23 my %args = Params(qw( name ))->args(@_);
460 6         525 my $name = $args{name};
461             # TODO - validate profile name
462 6         68 return $self->{profiles}->{$name}->{IsRelative};
463             }
464            
465             =item profile_path
466            
467             $path = $ini->profile_path($name);
468            
469             Returns the pathname of the profile.
470            
471             =cut
472            
473             sub profile_path {
474 30     30 1 45 my $self = shift;
475 30         86 my %args = Params(qw( name ))->args(@_);
476 30         1691 my $name = $args{name};
477            
478             # TODO - validate profile name
479            
480 30         65 my $path = $self->{profiles}->{$name}->{Path};
481             # if ($self->profile_is_relative($name) && (!-d $path)) {
482             # my @dirs = File::Spec->splitdir( $self->ini_file );
483             # $path = File::Spec->catdir( @dirs[0..$#dirs-1], $path );
484             # }
485 30         408 return $path;
486             }
487            
488             =item profile_is_default
489            
490             if ($ini->profile_is_default($name)) { ... }
491            
492             Returns the "Default" flag for the profile.
493            
494             =cut
495            
496             sub profile_is_default {
497 4     4 1 9 my $self = shift;
498 4         14 my %args = Params(qw( name ))->args(@_);
499 4         262 my $name = $args{name};
500 4         23 return $self->{profiles}->{$name}->{Default};
501             }
502            
503             =item profile_id
504            
505             $section = $ini->profile_id($name);
506            
507             Returns the L identifier of the profile.
508            
509             =cut
510            
511             sub profile_id {
512 5     5 1 14 my $self = shift;
513 5         18 my %args = Params(qw( name ))->args(@_);
514 5         389 my $name = $args{name};
515 5         43 return $self->{profiles}->{$name}->{ProfileId};
516             }
517            
518             =item profile_is_locked
519            
520             if ($ini->profile_is_locked($name)) { ... }
521            
522             Returns true if there is a lock file in the profile.
523            
524             =cut
525            
526             sub profile_is_locked {
527 7     7 1 16 my $self = shift;
528 7         27 my %args = Params(qw( name ))->args(@_);
529 7         463 my $name = $args{name};
530 7         17 foreach ('parent.lock', 'lock', '.parentlock') {
531 21 50       51 if (_catfile($self->profile_path(name => $name), $_ )) {
532 0         0 return 1;
533             }
534             }
535 7         38 return;
536             }
537            
538             =begin internal
539            
540             =item _catdir
541            
542             $path = _catdir( @names );
543            
544             Returns the C<$path> if the concatenation of C<@names> exists as a
545             directory, or C otherwise.
546            
547             =item _catfile
548            
549             $path = _catdir( @names );
550            
551             Returns the C<$path> if the concatenation of C<@names> exists as a
552             file, or C otherwise.
553            
554             =end internal
555            
556             =cut
557            
558             sub _catdir {
559 249 100   249   2064 if ($_[0]) { # otherwise blank "" is translated to root directory
560 169         799 my $path = File::Spec->catdir(@_);
561 169 100       1950 return (-d $path) ? $path : undef;
562             }
563             else {
564 80         198 return;
565             }
566             }
567            
568             sub _catfile {
569 31     31   1396 my $path = File::Spec->catfile(@_);
570 31 100       999 return (-r $path) ? $path : undef;
571             }
572            
573             =item _find_profile_path
574            
575             $path = _find_profile_path( home => $home, type => $type );
576            
577             $path = _find_profile_path( $home, $type );
578            
579             Looks for a directory corresponding to where profile type of C<$type>
580             should be, generally somewhere in the C<$home> directory, where
581             C<$home> is the platform-specific "home" directory (not necessarily
582             C<$ENV{HOME}>).
583            
584             Returns C if no path for that type was found.
585            
586             In cases where profile paths cannot be found, use the C
587             or C environment variable to indicate where it is.
588            
589             =cut
590            
591             sub _find_profile_path {
592 40     40   89 my %args = Params(qw( home type ))->args(@_);
593 40         2632 my $home = $args{home};
594 40         78 my $type = $args{type};
595            
596             # Known Issue: the first profile that it finds for a type is the one
597             # it uses. If for some reason there are profiles for the same
598             # application in multiple places (maybe due to an upgrade), it will
599             # use the first that it finds.
600            
601 40         36 my $path;
602            
603             # The MOZILLA_HOME environment variables are for OS/2, but putting
604             # them here first allows one to override settings.
605            
606             # Problem? On some OSs, these may indicate where Mozilla binaries are,
607             # and not profiles!
608            
609 40 50       144 if ($path = _catdir($ENV{uc($type)."_HOME"})) {
610 0         0 return $path;
611             }
612 40 50       151 if ($path = _catdir($ENV{MOZILLA_HOME}, ucfirst($type))) {
613 0         0 return $path;
614             }
615             # if ($path = _catdir($ENV{MOZILLA_FIVE_HOME})) {
616             # return $path;
617             # }
618             # if ($path = _catdir($ENV{MOZILLA_FIVE_HOME}, ucfirst($type))) {
619             # return $path;
620             # }
621            
622 40 50       98 if ($path = _catdir($home, "\.$type")) {
623 0         0 return $path;
624             }
625 40 50       84 if ($path = _catdir($home, "\.mozilla", $type)) {
626 0         0 return $path;
627             }
628 40 50       95 if ($path = _catdir($home, ucfirst($type))) {
629 0         0 return $path;
630             }
631 40 50       96 if ($path = _catdir($home, "Mozilla", ucfirst($type))) {
632 0         0 return $path;
633             }
634            
635 40 50       172 if ($^O eq "darwin") {
    50          
    50          
636 0 0       0 if ($path = _catdir($home, "Library", "Application Support",
637             ucfirst($type))) {
638 0         0 return $path;
639             }
640 0 0       0 if ($path = _catdir($home, "Library", "Application Support",
641             "Mozilla", ucfirst($type))) {
642 0         0 return $path;
643             }
644 0 0       0 if ($path = _catdir($home, "Library", ucfirst($type))) {
645 0         0 return $path;
646             }
647 0 0       0 if ($path = _catdir($home, "Library", "Mozilla", ucfirst($type))) {
648 0         0 return $path;
649             }
650             }
651             elsif ($^O eq "MSWin32") {
652 0   0     0 my $program_files = $ENV{ProgramFiles} || "Program Files";
653 0 0       0 if ($path = _catdir($program_files, ucfirst($type))) {
654 0         0 return $path;
655             }
656 0 0       0 if ($path = _catdir($program_files, "Mozilla", ucfirst($type))) {
657 0         0 return $path;
658             }
659             }
660            
661             # If we're here in Cygwin, it means that Mozilla builds are probably
662             # native-Windows instead of Cygwin. So we need to look in the
663             # Windows native drive.
664            
665             # Known Issue: if you have separate Cygwin and Windows Moz profiles,
666             # then it will recognize the Cygwin profile first.
667            
668             elsif ($^O eq "cygwin") {
669 0 0       0 if ((caller(1))[3] !~ /_find_profile_path$/) {
670 0         0 $home = $ENV{APPDATA}; # Win 2000/XP/2003
671 0         0 $home =~ s/^(\w):/\/cygdrive\/$1/;
672 0         0 return _find_profile_path($home,$type);
673             }
674             }
675            
676 40         275 return;
677             }
678            
679             =begin internal
680            
681             =item _log
682            
683             $moz->_log( $message, $level );
684            
685             $moz->_log( $message => $message, level => $level );
686            
687             Logs an event to the dispatcher. If C<$level> is unspecified, "error"
688             is assumed.
689            
690             =end internal
691            
692             =cut
693            
694             sub _log {
695 9     9   20 my $self = shift;
696 9         27 my %args = Params(qw( message ?level=error ))->args(@_);
697 9         2404 my $msg = $args{message};
698            
699             # we want log messages to always have a newline, but not necessarily
700             # the returned value that we pass to carp/croak/return value
701            
702 9 100       74 $args{message} .= "\n" unless ($args{message} =~ /\n$/);
703 9 50       87 $self->{log}->log(%args) if ($self->{log});
704 9         204 return $msg; # when used by carp/croak/return value
705             }
706            
707             =back
708            
709             =head1 CAVEATS
710            
711             This module is a prototype. Use at your own risk!
712            
713             =head1 SEE ALSO
714            
715             L
716            
717             =head1 AUTHOR
718            
719             Robert Rothenberg
720            
721             =head1 LICENSE
722            
723             Copyright (c) 2005 Robert Rothenberg. All rights reserved.
724             This program is free software; you can redistribute it and/or
725             modify it under the same terms as Perl itself.
726            
727             =cut
728            
729             1;
730            
731             __END__