File Coverage

blib/lib/Mail/Box.pm
Criterion Covered Total %
statement 305 362 84.2
branch 142 222 63.9
condition 56 106 52.8
subroutine 63 84 75.0
pod 49 56 87.5
total 615 830 74.1


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Box version 4.01.
2             # The POD got stripped from this file by OODoc version 3.05.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Box;{
13             our $VERSION = '4.01';
14             }
15              
16 34     34   47217 use parent 'Mail::Reporter';
  34         79  
  34         320  
17              
18 34     34   291426 use strict;
  34         69  
  34         1174  
19 34     34   199 use warnings;
  34         91  
  34         2648  
20              
21 34     34   395 use Log::Report 'mail-box', import => [ qw/__x error info mistake panic trace warning/ ];
  34         187  
  34         514  
22              
23 34     34   28974 use Mail::Box::Message ();
  34         127  
  34         974  
24 34     34   18480 use Mail::Box::Locker ();
  34         117  
  34         1177  
25              
26 34     34   222 use Scalar::Util qw/weaken/;
  34         74  
  34         2182  
27 34     34   215 use List::Util qw/sum first/;
  34         68  
  34         2728  
28 34     34   274 use Devel::GlobalDestruction 'in_global_destruction';
  34         108  
  34         375  
29              
30             #--------------------
31              
32             #--------------------
33              
34             use overload
35 0     0   0 '@{}' => sub { $_[0]->{MB_messages} },
36             '""' => 'name',
37 34     34   6117 'cmp' => sub { $_[0]->name cmp "${_[1]}" };
  34     42   91  
  34         552  
  42         143  
38              
39             #--------------------
40              
41             sub new(@)
42 84     84 1 2275105 { my ($class, %args) = @_;
43              
44 84 50       416 if($class eq __PACKAGE__)
45 0         0 { my $package = __PACKAGE__;
46              
47 0         0 print STDERR <
48             You should not instantiate $package directly, but rather one of the
49             sub-classes, such as Mail::Box::Mbox. If you need automatic folder
50             type detection then use Mail::Box::Manager.
51             USAGE
52 0         0 exit 1;
53             }
54              
55 84         328 weaken $args{manager}; # otherwise, the manager object may live too long
56 84         819 my $self = $class->SUPER::new(%args, init_options => \%args);
57              
58             $self->read or return
59 83 100 50     1480 if $self->{MB_access} =~ /r|a/;
60              
61 83         494 $self;
62             }
63              
64             sub init($)
65 84     84 0 219 { my ($self, $args) = @_;
66 84         441 $self->SUPER::init($args);
67              
68 84         353 my $class = ref $self;
69             my $foldername = $args->{folder} || $ENV{MAIL}
70 84 50 33     478 or error __x"no folder name specified.";
71              
72 84         1934 $self->{MB_foldername} = $foldername;
73 84         420 $self->{MB_init_options} = $args->{init_options};
74 84   50     634 $self->{MB_coerce_opts} = $args->{coerce_options} || [];
75 84   100     391 $self->{MB_access} = $args->{access} || 'r';
76 84 50       371 $self->{MB_remove_empty} = exists $args->{remove_when_empty} ? $args->{remove_when_empty} : 1;
77 84 100       402 $self->{MB_save_on_exit} = exists $args->{save_on_exit} ? $args->{save_on_exit} : 1;
78 84         215 $self->{MB_messages} = [];
79 84         221 $self->{MB_msgid} = {};
80 84   50     522 $self->{MB_organization} = $args->{organization} || 'FILE';
81 84         260 $self->{MB_linesep} = "\n";
82 84         300 $self->{MB_keep_dups} = $args->{keep_dups};
83 84         477 $self->{MB_fix_headers} = $args->{fix_headers};
84              
85 84         483 my $folderdir = $self->folderdir($args->{folderdir});
86             $self->{MB_trusted} = exists $args->{trusted} ? $args->{trusted}
87 84 50       1169 : substr($foldername, 0, 1) eq '=' ? 1
    100          
    50          
88             : !defined $folderdir ? 0
89             : substr($foldername, 0, length $folderdir) eq $folderdir;
90              
91 84 100       315 if(exists $args->{manager})
92 39         130 { $self->{MB_manager} = $args->{manager};
93 39         109 weaken($self->{MB_manager});
94             }
95              
96 84   33     551 $self->{MB_message_type} = $args->{message_type} || $class . '::Message';
97 84   50     506 $self->{MB_head_type} = $args->{head_type} || 'Mail::Message::Head::Complete';
98 84   50     322 $self->{MB_body_type} = $args->{body_type} || 'Mail::Message::Body::Lines';
99 84   50     452 $self->{MB_body_delayed_type} = $args->{body_delayed_type} || 'Mail::Message::Body::Delayed';
100 84   50     398 $self->{MB_head_delayed_type} = $args->{head_delayed_type} || 'Mail::Message::Head::Delayed';
101 84   50     467 $self->{MB_multipart_type} = $args->{multipart_type} || 'Mail::Message::Body::Multipart';
102 84         269 $self->{MB_field_type} = $args->{field_type};
103              
104 84   100     365 my $extract = $args->{extract} || 'extractDefault';
105             $self->{MB_extract}
106             = ref $extract eq 'CODE' ? $extract
107 486     486   1951 : $extract eq 'ALWAYS' ? sub { 1 }
108 600     600   4789 : $extract eq 'LAZY' ? sub { 0 }
109 0     0   0 : $extract eq 'NEVER' ? sub { 1 } # compatibility
110 34     34   59809 : $extract =~ m/\D/ ? sub { no strict 'refs'; shift->$extract(@_) }
  34     440   136  
  34         206601  
  440         1631  
111 84 50   18   1140 : sub { my $size = $_[1]->guessBodySize; defined $size && $size < $extract };
  18 100       117  
  18 50       1307  
    100          
    100          
    50          
112              
113             #
114             # Create a locker.
115             #
116              
117             $self->{MB_locker} = $args->{locker} || Mail::Box::Locker->new(
118             folder => $self,
119             method => $args->{lock_type},
120             timeout => $args->{lock_timeout},
121             expires => $args->{lock_wait},
122 84   33     1672 file => ($args->{lockfile} || $args->{lock_file}),
123             );
124              
125 84         432 $self;
126             }
127              
128             #--------------------
129              
130 14     14 1 76 sub removeEmpty() { $_[0]->{MB_remove_empty} }
131              
132              
133 196 100   196 1 490 sub folderdir(;$) { my $self = shift; @_ ? $self->{MB_folderdir} = shift : $self->{MB_folderdir} }
  196         1686  
134              
135              
136 0     0 1 0 sub type() { $_[0]->notImplemented }
137              
138              
139 4575     4575 1 31010 sub name() { $_[0]->{MB_foldername} }
140              
141              
142 40     40 1 236 sub isTrusted() { $_[0]->{MB_trusted} }
143 776     776 1 3569 sub fixHeaders() { $_[0]->{MB_fix_headers} }
144              
145             #--------------------
146              
147 0     0 1 0 sub foundIn($@) { $_[0]->notImplemented }
148              
149              
150             sub url()
151 0     0 1 0 { my $self = shift;
152 0         0 $self->type . ':' . $self->name;
153             }
154              
155              
156 0     0 1 0 sub size() { sum 0, map $_->size, $_[0]->messages('ACTIVE') }
157              
158              
159             sub update(@)
160 1     1 1 5 { my $self = shift;
161              
162             $self->updateMessages(
163             trusted => $self->isTrusted,
164             head_type => $self->{MB_head_type},
165             field_type => $self->{MB_field_type},
166             message_type => $self->{MB_message_type},
167             body_delayed_type => $self->{MB_body_delayed_type},
168             head_delayed_type => $self->{MB_head_delayed_type},
169 1         6 @_,
170             );
171              
172 1         5 $self;
173             }
174              
175              
176 0     0 1 0 sub organization() { $_[0]->notImplemented }
177              
178              
179             sub addMessage($@)
180 142     142 1 271983 { my $self = shift;
181 142 50       561 my $message = shift or return $self;
182 142         466 my %args = @_;
183              
184 142 50 33     842 $message->can('folder') && defined $message->folder
185             and error __x"you cannot add a message which is already part of a folder to a new one. Please use moveTo or copyTo.";
186              
187             # Force the message into the right folder-type.
188 142         551 my $coerced = $self->coerce($message);
189 142         12093 $coerced->folder($self);
190              
191 142 50       435 unless($coerced->head->isDelayed)
192             { # Do not add the same message twice, unless keep_dups.
193 142         1347 my $msgid = $coerced->messageId;
194              
195 142 50       969 unless($self->{MB_keep_dups})
196 142 100       500 { if(my $found = $self->messageId($msgid))
197 5         22 { $coerced->label(deleted => 1);
198 5         76 return $found;
199             }
200             }
201              
202 137         421 $self->messageId($msgid, $coerced);
203 137         394 $self->toBeThreaded($coerced);
204             }
205              
206 137         577 $self->storeMessage($coerced);
207 137         621 $coerced;
208             }
209              
210              
211             sub addMessages(@)
212 7     7 1 17 { my $self = shift;
213 7         43 map $self->addMessage($_), @_;
214             }
215              
216              
217             sub copyTo($@)
218 4     4 1 525 { my ($self, $to, %args) = @_;
219              
220 4   50     18 my $select = $args{select} || 'ACTIVE';
221 4 50       17 my $subfolders = exists $args{subfolders} ? $args{subfolders} : 1;
222 4         67 my $can_recurse = not $self->isa('Mail::Box::POP3');
223              
224 4 0       28 my ($flatten, $recurse)
    50          
    100          
    100          
225             = $subfolders eq 'FLATTEN' ? (1, 0)
226             : $subfolders eq 'RECURSE' ? (0, 1)
227             : !$subfolders ? (0, 0)
228             : $can_recurse ? (0, 1)
229             : (1, 0);
230              
231 4   50     23 my $delete = $args{delete_copied} || 0;
232 4   50     35 my $share = $args{share} || 0;
233              
234 4         22 $self->_copy_to($to, $select, $flatten, $recurse, $delete, $share);
235             }
236              
237             # Interface may change without warning.
238             sub _copy_to($@)
239 10     10   41 { my ($self, $to, @options) = @_;
240 10         33 my ($select, $flatten, $recurse, $delete, $share) = @options;
241              
242 10 50       34 $to->writable
243             or error __x"destination folder {name} is not writable.", name => $to;
244              
245             # Take messages from this folder.
246 10         48 my @select = $self->messages($select);
247 10         214 trace "Copying ".@select." messages from $self to $to.";
248              
249 10         329 foreach my $msg (@select)
250 87 50       365 { $msg->copyTo($to, share => $share)
251             or error __x"copying failed for one message.";
252              
253 87 50       313 $msg->label(deleted => 1) if $delete;
254             }
255              
256 10 100 100     105 $flatten || $recurse
257             or return $self;
258              
259             # Take subfolders
260              
261             SUBFOLDER:
262 8         95 foreach my $subf ($self->listSubFolders(check => 1))
263 6         42 { my $subfolder = $self->openSubFolder($subf, access => 'r');
264              
265 6 100       25 if($flatten) # flatten
266 3 50       22 { unless($subfolder->_copy_to($to, @options))
267 0         0 { $subfolder->close;
268 0         0 return;
269             }
270             }
271             else # recurse
272 3         24 { my $subto = $to->openSubFolder($subf, create => 1, access => 'rw');
273 3 50       23 unless($subfolder->_copy_to($subto, @options))
274 0         0 { $subfolder->close;
275 0         0 $subto->close;
276 0         0 return;
277             }
278              
279 3         19 $subto->close;
280             }
281              
282 6         30 $subfolder->close;
283             }
284              
285 8         81 $self;
286             }
287              
288              
289             sub close(@)
290 90     90 1 20013 { my ($self, %args) = @_;
291 90   50     576 my $force = $args{force} || 0;
292              
293 90 100       465 return 1 if $self->{MB_is_closed};
294 88         305 $self->{MB_is_closed}++;
295              
296             # Inform manager that the folder is closed.
297 88         383 my $manager = delete $self->{MB_manager};
298             $manager->close($self, close_by_self =>1)
299 88 100 100     645 if defined $manager && !$args{close_by_manager};
300              
301 88   100     458 my $when = $args{write} // 'MODIFIED';
302 88 50       669 my $write
    100          
    100          
303             = $when eq 'MODIFIED' ? $self->isModified
304             : $when eq 'ALWAYS' ? 1
305             : $when eq 'NEVER' ? 0
306             : error __x"unknown value to folder->close(write => {when}).", when => $when;
307              
308 88         702 my $locker = $self->locker;
309 88 50 66     567 if($write && !$force && !$self->writable)
      66        
310 0         0 { warning __x"changes not written to read-only folder {name}; suggestion: \$folder->close(write => 'NEVER')", name => $self->name;
311 0 0       0 $locker->unlock if $locker;
312 0         0 $self->{MB_messages} = []; # Boom!
313 0         0 return 0;
314             }
315              
316             my $rc = ! $write ||
317 88   66     684 $self->write(force => $force, save_deleted => $args{save_deleted} || 0);
318              
319 88 100       742 $locker->unlock if $locker;
320 88         511 $self->{MB_messages} = []; # Boom!
321 88         80464 $rc;
322             }
323              
324              
325             sub delete(@)
326 21     21 1 56 { my ($self, %args) = @_;
327 21 50       59 my $recurse = exists $args{recursive} ? $args{recursive} : 1;
328              
329             # Extra protection: do not remove read-only folders.
330 21 50       101 unless($self->writable)
331 0         0 { warning __x"folder {name} not deleted: not writable.", name => $self->name;
332 0         0 $self->close(write => 'NEVER');
333 0         0 return;
334             }
335              
336             # Sub-directories need to be removed first.
337 21 50       60 if($recurse)
338 21         66 { foreach ($self->listSubFolders)
339 14         81 { my $sub = $self->openRelatedFolder (folder => "$self/$_", access => 'd', create => 0);
340 14 50       90 defined $sub && $sub->delete(%args);
341             }
342             }
343              
344 21         138 $self->close(write => 'NEVER');
345 21         356 $self;
346             }
347              
348              
349 0     0 1 0 sub appendMessages(@) { $_[0]->notImplemented }
350              
351             #--------------------
352              
353 165     165 1 733 sub writable() { $_[0]->access =~ /w|a|d/ }
354 0     0 0 0 sub writeable() { $_[0]->writable } # compatibility [typo]
355             sub readable() { 1 } # compatibility
356              
357              
358 252 50   252 1 533 sub access(;$) { my $self = shift; @_ ? $self->{MB_access} = shift : $self->{MB_access} }
  252         4147  
359              
360              
361             sub modified(;$)
362 42     42 1 1259 { my $self = shift;
363 42 100       164 @_ or return $self->isModified; # compat 2.036
364              
365             return
366 38 100       241 if $self->{MB_modified} = shift; # force modified flag
367              
368             # Unmodify all messages as well
369 30         150 $_->modified(0) for $self->messages;
370 30         505 0;
371             }
372              
373              
374             sub isModified()
375 83     83 1 204 { my $self = shift;
376 83 100       380 return 1 if $self->{MB_modified};
377              
378 61         165 foreach my $msg (@{$self->{MB_messages}})
  61         299  
379 1485 100 100     10454 { return $self->{MB_modified} = 1
380             if $msg->isDeleted || $msg->isModified;
381             }
382 42     1272   642 $self->{MB_modified} = first { $_->isModified } @{$self->{MB_messages}};
  1272         6946  
  42         455  
383              
384 42         610 0;
385             }
386              
387             #--------------------
388              
389             sub message(;$$)
390 541     541 1 61709 { my ($self, $index) = (shift, shift);
391 541 50       2534 @_ ? $self->{MB_messages}[$index] = shift : $self->{MB_messages}[$index];
392             }
393              
394              
395             sub messageId($;$)
396 2024     2024 1 15226 { my ($self, $msgid) = (shift, shift);
397              
398 2024 100       8493 if($msgid =~ m/\<([^>]+)\>/s )
399 2         12 { $msgid = $1 =~ s/\s//grs;
400              
401 2 50       9 index($msgid, '@') >= 0
402             or warning __x"message-id '{msgid}' does not contain a domain.", msgid => $msgid;
403             }
404              
405 2024 100       5908 @_ or return $self->{MB_msgid}{$msgid};
406              
407 1737         3155 my $message = shift;
408              
409             # Undefine message?
410 1737 100       7034 unless($message)
411 55         221 { delete $self->{MB_msgid}{$msgid};
412 55         138 return;
413             }
414              
415 1682         5458 my $double = $self->{MB_msgid}{$msgid};
416 1682 50 33     4407 if(defined $double && !$self->{MB_keep_dups})
417 0         0 { my $head1 = $message->head;
418 0         0 my $head2 = $double->head;
419              
420 0   0     0 my $subj1 = $head1->get('subject') || '';
421 0   0     0 my $subj2 = $head2->get('subject') || '';
422              
423 0   0     0 my $to1 = $head1->get('to') || '';
424 0   0     0 my $to2 = $head2->get('to') || '';
425              
426             # Auto-delete doubles.
427 0 0 0     0 return $message->label(deleted => 1)
428             if $subj1 eq $subj2 && $to1 eq $to2;
429              
430 0         0 warning __x"different messages with id {msgid}.", msgid => $msgid;
431 0         0 $msgid = $message->takeMessageId(undef);
432             }
433              
434 1682         5925 $self->{MB_msgid}{$msgid} = $message;
435 1682         4497 weaken($self->{MB_msgid}{$msgid});
436 1682         4077 $message;
437             }
438              
439 4     4 0 7679 sub messageID(@) { shift->messageId(@_) } # compatibility
440              
441              
442             sub find($)
443 1     1 1 4 { my ($self, $msgid) = (shift, shift);
444 1         4 my $msgids = $self->{MB_msgid};
445              
446 1 50       6 if($msgid =~ m/\<([^>]*)\>/s)
447 0         0 { $msgid = $1 =~ s/\s//grs;
448             }
449             else
450             { # Illegal message-id
451 1         4 $msgid =~ s/\s/+/gs;
452             }
453              
454 1   33     11 $msgids->{$msgid} // $self->scanForMessages(undef, $msgid, 'EVER', 'ALL');
455             }
456              
457              
458             sub messages($;$)
459 478     478 1 69665 { my $self = shift;
460 478         1102 my $msgs = $self->{MB_messages};
461              
462 478 100       3052 @_ or return @$msgs;
463              
464 14 100       56 if(@_==2) # range
465 2         7 { my ($begin, $end) = @_;
466 2         4 my $nr = @$msgs;
467 2 50       5 $begin += $nr if $begin < 0;
468 2 50       107 $begin = 0 if $begin < 0;
469 2 50       5 $end += $nr if $end < 0;
470 2 50       7 $end = $nr-1 if $end >= $nr;
471 2 50       37 return $begin > $end ? () : @{$msgs}[$begin..$end];
  2         13  
472             }
473              
474 14         27 my $what = shift;
475             my $action
476             = ref $what eq 'CODE'? $what
477 55     55   376 : $what eq 'DELETED' ? sub { $_[0]->isDeleted }
478 125     125   848 : $what eq 'ACTIVE' ? sub { not $_[0]->isDeleted }
479 10     10   23 : $what eq 'ALL' ? sub { 1 }
480 0     0   0 : $what =~ s/^\!// ? sub { not $_[0]->label($what) }
481 14 0   0   967 : sub { $_[0]->label($what) };
  0 50       0  
    100          
    100          
    50          
482              
483 14         63 grep $action->($_), @$msgs;
484             }
485              
486              
487 2     2 1 419 sub nrMessages(@) { scalar shift->messages(@_) }
488              
489              
490 0     0 1 0 sub messageIds() { map $_->messageId, $_[0]->messages }
491 0     0 0 0 sub allMessageIds() { $_[0]->messageIds } # compatibility
492 0     0 0 0 sub allMessageIDs() { $_[0]->messageIds } # compatibility
493              
494              
495             sub current(;$)
496 2     2 1 441 { my $self = shift;
497              
498 2 100       14 unless(@_)
499             { return $self->{MB_current}
500 1 50       6 if exists $self->{MB_current};
501              
502             # Which one becomes current?
503 1   0     10 my $current
504             = $self->findFirstLabeled(current => 1)
505             || $self->findFirstLabeled(seen => 0)
506             || $self->message(-1)
507             || return undef;
508              
509 1         19 $current->label(current => 1);
510 1         34 return $self->{MB_current} = $current;
511             }
512              
513 1         4 my $next = shift;
514 1 50       6 if(my $previous = $self->{MB_current})
515 1         6 { $previous->label(current => 0);
516             }
517              
518 1         21 ($self->{MB_current} = $next)->label(current => 1);
519 1         14 $next;
520             }
521              
522              
523             sub scanForMessages($$$$)
524 2     2 1 9 { my ($self, $startid, $msgids, $moment, $window) = @_;
525              
526             # Set-up msgid-list
527 2 50       17 my %search = map +($_ => 1), ref $msgids ? @$msgids : $msgids;
528 2 50       9 keys %search or return ();
529              
530             # do not run on empty folder
531 2 50       10 my $nr_messages = $self->messages
532             or return keys %search;
533              
534 2 100       11 my $startmsg = defined $startid ? $self->messageId($startid) : undef;
535              
536             # Set-up window-bound.
537 2         5 my $bound = 0;
538 2 100 66     15 if($window ne 'ALL' && defined $startmsg)
539 1         6 { $bound = $startmsg->seqnr - $window;
540 1 50       4 $bound = 0 if $bound < 0;
541             }
542              
543 2   33     38 my $last = ($self->{MBM_last} || $nr_messages) -1;
544 2 50 33     16 return keys %search if defined $bound && $bound > $last;
545              
546             # Set-up time-bound
547 2 0       16 my $after
    50          
    100          
548             = $moment eq 'EVER' ? 0
549             : $moment =~ m/^\d+$/ ? $moment
550             : !$startmsg ? 0
551             : $startmsg->timestamp - $self->timespan2seconds($moment);
552              
553 2         7 while($last >= $bound)
554 82         318 { my $message = $self->message($last);
555 82         341 my $msgid = $message->messageId; # triggers load
556              
557 82 50       5597 if(delete $search{$msgid}) # where we looking for this one?
558 0 0       0 { keys %search or last;
559             }
560              
561 82 100       261 last if $message->timestamp < $after;
562 81         52126 $last--;
563             }
564              
565 2         584 $self->{MBM_last} = $last;
566 2         23 keys %search;
567             }
568              
569              
570             sub findFirstLabeled($;$$)
571 1     1 1 5 { my ($self, $label, $set, $msgs) = @_;
572              
573             !defined $set || $set
574 5     5   32 ? (first { $_->label($label) } (defined $msgs ? @$msgs : $self->messages))
575 1 50 33 0   23 : (first { not $_->label($label) } (defined $msgs ? @$msgs : $self->messages));
  0 0       0  
    50          
576             }
577              
578             #--------------------
579              
580 0     0 1 0 sub listSubFolders(@) { () } # by default no sub-folders
581              
582              
583             sub openRelatedFolder(@)
584 27     27 1 52 { my $self = shift;
585 27         46 my @options = (%{$self->{MB_init_options}}, @_);
  27         253  
586              
587             $self->{MB_manager}
588 27 100       246 ? $self->{MB_manager}->open(type => ref($self), @options)
589             : (ref $self)->new(@options);
590             }
591              
592              
593             sub openSubFolder($@)
594 13     13 1 2609 { my $self = shift;
595 13         57 my $name = $self->nameOfSubFolder(shift);
596 13         61 $self->openRelatedFolder(@_, folder => $name);
597             }
598              
599              
600             sub nameOfSubFolder($;$)
601 4     4 1 14 { my ($thing, $name) = (shift, shift);
602 4 50       31 my $parent = @_ ? shift : ref $thing ? $thing->name : undef;
    50          
603 4 50       22 defined $parent ? "$parent/$name" : $name;
604             }
605              
606              
607             sub topFolderWithMessages() { 1 }
608              
609             #--------------------
610              
611             sub read(@)
612 63     63 1 149 { my $self = shift;
613 63         209 $self->{MB_open_time} = time;
614              
615 63         221 local $self->{MB_lazy_permitted} = 1;
616              
617             # Read from existing folder.
618             $self->readMessages(
619             trusted => $self->{MB_trusted},
620             head_type => $self->{MB_head_type},
621             field_type => $self->{MB_field_type},
622             message_type => $self->{MB_message_type},
623             body_delayed_type => $self->{MB_body_delayed_type},
624             head_delayed_type => $self->{MB_head_delayed_type},
625             @_
626 63 50       603 ) or return;
627              
628 63 50       318 $self->{MB_modified} and panic "Modified $self->{MB_modified}";
629 63         277 $self;
630             }
631              
632              
633             sub write(@)
634 31     31 1 3418 { my ($self, %args) = @_;
635              
636 31 50 33     292 $args{force} || $self->writable
637             or error __x"folder {name} is opened read-only.", name => $self->name;
638              
639 31         117 my (@keep, @destroy);
640 31 50       156 if($args{save_deleted})
641 0         0 { @keep = $self->messages;
642             }
643             else
644 31         132 { foreach my $msg ($self->messages)
645 964 100       2159 { if($msg->isDeleted)
646 28         94 { push @destroy, $msg;
647 28         146 $msg->diskDelete;
648             }
649 936         5217 else { push @keep, $msg }
650             }
651             }
652              
653 31 100 100     332 @destroy || $self->isModified
654             or trace("Folder $self not changed, so not updated."), return $self;
655              
656 30         151 $args{messages} = \@keep;
657 30         291 $self->writeMessages(\%args);
658 30         260 $self->modified(0);
659 30         192 $self->{MB_messages} = \@keep;
660 30         268 $self;
661             }
662              
663              
664             sub determineBodyType($$)
665 1658     1658 1 4786 { my ($self, $message, $head) = @_;
666              
667             return $self->{MB_body_delayed_type}
668             if $self->{MB_lazy_permitted}
669             && ! $message->isPart
670 1658 100 100     16409 && ! $self->{MB_extract}->($self, $head);
      100        
671              
672 1044         2713 my $bodytype = $self->{MB_body_type};
673 1044 50       4310 ref $bodytype ? $bodytype->($head) : $bodytype;
674             }
675              
676             sub extractDefault($)
677 440     440 0 824 { my ($self, $head) = @_;
678 440         1572 my $size = $head->guessBodySize;
679 440 50       26347 defined $size ? $size < 10000 : 0 # immediately extract < 10kb
680             }
681              
682             sub lazyPermitted($)
683 562     562 0 1259 { my $self = shift;
684 562         1787 $self->{MB_lazy_permitted} = shift;
685             }
686              
687              
688             sub storeMessage($)
689 2138     2138 1 4394 { my ($self, $message) = @_;
690              
691 2138         3306 push @{$self->{MB_messages}}, $message;
  2138         6717  
692 2138         4587 $message->seqnr( @{$self->{MB_messages}} -1);
  2138         13562  
693 2138         4836 $message;
694             }
695              
696              
697             my %seps = (CR => "\015", LF => "\012", CRLF => "\015\012");
698              
699             sub lineSeparator(;$)
700 0     0 1 0 { my $self = shift;
701 0 0       0 @_ or return $self->{MB_linesep};
702              
703 0         0 my $sep = shift;
704 0 0       0 $sep = $seps{$sep} if exists $seps{$sep};
705              
706 0         0 $self->{MB_linesep} = $sep;
707 0         0 $_->lineSeparator($sep) for $self->messages;
708 0         0 $sep;
709             }
710              
711              
712 0     0 1 0 sub create($@) { $_[0]->notImplemented }
713              
714              
715              
716             sub coerce($@)
717 142     142 1 318 { my ($self, $message) = (shift, shift);
718 142         494 my $mmtype = $self->{MB_message_type};
719 142 50       1707 $message->isa($mmtype) ? $message : $mmtype->coerce($message, @_);
720             }
721              
722              
723 0     0 1 0 sub readMessages(@) { $_[0]->notImplemented }
724              
725              
726 0     0 1 0 sub updateMessages(@) { $_[0] }
727              
728              
729 0     0 1 0 sub writeMessages(@) { $_[0]->notImplemented }
730              
731              
732 200     200 1 746 sub locker() { $_[0]->{MB_locker} }
733              
734              
735             sub toBeThreaded(@)
736 1682     1682 1 2766 { my $self = shift;
737 1682 100       6485 my $manager = $self->{MB_manager} or return $self;
738 578         4022 $manager->toBeThreaded($self, @_);
739 578         1182 $self;
740             }
741              
742              
743             sub toBeUnthreaded(@)
744 55     55 1 94 { my $self = shift;
745 55 50       191 my $manager = $self->{MB_manager} or return $self;
746 0         0 $manager->toBeThreaded($self, @_);
747 0         0 $self;
748             }
749              
750             #--------------------
751              
752             sub timespan2seconds($)
753             {
754 3 50   3 1 45 $_[1] =~ /^\s*(\d+\.?\d*|\.\d+)\s*(hour|day|week)s?\s*$/
755             or error(__x"invalid timespan '{span}'.", span => $_[1]), return undef;
756              
757 3 50       35 $2 eq 'hour' ? $1 * 3600
    50          
758             : $2 eq 'day' ? $1 * 86400
759             : $1 * 604800; # week
760             }
761              
762             #--------------------
763              
764             sub DESTROY
765 88     88   32002 { my $self = shift;
766             in_global_destruction || $self->{MB_is_closed}
767 88 100 66     4146 or $self->close;
768             }
769              
770             #--------------------
771              
772             1;