File Coverage

blib/lib/Mail/Box/Mbox.pm
Criterion Covered Total %
statement 103 105 98.1
branch 45 54 83.3
condition 34 54 62.9
subroutine 15 15 100.0
pod 8 9 88.8
total 205 237 86.5


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Box version 4.01.
2             # The POD got stripped from this file by OODoc version 3.05.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Box::Mbox;{
13             our $VERSION = '4.01';
14             }
15              
16 25     25   22630 use parent 'Mail::Box::File';
  25         645  
  25         199  
17              
18 25     25   1743 use strict;
  25         75  
  25         637  
19 25     25   120 use warnings;
  25         46  
  25         1599  
20              
21 25     25   141 use Log::Report 'mail-box', import => [ qw// ];
  25         69  
  25         145  
22              
23 25     25   21798 use Mail::Box::Mbox::Message ();
  25         76  
  25         813  
24              
25 25     25   163 use File::Spec::Functions qw/catdir catfile/;
  25         51  
  25         45560  
26              
27             #--------------------
28              
29             our $default_folder_dir = exists $ENV{HOME} ? $ENV{HOME} . '/Mail' : '.';
30             our $default_sub_extension = '.d';
31              
32             sub init($)
33 49     49 0 666 { my ($self, $args) = @_;
34 49   66     1593 $self->{MBM_sub_ext} = $args->{subfolder_extension} || $default_sub_extension;
35 49         344 $self->SUPER::init($args);
36             }
37              
38              
39             sub create($@)
40 11     11 1 127 { my ($thingy, $name, %args) = @_;
41 11   33     72 my $class = ref $thingy || $thingy;
42 11   33     44 $args{folderdir} ||= $default_folder_dir;
43 11   66     270 $args{subfolder_extension} ||= $default_sub_extension;
44              
45 11         95 $class->SUPER::create($name, %args);
46             }
47              
48             #--------------------
49              
50 87     87 1 1016 sub subfolderExtension() { $_[0]->{MBM_sub_ext} }
51              
52             sub delete(@)
53 12     12 1 31 { my $self = shift;
54 12         111 $self->SUPER::delete(@_);
55              
56 12         85 my $subfdir = $self->filename . $default_sub_extension;
57 12         402 rmdir $subfdir; # may fail, when there are still subfolders (no recurse)
58             }
59              
60             sub writeMessages($)
61 14     14 1 49 { my ($self, $args) = @_;
62 14         141 $self->SUPER::writeMessages($args);
63              
64 14 50       179 if($self->removeEmpty)
65             { # Can the sub-folder directory be removed? Don't mind if this
66             # doesn't work: probably no subdir or still something in it. This
67             # is a rather blunt approach...
68 14         97 rmdir $self->filename . $self->subfolderExtension;
69             }
70              
71 14         73 $self;
72             }
73              
74             sub type() {'mbox'}
75              
76             #--------------------
77              
78             sub listSubFolders(@)
79 20     20 1 6450 { my ($thingy, %args) = @_;
80 20   66     86 my $class = ref $thingy || $thingy;
81              
82 20   100     96 my $skip_empty = $args{skip_empty} || 0;
83 20   100     86 my $check = $args{check} || 0;
84 20   100     67 my $folder = $args{folder} // '=';
85 20   66     75 my $folderdir = $args{folderdir} // $default_folder_dir;
86 20         38 my $extension = $args{subfolder_extension};
87              
88 20         32 my $dir;
89 20 100       51 if(ref $thingy) # Mail::Box::Mbox
90 13   33     61 { $extension ||= $thingy->subfolderExtension;
91 13         52 $dir = $thingy->filename;
92             }
93             else
94 7   33     27 { $extension ||= $default_sub_extension;
95 7         18 $dir = $class->folderToFilename($folder, $folderdir, $extension);
96             }
97              
98 20 100       400 my $real = -d $dir ? $dir : "$dir$extension";
99 20 100       966 opendir my $dh, $real or return ();
100              
101             # Some files have to be removed because they are created by all
102             # kinds of programs, but are no folders.
103              
104 11         472 my @entries = grep !m/\.lo?ck$|^\./, readdir $dh;
105 11         130 closedir $dh;
106              
107             # Look for files in the folderdir. They should be readable to
108             # avoid warnings for usage later. Furthermore, if we check on
109             # the size too, we avoid a syscall especially to get the size
110             # of the file by performing that check immediately.
111              
112 11         21 my %folders; # hash to immediately un-double names.
113              
114 11         30 foreach my $b (@entries)
115 48         171 { my $entry = catfile $real, $b;
116 48 100       482 if( -f $entry )
    50          
117 34 100 100     124 { next if $args{skip_empty} && ! -s _;
118 33 100 100     81 next if $args{check} && !$class->foundIn($entry);
119 32         80 $folders{$b}++;
120             }
121             elsif( -d _ )
122             { # Directories may create fake folders.
123 14 100       26 if($args{skip_empty})
124 3 50       79 { opendir my $dh, $entry or next;
125 3         69 my @sub = grep !/^\./, readdir $dh;
126 3         24 closedir $dh;
127 3 100       15 @sub or next;
128             }
129              
130 13         89 my $folder = $b =~ s/$extension$//r;
131 13         31 $folders{$folder}++;
132             }
133             }
134              
135 11   33     295 map +(m/(.*)/ && $1), keys %folders; # untained names
136             }
137              
138             sub openRelatedFolder(@)
139 11     11 1 24 { my $self = shift;
140 11         40 $self->SUPER::openRelatedFolder(subfolder_extension => $self->subfolderExtension, @_);
141             }
142              
143             #--------------------
144              
145             sub folderToFilename($$;$)
146 88     88 1 336 { my ($thingy, $name, $folderdir, $extension) = @_;
147 88 50 66     466 $extension ||= ref $thingy ? $thingy->subfolderExtension : $default_sub_extension;
148              
149 88         491 $name =~ s#^=#$folderdir/#;
150 88         390 my @parts = split m!/!, $name;
151              
152 88         190 my $real = shift @parts;
153 88 100       299 $real = '/' if $real eq '';
154              
155 88 100       303 if(@parts)
156 79         164 { my $file = pop @parts;
157 79 100       3363 $real = catdir $real.(-d $real ? '' : $extension), $_ for @parts;
158 79 100       1540 $real = catfile $real.(-d $real ? '' : $extension), $file;
159             }
160              
161 88         599 $real;
162             }
163              
164              
165             sub foundIn($@)
166 21     21 1 313940 { my $class = shift;
167 21 100       90 my $name = @_ % 2 ? shift : undef;
168 21         122 my %args = @_;
169 21 50 66     104 $name ||= $args{folder} or return;
170              
171 21   66     87 my $folderdir = $args{folderdir} || $default_folder_dir;
172 21   33     129 my $extension = $args{subfolder_extension} || $default_sub_extension;
173 21         112 my $filename = $class->folderToFilename($name, $folderdir, $extension);
174              
175 21 100       240 if(-d $filename)
176             { # Maildir and MH Sylpheed have a 'new' sub-directory
177 7 100       405 return 0 if -d catdir $filename, 'new';
178 5 50       308 if(opendir my $dir, $filename)
179 5         404 { my @f = grep !/^\./, readdir $dir; # skip . .. and hidden
180 5 100 66     261 return 0 if @f && ! grep /\D/, @f; # MH
181 1         80 closedir $dir;
182             }
183              
184 1 50       43 return 0 # Other MH
185             if -f "$filename/.mh_sequences";
186              
187 0         0 return 1; # faked empty Mbox sub-folder (with subsub-folders?)
188             }
189              
190 14 100       125 return 0 unless -f $filename;
191 13 100       161 return 1 if -z $filename; # empty folder is ok
192              
193 12 50       582 open my $file, '<:raw', $filename or return 0;
194 12         29 local $_;
195 12         551 while(<$file>)
196 12 50       86 { next if /^\s*$/; # skip empty lines
197 12         126 $file->close;
198 12         416 return substr($_, 0, 5) eq 'From '; # found Mbox separator?
199             }
200              
201 0           return 1;
202             }
203              
204             #--------------------
205              
206             1;