File Coverage

blib/lib/Mail/Box/Thread/Node.pm
Criterion Covered Total %
statement 125 152 82.2
branch 42 58 72.4
condition 29 50 58.0
subroutine 29 36 80.5
pod 19 22 86.3
total 244 318 76.7


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::Thread::Node;{
13             our $VERSION = '4.01';
14             }
15              
16 4     4   30 use parent 'Mail::Reporter';
  4         11  
  4         28  
17              
18 4     4   304 use strict;
  4         8  
  4         109  
19 4     4   18 use warnings;
  4         36  
  4         260  
20              
21 4     4   22 use Log::Report 'mail-box', import => [ qw/__x error/ ];
  4         10  
  4         30  
22              
23 4     4   793 use List::Util qw/first/;
  4         17  
  4         9636  
24              
25             #--------------------
26              
27             sub new(@)
28 150     150 1 622 { my ($class, %args) = @_;
29 150         501 (bless {}, $class)->init(\%args);
30             }
31              
32             sub init($)
33 150     150 0 330 { my ($self, $args) = @_;
34              
35 150 100       417 if(my $message = $args->{message})
    50          
36 135         231 { push @{$self->{MBTN_messages}}, $message;
  135         487  
37 135   33     492 $self->{MBTN_msgid} = $args->{msgid} || $message->messageId;
38             }
39             elsif(my $msgid = $args->{msgid})
40 15         40 { $self->{MBTN_msgid} = $msgid;
41             }
42             else
43 0         0 { error __x"thread node needs a message object or msgid.";
44             }
45              
46 150         346 $self->{MBTN_dummy_type} = $args->{dummy_type};
47 150         416 $self;
48             }
49              
50             #--------------------
51              
52             sub isDummy()
53 202     202 1 315 { my $self = shift;
54 202         376 my $msgs = $self->{MBTN_messages};
55 202 100 66     1564 ! defined $msgs || ! @$msgs || $msgs->[0]->isDummy;
56             }
57              
58              
59 653     653 1 1772 sub messageId() { $_[0]->{MBTN_msgid} }
60              
61             #--------------------
62              
63             sub message()
64 504     504 1 2238 { my $self = shift;
65 504   100     1390 my $messages = $self->{MBTN_messages} ||= [];
66              
67 504 100       1166 unless(@$messages)
68 10 50       31 { return () if wantarray;
69              
70 10         78 my $dummy = $self->{MBTN_dummy_type}->new(messageId => $self->{MBTN_msgid});
71 10         40 push @$messages, $dummy;
72 10         26 return $dummy;
73             }
74              
75 494 100       1782 return @$messages if wantarray;
76 351   33 351   1857 (first { ! $_->isDeleted } @$messages) // $messages->[0];
  351         1214  
77             }
78              
79              
80             sub addMessage($)
81 0     0 1 0 { my ($self, $message) = @_;
82              
83 0 0       0 return $self->{MBTN_messages} = [ $message ]
84             if $self->isDummy;
85              
86 0         0 push @{$self->{MBTN_messages}}, $message;
  0         0  
87 0         0 $message;
88             }
89              
90              
91             sub expand(;$)
92 121     121 1 173 { my $self = shift;
93 121 100 100     348 @_ or return $self->message->label('folded') || 0;
94              
95 2         5 my $fold = not shift;
96 2         9 $_->label(folded => $fold) for $self->message;
97 2         40 $fold;
98             }
99              
100             # compatibility <2.0
101 121 100   121 0 189 sub folded(;$) { my $s = shift; @_ ? $s->expand(not $_[0]) : $s->expand }
  121         343  
102              
103             #--------------------
104              
105             sub repliedTo()
106 306     306 1 1327 { my $self = shift;
107 306 50       970 wantarray ? ($self->{MBTN_parent}, $self->{MBTN_quality}) : $self->{MBTN_parent};
108             }
109              
110              
111             sub follows($$)
112 138     138 1 392 { my ($self, $thread, $how) = @_;
113 138         272 my $quality = $self->{MBTN_quality};
114              
115             # Do not create cyclic constructs caused by erroneous refs.
116              
117 138         290 my $msgid = $self->messageId; # Look up for myself, upwards in thread
118 138         361 for(my $walker = $thread; defined $walker; $walker = $walker->repliedTo)
119 201 50       437 { return undef if $walker->messageId eq $msgid;
120             }
121              
122 138         304 my $threadid = $thread->messageId; # a->b and b->a (ref order reversed)
123 138         313 foreach ($self->followUps)
124 36 50       77 { return undef if $_->messageId eq $threadid;
125             }
126              
127             # Register
128              
129 138 100 100     634 if($how eq 'REPLY' || !defined $quality)
130 74         171 { $self->{MBTN_parent} = $thread;
131 74         169 $self->{MBTN_quality} = $how;
132 74         337 return $self;
133             }
134              
135 64 100       348 return $self if $quality eq 'REPLY';
136              
137 9 50 0     36 if($how eq 'REFERENCE' || ($how eq 'GUESS' && $quality ne 'REFERENCE'))
      33        
138 9         19 { $self->{MBTN_parent} = $thread;
139 9         19 $self->{MBTN_quality} = $how;
140             }
141              
142 9         44 $self;
143             }
144              
145              
146             sub followedBy(@)
147 138     138 1 221 { my $self = shift;
148 138         378 $self->{MBTN_followUps}{$_->messageId} = $_ for @_;
149 138         288 $self;
150             }
151              
152              
153             sub followUps()
154 337     337 1 578 { my $self = shift;
155 337 100       899 $self->{MBTN_followUps} ? values %{$self->{MBTN_followUps}} : ();
  131         651  
156             }
157              
158              
159             sub sortedFollowUps()
160 119     119 1 205 { my $self = shift;
161 119   33 60   552 my $prepare = shift || sub { $_[0]->startTimeEstimate || 0 };
  60         277  
162 119   33 7   565 my $compare = shift || sub { $_[0] <=> $_[1]};
  7         75  
163              
164 119         2238 my %value = map +($prepare->($_) => $_), $self->followUps;
165 119         3739 map $value{$_}, sort { $compare->($a, $b) } keys %value;
  7         23  
166             }
167              
168             #--------------------
169              
170             sub threadToString(;$$$) # two undocumented parameters for layout args
171 119     119 1 5319 { my $self = shift;
172 119   66 106   599 my $code = shift || sub { $_[0]->head->study('subject') };
  106         334  
173 119   100     586 my ($first, $other) = (shift || '', shift || '');
      100        
174 119         315 my $message = $self->message;
175 119         1462 my @follows = $self->sortedFollowUps;
176              
177 119         225 my @out;
178 119 100       272 if($self->folded)
    100          
179 1   50     11 { my $text = $code->($message) || '';
180 1         170 chomp $text;
181 1         60 return " $first [" . $self->nrMessages . "] $text\n";
182             }
183             elsif($message->isDummy)
184 13 100       329 { $first .= $first ? '-*-' : ' *-';
185 13 100       63 return (shift @follows)->threadToString($code, $first, "$other " )
186             if @follows==1;
187              
188 7         70 push @out, (shift @follows)->threadToString($code, $first, "$other | " )
189             while @follows > 1;
190             }
191             else
192 105   100     1700 { my $text = $code->($message) || '';
193 105         153431 chomp $text;
194 105         9421 my $size = $message->shortSize;
195 105         10321 @out = "$size$first $text\n";
196 105         9661 push @out, (shift @follows)->threadToString($code, "$other |-", "$other | " )
197             while @follows > 1;
198             }
199              
200 112 100       907 push @out, (shift @follows)->threadToString($code, "$other `-","$other " )
201             if @follows;
202              
203 112         906 join '', @out;
204             }
205              
206              
207             sub startTimeEstimate()
208 130     130 1 212 { my $self = shift;
209 130 100       324 $self->isDummy or return $self->message->timestamp;
210              
211 10         23 my $earliest;
212 10         27 foreach ($self->followUps)
213 14         56 { my $stamp = $_->startTimeEstimate;
214 14 100 66     1700 $earliest = $stamp if !defined $earliest || (defined $stamp && $stamp < $earliest);
      100        
215             }
216              
217 10         71 $earliest;
218             }
219              
220              
221             sub endTimeEstimate()
222 0     0 1 0 { my $self = shift;
223              
224 0         0 my $latest;
225             $self->recurse( sub {
226 0     0   0 my $node = shift;
227 0 0       0 return 1 if $node->isDummy;
228 0         0 my $stamp = $node->message->timestamp;
229 0 0 0     0 $latest = $stamp if !$latest || $stamp > $latest;
230 0         0 1;
231 0         0 });
232              
233 0         0 $latest;
234             }
235              
236              
237             sub recurse($)
238 70     70 1 135 { my ($self, $code) = @_;
239              
240 70 50       141 $code->($self) or return $self;
241              
242             $_->recurse($code) or last
243 70   50     142 for $self->followUps;
244              
245 70         146 $self;
246             }
247              
248              
249             sub totalSize()
250 0     0 1 0 { my $self = shift;
251 0         0 my $total = 0;
252              
253             $self->recurse(sub {
254 0     0   0 my @msgs = shift->messages;
255 0 0       0 $total += $msgs[0]->size if @msgs;
256 0         0 1;
257 0         0 });
258              
259 0         0 $total;
260             }
261              
262              
263             sub numberOfMessages()
264 29     29 1 1263 { my $self = shift;
265 29         46 my $total = 0;
266 29 100   54   119 $self->recurse( sub { $_[0]->isDummy or ++$total; 1 } );
  54         110  
  54         140  
267 29         174 $total;
268             }
269              
270 1     1 0 5 sub nrMessages() { $_[0]->numberOfMessages } # compatibility
271              
272              
273             sub threadMessages()
274 3     3 1 11 { my $self = shift;
275 3         6 my @messages;
276             $self->recurse( sub {
277 6     6   13 my $node = shift;
278 6 50       15 push @messages, $node->message unless $node->isDummy;
279 6         16 1;
280 3         21 });
281              
282 3         18 @messages;
283             }
284              
285              
286             sub ids()
287 0     0 1   { my $self = shift;
288 0           my @ids;
289 0     0     $self->recurse( sub { push @ids, $_[0]->messageId } );
  0            
290 0           @ids;
291             }
292              
293             1;