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