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-2020 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   2845 use vars '$VERSION';
  34         65  
  34         1731  
11             $VERSION = '3.009';
12              
13 34     34   202 use base 'Mail::Reporter';
  34         66  
  34         3837  
14              
15 34     34   282 use strict;
  34         83  
  34         1014  
16 34     34   200 use warnings;
  34         112  
  34         1291  
17              
18 34     34   14596 use Mail::Box::Message;
  34         119  
  34         1223  
19 34     34   14220 use Mail::Box::Locker;
  34         92  
  34         1082  
20 34     34   247 use File::Spec;
  34         92  
  34         729  
21              
22 34     34   181 use Carp;
  34         76  
  34         1966  
23 34     34   226 use Scalar::Util 'weaken';
  34         78  
  34         1478  
24 34     34   215 use List::Util qw/sum first/;
  34         92  
  34         2317  
25 34     34   247 use Devel::GlobalDestruction 'in_global_destruction';
  34         81  
  34         341  
26              
27              
28             #-------------------------------------------
29              
30              
31 0     0   0 use overload '@{}' => sub { shift->{MB_messages} }
32             , '""' => 'name'
33 34     34   5537 , 'cmp' => sub {$_[0]->name cmp "${_[1]}"};
  34     42   89  
  34         602  
  42         92  
34              
35             #-------------------------------------------
36              
37              
38             sub new(@)
39 84     84 1 8701 { my $class = shift;
40              
41 84 50       325 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         551 my %args = @_;
52 84         449 weaken $args{manager}; # otherwise, the manager object may live too long
53              
54 84 100       625 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     1108 if $self->{MB_access} =~ /r|a/;
61              
62 83         400 $self;
63             }
64              
65             sub init($)
66 84     84 0 233 { my ($self, $args) = @_;
67              
68 84 50       354 return unless defined $self->SUPER::init($args);
69              
70 84         1833 my $class = ref $self;
71 84   33     350 my $foldername = $args->{folder} || $ENV{MAIL};
72 84 50       240 unless($foldername)
73 0         0 { $self->log(ERROR => "No folder name specified.");
74 0         0 return;
75             }
76              
77 84         296 $self->{MB_foldername} = $foldername;
78 84         221 $self->{MB_init_options} = $args->{init_options};
79 84   50     429 $self->{MB_coerce_opts} = $args->{coerce_options} || [];
80 84   100     359 $self->{MB_access} = $args->{access} || 'r';
81             $self->{MB_remove_empty}
82 84 50       629 = defined $args->{remove_when_empty} ? $args->{remove_when_empty} : 1;
83              
84             $self->{MB_save_on_exit}
85 84 100       318 = defined $args->{save_on_exit} ? $args->{save_on_exit} : 1;
86              
87 84         211 $self->{MB_messages} = [];
88 84         198 $self->{MB_msgid} = {};
89 84   50     404 $self->{MB_organization} = $args->{organization} || 'FILE';
90 84         369 $self->{MB_linesep} = "\n";
91 84   66     379 $self->{MB_keep_dups} = !$self->writable || $args->{keep_dups};
92 84         252 $self->{MB_fix_headers} = $args->{fix_headers};
93              
94 84         418 my $folderdir = $self->folderdir($args->{folderdir});
95             $self->{MB_trusted} = exists $args->{trusted} ? $args->{trusted}
96 84 50       658 : substr($foldername, 0, 1) eq '=' ? 1
    100          
    50          
97             : !defined $folderdir ? 0
98             : substr($foldername, 0, length $folderdir) eq $folderdir;
99              
100 84 100       279 if(exists $args->{manager})
101 39         87 { $self->{MB_manager} = $args->{manager};
102 39         135 weaken($self->{MB_manager});
103             }
104              
105             my $message_type = $self->{MB_message_type}
106 84   33     578 = $args->{message_type} || $class . '::Message';
107             $self->{MB_body_type}
108 84   50     387 = $args->{body_type} || 'Mail::Message::Body::Lines';
109             $self->{MB_body_delayed_type}
110 84   50     397 = $args->{body_delayed_type}|| 'Mail::Message::Body::Delayed';
111             $self->{MB_head_delayed_type}
112 84   50     376 = $args->{head_delayed_type}|| 'Mail::Message::Head::Delayed';
113             $self->{MB_multipart_type}
114 84   50     384 = $args->{multipart_type} || 'Mail::Message::Body::Multipart';
115 84         235 $self->{MB_field_type} = $args->{field_type};
116              
117             my $headtype = $self->{MB_head_type}
118 84   50     389 = $args->{head_type} || 'Mail::Message::Head::Complete';
119              
120 84   100     337 my $extract = $args->{extract} || 'extractDefault';
121             $self->{MB_extract}
122             = ref $extract eq 'CODE' ? $extract
123 486     486   1505 : $extract eq 'ALWAYS' ? sub {1}
124 598     598   2659 : $extract eq 'LAZY' ? sub {0}
125 0     0   0 : $extract eq 'NEVER' ? sub {1} # compatibility
126 34     34   22531 : $extract =~ m/\D/ ? sub {no strict 'refs';shift->$extract(@_)}
  34     440   83  
  34         166996  
  440         1576  
127 18     18   64 : sub { my $size = $_[1]->guessBodySize;
128 18 50       905 defined $size && $size < $extract;
129 84 100       965 };
    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     1200 , $self->logSettings
144             );
145              
146 84         479 $self;
147             }
148              
149             #-------------------------------------------
150              
151              
152             sub folderdir(;$)
153 196     196 1 398 { my $self = shift;
154 196 100       659 $self->{MB_folderdir} = shift if @_;
155 196         670 $self->{MB_folderdir};
156             }
157              
158 0     0 1 0 sub foundIn($@) { shift->notImplemented }
159              
160              
161 4826     4826 1 16518 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 4 { 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         13 );
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 225829 { my $self = shift;
198 142 50       434 my $message = shift or return $self;
199 142         283 my %args = @_;
200              
201 142 50 33     626 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         414 my $coerced = $self->coerce($message);
208 142         8139 $coerced->folder($self);
209              
210 142 50       350 unless($coerced->head->isDelayed)
211             { # Do not add the same message twice, unless keep_dups.
212 142         974 my $msgid = $coerced->messageId;
213              
214 142 50       711 unless($self->{MB_keep_dups})
215 142 100       350 { if(my $found = $self->messageId($msgid))
216 5         24 { $coerced->label(deleted => 1);
217 5         40 return $found;
218             }
219             }
220              
221 137         392 $self->messageId($msgid, $coerced);
222 137         342 $self->toBeThreaded($coerced);
223             }
224              
225 137         406 $self->storeMessage($coerced);
226 137         470 $coerced;
227             }
228              
229              
230             sub addMessages(@)
231 7     7 1 16 { my $self = shift;
232 7         54 map $self->addMessage($_), @_;
233             }
234              
235              
236             sub copyTo($@)
237 4     4 1 586 { my ($self, $to, %args) = @_;
238              
239 4   50     17 my $select = $args{select} || 'ACTIVE';
240 4 50       13 my $subfolders = exists $args{subfolders} ? $args{subfolders} : 1;
241 4         34 my $can_recurse = not $self->isa('Mail::Box::POP3');
242              
243 4 0       21 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     19 my $delete = $args{delete_copied} || 0;
251 4   50     15 my $share = $args{share} || 0;
252              
253 4         19 $self->_copy_to($to, $select, $flatten, $recurse, $delete, $share);
254             }
255              
256             # Interface may change without warning.
257             sub _copy_to($@)
258 10     10   33 { my ($self, $to, @options) = @_;
259 10         27 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         44 my @select = $self->messages($select);
266 10         141 $self->log(PROGRESS =>
267             "Copying ".@select." messages from $self to $to.");
268              
269 10         192 foreach my $msg (@select)
270 87 50       248 { if($msg->copyTo($to, share => $share))
271 87 50       279 { $msg->label(deleted => 1) if $delete }
272 0         0 else { $self->log(ERROR => "Copying failed for one message.") }
273             }
274              
275 10 100 100     91 return $self unless $flatten || $recurse;
276              
277             # Take subfolders
278              
279             SUBFOLDER:
280 8         61 foreach ($self->listSubFolders(check => 1))
281 6         40 { my $subfolder = $self->openSubFolder($_, access => 'r');
282 6 50       26 $self->log(ERROR => "Unable to open subfolder $_"), next
283             unless defined $subfolder;
284              
285 6 100       18 if($flatten) # flatten
286 3 50       20 { unless($subfolder->_copy_to($to, @options))
287 0         0 { $subfolder->close;
288 0         0 return;
289             }
290             }
291             else # recurse
292 3         22 { my $subto = $to->openSubFolder($_, create => 1, access => 'rw');
293 3 50       7 unless($subto)
294 0         0 { $self->log(ERROR => "Unable to create subfolder $_ of $to");
295 0         0 next SUBFOLDER;
296             }
297              
298 3 50       15 unless($subfolder->_copy_to($subto, @options))
299 0         0 { $subfolder->close;
300 0         0 $subto->close;
301 0         0 return;
302             }
303              
304 3         20 $subto->close;
305             }
306              
307 6         28 $subfolder->close;
308             }
309              
310 8         77 $self;
311             }
312              
313              
314             sub close(@)
315 90     90 1 22512 { my ($self, %args) = @_;
316 90   50     496 my $force = $args{force} || 0;
317              
318 90 100       464 return 1 if $self->{MB_is_closed};
319 88         295 $self->{MB_is_closed}++;
320              
321             # Inform manager that the folder is closed.
322 88         217 my $manager = delete $self->{MB_manager};
323             $manager->close($self, close_by_self =>1)
324 88 100 100     547 if defined $manager && !$args{close_by_manager};
325              
326 88         149 my $write;
327 88   100     481 for($args{write} || 'MODIFIED')
328 88 50       603 { $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         553 my $locker = $self->locker;
335 88 100 66     459 if($write && !$force && !$self->writable)
      100        
336 2         11 { $self->log(WARNING => "Changes not written to read-only folder $self.
337             Suggestion: \$folder->close(write => 'NEVER')");
338 2 50       68 $locker->unlock if $locker;
339 2         16 $self->{MB_messages} = []; # Boom!
340 2         12 return 0;
341             }
342              
343             my $rc = !$write
344             || $self->write
345             ( force => $force
346 86   66     556 , save_deleted => $args{save_deleted} || 0
347             );
348              
349 86 100       532 $locker->unlock if $locker;
350 86         363 $self->{MB_messages} = []; # Boom!
351 86         63025 $rc;
352             }
353              
354              
355             sub delete(@)
356 21     21 1 51 { my ($self, %args) = @_;
357 21 50       57 my $recurse = exists $args{recursive} ? $args{recursive} : 1;
358              
359             # Extra protection: do not remove read-only folders.
360 21 50       49 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       60 if($recurse)
368 21         69 { foreach ($self->listSubFolders)
369 14         53 { my $sub = $self->openRelatedFolder
370             (folder => "$self/$_", access => 'd', create => 0);
371 14 50       92 defined $sub && $sub->delete(%args);
372             }
373             }
374              
375 21         103 $self->close(write => 'NEVER');
376 21         242 $self;
377             }
378              
379             #-------------------------------------------
380              
381              
382 0     0 1 0 sub appendMessages(@) {shift->notImplemented}
383              
384             #-------------------------------------------
385              
386              
387 251     251 1 4492 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 972 { my $self = shift;
397 42 100       208 return $self->isModified unless @_; # compat 2.036
398              
399             return
400 38 100       166 if $self->{MB_modified} = shift; # force modified flag
401              
402             # unmodify all messages
403 30         122 $_->modified(0) foreach $self->messages;
404 30         385 0;
405             }
406              
407              
408             sub isModified()
409 85     85 1 177 { my $self = shift;
410 85 100       344 return 1 if $self->{MB_modified};
411              
412 63         158 foreach (@{$self->{MB_messages}})
  63         354  
413 1513 100 100     8498 { return $self->{MB_modified} = 1
414             if $_->isDeleted || $_->isModified;
415             }
416              
417 42         411 0;
418             }
419              
420             #-------------------------------------------
421              
422              
423             sub message(;$$)
424 541     541 1 38652 { my ($self, $index) = (shift, shift);
425 541 50       2170 @_ ? $self->{MB_messages}[$index] = shift : $self->{MB_messages}[$index];
426             }
427              
428              
429             sub messageId($;$)
430 2024     2024 1 9864 { my ($self, $msgid) = (shift, shift);
431              
432 2024 100       5088 if($msgid =~ m/\<([^>]+)\>/s )
433 2         6 { $msgid = $1;
434 2         6 $msgid =~ s/\s//gs;
435              
436 2 50       7 $self->log(WARNING => "Message-id '$msgid' does not contain a domain.")
437             unless index($msgid, '@') >= 0;
438             }
439              
440 2024 100       4752 return $self->{MB_msgid}{$msgid} unless @_;
441              
442 1737         2420 my $message = shift;
443              
444             # Undefine message?
445 1737 100       3367 unless($message)
446 55         157 { delete $self->{MB_msgid}{$msgid};
447 55         118 return;
448             }
449              
450 1682         3864 my $double = $self->{MB_msgid}{$msgid};
451 1682 50 33     3748 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         4508 $self->{MB_msgid}{$msgid} = $message;
470 1682         5694 weaken($self->{MB_msgid}{$msgid});
471 1682         3360 $message;
472             }
473              
474 4     4 0 1887 sub messageID(@) {shift->messageId(@_)} # compatibility
475              
476              
477             sub find($)
478 1     1 1 3 { my ($self, $msgid) = (shift, shift);
479 1         3 my $msgids = $self->{MB_msgid};
480              
481 1 50       6 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         4 $msgid =~ s/\s/+/gs;
488             }
489              
490             $self->scanForMessages(undef, $msgid, 'EVER', 'ALL')
491 1 50       10 unless exists $msgids->{$msgid};
492              
493 1         5 $msgids->{$msgid};
494             }
495              
496              
497             sub messages($;$)
498 474     474 1 54228 { my $self = shift;
499              
500 474 100       1067 return @{$self->{MB_messages}} unless @_;
  460         2399  
501 14         25 my $nr = @{$self->{MB_messages}};
  14         41  
502              
503 14 100       52 if(@_==2) # range
504 2         6 { my ($begin, $end) = @_;
505 2 50       9 $begin += $nr if $begin < 0;
506 2 50       6 $begin = 0 if $begin < 0;
507 2 50       6 $end += $nr if $end < 0;
508 2 50       6 $end = $nr-1 if $end >= $nr;
509              
510 2 50       7 return () if $begin > $end;
511              
512 2         7 my @range = @{$self->{MB_messages}}[$begin..$end];
  2         12  
513 2         10 return @range;
514             }
515              
516 12         32 my $what = shift;
517             my $action
518             = ref $what eq 'CODE'? $what
519 55     55   98 : $what eq 'DELETED' ? sub {$_[0]->isDeleted}
520 125     125   238 : $what eq 'ACTIVE' ? sub {not $_[0]->isDeleted}
521 10     10   18 : $what eq 'ALL' ? sub {1}
522 0     0   0 : $what =~ s/^\!// ? sub {not $_[0]->label($what)}
523 12 0   0   112 : sub {$_[0]->label($what)};
  0 50       0  
    100          
    100          
    50          
524              
525 12         23 grep {$action->($_)} @{$self->{MB_messages}};
  190         677  
  12         72  
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 291 { my $self = shift;
539              
540 2 100       8 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         15 $current->label(current => 1);
552 1         14 $self->{MB_current} = $current;
553 1         10 return $current;
554             }
555              
556 1         3 my $next = shift;
557 1 50       4 if(my $previous = $self->{MB_current})
558 1         4 { $previous->label(current => 0);
559             }
560              
561 1         17 ($self->{MB_current} = $next)->label(current => 1);
562 1         11 $next;
563             }
564              
565              
566             sub scanForMessages($$$$)
567 2     2 1 9 { my ($self, $startid, $msgids, $moment, $window) = @_;
568              
569             # Set-up msgid-list
570 2 50       15 my %search = map +($_ => 1), ref $msgids ? @$msgids : $msgids;
571 2 50       10 return () unless keys %search;
572              
573             # do not run on empty folder
574 2 50       9 my $nr_messages = $self->messages
575             or return keys %search;
576              
577 2 100       10 my $startmsg = defined $startid ? $self->messageId($startid) : undef;
578              
579             # Set-up window-bound.
580 2         5 my $bound = 0;
581 2 100 66     13 if($window ne 'ALL' && defined $startmsg)
582 1         7 { $bound = $startmsg->seqnr - $window;
583 1 50       3 $bound = 0 if $bound < 0;
584             }
585              
586 2   33     13 my $last = ($self->{MBM_last} || $nr_messages) -1;
587 2 50 33     12 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         8 while($last >= $bound)
596 82         222 { my $message = $self->message($last);
597 82         220 my $msgid = $message->messageId; # triggers load
598              
599 82 50       4096 if(delete $search{$msgid}) # where we looking for this one?
600 0 0       0 { last unless keys %search;
601             }
602              
603 82 100       181 last if $message->timestamp < $after;
604 81         32090 $last--;
605             }
606              
607 2         404 $self->{MBM_last} = $last;
608 2         16 keys %search;
609             }
610              
611              
612             sub findFirstLabeled($;$$)
613 1     1 1 4 { my ($self, $label, $set, $msgs) = @_;
614              
615 1 50 33     10 if(!defined $set || $set)
616 5     5   25 { my $f = first { $_->label($label) }
617 1 50       23 (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 59 { my $self = shift;
633 27         49 my @options = (%{$self->{MB_init_options}}, @_);
  27         232  
634              
635             $self->{MB_manager}
636 27 100       193 ? $self->{MB_manager}->open(type => ref($self), @options)
637             : (ref $self)->new(@options);
638             }
639              
640              
641             sub openSubFolder($@)
642 13     13 1 2036 { my $self = shift;
643 13         60 my $name = $self->nameOfSubFolder(shift);
644 13         70 $self->openRelatedFolder(@_, folder => $name);
645             }
646              
647              
648             sub nameOfSubFolder($;$)
649 4     4 1 14 { my ($thing, $name) = (shift, shift);
650 4 50       37 my $parent = @_ ? shift : ref $thing ? $thing->name : undef;
    50          
651 4 50       19 defined $parent ? "$parent/$name" : $name;
652             }
653              
654              
655             sub topFolderWithMessages() { 1 }
656              
657             #-------------------------------------------
658              
659              
660             sub read(@)
661 63     63 1 168 { my $self = shift;
662 63         196 $self->{MB_open_time} = time;
663              
664 63         284 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       468 );
676              
677 63 50       289 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         248 $self;
683             }
684              
685             #-------------------------------------------
686              
687              
688             sub write(@)
689 31     31 1 1205 { my ($self, %args) = @_;
690              
691 31 50 33     180 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         90 my (@keep, @destroy);
697 31 50       134 if($args{save_deleted})
698 0         0 { @keep = $self->messages;
699             }
700             else
701 31         114 { foreach ($self->messages)
702 964 100       1864 { if($_->isDeleted)
703 28         66 { push @destroy, $_;
704 28         114 $_->diskDelete;
705             }
706 936         2959 else {push @keep, $_}
707             }
708             }
709              
710 31 100 100     220 unless(@destroy || $self->isModified)
711 1         6 { $self->log(PROGRESS => "Folder $self not changed, so not updated.");
712 1         27 return $self;
713             }
714              
715 30         117 $args{messages} = \@keep;
716 30 50       170 unless($self->writeMessages(\%args))
717 0         0 { $self->log(WARNING => "Writing folder $self failed.");
718 0         0 return undef;
719             }
720              
721 30         232 $self->modified(0);
722 30         144 $self->{MB_messages} = \@keep;
723              
724 30         174 $self;
725             }
726              
727              
728             sub determineBodyType($$)
729 1662     1662 1 3229 { 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     11192 && ! $self->{MB_extract}->($self, $head);
      100        
735              
736 1050         2283 my $bodytype = $self->{MB_body_type};
737 1050 50       3574 ref $bodytype ? $bodytype->($head) : $bodytype;
738             }
739              
740             sub extractDefault($)
741 440     440 0 773 { my ($self, $head) = @_;
742 440         1261 my $size = $head->guessBodySize;
743 440 50       21632 defined $size ? $size < 10000 : 0 # immediately extract < 10kb
744             }
745              
746             sub lazyPermitted($)
747 558     558 0 910 { my $self = shift;
748 558         1321 $self->{MB_lazy_permitted} = shift;
749             }
750              
751              
752             sub storeMessage($)
753 2138     2138 1 3624 { my ($self, $message) = @_;
754              
755 2138         2853 push @{$self->{MB_messages}}, $message;
  2138         5129  
756 2138         3101 $message->seqnr( @{$self->{MB_messages}} -1);
  2138         7148  
757 2138         3588 $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 262 { my ($self, $message) = (shift, shift);
782 142         698 my $mmtype = $self->{MB_message_type};
783 142 50       1283 $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 1210 sub locker() { shift->{MB_locker} }
797              
798              
799             sub toBeThreaded(@)
800 1682     1682 1 2429 { my $self = shift;
801              
802             my $manager = $self->{MB_manager}
803 1682 100       4745 or return $self;
804              
805 578         2222 $manager->toBeThreaded($self, @_);
806 578         1085 $self;
807             }
808              
809              
810             sub toBeUnthreaded(@)
811 55     55 1 82 { my $self = shift;
812              
813             my $manager = $self->{MB_manager}
814 55 50       144 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 37 if( $_[1] =~ /^\s*(\d+\.?\d*|\.\d+)\s*(hour|day|week)s?\s*$/ )
826 3 50       38 { $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   16652 { my $self = shift;
841 88 100 66     2657 $self->close unless in_global_destruction || $self->{MB_is_closed};
842             }
843              
844             #-------------------------------------------
845              
846              
847             1;