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-2023 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.03.
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   2864 use vars '$VERSION';
  34         68  
  34         1725  
11             $VERSION = '3.010';
12              
13 34     34   203 use base 'Mail::Reporter';
  34         90  
  34         3523  
14              
15 34     34   258 use strict;
  34         78  
  34         1287  
16 34     34   203 use warnings;
  34         122  
  34         1277  
17              
18 34     34   14640 use Mail::Box::Message;
  34         116  
  34         1345  
19 34     34   14422 use Mail::Box::Locker;
  34         87  
  34         1141  
20 34     34   274 use File::Spec;
  34         78  
  34         782  
21              
22 34     34   190 use Carp;
  34         72  
  34         1826  
23 34     34   215 use Scalar::Util 'weaken';
  34         77  
  34         1496  
24 34     34   264 use List::Util qw/sum first/;
  34         109  
  34         2404  
25 34     34   244 use Devel::GlobalDestruction 'in_global_destruction';
  34         98  
  34         454  
26              
27              
28             #-------------------------------------------
29              
30              
31 0     0   0 use overload '@{}' => sub { shift->{MB_messages} }
32             , '""' => 'name'
33 34     34   5431 , 'cmp' => sub {$_[0]->name cmp "${_[1]}"};
  34     42   77  
  34         545  
  42         89  
34              
35             #-------------------------------------------
36              
37              
38             sub new(@)
39 84     84 1 9753 { my $class = shift;
40              
41 84 50       348 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         557 my %args = @_;
52 84         453 weaken $args{manager}; # otherwise, the manager object may live too long
53              
54 84 100       671 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     1255 if $self->{MB_access} =~ /r|a/;
61              
62 83         491 $self;
63             }
64              
65             sub init($)
66 84     84 0 244 { my ($self, $args) = @_;
67              
68 84 50       696 return unless defined $self->SUPER::init($args);
69              
70 84         1935 my $class = ref $self;
71 84   33     377 my $foldername = $args->{folder} || $ENV{MAIL};
72 84 50       249 unless($foldername)
73 0         0 { $self->log(ERROR => "No folder name specified.");
74 0         0 return;
75             }
76              
77 84         230 $self->{MB_foldername} = $foldername;
78 84         255 $self->{MB_init_options} = $args->{init_options};
79 84   50     442 $self->{MB_coerce_opts} = $args->{coerce_options} || [];
80 84   100     319 $self->{MB_access} = $args->{access} || 'r';
81             $self->{MB_remove_empty}
82 84 50       576 = defined $args->{remove_when_empty} ? $args->{remove_when_empty} : 1;
83              
84             $self->{MB_save_on_exit}
85 84 100       322 = defined $args->{save_on_exit} ? $args->{save_on_exit} : 1;
86              
87 84         200 $self->{MB_messages} = [];
88 84         266 $self->{MB_msgid} = {};
89 84   50     439 $self->{MB_organization} = $args->{organization} || 'FILE';
90 84         373 $self->{MB_linesep} = "\n";
91 84   66     406 $self->{MB_keep_dups} = !$self->writable || $args->{keep_dups};
92 84         280 $self->{MB_fix_headers} = $args->{fix_headers};
93              
94 84         410 my $folderdir = $self->folderdir($args->{folderdir});
95             $self->{MB_trusted} = exists $args->{trusted} ? $args->{trusted}
96 84 50       646 : substr($foldername, 0, 1) eq '=' ? 1
    100          
    50          
97             : !defined $folderdir ? 0
98             : substr($foldername, 0, length $folderdir) eq $folderdir;
99              
100 84 100       316 if(exists $args->{manager})
101 39         97 { $self->{MB_manager} = $args->{manager};
102 39         121 weaken($self->{MB_manager});
103             }
104              
105             my $message_type = $self->{MB_message_type}
106 84   33     586 = $args->{message_type} || $class . '::Message';
107             $self->{MB_body_type}
108 84   50     409 = $args->{body_type} || 'Mail::Message::Body::Lines';
109             $self->{MB_body_delayed_type}
110 84   50     443 = $args->{body_delayed_type}|| 'Mail::Message::Body::Delayed';
111             $self->{MB_head_delayed_type}
112 84   50     355 = $args->{head_delayed_type}|| 'Mail::Message::Head::Delayed';
113             $self->{MB_multipart_type}
114 84   50     396 = $args->{multipart_type} || 'Mail::Message::Body::Multipart';
115 84         227 $self->{MB_field_type} = $args->{field_type};
116              
117             my $headtype = $self->{MB_head_type}
118 84   50     392 = $args->{head_type} || 'Mail::Message::Head::Complete';
119              
120 84   100     276 my $extract = $args->{extract} || 'extractDefault';
121             $self->{MB_extract}
122             = ref $extract eq 'CODE' ? $extract
123 486     486   1418 : $extract eq 'ALWAYS' ? sub {1}
124 598     598   2645 : $extract eq 'LAZY' ? sub {0}
125 0     0   0 : $extract eq 'NEVER' ? sub {1} # compatibility
126 34     34   24335 : $extract =~ m/\D/ ? sub {no strict 'refs';shift->$extract(@_)}
  34     440   103  
  34         169930  
  440         1389  
127 18     18   101 : sub { my $size = $_[1]->guessBodySize;
128 18 50       901 defined $size && $size < $extract;
129 84 100       931 };
    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     1214 , $self->logSettings
144             );
145              
146 84         547 $self;
147             }
148              
149             #-------------------------------------------
150              
151              
152             sub folderdir(;$)
153 196     196 1 364 { my $self = shift;
154 196 100       637 $self->{MB_folderdir} = shift if @_;
155 196         743 $self->{MB_folderdir};
156             }
157              
158 0     0 1 0 sub foundIn($@) { shift->notImplemented }
159              
160              
161 4826     4826 1 15996 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         3 $self;
190             }
191              
192              
193 0     0 1 0 sub organization() { shift->notImplemented }
194              
195              
196             sub addMessage($@)
197 142     142 1 244858 { my $self = shift;
198 142 50       399 my $message = shift or return $self;
199 142         279 my %args = @_;
200              
201 142 50 33     636 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         401 my $coerced = $self->coerce($message);
208 142         8209 $coerced->folder($self);
209              
210 142 50       359 unless($coerced->head->isDelayed)
211             { # Do not add the same message twice, unless keep_dups.
212 142         1014 my $msgid = $coerced->messageId;
213              
214 142 50       762 unless($self->{MB_keep_dups})
215 142 100       328 { if(my $found = $self->messageId($msgid))
216 5         35 { $coerced->label(deleted => 1);
217 5         33 return $found;
218             }
219             }
220              
221 137         337 $self->messageId($msgid, $coerced);
222 137         291 $self->toBeThreaded($coerced);
223             }
224              
225 137         399 $self->storeMessage($coerced);
226 137         425 $coerced;
227             }
228              
229              
230             sub addMessages(@)
231 7     7 1 16 { my $self = shift;
232 7         51 map $self->addMessage($_), @_;
233             }
234              
235              
236             sub copyTo($@)
237 4     4 1 412 { my ($self, $to, %args) = @_;
238              
239 4   50     13 my $select = $args{select} || 'ACTIVE';
240 4 50       15 my $subfolders = exists $args{subfolders} ? $args{subfolders} : 1;
241 4         30 my $can_recurse = not $self->isa('Mail::Box::POP3');
242              
243 4 0       30 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     69 my $delete = $args{delete_copied} || 0;
251 4   50     22 my $share = $args{share} || 0;
252              
253 4         17 $self->_copy_to($to, $select, $flatten, $recurse, $delete, $share);
254             }
255              
256             # Interface may change without warning.
257             sub _copy_to($@)
258 10     10   35 { my ($self, $to, @options) = @_;
259 10         26 my ($select, $flatten, $recurse, $delete, $share) = @options;
260              
261 10 50       26 $self->log(ERROR => "Destination folder $to is not writable."),
262             return unless $to->writable;
263              
264             # Take messages from this folder.
265 10         51 my @select = $self->messages($select);
266 10         156 $self->log(PROGRESS =>
267             "Copying ".@select." messages from $self to $to.");
268              
269 10         185 foreach my $msg (@select)
270 87 50       236 { if($msg->copyTo($to, share => $share))
271 87 50       238 { $msg->label(deleted => 1) if $delete }
272 0         0 else { $self->log(ERROR => "Copying failed for one message.") }
273             }
274              
275 10 100 100     127 return $self unless $flatten || $recurse;
276              
277             # Take subfolders
278              
279             SUBFOLDER:
280 8         63 foreach ($self->listSubFolders(check => 1))
281 6         1396 { my $subfolder = $self->openSubFolder($_, access => 'r');
282 6 50       30 $self->log(ERROR => "Unable to open subfolder $_"), next
283             unless defined $subfolder;
284              
285 6 100       22 if($flatten) # flatten
286 3 50       23 { unless($subfolder->_copy_to($to, @options))
287 0         0 { $subfolder->close;
288 0         0 return;
289             }
290             }
291             else # recurse
292 3         19 { 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       18 unless($subfolder->_copy_to($subto, @options))
299 0         0 { $subfolder->close;
300 0         0 $subto->close;
301 0         0 return;
302             }
303              
304 3         29 $subto->close;
305             }
306              
307 6         30 $subfolder->close;
308             }
309              
310 8         2247 $self;
311             }
312              
313              
314             sub close(@)
315 90     90 1 22035 { my ($self, %args) = @_;
316 90   50     485 my $force = $args{force} || 0;
317              
318 90 100       481 return 1 if $self->{MB_is_closed};
319 88         241 $self->{MB_is_closed}++;
320              
321             # Inform manager that the folder is closed.
322 88         230 my $manager = delete $self->{MB_manager};
323             $manager->close($self, close_by_self =>1)
324 88 100 100     527 if defined $manager && !$args{close_by_manager};
325              
326 88         168 my $write;
327 88   100     492 for($args{write} || 'MODIFIED')
328 88 50       582 { $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         587 my $locker = $self->locker;
335 88 100 66     428 if($write && !$force && !$self->writable)
      100        
336 2         9 { $self->log(WARNING => "Changes not written to read-only folder $self.
337             Suggestion: \$folder->close(write => 'NEVER')");
338 2 50       88 $locker->unlock if $locker;
339 2         18 $self->{MB_messages} = []; # Boom!
340 2         12 return 0;
341             }
342              
343             my $rc = !$write
344             || $self->write
345             ( force => $force
346 86   66     589 , save_deleted => $args{save_deleted} || 0
347             );
348              
349 86 100       493 $locker->unlock if $locker;
350 86         346 $self->{MB_messages} = []; # Boom!
351 86         67308 $rc;
352             }
353              
354              
355             sub delete(@)
356 21     21 1 49 { my ($self, %args) = @_;
357 21 50       53 my $recurse = exists $args{recursive} ? $args{recursive} : 1;
358              
359             # Extra protection: do not remove read-only folders.
360 21 50       55 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       63 if($recurse)
368 21         74 { foreach ($self->listSubFolders)
369 14         55 { my $sub = $self->openRelatedFolder
370             (folder => "$self/$_", access => 'd', create => 0);
371 14 50       95 defined $sub && $sub->delete(%args);
372             }
373             }
374              
375 21         102 $self->close(write => 'NEVER');
376 21         244 $self;
377             }
378              
379             #-------------------------------------------
380              
381              
382 0     0 1 0 sub appendMessages(@) {shift->notImplemented}
383              
384             #-------------------------------------------
385              
386              
387 251     251 1 4587 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 948 { my $self = shift;
397 42 100       173 return $self->isModified unless @_; # compat 2.036
398              
399             return
400 38 100       178 if $self->{MB_modified} = shift; # force modified flag
401              
402             # unmodify all messages
403 30         99 $_->modified(0) foreach $self->messages;
404 30         378 0;
405             }
406              
407              
408             sub isModified()
409 85     85 1 175 { my $self = shift;
410 85 100       374 return 1 if $self->{MB_modified};
411              
412 63         121 foreach (@{$self->{MB_messages}})
  63         352  
413 1513 100 100     8374 { return $self->{MB_modified} = 1
414             if $_->isDeleted || $_->isModified;
415             }
416              
417 42         378 0;
418             }
419              
420             #-------------------------------------------
421              
422              
423             sub message(;$$)
424 541     541 1 43463 { my ($self, $index) = (shift, shift);
425 541 50       2260 @_ ? $self->{MB_messages}[$index] = shift : $self->{MB_messages}[$index];
426             }
427              
428              
429             sub messageId($;$)
430 2024     2024 1 9986 { my ($self, $msgid) = (shift, shift);
431              
432 2024 100       5067 if($msgid =~ m/\<([^>]+)\>/s )
433 2         6 { $msgid = $1;
434 2         8 $msgid =~ s/\s//gs;
435              
436 2 50       17 $self->log(WARNING => "Message-id '$msgid' does not contain a domain.")
437             unless index($msgid, '@') >= 0;
438             }
439              
440 2024 100       4852 return $self->{MB_msgid}{$msgid} unless @_;
441              
442 1737         2387 my $message = shift;
443              
444             # Undefine message?
445 1737 100       3349 unless($message)
446 55         159 { delete $self->{MB_msgid}{$msgid};
447 55         108 return;
448             }
449              
450 1682         3679 my $double = $self->{MB_msgid}{$msgid};
451 1682 50 33     3631 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         5512 $self->{MB_msgid}{$msgid} = $message;
470 1682         5575 weaken($self->{MB_msgid}{$msgid});
471 1682         3431 $message;
472             }
473              
474 4     4 0 2359 sub messageID(@) {shift->messageId(@_)} # compatibility
475              
476              
477             sub find($)
478 1     1 1 4 { my ($self, $msgid) = (shift, shift);
479 1         2 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         5 $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 63793 { my $self = shift;
499              
500 474 100       1065 return @{$self->{MB_messages}} unless @_;
  460         2335  
501 14         33 my $nr = @{$self->{MB_messages}};
  14         36  
502              
503 14 100       51 if(@_==2) # range
504 2         8 { my ($begin, $end) = @_;
505 2 50       10 $begin += $nr if $begin < 0;
506 2 50       7 $begin = 0 if $begin < 0;
507 2 50       9 $end += $nr if $end < 0;
508 2 50       6 $end = $nr-1 if $end >= $nr;
509              
510 2 50       8 return () if $begin > $end;
511              
512 2         10 my @range = @{$self->{MB_messages}}[$begin..$end];
  2         12  
513 2         11 return @range;
514             }
515              
516 12         27 my $what = shift;
517             my $action
518             = ref $what eq 'CODE'? $what
519 55     55   109 : $what eq 'DELETED' ? sub {$_[0]->isDeleted}
520 125     125   238 : $what eq 'ACTIVE' ? sub {not $_[0]->isDeleted}
521 10     10   26 : $what eq 'ALL' ? sub {1}
522 0     0   0 : $what =~ s/^\!// ? sub {not $_[0]->label($what)}
523 12 0   0   125 : sub {$_[0]->label($what)};
  0 50       0  
    100          
    100          
    50          
524              
525 12         26 grep {$action->($_)} @{$self->{MB_messages}};
  190         658  
  12         53  
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 285 { my $self = shift;
539              
540 2 100       6 unless(@_)
541             { return $self->{MB_current}
542 1 50       5 if exists $self->{MB_current};
543            
544             # Which one becomes current?
545 1   0     6 my $current
546             = $self->findFirstLabeled(current => 1)
547             || $self->findFirstLabeled(seen => 0)
548             || $self->message(-1)
549             || return undef;
550              
551 1         14 $current->label(current => 1);
552 1         14 $self->{MB_current} = $current;
553 1         9 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         36 ($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       9 return () unless keys %search;
572              
573             # do not run on empty folder
574 2 50       11 my $nr_messages = $self->messages
575             or return keys %search;
576              
577 2 100       12 my $startmsg = defined $startid ? $self->messageId($startid) : undef;
578              
579             # Set-up window-bound.
580 2         4 my $bound = 0;
581 2 100 66     13 if($window ne 'ALL' && defined $startmsg)
582 1         7 { $bound = $startmsg->seqnr - $window;
583 1 50       4 $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       26 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         10 while($last >= $bound)
596 82         217 { my $message = $self->message($last);
597 82         298 my $msgid = $message->messageId; # triggers load
598              
599 82 50       4308 if(delete $search{$msgid}) # where we looking for this one?
600 0 0       0 { last unless keys %search;
601             }
602              
603 82 100       190 last if $message->timestamp < $after;
604 81         32038 $last--;
605             }
606              
607 2         426 $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       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 64 { my $self = shift;
633 27         48 my @options = (%{$self->{MB_init_options}}, @_);
  27         236  
634              
635             $self->{MB_manager}
636 27 100       182 ? $self->{MB_manager}->open(type => ref($self), @options)
637             : (ref $self)->new(@options);
638             }
639              
640              
641             sub openSubFolder($@)
642 13     13 1 1626 { my $self = shift;
643 13         47 my $name = $self->nameOfSubFolder(shift);
644 13         111 $self->openRelatedFolder(@_, folder => $name);
645             }
646              
647              
648             sub nameOfSubFolder($;$)
649 4     4 1 14 { my ($thing, $name) = (shift, shift);
650 4 50       25 my $parent = @_ ? shift : ref $thing ? $thing->name : undef;
    50          
651 4 50       20 defined $parent ? "$parent/$name" : $name;
652             }
653              
654              
655             sub topFolderWithMessages() { 1 }
656              
657             #-------------------------------------------
658              
659              
660             sub read(@)
661 63     63 1 192 { my $self = shift;
662 63         260 $self->{MB_open_time} = time;
663              
664 63         251 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       509 );
676              
677 63 50       357 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         274 $self;
683             }
684              
685             #-------------------------------------------
686              
687              
688             sub write(@)
689 31     31 1 1592 { 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         93 my (@keep, @destroy);
697 31 50       120 if($args{save_deleted})
698 0         0 { @keep = $self->messages;
699             }
700             else
701 31         111 { foreach ($self->messages)
702 964 100       1780 { if($_->isDeleted)
703 28         92 { push @destroy, $_;
704 28         125 $_->diskDelete;
705             }
706 936         2973 else {push @keep, $_}
707             }
708             }
709              
710 31 100 100     333 unless(@destroy || $self->isModified)
711 1         5 { $self->log(PROGRESS => "Folder $self not changed, so not updated.");
712 1         27 return $self;
713             }
714              
715 30         129 $args{messages} = \@keep;
716 30 50       238 unless($self->writeMessages(\%args))
717 0         0 { $self->log(WARNING => "Writing folder $self failed.");
718 0         0 return undef;
719             }
720              
721 30         221 $self->modified(0);
722 30         143 $self->{MB_messages} = \@keep;
723              
724 30         191 $self;
725             }
726              
727              
728             sub determineBodyType($$)
729 1656     1656 1 3184 { my ($self, $message, $head) = @_;
730              
731             return $self->{MB_body_delayed_type}
732             if $self->{MB_lazy_permitted}
733             && ! $message->isPart
734 1656 100 100     10517 && ! $self->{MB_extract}->($self, $head);
      100        
735              
736 1044         2138 my $bodytype = $self->{MB_body_type};
737 1044 50       3420 ref $bodytype ? $bodytype->($head) : $bodytype;
738             }
739              
740             sub extractDefault($)
741 440     440 0 765 { my ($self, $head) = @_;
742 440         1194 my $size = $head->guessBodySize;
743 440 50       21551 defined $size ? $size < 10000 : 0 # immediately extract < 10kb
744             }
745              
746             sub lazyPermitted($)
747 558     558 0 939 { my $self = shift;
748 558         1290 $self->{MB_lazy_permitted} = shift;
749             }
750              
751              
752             sub storeMessage($)
753 2138     2138 1 3458 { my ($self, $message) = @_;
754              
755 2138         2782 push @{$self->{MB_messages}}, $message;
  2138         4926  
756 2138         3100 $message->seqnr( @{$self->{MB_messages}} -1);
  2138         6856  
757 2138         3530 $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 286 { my ($self, $message) = (shift, shift);
782 142         679 my $mmtype = $self->{MB_message_type};
783 142 50       1212 $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 1252 sub locker() { shift->{MB_locker} }
797              
798              
799             sub toBeThreaded(@)
800 1682     1682 1 2419 { my $self = shift;
801              
802             my $manager = $self->{MB_manager}
803 1682 100       4709 or return $self;
804              
805 578         2046 $manager->toBeThreaded($self, @_);
806 578         1050 $self;
807             }
808              
809              
810             sub toBeUnthreaded(@)
811 55     55 1 77 { my $self = shift;
812              
813             my $manager = $self->{MB_manager}
814 55 50       152 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 39 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   16468 { my $self = shift;
841 88 100 66     2572 $self->close unless in_global_destruction || $self->{MB_is_closed};
842             }
843              
844             #-------------------------------------------
845              
846              
847             1;