| Net-IMAP-Server documentation | Contained in the Net-IMAP-Server distribution. |
Net::IMAP::Server::Mailbox - A user's view of a mailbox
This class encapsulates the view of messages in a mailbox. You may wish to subclass this class in order to source our messages from, say, a database.
Creates a new mailbox; returns undef if a mailbox with the same
full path already exists. It calls init, then load_data.
Sets up basic properties of the mailbox:
This default mailbox implementation simply returns an empty mailbox. Subclasses will probably wish to override this method.
Gets or sets the name of the mailbox. This includes a workaround for Zimbra, which doesn't understand mailbox names with colons in them -- so we substitute dashes.
Called when the server wishes the mailbox to update its state. By default, does nothing. Subclasses will probably wish to override this method.
Adds the given Net::IMAP::Server::Message MESSAGE to the mailbox,
setting its sequence in Net::IMAP::Server::Message and
mailbox in Net::IMAP::Server::Message.
uid in Net::IMAP::Server::Message is set to uidnext if the message
does not already have a uid.
Creates a mailbox under this mailbox, of the same class as this mailbox is. Any arguments are passed to new. Returns the newly added subfolder, or undef if a folder with that name already exists.
Identical to add_child. Should return false if the create is denied or fails.
Reparents this mailbox to be a child of the given
Net::IMAP::Server::Mailbox MAILBOX, with the given NAME.
Should return 0 if the reparenting is denied or fails.
Deletes this mailbox, removing it from its parent's list of children. Should return false if the deletion is denied or fails.
Expunges messages marked as \Deleted. If an arrayref of message
sequence numbers is provided, only expunges message from that set.
Appends, and returns, the given MESSAGE, which should be a string
containing the message. Returns false is the append is denied or
fails.
Called when the client selects a different mailbox, or when the client's connection closes. By default, does nothing.
Returns the path separator. Note that only the path separator of the root mailbox matters. Defaults to a forward slash.
If the function returns is undef, the server supports only flat mailboxes (i.e. no child mailboxes are allowed).
Returns the full path to this mailbox. This value is cached
aggressively on a per-connection basis; passing purge flushes this
cache, if the path name has changed.
Returns the list of flags that this mailbox supports.
Returns true if the client is allowed to set the given flag in this mailbox; this simply scans flags to check.
Returns the number of messages in this mailbox. Observing this also sets the "high water mark" for notifying the client of messages added.
Returns the number of messages which have the \Recent flag set.
Returns the sequence number of the first message which does not have
the \Seen flag set. Returns 0 if all messages have been marked as
\Seen.
Returns the number of messages which do not have the \Seen flag set.
Returns the flags which will be stored permanently for this mailbox; defaults to the same set as flags returns.
Called when the clients requests a status update (via
Net::IMAP::Server::Command::Status). TYPES should be the types
of information requested, chosen from this list:
Returns true if this mailbox is read-only. By default, the value of
this depends on if the mailbox was selected using EXAMINE or
SELECT (see Net::IMAP::Server::Command::Select and
selected_read_only in Net::IMAP::Server::Connection)
Called when the mailbox is selected; by default does nothing. Note that this could be called a a result of either a SELECT or an EXAMINE.
Returns true if this mailbox is the mailbox selected by the current Net::IMAP::Server::Connection.
This method exists to choose the most apppriate strategy to take the intersection of (uids asked for) n (uids we have), by examining the cardinality of each set, and iterating over the smaller of the two. This is particularly important, as many clients try to fetch UIDs 1:*, which will exhaust memory if the naive approach is taken, and there is one message with UID 100_000_000.
Parses and returns messages fitting the given UID range.
Parses and returns messages fitting the given sequence range. Note that since sequence numbers are connection-dependent, this simply passes the buck to Net::IMAP::Server::Connection/get_messages.
Called before the model's children are listed to the client. This is the right place to hook into for mailboxes whose children shift with time.
Called before the mailbox is destroyed; this deals with cleaning up the several circular references involved. In turn, it calls prep_for_destroy on all child mailboxes, as well as all messages it has.
| Net-IMAP-Server documentation | Contained in the Net-IMAP-Server distribution. |
package Net::IMAP::Server::Mailbox; use warnings; use strict; use Net::IMAP::Server::Message; use base 'Class::Accessor'; __PACKAGE__->mk_accessors( qw(is_inbox parent children _path uidnext uids uidvalidity messages subscribed is_selectable) );
sub new { my $class = shift; my $self = $class->SUPER::new(@_); return if $self->parent and grep { $self->full_path eq $_->full_path } @{ $self->parent->children }; $self->is_inbox(1) if $self->parent and not $self->parent->parent and $self->name =~ /^inbox$/i; $self->init; $self->load_data; return $self; }
sub init { my $self = shift; $self->uidnext(1000); $self->messages( [] ); $self->uids( {} ); $self->children( [] ); $self->uidvalidity(time); $self->subscribed(1); $self->is_selectable(1); }
sub load_data { }
sub name { my $self = shift; if (@_) { $self->{name} = shift; } # Zimbra can't handle mailbox names with colons in them, for no # obvious reason. Handily, it identifies itself as Zimbra before # login, so we know when to perform a colonoscopy. We do this on # get, and not on set, because the same model might be used by # other clients. my $name = $self->{name}; $name =~ s/:+/-/g if Net::IMAP::Server->connection and exists Net::IMAP::Server->connection->client_id->{vendor} and Net::IMAP::Server->connection->client_id->{vendor} eq "Zimbra"; return $name; }
sub poll { }
sub add_message { my $self = shift; my $message = shift; # Basic message setup first $message->mailbox($self); $message->sequence( @{ $self->messages } + 1 ); push @{ $self->messages }, $message; # Some messages may supply their own uids if ( $message->uid ) { $self->uidnext( $message->uid + 1 ) if $message->uid >= $self->uidnext; } else { $message->uid( $self->uidnext ); $self->uidnext( $self->uidnext + 1 ); } $self->uids->{ $message->uid } = $message; # Also need to add it to anyone that has this folder as a # temporary message store for my $c ( Net::IMAP::Server->concurrent_mailbox_connections($self) ) { next unless $c->temporary_messages; push @{ $c->temporary_messages }, $message; $c->temporary_sequence_map->{$message} = scalar @{ $c->temporary_messages }; } return $message; }
sub add_child { my $self = shift; my $node = ( ref $self )->new( { @_, parent => $self } ); return unless $node; push @{ $self->children }, $node; return $node; }
sub create { my $self = shift; return $self->add_child(@_); }
sub reparent { my $self = shift; my $parent = shift; $self->parent->children( [ grep { $_ ne $self } @{ $self->parent->children } ] ); push @{ $parent->children }, $self; $self->parent($parent); $self->name(shift) if @_; $self->full_path( purge => 1 ); return 1; }
sub delete { my $self = shift; $self->parent->children( [ grep { $_ ne $self } @{ $self->parent->children } ] ); return 1; }
sub expunge { my $self = shift; my $only = shift; return if $only and not @{$only}; my %only; $only{$_}++ for @{ $only || [] }; my @ids; my $offset = 0; my @messages = @{ $self->messages }; $self->messages( [ grep { not( $_->has_flag('\Deleted') and ( not $only or $only{ $_->sequence } ) ) } @messages ] ); for my $c ( Net::IMAP::Server->concurrent_mailbox_connections($self) ) { # Ensure that all other connections with this selected get a # temporary message list, if they don't already have one unless ( # Except if we find our own connection; if this is # *not* part of a poll, we asked for it, so no need to # set up temporary messages. ( Net::IMAP::Server->connection and $c eq Net::IMAP::Server->connection and not $c->in_poll ) or $c->temporary_messages ) { $c->temporary_messages( [@messages] ); $c->temporary_sequence_map( {} ); $c->temporary_sequence_map->{$_} = $_->sequence for @messages; } } for my $m (@messages) { if ( $m->has_flag('\Deleted') and ( not $only or $only{ $m->sequence } ) ) { push @ids, $m->sequence - $offset; delete $self->uids->{ $m->uid }; $offset++; $m->expunge; } elsif ($offset) { $m->sequence( $m->sequence - $offset ); } } for my $c ( Net::IMAP::Server->concurrent_mailbox_connections($self) ) { # Also, each connection gets these added to their expunge list push @{ $c->_unsent_expunge }, @ids; } return 1; }
sub append { my $self = shift; my $m = Net::IMAP::Server::Message->new(@_); $m->set_flag( '\Recent', 1 ); $self->add_message($m); return $m; }
sub close { }
sub separator { return "/"; }
sub full_path { my $self = shift; my %args = @_; my $cache = Net::IMAP::Server->connection ? ( Net::IMAP::Server->connection->{path_cache} ||= {} ) : {}; if ($args{purge}) { my @uncache = ($self); while (@uncache) { my $o = shift @uncache; delete $cache->{$o.""}; push @uncache, @{ $o->children }; } } return $cache->{$self.""} if defined $cache->{$self.""}; $cache->{$self.""} = !$self->parent ? "" : !$self->parent->parent ? $self->name : $self->parent->full_path . $self->separator . $self->name; return $cache->{$self.""}; }
sub flags { my $self = shift; return qw(\Answered \Flagged \Deleted \Seen \Draft); }
sub can_set_flag { my $self = shift; my $flag = shift; return 1 if grep { lc $_ eq lc $flag } $self->flags; return; }
sub exists { my $self = shift; Net::IMAP::Server->connection->previous_exists( scalar @{ $self->messages } ) if $self->selected; return scalar @{ $self->messages }; }
sub recent { my $self = shift; return scalar grep { $_->has_flag('\Recent') } @{ $self->messages }; }
sub first_unseen { my $self = shift; for ( @{ $self->messages } ) { next if $_->has_flag('\Seen'); return Net::IMAP::Server->connection ? Net::IMAP::Server->connection->sequence($_) : $_->sequence; } return 0; }
sub unseen { my $self = shift; return scalar grep { not $_->has_flag('\Seen') } @{ $self->messages }; }
sub permanentflags { my $self = shift; return $self->flags; }
sub status { my $self = shift; my (@keys) = @_; $self->poll; my %items; for my $i ( @keys ) { if ( $i eq "MESSAGES" ) { $items{$i} = $self->exists; } elsif ( $i eq "RECENT" ) { $items{$i} = $self->recent; } elsif ( $i eq "UNSEEN" ) { $items{$i} = $self->unseen; } elsif ( $i eq "UIDVALIDITY" ) { my $uidvalidity = $self->uidvalidity; $items{$i} = $uidvalidity if defined $uidvalidity; } elsif ( $i eq "UIDNEXT" ) { my $uidnext = $self->uidnext; $items{$i} = $uidnext if defined $uidnext; } } return %items; }
sub read_only { my $self = shift; return unless Net::IMAP::Server->connection; return $self->selected && Net::IMAP::Server->connection->selected_read_only; }
sub select {}
sub selected { my $self = shift; return Net::IMAP::Server->connection and Net::IMAP::Server->connection->selected and Net::IMAP::Server->connection->selected eq $self; }
sub _uids_in_range { my $self = shift; my ( $low, $high ) = @_; ( $low, $high ) = ( $high, $low ) if $low > $high; my $count = scalar @{ $self->messages }; if ( $high - $low > $count ) { # More UIDs to enumerate than we actually have; check each # existing UID for being in the range return grep {$_ >= $low and $_ <= $high} map $_->uid, @{ $self->messages }; } else { # More messages than in the UID range; enumerate the range and # check each against UIDs which exist my $uids = $self->uids; return grep {defined $uids->{$_}} $low .. $high; } }
sub get_uids { my $self = shift; my $str = shift; # Otherwise $self->messages->[-1] explodes return () unless @{ $self->messages }; my %found; my $last = $self->messages->[-1]->uid; my $uids = $self->uids; for ( split ',', $str ) { if (/^(\d+):(\d+)$/) { @found{ $self->_uids_in_range( $1, $2 ) } = (); } elsif ( /^(\d+):\*$/ or /^\*:(\d+)$/ ) { $found{$last}++; @found{ $self->_uids_in_range( $1, $last ) } = (); } elsif (/^(\d+)$/) { $found{$_}++ if defined $uids->{$1}; } elsif (/^\*$/) { $found{$last}++; } } return map { $uids->{$_} } sort { $a <=> $b } keys %found; }
sub get_messages { my $self = shift; return () unless Net::IMAP::Server->connection; return Net::IMAP::Server->connection->get_messages(@_); }
sub update_tree { my $self = shift; $_->update_tree for @{ $self->children }; }
sub prep_for_destroy { my $self = shift; my @kids = @{ $self->children || [] }; $self->children( [] ); $_->prep_for_destroy for @kids; my @messages = @{ $self->messages || [] }; $self->messages( [] ); $self->uids( {} ); $_->prep_for_destroy for @messages; $self->parent(undef); } 1;