void Select::execute() { if ( state() != Executing ) return; if ( Flag::id( "\\Deleted" ) == 0 ) { // should only happen when we flush the entire database during // testing, so we don't bother being accurate or fast, but // simply try again in a second. (void)new Timer( this, 1 ); return; } if ( !d->permissions ) { if ( d->condstore ) imap()->setClientSupports( IMAP::Condstore ); if ( d->annotate ) imap()->setClientSupports( IMAP::Annotate ); if ( d->mailbox->deleted() ) error( No, d->mailbox->name().ascii() + " is deleted" ); if ( !ok() ) { finish(); return; } d->permissions = new Permissions( d->mailbox, imap()->user(), this ); } if ( d->permissions && !d->session ) { if ( !d->permissions->ready() ) return; if ( !d->permissions->allowed( Permissions::Read ) ) { error( No, d->mailbox->name().ascii() + " is not accessible" ); finish(); return; } if ( !d->readOnly && !d->permissions->allowed( Permissions::KeepSeen ) ) d->readOnly = true; } if ( !::firstUnseenCache ) ::firstUnseenCache = new SelectData::FirstUnseenCache; if ( mailboxGroup() && !d->sessionPreloader ) { d->sessionPreloader = new SessionPreloader( mailboxGroup()->contents(), this ); d->sessionPreloader->execute(); IntegerSet s; List<Mailbox>::Iterator i( mailboxGroup()->contents() ); while ( i ) { if ( !::firstUnseenCache->find( i, i->nextModSeq() ) ) s.add( i->id() ); ++i; } if ( s.count() > 2 ) { d->cacheFirstUnseen = new Query( "select min(uid) as uid, mailbox, " "max(modseq) as modseq " "from mailbox_messages mm " "where mailbox=any($1) and not seen " "group by mailbox", this ); d->cacheFirstUnseen->bind( 1, s ); d->cacheFirstUnseen->execute(); } } if ( d->sessionPreloader ) { while ( d->cacheFirstUnseen && d->cacheFirstUnseen->hasResults() ) { Row * r = d->cacheFirstUnseen->nextRow(); Mailbox * m = Mailbox::find( r->getInt( "mailbox" ) ); ::firstUnseenCache->insert( m, 1 + r->getBigint( "modseq" ), r->getInt( "uid" ) ); } if ( d->cacheFirstUnseen && !d->cacheFirstUnseen->done() ) return; if ( !d->sessionPreloader->done() ) return; } if ( !d->session ) { d->session = new ImapSession( imap(), d->mailbox, d->readOnly ); d->session->setPermissions( d->permissions ); imap()->beginSession( d->session ); } if ( !d->session->initialised() ) return; if ( d->session->isEmpty() ) d->needFirstUnseen = false; else if ( ::firstUnseenCache && ::firstUnseenCache->find( d->mailbox, d->session->nextModSeq() ) ) d->needFirstUnseen = false; else d->needFirstUnseen = true; if ( d->needFirstUnseen && !d->firstUnseen ) { d->firstUnseen = new Query( "select uid from mailbox_messages mm " "where mailbox=$1 and not seen " "order by uid limit 1", this ); d->firstUnseen->bind( 1, d->mailbox->id() ); d->firstUnseen->execute(); } if ( d->firstUnseen && !d->firstUnseen->done() ) return; d->session->emitUpdates( 0 ); // emitUpdates often calls Imap::runCommands, which calls this // function, which will then change its state to Finished. so // check that and don't repeat the last few responses. if ( state() != Executing ) return; respond( "OK [UIDVALIDITY " + fn( d->session->uidvalidity() ) + "]" " uid validity" ); if ( d->firstUnseen ) { if ( !::firstUnseenCache ) ::firstUnseenCache = new SelectData::FirstUnseenCache; Row * r = d->firstUnseen->nextRow(); if ( r ) ::firstUnseenCache->insert( d->mailbox, d->session->nextModSeq(), r->getInt( "uid" ) ); } if ( ::firstUnseenCache ) { uint unseen = ::firstUnseenCache->find( d->mailbox, d->session->nextModSeq() ); if ( unseen ) respond( "OK [UNSEEN " + fn( d->session->msn( unseen ) ) + "] first unseen" ); } if ( imap()->clientSupports( IMAP::Condstore ) && !d->session->isEmpty() ) { uint nms = d->session->nextModSeq(); if ( nms < 2 ) nms = 2; respond( "OK [HIGHESTMODSEQ " + fn( nms-1 ) + "] highest modseq" ); } if ( imap()->clientSupports( IMAP::Annotate ) ) { Permissions * p = d->session->permissions(); if ( p && p->allowed( Permissions::WriteSharedAnnotation ) ) respond( "OK [ANNOTATIONS 262144] Arbitrary limit" ); else respond( "OK [ANNOTATIONS READ-ONLY] Missing 'n' right" ); } if ( d->session->readOnly() ) setRespTextCode( "READ-ONLY" ); else setRespTextCode( "READ-WRITE" ); finish(); }
void Status::execute() { if ( state() != Executing ) return; // first part. set up. if ( !permitted() ) return; Session * session = imap()->session(); Mailbox * current = 0; if ( session ) current = session->mailbox(); // second part. see if anything has happened, and feed the cache if // so. make sure we feed the cache at once. if ( d->unseenCount || d->recentCount || d->messageCount ) { if ( d->unseenCount && !d->unseenCount->done() ) return; if ( d->messageCount && !d->messageCount->done() ) return; if ( d->recentCount && !d->recentCount->done() ) return; } if ( !::cache ) ::cache = new StatusData::StatusCache; if ( d->unseenCount ) { while ( d->unseenCount->hasResults() ) { Row * r = d->unseenCount->nextRow(); StatusData::CacheItem * ci = ::cache->find( r->getInt( "mailbox" ) ); if ( ci ) { ci->hasUnseen = true; ci->unseen = r->getInt( "unseen" ); } } } if ( d->recentCount ) { while ( d->recentCount->hasResults() ) { Row * r = d->recentCount->nextRow(); StatusData::CacheItem * ci = ::cache->find( r->getInt( "mailbox" ) ); if ( ci ) { ci->hasRecent = true; ci->recent = r->getInt( "recent" ); } } } if ( d->messageCount ) { while ( d->messageCount->hasResults() ) { Row * r = d->messageCount->nextRow(); StatusData::CacheItem * ci = ::cache->find( r->getInt( "mailbox" ) ); if ( ci ) { ci->hasMessages = true; ci->messages = r->getInt( "messages" ); } } } // third part. are we processing the first command in a STATUS // loop? if so, see if we ought to preload the cache. if ( mailboxGroup() && d->cacheState < 3 ) { IntegerSet mailboxes; if ( d->cacheState < 1 ) { // cache state 0: decide which messages List<Mailbox>::Iterator i( mailboxGroup()->contents() ); while ( i ) { StatusData::CacheItem * ci = ::cache->provide( i ); bool need = false; if ( d->unseen || d->recent || d->messages ) need = true; if ( ci->hasUnseen || ci->hasRecent || ci->hasMessages ) need = false; if ( need ) mailboxes.add( i->id() ); ++i; } if ( mailboxes.count() < 3 ) d->cacheState = 3; } if ( d->cacheState == 1 ) { // state 1: send queries if ( d->unseen ) { d->unseenCount = new Query( "select mailbox, count(uid)::int as unseen " "from mailbox_messages " "where mailbox=any($1) and not seen " "group by mailbox", this ); d->unseenCount->bind( 1, mailboxes ); d->unseenCount->execute(); } if ( d->recent ) { d->recentCount = new Query( "select id as mailbox, " "uidnext-first_recent as recent " "from mailboxes where id=any($1)", this ); d->recentCount->bind( 1, mailboxes ); d->recentCount->execute(); } if ( d->messages ) { d->messageCount = new Query( "select count(*)::int as messages, mailbox " "from mailbox_messages where mailbox=any($1) " "group by mailbox", this ); d->messageCount->bind( 1, mailboxes ); d->messageCount->execute(); } d->cacheState = 2; } if ( d->cacheState == 2 ) { // state 2: mark the cache as complete. IntegerSet mailboxes; List<Mailbox>::Iterator i( mailboxGroup()->contents() ); while ( i ) { StatusData::CacheItem * ci = ::cache->find( i->id() ); if ( ci && d->unseenCount ) ci->hasUnseen = true; if ( ci && d->recentCount ) ci->hasRecent = true; if ( ci && d->messageCount ) ci->hasMessages = true; ++i; } // and drop the queries d->cacheState = 3; d->unseenCount = 0; d->recentCount = 0; d->messageCount = 0; } } // the cache item we'll actually read from StatusData::CacheItem * i = ::cache->provide( d->mailbox ); // fourth part: send individual queries if there's anything we need if ( d->unseen && !d->unseenCount && !i->hasUnseen ) { d->unseenCount = new Query( "select $1::int as mailbox, " "count(uid)::int as unseen " "from mailbox_messages " "where mailbox=$1 and not seen", this ); d->unseenCount->bind( 1, d->mailbox->id() ); d->unseenCount->execute(); } if ( !d->recent ) { // nothing doing } else if ( d->mailbox == current ) { // we'll pick it up from the session } else if ( i->hasRecent ) { // the cache has it } else if ( !d->recentCount ) { d->recentCount = new Query( "select id as mailbox, " "uidnext-first_recent as recent " "from mailboxes where id=$1", this ); d->recentCount->bind( 1, d->mailbox->id() ); d->recentCount->execute(); } if ( !d->messages ) { // we don't need to collect } else if ( d->mailbox == current ) { // we'll pick it up } else if ( i->hasMessages ) { // the cache has it } else if ( d->messages && !d->messageCount ) { d->messageCount = new Query( "select count(*)::int as messages, " "$1::int as mailbox " "from mailbox_messages where mailbox=$1", this ); d->messageCount->bind( 1, d->mailbox->id() ); d->messageCount->execute(); } if ( d->unseenCount || d->recentCount || d->messageCount ) { if ( d->unseenCount && !d->unseenCount->done() ) return; if ( d->messageCount && !d->messageCount->done() ) return; if ( d->recentCount && !d->recentCount->done() ) return; } // fifth part: return the payload. EStringList status; if ( d->messages && i->hasMessages ) status.append( "MESSAGES " + fn( i->messages ) ); else if ( d->messages && d->mailbox == current ) status.append( "MESSAGES " + fn( session->messages().count() ) ); if ( d->recent && i->hasRecent ) status.append( "RECENT " + fn( i->recent ) ); else if ( d->recent && d->mailbox == current ) status.append( "RECENT " + fn( session->recent().count() ) ); if ( d->uidnext ) status.append( "UIDNEXT " + fn( d->mailbox->uidnext() ) ); if ( d->uidvalidity ) status.append( "UIDVALIDITY " + fn( d->mailbox->uidvalidity() ) ); if ( d->unseen && i->hasUnseen ) status.append( "UNSEEN " + fn( i->unseen ) ); if ( d->modseq ) { int64 hms = d->mailbox->nextModSeq(); // don't like this. an empty mailbox will have a STATUS HMS of // 1 before it receives its first message, and also 1 after. if ( hms > 1 ) hms--; status.append( "HIGHESTMODSEQ " + fn( hms ) ); } respond( "STATUS " + imapQuoted( d->mailbox ) + " (" + status.join( " " ) + ")" ); finish(); }