1/*
2 * Copyright 2011-2016, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "IMAPConnectionWorker.h"
8
9#include <Autolock.h>
10#include <Messenger.h>
11
12#include <AutoDeleter.h>
13
14#include "IMAPFolder.h"
15#include "IMAPMailbox.h"
16#include "IMAPProtocol.h"
17
18
19using IMAP::MessageUIDList;
20
21
22static const uint32 kMaxFetchEntries = 500;
23static const uint32 kMaxDirectDownloadSize = 4096;
24
25
26class WorkerPrivate {
27public:
28	WorkerPrivate(IMAPConnectionWorker& worker)
29		:
30		fWorker(worker)
31	{
32	}
33
34	IMAP::Protocol& Protocol()
35	{
36		return fWorker.fProtocol;
37	}
38
39	status_t AddFolders(BObjectList<IMAPFolder>& folders)
40	{
41		IMAPConnectionWorker::MailboxMap::iterator iterator
42			= fWorker.fMailboxes.begin();
43		for (; iterator != fWorker.fMailboxes.end(); iterator++) {
44			IMAPFolder* folder = iterator->first;
45			if (!folders.AddItem(folder))
46				return B_NO_MEMORY;
47		}
48		return B_OK;
49	}
50
51	status_t SelectMailbox(IMAPFolder& folder)
52	{
53		return fWorker._SelectMailbox(folder, NULL);
54	}
55
56	status_t SelectMailbox(IMAPFolder& folder, uint32& nextUID)
57	{
58		return fWorker._SelectMailbox(folder, &nextUID);
59	}
60
61	IMAPMailbox* MailboxFor(IMAPFolder& folder)
62	{
63		return fWorker._MailboxFor(folder);
64	}
65
66	int32 BodyFetchLimit() const
67	{
68		return fWorker.fSettings.BodyFetchLimit();
69	}
70
71	uint32 MessagesExist() const
72	{
73		return fWorker._MessagesExist();
74	}
75
76	status_t EnqueueCommand(WorkerCommand* command)
77	{
78		return fWorker._EnqueueCommand(command);
79	}
80
81	void SyncCommandDone()
82	{
83		fWorker._SyncCommandDone();
84	}
85
86	void Quit()
87	{
88		fWorker.fStopped = true;
89	}
90
91private:
92	IMAPConnectionWorker&	fWorker;
93};
94
95
96class WorkerCommand {
97public:
98	WorkerCommand()
99		:
100		fContinuation(false)
101	{
102	}
103
104	virtual ~WorkerCommand()
105	{
106	}
107
108	virtual	status_t Process(IMAPConnectionWorker& worker) = 0;
109
110	virtual bool IsDone() const
111	{
112		return true;
113	}
114
115	bool IsContinuation() const
116	{
117		return fContinuation;
118	}
119
120	void SetContinuation()
121	{
122		fContinuation = true;
123	}
124
125private:
126			bool				fContinuation;
127};
128
129
130/*!	All commands that inherit from this class will automatically maintain the
131	worker's fSyncPending member, and will thus prevent syncing more than once
132	concurrently.
133*/
134class SyncCommand : public WorkerCommand {
135};
136
137
138class QuitCommand : public WorkerCommand {
139public:
140	QuitCommand()
141	{
142	}
143
144	virtual status_t Process(IMAPConnectionWorker& worker)
145	{
146		WorkerPrivate(worker).Quit();
147		return B_OK;
148	}
149};
150
151
152class CheckSubscribedFoldersCommand : public WorkerCommand {
153public:
154	virtual status_t Process(IMAPConnectionWorker& worker)
155	{
156		IMAP::Protocol& protocol = WorkerPrivate(worker).Protocol();
157
158		// The main worker checks the subscribed folders, and creates
159		// other workers as needed
160		return worker.Owner().CheckSubscribedFolders(protocol,
161			worker.UsesIdle());
162	}
163};
164
165
166class FetchBodiesCommand : public SyncCommand, public IMAP::FetchListener {
167public:
168	FetchBodiesCommand(IMAPFolder& folder, IMAPMailbox& mailbox,
169		MessageUIDList& entries, const BMessenger* replyTo = NULL)
170		:
171		fFolder(folder),
172		fMailbox(mailbox),
173		fEntries(entries)
174	{
175		folder.RegisterPendingBodies(entries, replyTo);
176	}
177
178	virtual status_t Process(IMAPConnectionWorker& worker)
179	{
180		IMAP::Protocol& protocol = WorkerPrivate(worker).Protocol();
181
182		if (fEntries.empty())
183			return B_OK;
184
185		fUID = *fEntries.begin();
186		fEntries.erase(fEntries.begin());
187
188		status_t status = WorkerPrivate(worker).SelectMailbox(fFolder);
189		if (status == B_OK) {
190			printf("IMAP: fetch body for %" B_PRIu32 "\n", fUID);
191			// Since RFC3501 does not specify whether the FETCH response may
192			// alter the order of the message data items we request, we cannot
193			// request more than a single UID at a time, or else we may not be
194			// able to assign the data to the correct message beforehand.
195			IMAP::FetchCommand fetch(fUID, fUID, IMAP::kFetchBody);
196			fetch.SetListener(this);
197
198			status = protocol.ProcessCommand(fetch);
199		}
200		if (status == B_OK)
201			status = fFetchStatus;
202		if (status != B_OK)
203			fFolder.StoringBodyFailed(fRef, fUID, status);
204
205		return status;
206	}
207
208	virtual bool IsDone() const
209	{
210		return fEntries.empty();
211	}
212
213	virtual bool FetchData(uint32 fetchFlags, BDataIO& stream, size_t& length)
214	{
215		fFetchStatus = fFolder.StoreBody(fUID, stream, length, fRef, fFile);
216		return true;
217	}
218
219	virtual void FetchedData(uint32 fetchFlags, uint32 uid, uint32 flags)
220	{
221		if (fFetchStatus == B_OK)
222			fFolder.BodyStored(fRef, fFile, uid);
223	}
224
225private:
226	IMAPFolder&				fFolder;
227	IMAPMailbox&			fMailbox;
228	MessageUIDList			fEntries;
229	uint32					fUID;
230	entry_ref				fRef;
231	BFile					fFile;
232	status_t				fFetchStatus;
233};
234
235
236class FetchHeadersCommand : public SyncCommand, public IMAP::FetchListener {
237public:
238	FetchHeadersCommand(IMAPFolder& folder, IMAPMailbox& mailbox,
239		MessageUIDList& uids, int32 bodyFetchLimit)
240		:
241		fFolder(folder),
242		fMailbox(mailbox),
243		fUIDs(uids),
244		fBodyFetchLimit(bodyFetchLimit)
245	{
246	}
247
248	virtual status_t Process(IMAPConnectionWorker& worker)
249	{
250		IMAP::Protocol& protocol = WorkerPrivate(worker).Protocol();
251
252		status_t status = WorkerPrivate(worker).SelectMailbox(fFolder);
253		if (status != B_OK)
254			return status;
255
256		printf("IMAP: fetch %" B_PRIuSIZE "u headers\n", fUIDs.size());
257
258		IMAP::FetchCommand fetch(fUIDs, kMaxFetchEntries,
259			IMAP::kFetchHeader | IMAP::kFetchFlags);
260		fetch.SetListener(this);
261
262		status = protocol.ProcessCommand(fetch);
263		if (status != B_OK)
264			return status;
265
266		if (IsDone() && !fFetchBodies.empty()) {
267			// Enqueue command to fetch the message bodies
268			WorkerPrivate(worker).EnqueueCommand(new FetchBodiesCommand(fFolder,
269				fMailbox, fFetchBodies));
270		}
271
272		return B_OK;
273	}
274
275	virtual bool IsDone() const
276	{
277		return fUIDs.empty();
278	}
279
280	virtual bool FetchData(uint32 fetchFlags, BDataIO& stream, size_t& length)
281	{
282		fFetchStatus = fFolder.StoreMessage(fetchFlags, stream, length,
283			fRef, fFile);
284		return true;
285	}
286
287	virtual void FetchedData(uint32 fetchFlags, uint32 uid, uint32 flags)
288	{
289		if (fFetchStatus == B_OK) {
290			fFolder.MessageStored(fRef, fFile, fetchFlags, uid, flags);
291
292			uint32 size = fMailbox.MessageSize(uid);
293			if (fBodyFetchLimit < 0 || size < fBodyFetchLimit)
294				fFetchBodies.push_back(uid);
295		}
296	}
297
298private:
299	IMAPFolder&				fFolder;
300	IMAPMailbox&			fMailbox;
301	MessageUIDList			fUIDs;
302	MessageUIDList			fFetchBodies;
303	uint32					fBodyFetchLimit;
304	entry_ref				fRef;
305	BFile					fFile;
306	status_t				fFetchStatus;
307};
308
309
310class CheckMailboxesCommand : public SyncCommand {
311public:
312	CheckMailboxesCommand(IMAPConnectionWorker& worker)
313		:
314		fWorker(worker),
315		fFolders(5, false),
316		fState(INIT),
317		fFolder(NULL),
318		fMailbox(NULL)
319	{
320	}
321
322	virtual status_t Process(IMAPConnectionWorker& worker)
323	{
324		IMAP::Protocol& protocol = WorkerPrivate(worker).Protocol();
325
326		if (fState == INIT) {
327			// Collect folders
328			status_t status = WorkerPrivate(worker).AddFolders(fFolders);
329			if (status != B_OK || fFolders.IsEmpty()) {
330				fState = DONE;
331				return status;
332			}
333
334			fState = SELECT;
335		}
336
337		if (fState == SELECT) {
338			// Get next mailbox from list, and select it
339			fFolder = fFolders.RemoveItemAt(fFolders.CountItems() - 1);
340			if (fFolder == NULL) {
341				for (int32 i = 0; i < fFetchCommands.CountItems(); i++) {
342					WorkerPrivate(worker).EnqueueCommand(
343						fFetchCommands.ItemAt(i));
344				}
345
346				fState = DONE;
347				return B_OK;
348			}
349
350			fMailbox = WorkerPrivate(worker).MailboxFor(*fFolder);
351
352			status_t status = WorkerPrivate(worker).SelectMailbox(*fFolder);
353			if (status != B_OK)
354				return status;
355
356			fLastIndex = WorkerPrivate(worker).MessagesExist();
357			fFirstIndex = fMailbox->CountMessages() + 1;
358			if (fLastIndex > 0)
359				fState = FETCH_ENTRIES;
360		}
361
362		if (fState == FETCH_ENTRIES) {
363			status_t status = WorkerPrivate(worker).SelectMailbox(*fFolder);
364			if (status != B_OK)
365				return status;
366
367			uint32 to = fLastIndex;
368			uint32 from = fFirstIndex + kMaxFetchEntries < to
369				? fLastIndex - kMaxFetchEntries : fFirstIndex;
370
371			printf("IMAP: get entries from %" B_PRIu32 " to %" B_PRIu32 "\n",
372				from, to);
373
374			IMAP::MessageEntryList entries;
375			IMAP::FetchMessageEntriesCommand fetch(entries, from, to, false);
376			status = protocol.ProcessCommand(fetch);
377			if (status != B_OK)
378				return status;
379
380			// Determine how much we need to download
381			// TODO: also retrieve the header size, and only take the body
382			// size into account if it's below the limit -- that does not
383			// seem to be possible, though
384			for (size_t i = 0; i < entries.size(); i++) {
385				printf("%10" B_PRIu32 " %8" B_PRIu32 " bytes, flags: %#"
386					B_PRIx32 "\n", entries[i].uid, entries[i].size,
387					entries[i].flags);
388				fMailbox->AddMessageEntry(from + i, entries[i].uid,
389					entries[i].flags, entries[i].size);
390
391				if (entries[i].uid > fFolder->LastUID()) {
392					fTotalBytes += entries[i].size;
393					fUIDsToFetch.push_back(entries[i].uid);
394				} else {
395					fFolder->SyncMessageFlags(entries[i].uid, entries[i].flags);
396				}
397			}
398
399			fTotalEntries += fUIDsToFetch.size();
400			fLastIndex = from - 1;
401
402			if (from == 1) {
403				fFolder->MessageEntriesFetched();
404
405				if (fUIDsToFetch.size() > 0) {
406					// Add pending command to fetch the message headers
407					WorkerCommand* command = new FetchHeadersCommand(*fFolder,
408						*fMailbox, fUIDsToFetch,
409						WorkerPrivate(worker).BodyFetchLimit());
410					if (!fFetchCommands.AddItem(command))
411						delete command;
412
413					fUIDsToFetch.clear();
414				}
415				fState = SELECT;
416			}
417		}
418
419		return B_OK;
420	}
421
422	virtual bool IsDone() const
423	{
424		return fState == DONE;
425	}
426
427private:
428	enum State {
429		INIT,
430		SELECT,
431		FETCH_ENTRIES,
432		DONE
433	};
434
435	IMAPConnectionWorker&	fWorker;
436	BObjectList<IMAPFolder>	fFolders;
437	State					fState;
438	IMAPFolder*				fFolder;
439	IMAPMailbox*			fMailbox;
440	uint32					fFirstIndex;
441	uint32					fLastIndex;
442	uint64					fTotalEntries;
443	uint64					fTotalBytes;
444	WorkerCommandList		fFetchCommands;
445	MessageUIDList			fUIDsToFetch;
446};
447
448
449class UpdateFlagsCommand : public WorkerCommand {
450public:
451	UpdateFlagsCommand(IMAPFolder& folder, IMAPMailbox& mailbox,
452		MessageUIDList& entries, uint32 flags)
453		:
454		fFolder(folder),
455		fMailbox(mailbox),
456		fEntries(entries),
457		fFlags(flags)
458	{
459	}
460
461	virtual status_t Process(IMAPConnectionWorker& worker)
462	{
463		if (fEntries.empty())
464			return B_OK;
465
466		fUID = *fEntries.begin();
467		fEntries.erase(fEntries.begin());
468
469		status_t status = WorkerPrivate(worker).SelectMailbox(fFolder);
470		if (status == B_OK) {
471			IMAP::Protocol& protocol = WorkerPrivate(worker).Protocol();
472			IMAP::SetFlagsCommand set(fUID, fFlags);
473			status = protocol.ProcessCommand(set);
474		}
475
476		return status;
477	}
478
479	virtual bool IsDone() const
480	{
481		return fEntries.empty();
482	}
483
484private:
485	IMAPFolder&				fFolder;
486	IMAPMailbox&			fMailbox;
487	MessageUIDList			fEntries;
488	uint32					fUID;
489	uint32					fFlags;
490};
491
492
493struct CommandDelete
494{
495	inline void operator()(WorkerCommand* command)
496	{
497		delete command;
498	}
499};
500
501
502/*!	An auto deleter similar to ObjectDeleter that calls SyncCommandDone()
503	for all SyncCommands.
504*/
505struct CommandDeleter : BPrivate::AutoDeleter<WorkerCommand, CommandDelete>
506{
507	CommandDeleter(IMAPConnectionWorker& worker, WorkerCommand* command)
508		:
509		BPrivate::AutoDeleter<WorkerCommand, CommandDelete>(command),
510		fWorker(worker)
511	{
512	}
513
514	~CommandDeleter()
515	{
516		if (dynamic_cast<SyncCommand*>(fObject) != NULL)
517			WorkerPrivate(fWorker).SyncCommandDone();
518	}
519
520private:
521	IMAPConnectionWorker&	fWorker;
522};
523
524
525// #pragma mark -
526
527
528IMAPConnectionWorker::IMAPConnectionWorker(IMAPProtocol& owner,
529	const Settings& settings, bool main)
530	:
531	fOwner(owner),
532	fSettings(settings),
533	fPendingCommandsSemaphore(-1),
534	fIdleBox(NULL),
535	fMain(main),
536	fStopped(false)
537{
538	fExistsHandler.SetListener(this);
539	fProtocol.AddHandler(fExistsHandler);
540
541	fExpungeHandler.SetListener(this);
542	fProtocol.AddHandler(fExpungeHandler);
543}
544
545
546IMAPConnectionWorker::~IMAPConnectionWorker()
547{
548	puts("worker quit");
549	delete_sem(fPendingCommandsSemaphore);
550	_Disconnect();
551}
552
553
554bool
555IMAPConnectionWorker::HasMailboxes() const
556{
557	BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
558	return !fMailboxes.empty();
559}
560
561
562uint32
563IMAPConnectionWorker::CountMailboxes() const
564{
565	BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
566	return fMailboxes.size();
567}
568
569
570void
571IMAPConnectionWorker::AddMailbox(IMAPFolder* folder)
572{
573	BAutolock locker(fLocker);
574
575	fMailboxes.insert(std::make_pair(folder, (IMAPMailbox*)NULL));
576
577	// Prefer to have the INBOX in idle mode over other mail boxes
578	if (fIdleBox == NULL || folder->MailboxName().ICompare("INBOX") == 0)
579		fIdleBox = folder;
580}
581
582
583void
584IMAPConnectionWorker::RemoveAllMailboxes()
585{
586	BAutolock locker(fLocker);
587
588	// Reset listeners, and delete the mailboxes
589	MailboxMap::iterator iterator = fMailboxes.begin();
590	for (; iterator != fMailboxes.end(); iterator++) {
591		iterator->first->SetListener(NULL);
592		delete iterator->second;
593	}
594
595	fIdleBox = NULL;
596	fMailboxes.clear();
597}
598
599
600status_t
601IMAPConnectionWorker::Run()
602{
603	fPendingCommandsSemaphore = create_sem(0, "imap pending commands");
604	if (fPendingCommandsSemaphore < 0)
605		return fPendingCommandsSemaphore;
606
607	fThread = spawn_thread(&_Worker, "imap connection worker",
608		B_NORMAL_PRIORITY, this);
609	if (fThread < 0)
610		return fThread;
611
612	resume_thread(fThread);
613	return B_OK;
614}
615
616
617void
618IMAPConnectionWorker::Quit()
619{
620	printf("IMAP: worker %p: enqueue quit\n", this);
621	BAutolock qlocker(fQueueLocker);
622	while (!fPendingCommands.IsEmpty())
623		delete(fPendingCommands.RemoveItemAt(0));
624	_EnqueueCommand(new QuitCommand());
625}
626
627
628status_t
629IMAPConnectionWorker::EnqueueCheckSubscribedFolders()
630{
631	printf("IMAP: worker %p: enqueue check subscribed folders\n", this);
632	return _EnqueueCommand(new CheckSubscribedFoldersCommand());
633}
634
635
636status_t
637IMAPConnectionWorker::EnqueueCheckMailboxes()
638{
639	// Do not schedule checking mailboxes again if we're still working on
640	// those.
641	if (fSyncPending > 0)
642		return B_OK;
643
644	printf("IMAP: worker %p: enqueue check mailboxes\n", this);
645	return _EnqueueCommand(new CheckMailboxesCommand(*this));
646}
647
648
649status_t
650IMAPConnectionWorker::EnqueueFetchBody(IMAPFolder& folder, uint32 uid,
651	const BMessenger& replyTo)
652{
653	IMAPMailbox* mailbox = _MailboxFor(folder);
654	if (mailbox == NULL)
655		return B_ENTRY_NOT_FOUND;
656
657	std::vector<uint32> uids;
658	uids.push_back(uid);
659
660	return _EnqueueCommand(new FetchBodiesCommand(folder, *mailbox, uids,
661		&replyTo));
662}
663
664
665status_t
666IMAPConnectionWorker::EnqueueUpdateFlags(IMAPFolder& folder, uint32 uid,
667	uint32 flags)
668{
669	IMAPMailbox* mailbox = _MailboxFor(folder);
670	if (mailbox == NULL)
671		return B_ENTRY_NOT_FOUND;
672
673	std::vector<uint32> uids;
674	uids.push_back(uid);
675
676	return _EnqueueCommand(new UpdateFlagsCommand(folder, *mailbox, uids,
677		flags));
678}
679
680
681// #pragma mark - Handler listener
682
683
684void
685IMAPConnectionWorker::MessageExistsReceived(uint32 count)
686{
687	printf("Message exists: %" B_PRIu32 "\n", count);
688	fMessagesExist = count;
689
690	// TODO: We might want to trigger another check even during sync
691	// (but only one), if this isn't the result of a SELECT
692	EnqueueCheckMailboxes();
693}
694
695
696void
697IMAPConnectionWorker::MessageExpungeReceived(uint32 index)
698{
699	printf("Message expunge: %" B_PRIu32 "\n", index);
700	if (fSelectedBox == NULL)
701		return;
702
703	BAutolock locker(fLocker);
704
705	IMAPMailbox* mailbox = _MailboxFor(*fSelectedBox);
706	if (mailbox != NULL) {
707		mailbox->RemoveMessageEntry(index);
708		// TODO: remove message from folder
709	}
710}
711
712
713// #pragma mark - private
714
715
716status_t
717IMAPConnectionWorker::_Worker()
718{
719	status_t status = B_OK;
720
721	while (!fStopped) {
722		BAutolock qlocker(fQueueLocker);
723
724		if (fPendingCommands.IsEmpty()) {
725			if (!fIdle)
726				_Disconnect();
727			qlocker.Unlock();
728
729			// TODO: in idle mode, we'd need to parse any incoming message here
730			_WaitForCommands();
731			continue;
732		}
733
734		WorkerCommand* command = fPendingCommands.RemoveItemAt(0);
735		if (command == NULL)
736			continue;
737
738		qlocker.Unlock();
739		BAutolock locker(fLocker);
740		CommandDeleter deleter(*this, command);
741
742		if (dynamic_cast<QuitCommand*>(command) == NULL) { // do not connect on QuitCommand
743			status = _Connect();
744			if (status != B_OK)
745				break;
746		}
747
748		status = command->Process(*this);
749		if (status != B_OK)
750			break;
751
752		if (!command->IsDone()) {
753			deleter.Detach();
754			command->SetContinuation();
755			locker.Unlock();
756			_EnqueueCommand(command);
757		}
758	}
759
760	fOwner.WorkerQuit(this);
761	return status;
762}
763
764
765/*!	Enqueues the given command to the worker queue. This method will take
766	over ownership of the given command even in the error case.
767*/
768status_t
769IMAPConnectionWorker::_EnqueueCommand(WorkerCommand* command)
770{
771	BAutolock qlocker(fQueueLocker);
772
773	if (!fPendingCommands.AddItem(command)) {
774		delete command;
775		return B_NO_MEMORY;
776	}
777
778	if (dynamic_cast<SyncCommand*>(command) != NULL
779		&& !command->IsContinuation())
780		fSyncPending++;
781
782	qlocker.Unlock();
783	release_sem(fPendingCommandsSemaphore);
784	return B_OK;
785}
786
787
788void
789IMAPConnectionWorker::_WaitForCommands()
790{
791	int32 count = 1;
792	get_sem_count(fPendingCommandsSemaphore, &count);
793	if (count < 1)
794		count = 1;
795
796	while (acquire_sem_etc(fPendingCommandsSemaphore, count, 0,
797			B_INFINITE_TIMEOUT) == B_INTERRUPTED);
798}
799
800
801status_t
802IMAPConnectionWorker::_SelectMailbox(IMAPFolder& folder, uint32* _nextUID)
803{
804	if (fSelectedBox == &folder && _nextUID == NULL)
805		return B_OK;
806
807	IMAP::SelectCommand select(folder.MailboxName().String());
808
809	status_t status = fProtocol.ProcessCommand(select);
810	if (status == B_OK) {
811		folder.SetUIDValidity(select.UIDValidity());
812		if (_nextUID != NULL)
813			*_nextUID = select.NextUID();
814		fSelectedBox = &folder;
815	}
816
817	return status;
818}
819
820
821IMAPMailbox*
822IMAPConnectionWorker::_MailboxFor(IMAPFolder& folder)
823{
824	MailboxMap::iterator found = fMailboxes.find(&folder);
825	if (found == fMailboxes.end())
826		return NULL;
827
828	IMAPMailbox* mailbox = found->second;
829	if (mailbox == NULL) {
830		mailbox = new IMAPMailbox(fProtocol, folder.MailboxName());
831		folder.SetListener(mailbox);
832		found->second = mailbox;
833	}
834	return mailbox;
835}
836
837
838void
839IMAPConnectionWorker::_SyncCommandDone()
840{
841	fSyncPending--;
842}
843
844
845bool
846IMAPConnectionWorker::_IsQuitPending()
847{
848	BAutolock locker(fQueueLocker);
849	WorkerCommand* nextCommand = fPendingCommands.ItemAt(0);
850	return dynamic_cast<QuitCommand*>(nextCommand) != NULL;
851}
852
853
854status_t
855IMAPConnectionWorker::_Connect()
856{
857	if (fProtocol.IsConnected())
858		return B_OK;
859
860	status_t status = B_INTERRUPTED;
861	int tries = 10;
862	while (tries-- > 0) {
863		if (_IsQuitPending())
864			break;
865		status = fProtocol.Connect(fSettings.ServerAddress(),
866			fSettings.Username(), fSettings.Password(), fSettings.UseSSL());
867		if (status == B_OK)
868			break;
869
870		// Wait for 1 second, and try again
871		snooze(1000000);
872	}
873	// TODO: if other workers are connected, but it fails for us, we need to
874	// remove this worker, and reduce the number of concurrent connections
875	if (status != B_OK)
876		return status;
877
878	//fIdle = fSettings.IdleMode() && fProtocol.Capabilities().Contains("IDLE");
879	// TODO: Idle mode is not yet implemented!
880	fIdle = false;
881	return B_OK;
882}
883
884
885void
886IMAPConnectionWorker::_Disconnect()
887{
888	fProtocol.Disconnect();
889	fSelectedBox = NULL;
890}
891
892
893/*static*/ status_t
894IMAPConnectionWorker::_Worker(void* _self)
895{
896	IMAPConnectionWorker* self = (IMAPConnectionWorker*)_self;
897	status_t status = self->_Worker();
898
899	delete self;
900	return status;
901}
902