File Coverage

blib/lib/Mail/Box.pm
Criterion Covered Total %
statement 323 394 81.9
branch 150 232 64.6
condition 58 106 54.7
subroutine 59 82 71.9
pod 46 53 86.7
total 636 867 73.3


line stmt bran cond sub pod time code
1             # Copyrights 2001-2019 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Mail-Box. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Mail::Box;
10 34     34   2474 use vars '$VERSION';
  34         55  
  34         1421  
11             $VERSION = '3.008';
12              
13 34     34   161 use base 'Mail::Reporter';
  34         45  
  34         18190  
14              
15 34     34   217 use strict;
  34         107  
  34         796  
16 34     34   177 use warnings;
  34         86  
  34         1037  
17              
18 34     34   11998 use Mail::Box::Message;
  34         85  
  34         1011  
19 34     34   11659 use Mail::Box::Locker;
  34         70  
  34         901  
20 34     34   192 use File::Spec;
  34         63  
  34         916  
21              
22 34     34   140 use Carp;
  34         56  
  34         1660  
23 34     34   167 use Scalar::Util 'weaken';
  34         63  
  34         1238  
24 34     34   177 use List::Util qw/sum first/;
  34         82  
  34         1992  
25 34     34   200 use Devel::GlobalDestruction 'in_global_destruction';
  34         70  
  34         263  
26              
27              
28             #-------------------------------------------
29              
30              
31 0     0   0 use overload '@{}' => sub { shift->{MB_messages} }
32             , '""' => 'name'
33 34     34   4706 , 'cmp' => sub {$_[0]->name cmp "${_[1]}"};
  34     42   67  
  34         387  
  42         78  
34              
35             #-------------------------------------------
36              
37              
38             sub new(@)
39 84     84 1 7483 { my $class = shift;
40              
41 84 50       270 if($class eq __PACKAGE__)
42 0         0 { my $package = __PACKAGE__;
43              
44 0         0 croak <
45             You should not instantiate $package directly, but rather one of the
46             sub-classes, such as Mail::Box::Mbox. If you need automatic folder
47             type detection then use Mail::Box::Manager.
48             USAGE
49             }
50              
51 84         460 my %args = @_;
52 84         374 weaken $args{manager}; # otherwise, the manager object may live too long
53              
54 84 100       527 my $self = $class->SUPER::new
55             ( @_
56             , init_options => \%args # for clone
57             ) or return;
58              
59             $self->read or return
60 83 100 50     856 if $self->{MB_access} =~ /r|a/;
61              
62 83         457 $self;
63             }
64              
65             sub init($)
66 84     84 0 179 { my ($self, $args) = @_;
67              
68 84 50       274 return unless defined $self->SUPER::init($args);
69              
70 84         1495 my $class = ref $self;
71 84   33     260 my $foldername = $args->{folder} || $ENV{MAIL};
72 84 50       220 unless($foldername)
73 0         0 { $self->log(ERROR => "No folder name specified.");
74 0         0 return;
75             }
76              
77 84         184 $self->{MB_foldername} = $foldername;
78 84         175 $self->{MB_init_options} = $args->{init_options};
79 84   50     372 $self->{MB_coerce_opts} = $args->{coerce_options} || [];
80 84   100     283 $self->{MB_access} = $args->{access} || 'r';
81             $self->{MB_remove_empty}
82 84 50       513 = defined $args->{remove_when_empty} ? $args->{remove_when_empty} : 1;
83              
84             $self->{MB_save_on_exit}
85 84 100       257 = defined $args->{save_on_exit} ? $args->{save_on_exit} : 1;
86              
87 84         181 $self->{MB_messages} = [];
88 84         165 $self->{MB_msgid} = {};
89 84   50     341 $self->{MB_organization} = $args->{organization} || 'FILE';
90 84         302 $self->{MB_linesep} = "\n";
91 84   66     313 $self->{MB_keep_dups} = !$self->writable || $args->{keep_dups};
92 84         207 $self->{MB_fix_headers} = $args->{fix_headers};
93              
94 84         299 my $folderdir = $self->folderdir($args->{folderdir});
95             $self->{MB_trusted} = exists $args->{trusted} ? $args->{trusted}
96 84 50       555 : substr($foldername, 0, 1) eq '=' ? 1
    100          
    50          
97             : !defined $folderdir ? 0
98             : substr($foldername, 0, length $folderdir) eq $folderdir;
99              
100 84 100       268 if(exists $args->{manager})
101 39         80 { $self->{MB_manager} = $args->{manager};
102 39         90 weaken($self->{MB_manager});
103             }
104              
105             my $message_type = $self->{MB_message_type}
106 84   33     455 = $args->{message_type} || $class . '::Message';
107             $self->{MB_body_type}
108 84   50     290 = $args->{body_type} || 'Mail::Message::Body::Lines';
109             $self->{MB_body_delayed_type}
110 84   50     362 = $args->{body_delayed_type}|| 'Mail::Message::Body::Delayed';
111             $self->{MB_head_delayed_type}
112 84   50     302 = $args->{head_delayed_type}|| 'Mail::Message::Head::Delayed';
113             $self->{MB_multipart_type}
114 84   50     323 = $args->{multipart_type} || 'Mail::Message::Body::Multipart';
115 84         155 $self->{MB_field_type} = $args->{field_type};
116              
117             my $headtype = $self->{MB_head_type}
118 84   50     342 = $args->{head_type} || 'Mail::Message::Head::Complete';
119              
120 84   100     239 my $extract = $args->{extract} || 'extractDefault';
121             $self->{MB_extract}
122             = ref $extract eq 'CODE' ? $extract
123 486     486   1279 : $extract eq 'ALWAYS' ? sub {1}
124 598     598   2486 : $extract eq 'LAZY' ? sub {0}
125 0     0   0 : $extract eq 'NEVER' ? sub {1} # compatibility
126 34     34   18109 : $extract =~ m/\D/ ? sub {no strict 'refs';shift->$extract(@_)}
  34     440   74  
  34         131766  
  440         1225  
127 18     18   55 : sub { my $size = $_[1]->guessBodySize;
128 18 50       751 defined $size && $size < $extract;
129 84 100       784 };
    50          
    100          
    100          
    50          
130              
131             #
132             # Create a locker.
133             #
134              
135             $self->{MB_locker}
136             = $args->{locker}
137             || Mail::Box::Locker->new
138             ( folder => $self
139             , method => $args->{lock_type}
140             , timeout => $args->{lock_timeout}
141             , expires => $args->{lock_wait}
142             , file => ($args->{lockfile} || $args->{lock_file})
143 84   33     984 , $self->logSettings
144             );
145              
146 84         411 $self;
147             }
148              
149             #-------------------------------------------
150              
151              
152             sub folderdir(;$)
153 196     196 1 288 { my $self = shift;
154 196 100       515 $self->{MB_folderdir} = shift if @_;
155 196         545 $self->{MB_folderdir};
156             }
157              
158 0     0 1 0 sub foundIn($@) { shift->notImplemented }
159              
160              
161 4826     4826 1 13827 sub name() {shift->{MB_foldername}}
162              
163              
164 0     0 1 0 sub type() {shift->notImplemented}
165              
166              
167             sub url()
168 0     0 1 0 { my $self = shift;
169 0         0 $self->type . ':' . $self->name;
170             }
171              
172              
173 0     0 1 0 sub size() { sum map { $_->size } shift->messages('ACTIVE') }
  0         0  
174              
175              
176             sub update(@)
177 1     1 1 3 { my $self = shift;
178              
179             $self->updateMessages
180             ( trusted => $self->{MB_trusted}
181             , head_type => $self->{MB_head_type}
182             , field_type => $self->{MB_field_type}
183             , message_type => $self->{MB_message_type}
184             , body_delayed_type => $self->{MB_body_delayed_type}
185             , head_delayed_type => $self->{MB_head_delayed_type}
186             , @_
187 1         10 );
188              
189 1         2 $self;
190             }
191              
192              
193 0     0 1 0 sub organization() { shift->notImplemented }
194              
195              
196             sub addMessage($@)
197 142     142 1 177502 { my $self = shift;
198 142 50       321 my $message = shift or return $self;
199 142         286 my %args = @_;
200              
201 142 50 33     536 confess <can('folder') && defined $message->folder;
202             You cannot add a message which is already part of a folder to a new
203             one. Please use moveTo or copyTo.
204             ERROR
205              
206             # Force the message into the right folder-type.
207 142         332 my $coerced = $self->coerce($message);
208 142         6453 $coerced->folder($self);
209              
210 142 50       264 unless($coerced->head->isDelayed)
211             { # Do not add the same message twice, unless keep_dups.
212 142         771 my $msgid = $coerced->messageId;
213              
214 142 50       580 unless($self->{MB_keep_dups})
215 142 100       273 { if(my $found = $self->messageId($msgid))
216 5         19 { $coerced->label(deleted => 1);
217 5         30 return $found;
218             }
219             }
220              
221 137         316 $self->messageId($msgid, $coerced);
222 137         239 $self->toBeThreaded($coerced);
223             }
224              
225 137         297 $self->storeMessage($coerced);
226 137         336 $coerced;
227             }
228              
229              
230             sub addMessages(@)
231 7     7 1 17 { my $self = shift;
232 7         44 map $self->addMessage($_), @_;
233             }
234              
235              
236             sub copyTo($@)
237 4     4 1 278 { my ($self, $to, %args) = @_;
238              
239 4   50     12 my $select = $args{select} || 'ACTIVE';
240 4 50       11 my $subfolders = exists $args{subfolders} ? $args{subfolders} : 1;
241 4         38 my $can_recurse = not $self->isa('Mail::Box::POP3');
242              
243 4 0       18 my ($flatten, $recurse)
    50          
    100          
    100          
244             = $subfolders eq 'FLATTEN' ? (1, 0)
245             : $subfolders eq 'RECURSE' ? (0, 1)
246             : !$subfolders ? (0, 0)
247             : $can_recurse ? (0, 1)
248             : (1, 0);
249              
250 4   50     18 my $delete = $args{delete_copied} || 0;
251 4   50     13 my $share = $args{share} || 0;
252              
253 4         15 $self->_copy_to($to, $select, $flatten, $recurse, $delete, $share);
254             }
255              
256             # Interface may change without warning.
257             sub _copy_to($@)
258 10     10   32 { my ($self, $to, @options) = @_;
259 10         20 my ($select, $flatten, $recurse, $delete, $share) = @options;
260              
261 10 50       25 $self->log(ERROR => "Destination folder $to is not writable."),
262             return unless $to->writable;
263              
264             # Take messages from this folder.
265 10         31 my @select = $self->messages($select);
266 10         101 $self->log(PROGRESS =>
267             "Copying ".@select." messages from $self to $to.");
268              
269 10         149 foreach my $msg (@select)
270 87 50       197 { if($msg->copyTo($to, share => $share))
271 87 50       198 { $msg->label(deleted => 1) if $delete }
272 0         0 else { $self->log(ERROR => "Copying failed for one message.") }
273             }
274              
275 10 100 100     72 return $self unless $flatten || $recurse;
276              
277             # Take subfolders
278              
279             SUBFOLDER:
280 8         41 foreach ($self->listSubFolders(check => 1))
281 6         56 { my $subfolder = $self->openSubFolder($_, access => 'r');
282 6 50       16 $self->log(ERROR => "Unable to open subfolder $_"), next
283             unless defined $subfolder;
284              
285 6 100       17 if($flatten) # flatten
286 3 50       16 { unless($subfolder->_copy_to($to, @options))
287 0         0 { $subfolder->close;
288 0         0 return;
289             }
290             }
291             else # recurse
292 3         15 { my $subto = $to->openSubFolder($_, create => 1, access => 'rw');
293 3 50       9 unless($subto)
294 0         0 { $self->log(ERROR => "Unable to create subfolder $_ of $to");
295 0         0 next SUBFOLDER;
296             }
297              
298 3 50       12 unless($subfolder->_copy_to($subto, @options))
299 0         0 { $subfolder->close;
300 0         0 $subto->close;
301 0         0 return;
302             }
303              
304 3         16 $subto->close;
305             }
306              
307 6         20 $subfolder->close;
308             }
309              
310 8         92 $self;
311             }
312              
313              
314             sub close(@)
315 90     90 1 17997 { my ($self, %args) = @_;
316 90   50     430 my $force = $args{force} || 0;
317              
318 90 100       279 return 1 if $self->{MB_is_closed};
319 88         242 $self->{MB_is_closed}++;
320              
321             # Inform manager that the folder is closed.
322 88         188 my $manager = delete $self->{MB_manager};
323             $manager->close($self, close_by_self =>1)
324 88 100 100     457 if defined $manager && !$args{close_by_manager};
325              
326 88         184 my $write;
327 88   100     385 for($args{write} || 'MODIFIED')
328 88 50       495 { $write = $_ eq 'MODIFIED' ? $self->isModified
    100          
    100          
329             : $_ eq 'ALWAYS' ? 1
330             : $_ eq 'NEVER' ? 0
331             : croak "Unknown value to folder->close(write => $_).";
332             }
333              
334 88         523 my $locker = $self->locker;
335 88 100 66     366 if($write && !$force && !$self->writable)
      100        
336 2         8 { $self->log(WARNING => "Changes not written to read-only folder $self.
337             Suggestion: \$folder->close(write => 'NEVER')");
338 2 50       60 $locker->unlock if $locker;
339 2         13 $self->{MB_messages} = []; # Boom!
340 2         2928 return 0;
341             }
342              
343             my $rc = !$write
344             || $self->write
345             ( force => $force
346 86   66     401 , save_deleted => $args{save_deleted} || 0
347             );
348              
349 86 100       469 $locker->unlock if $locker;
350 86         356 $self->{MB_messages} = []; # Boom!
351 86         63693 $rc;
352             }
353              
354              
355             sub delete(@)
356 21     21 1 45 { my ($self, %args) = @_;
357 21 50       49 my $recurse = exists $args{recursive} ? $args{recursive} : 1;
358              
359             # Extra protection: do not remove read-only folders.
360 21 50       38 unless($self->writable)
361 0         0 { $self->log(ERROR => "Folder $self not deleted: not writable.");
362 0         0 $self->close(write => 'NEVER');
363 0         0 return;
364             }
365              
366             # Sub-directories need to be removed first.
367 21 50       49 if($recurse)
368 21         56 { foreach ($self->listSubFolders)
369 14         40 { my $sub = $self->openRelatedFolder
370             (folder => "$self/$_", access => 'd', create => 0);
371 14 50       67 defined $sub && $sub->delete(%args);
372             }
373             }
374              
375 21         76 $self->close(write => 'NEVER');
376 21         188 $self;
377             }
378              
379             #-------------------------------------------
380              
381              
382 0     0 1 0 sub appendMessages(@) {shift->notImplemented}
383              
384             #-------------------------------------------
385              
386              
387 251     251 1 3372 sub writable() {shift->{MB_access} =~ /w|a|d/ }
388 0     0 0 0 sub writeable() {shift->writable} # compatibility [typo]
389             sub readable() {1} # compatibility
390              
391              
392 0     0 1 0 sub access() {shift->{MB_access}}
393              
394              
395             sub modified(;$)
396 42     42 1 1076 { my $self = shift;
397 42 100       141 return $self->isModified unless @_; # compat 2.036
398              
399             return
400 38 100       159 if $self->{MB_modified} = shift; # force modified flag
401              
402             # unmodify all messages
403 30         104 $_->modified(0) foreach $self->messages;
404 30         319 0;
405             }
406              
407              
408             sub isModified()
409 85     85 1 216 { my $self = shift;
410 85 100       275 return 1 if $self->{MB_modified};
411              
412 63         113 foreach (@{$self->{MB_messages}})
  63         203  
413 1513 100 100     8387 { return $self->{MB_modified} = 1
414             if $_->isDeleted || $_->isModified;
415             }
416              
417 42         269 0;
418             }
419              
420             #-------------------------------------------
421              
422              
423             sub message(;$$)
424 541     541 1 32982 { my ($self, $index) = (shift, shift);
425 541 50       1738 @_ ? $self->{MB_messages}[$index] = shift : $self->{MB_messages}[$index];
426             }
427              
428              
429             sub messageId($;$)
430 2024     2024 1 8421 { my ($self, $msgid) = (shift, shift);
431              
432 2024 100       4531 if($msgid =~ m/\<([^>]+)\>/s )
433 2         5 { $msgid = $1;
434 2         4 $msgid =~ s/\s//gs;
435              
436 2 50       8 $self->log(WARNING => "Message-id '$msgid' does not contain a domain.")
437             unless index($msgid, '@') >= 0;
438             }
439              
440 2024 100       4397 return $self->{MB_msgid}{$msgid} unless @_;
441              
442 1737         2214 my $message = shift;
443              
444             # Undefine message?
445 1737 100       2788 unless($message)
446 55         123 { delete $self->{MB_msgid}{$msgid};
447 55         106 return;
448             }
449              
450 1682         3414 my $double = $self->{MB_msgid}{$msgid};
451 1682 50 33     3377 if(defined $double && !$self->{MB_keep_dups})
452 0         0 { my $head1 = $message->head;
453 0         0 my $head2 = $double->head;
454              
455 0   0     0 my $subj1 = $head1->get('subject') || '';
456 0   0     0 my $subj2 = $head2->get('subject') || '';
457              
458 0   0     0 my $to1 = $head1->get('to') || '';
459 0   0     0 my $to2 = $head2->get('to') || '';
460              
461             # Auto-delete doubles.
462 0 0 0     0 return $message->label(deleted => 1)
463             if $subj1 eq $subj2 && $to1 eq $to2;
464              
465 0         0 $self->log(WARNING => "Different messages with id $msgid");
466 0         0 $msgid = $message->takeMessageId(undef);
467             }
468              
469 1682         3825 $self->{MB_msgid}{$msgid} = $message;
470 1682         4858 weaken($self->{MB_msgid}{$msgid});
471 1682         2900 $message;
472             }
473              
474 4     4 0 1559 sub messageID(@) {shift->messageId(@_)} # compatibility
475              
476              
477             sub find($)
478 1     1 1 2 { my ($self, $msgid) = (shift, shift);
479 1         3 my $msgids = $self->{MB_msgid};
480              
481 1 50       4 if($msgid =~ m/\<([^>]*)\>/s)
482 0         0 { $msgid = $1;
483 0         0 $msgid =~ s/\s//gs;
484             }
485             else
486             { # Illegal message-id
487 1         3 $msgid =~ s/\s/+/gs;
488             }
489              
490             $self->scanForMessages(undef, $msgid, 'EVER', 'ALL')
491 1 50       8 unless exists $msgids->{$msgid};
492              
493 1         6 $msgids->{$msgid};
494             }
495              
496              
497             sub messages($;$)
498 474     474 1 51621 { my $self = shift;
499              
500 474 100       912 return @{$self->{MB_messages}} unless @_;
  460         2021  
501 14         23 my $nr = @{$self->{MB_messages}};
  14         29  
502              
503 14 100       37 if(@_==2) # range
504 2         6 { my ($begin, $end) = @_;
505 2 50       6 $begin += $nr if $begin < 0;
506 2 50       6 $begin = 0 if $begin < 0;
507 2 50       5 $end += $nr if $end < 0;
508 2 50       5 $end = $nr-1 if $end >= $nr;
509              
510 2 50       6 return () if $begin > $end;
511              
512 2         5 my @range = @{$self->{MB_messages}}[$begin..$end];
  2         8  
513 2         8 return @range;
514             }
515              
516 12         23 my $what = shift;
517             my $action
518             = ref $what eq 'CODE'? $what
519 55     55   80 : $what eq 'DELETED' ? sub {$_[0]->isDeleted}
520 125     125   194 : $what eq 'ACTIVE' ? sub {not $_[0]->isDeleted}
521 10     10   15 : $what eq 'ALL' ? sub {1}
522 0     0   0 : $what =~ s/^\!// ? sub {not $_[0]->label($what)}
523 12 0   0   77 : sub {$_[0]->label($what)};
  0 50       0  
    100          
    100          
    50          
524              
525 12         24 grep {$action->($_)} @{$self->{MB_messages}};
  190         559  
  12         27  
526             }
527              
528              
529 0     0 1 0 sub nrMessages(@) { scalar shift->messages(@_) }
530              
531              
532 0     0 1 0 sub messageIds() { map {$_->messageId} shift->messages }
  0         0  
533 0     0 0 0 sub allMessageIds() {shift->messageIds} # compatibility
534 0     0 0 0 sub allMessageIDs() {shift->messageIds} # compatibility
535              
536              
537             sub current(;$)
538 2     2 1 239 { my $self = shift;
539              
540 2 100       6 unless(@_)
541             { return $self->{MB_current}
542 1 50       4 if exists $self->{MB_current};
543            
544             # Which one becomes current?
545 1   0     7 my $current
546             = $self->findFirstLabeled(current => 1)
547             || $self->findFirstLabeled(seen => 0)
548             || $self->message(-1)
549             || return undef;
550              
551 1         13 $current->label(current => 1);
552 1         12 $self->{MB_current} = $current;
553 1         7 return $current;
554             }
555              
556 1         2 my $next = shift;
557 1 50       3 if(my $previous = $self->{MB_current})
558 1         4 { $previous->label(current => 0);
559             }
560              
561 1         15 ($self->{MB_current} = $next)->label(current => 1);
562 1         9 $next;
563             }
564              
565              
566             sub scanForMessages($$$$)
567 2     2 1 7 { my ($self, $startid, $msgids, $moment, $window) = @_;
568              
569             # Set-up msgid-list
570 2 50       11 my %search = map +($_ => 1), ref $msgids ? @$msgids : $msgids;
571 2 50       9 return () unless keys %search;
572              
573             # do not run on empty folder
574 2 50       7 my $nr_messages = $self->messages
575             or return keys %search;
576              
577 2 100       8 my $startmsg = defined $startid ? $self->messageId($startid) : undef;
578              
579             # Set-up window-bound.
580 2         2 my $bound = 0;
581 2 100 66     11 if($window ne 'ALL' && defined $startmsg)
582 1         5 { $bound = $startmsg->seqnr - $window;
583 1 50       3 $bound = 0 if $bound < 0;
584             }
585              
586 2   33     10 my $last = ($self->{MBM_last} || $nr_messages) -1;
587 2 50 33     19 return keys %search if defined $bound && $bound > $last;
588              
589             # Set-up time-bound
590 2 0       13 my $after = $moment eq 'EVER' ? 0
    50          
    100          
591             : $moment =~ m/^\d+$/ ? $moment
592             : !$startmsg ? 0
593             : $startmsg->timestamp - $self->timespan2seconds($moment);
594              
595 2         6 while($last >= $bound)
596 82         176 { my $message = $self->message($last);
597 82         203 my $msgid = $message->messageId; # triggers load
598              
599 82 50       3296 if(delete $search{$msgid}) # where we looking for this one?
600 0 0       0 { last unless keys %search;
601             }
602              
603 82 100       186 last if $message->timestamp < $after;
604 81         25234 $last--;
605             }
606              
607 2         380 $self->{MBM_last} = $last;
608 2         16 keys %search;
609             }
610              
611              
612             sub findFirstLabeled($;$$)
613 1     1 1 3 { my ($self, $label, $set, $msgs) = @_;
614              
615 1 50 33     10 if(!defined $set || $set)
616 5     5   29 { my $f = first { $_->label($label) }
617 1 50       10 (defined $msgs ? @$msgs : $self->messages);
618             }
619             else
620 0     0   0 { return first { not $_->label($label) }
621 0 0       0 (defined $msgs ? @$msgs : $self->messages);
622             }
623             }
624              
625             #-------------------------------------------
626              
627              
628 0     0 1 0 sub listSubFolders(@) { () } # by default no sub-folders
629              
630              
631             sub openRelatedFolder(@)
632 27     27 1 41 { my $self = shift;
633 27         37 my @options = (%{$self->{MB_init_options}}, @_);
  27         166  
634              
635             $self->{MB_manager}
636 27 100       139 ? $self->{MB_manager}->open(type => ref($self), @options)
637             : (ref $self)->new(@options);
638             }
639              
640              
641             sub openSubFolder($@)
642 13     13 1 1299 { my $self = shift;
643 13         39 my $name = $self->nameOfSubFolder(shift);
644 13         50 $self->openRelatedFolder(@_, folder => $name);
645             }
646              
647              
648             sub nameOfSubFolder($;$)
649 4     4 1 9 { my ($thing, $name) = (shift, shift);
650 4 50       21 my $parent = @_ ? shift : ref $thing ? $thing->name : undef;
    50          
651 4 50       16 defined $parent ? "$parent/$name" : $name;
652             }
653              
654              
655             sub topFolderWithMessages() { 1 }
656              
657             #-------------------------------------------
658              
659              
660             sub read(@)
661 63     63 1 134 { my $self = shift;
662 63         171 $self->{MB_open_time} = time;
663              
664 63         191 local $self->{MB_lazy_permitted} = 1;
665              
666             # Read from existing folder.
667             return unless $self->readMessages
668             ( trusted => $self->{MB_trusted}
669             , head_type => $self->{MB_head_type}
670             , field_type => $self->{MB_field_type}
671             , message_type => $self->{MB_message_type}
672             , body_delayed_type => $self->{MB_body_delayed_type}
673             , head_delayed_type => $self->{MB_head_delayed_type}
674             , @_
675 63 50       398 );
676              
677 63 50       285 if($self->{MB_modified})
678 0         0 { $self->log(INTERNAL => "Modified $self->{MB_modified}");
679 0         0 $self->{MB_modified} = 0; #after reading, no changes found yet.
680             }
681              
682 63         201 $self;
683             }
684              
685             #-------------------------------------------
686              
687              
688             sub write(@)
689 31     31 1 1016 { my ($self, %args) = @_;
690              
691 31 50 33     162 unless($args{force} || $self->writable)
692 0         0 { $self->log(ERROR => "Folder $self is opened read-only.");
693 0         0 return;
694             }
695              
696 31         74 my (@keep, @destroy);
697 31 50       94 if($args{save_deleted})
698 0         0 { @keep = $self->messages;
699             }
700             else
701 31         88 { foreach ($self->messages)
702 964 100       1534 { if($_->isDeleted)
703 28         59 { push @destroy, $_;
704 28         95 $_->diskDelete;
705             }
706 936         2460 else {push @keep, $_}
707             }
708             }
709              
710 31 100 100     230 unless(@destroy || $self->isModified)
711 1         4 { $self->log(PROGRESS => "Folder $self not changed, so not updated.");
712 1         23 return $self;
713             }
714              
715 30         87 $args{messages} = \@keep;
716 30 50       147 unless($self->writeMessages(\%args))
717 0         0 { $self->log(WARNING => "Writing folder $self failed.");
718 0         0 return undef;
719             }
720              
721 30         260 $self->modified(0);
722 30         137 $self->{MB_messages} = \@keep;
723              
724 30         180 $self;
725             }
726              
727              
728             sub determineBodyType($$)
729 1662     1662 1 2788 { my ($self, $message, $head) = @_;
730              
731             return $self->{MB_body_delayed_type}
732             if $self->{MB_lazy_permitted}
733             && ! $message->isPart
734 1662 100 100     9390 && ! $self->{MB_extract}->($self, $head);
      100        
735              
736 1050         1920 my $bodytype = $self->{MB_body_type};
737 1050 50       3281 ref $bodytype ? $bodytype->($head) : $bodytype;
738             }
739              
740             sub extractDefault($)
741 440     440 0 663 { my ($self, $head) = @_;
742 440         1127 my $size = $head->guessBodySize;
743 440 50       26635 defined $size ? $size < 10000 : 0 # immediately extract < 10kb
744             }
745              
746             sub lazyPermitted($)
747 558     558 0 732 { my $self = shift;
748 558         1084 $self->{MB_lazy_permitted} = shift;
749             }
750              
751              
752             sub storeMessage($)
753 2138     2138 1 3072 { my ($self, $message) = @_;
754              
755 2138         2445 push @{$self->{MB_messages}}, $message;
  2138         4325  
756 2138         2654 $message->seqnr( @{$self->{MB_messages}} -1);
  2138         5982  
757 2138         3447 $message;
758             }
759              
760              
761             my %seps = (CR => "\015", LF => "\012", CRLF => "\015\012");
762              
763             sub lineSeparator(;$)
764 0     0 1 0 { my $self = shift;
765 0 0       0 return $self->{MB_linesep} unless @_;
766              
767 0         0 my $sep = shift;
768 0 0       0 $sep = $seps{$sep} if exists $seps{$sep};
769              
770 0         0 $self->{MB_linesep} = $sep;
771 0         0 $_->lineSeparator($sep) foreach $self->messages;
772 0         0 $sep;
773             }
774              
775              
776 0     0 1 0 sub create($@) {shift->notImplemented}
777              
778              
779              
780             sub coerce($@)
781 142     142 1 232 { my ($self, $message) = (shift, shift);
782 142         538 my $mmtype = $self->{MB_message_type};
783 142 50       912 $message->isa($mmtype) ? $message : $mmtype->coerce($message, @_);
784             }
785              
786              
787 0     0 1 0 sub readMessages(@) {shift->notImplemented}
788              
789              
790 0     0 1 0 sub updateMessages(@) { shift }
791              
792              
793 0     0 1 0 sub writeMessages(@) {shift->notImplemented}
794              
795              
796 200     200 1 994 sub locker() { shift->{MB_locker} }
797              
798              
799             sub toBeThreaded(@)
800 1682     1682 1 2073 { my $self = shift;
801              
802             my $manager = $self->{MB_manager}
803 1682 100       4047 or return $self;
804              
805 578         1823 $manager->toBeThreaded($self, @_);
806 578         904 $self;
807             }
808              
809              
810             sub toBeUnthreaded(@)
811 55     55 1 68 { my $self = shift;
812              
813             my $manager = $self->{MB_manager}
814 55 50       116 or return $self;
815              
816 0         0 $manager->toBeThreaded($self, @_);
817 0         0 $self;
818             }
819              
820             #-------------------------------------------
821              
822              
823             sub timespan2seconds($)
824             {
825 3 50   3 1 29 if( $_[1] =~ /^\s*(\d+\.?\d*|\.\d+)\s*(hour|day|week)s?\s*$/ )
826 3 50       29 { $2 eq 'hour' ? $1 * 3600
    50          
827             : $2 eq 'day' ? $1 * 86400
828             : $1 * 604800; # week
829             }
830             else
831 0         0 { $_[0]->log(ERROR => "Invalid timespan '$_' specified.");
832 0         0 undef;
833             }
834             }
835              
836             #-------------------------------------------
837              
838              
839             sub DESTROY
840 88     88   21350 { my $self = shift;
841 88 100 66     2135 $self->close unless in_global_destruction || $self->{MB_is_closed};
842             }
843              
844             #-------------------------------------------
845              
846              
847             1;