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