xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/IMAPConnectionWorker.cpp (revision bd6068614473f87449dfa2eaa67fad1527c61e11)
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 
19 using IMAP::MessageUIDList;
20 
21 
22 static const uint32 kMaxFetchEntries = 500;
23 static const uint32 kMaxDirectDownloadSize = 4096;
24 
25 
26 class WorkerPrivate {
27 public:
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 
91 private:
92 	IMAPConnectionWorker&	fWorker;
93 };
94 
95 
96 class WorkerCommand {
97 public:
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 
125 private:
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 */
134 class SyncCommand : public WorkerCommand {
135 };
136 
137 
138 class QuitCommand : public WorkerCommand {
139 public:
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 
152 class CheckSubscribedFoldersCommand : public WorkerCommand {
153 public:
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 
166 class FetchBodiesCommand : public SyncCommand, public IMAP::FetchListener {
167 public:
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 
225 private:
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 
236 class FetchHeadersCommand : public SyncCommand, public IMAP::FetchListener {
237 public:
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 
298 private:
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 
310 class CheckMailboxesCommand : public SyncCommand {
311 public:
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 			std::vector<uint32> uidsToFetch;
381 
382 			// Determine how much we need to download
383 			// TODO: also retrieve the header size, and only take the body
384 			// size into account if it's below the limit -- that does not
385 			// seem to be possible, though
386 			for (size_t i = 0; i < entries.size(); i++) {
387 				printf("%10" B_PRIu32 " %8" B_PRIu32 " bytes, flags: %#"
388 					B_PRIx32 "\n", entries[i].uid, entries[i].size,
389 					entries[i].flags);
390 				fMailbox->AddMessageEntry(from + i, entries[i].uid,
391 					entries[i].flags, entries[i].size);
392 
393 				if (entries[i].uid > fFolder->LastUID()) {
394 					fTotalBytes += entries[i].size;
395 					fUIDsToFetch.push_back(entries[i].uid);
396 				} else {
397 					fFolder->SyncMessageFlags(entries[i].uid, entries[i].flags);
398 				}
399 			}
400 
401 			fTotalEntries += fUIDsToFetch.size();
402 			fLastIndex = from - 1;
403 
404 			if (from == 1) {
405 				fFolder->MessageEntriesFetched();
406 
407 				if (fUIDsToFetch.size() > 0) {
408 					// Add pending command to fetch the message headers
409 					WorkerCommand* command = new FetchHeadersCommand(*fFolder,
410 						*fMailbox, fUIDsToFetch,
411 						WorkerPrivate(worker).BodyFetchLimit());
412 					if (!fFetchCommands.AddItem(command))
413 						delete command;
414 
415 					fUIDsToFetch.clear();
416 				}
417 				fState = SELECT;
418 			}
419 		}
420 
421 		return B_OK;
422 	}
423 
424 	virtual bool IsDone() const
425 	{
426 		return fState == DONE;
427 	}
428 
429 private:
430 	enum State {
431 		INIT,
432 		SELECT,
433 		FETCH_ENTRIES,
434 		DONE
435 	};
436 
437 	IMAPConnectionWorker&	fWorker;
438 	BObjectList<IMAPFolder>	fFolders;
439 	State					fState;
440 	IMAPFolder*				fFolder;
441 	IMAPMailbox*			fMailbox;
442 	uint32					fFirstIndex;
443 	uint32					fLastIndex;
444 	uint64					fTotalEntries;
445 	uint64					fTotalBytes;
446 	WorkerCommandList		fFetchCommands;
447 	MessageUIDList			fUIDsToFetch;
448 };
449 
450 
451 class UpdateFlagsCommand : public WorkerCommand {
452 public:
453 	UpdateFlagsCommand(IMAPFolder& folder, IMAPMailbox& mailbox,
454 		MessageUIDList& entries, uint32 flags)
455 		:
456 		fFolder(folder),
457 		fMailbox(mailbox),
458 		fEntries(entries),
459 		fFlags(flags)
460 	{
461 	}
462 
463 	virtual status_t Process(IMAPConnectionWorker& worker)
464 	{
465 		if (fEntries.empty())
466 			return B_OK;
467 
468 		fUID = *fEntries.begin();
469 		fEntries.erase(fEntries.begin());
470 
471 		status_t status = WorkerPrivate(worker).SelectMailbox(fFolder);
472 		if (status == B_OK) {
473 			IMAP::Protocol& protocol = WorkerPrivate(worker).Protocol();
474 			IMAP::SetFlagsCommand set(fUID, fFlags);
475 			status = protocol.ProcessCommand(set);
476 		}
477 
478 		return status;
479 	}
480 
481 	virtual bool IsDone() const
482 	{
483 		return fEntries.empty();
484 	}
485 
486 private:
487 	IMAPFolder&				fFolder;
488 	IMAPMailbox&			fMailbox;
489 	MessageUIDList			fEntries;
490 	uint32					fUID;
491 	uint32					fFlags;
492 };
493 
494 
495 struct CommandDelete
496 {
497 	inline void operator()(WorkerCommand* command)
498 	{
499 		delete command;
500 	}
501 };
502 
503 
504 /*!	An auto deleter similar to ObjectDeleter that calls SyncCommandDone()
505 	for all SyncCommands.
506 */
507 struct CommandDeleter : BPrivate::AutoDeleter<WorkerCommand, CommandDelete>
508 {
509 	CommandDeleter(IMAPConnectionWorker& worker, WorkerCommand* command)
510 		:
511 		BPrivate::AutoDeleter<WorkerCommand, CommandDelete>(command),
512 		fWorker(worker)
513 	{
514 	}
515 
516 	~CommandDeleter()
517 	{
518 		if (dynamic_cast<SyncCommand*>(fObject) != NULL)
519 			WorkerPrivate(fWorker).SyncCommandDone();
520 	}
521 
522 private:
523 	IMAPConnectionWorker&	fWorker;
524 };
525 
526 
527 // #pragma mark -
528 
529 
530 IMAPConnectionWorker::IMAPConnectionWorker(IMAPProtocol& owner,
531 	const Settings& settings, bool main)
532 	:
533 	fOwner(owner),
534 	fSettings(settings),
535 	fPendingCommandsSemaphore(-1),
536 	fIdleBox(NULL),
537 	fMain(main),
538 	fStopped(false)
539 {
540 	fExistsHandler.SetListener(this);
541 	fProtocol.AddHandler(fExistsHandler);
542 
543 	fExpungeHandler.SetListener(this);
544 	fProtocol.AddHandler(fExpungeHandler);
545 }
546 
547 
548 IMAPConnectionWorker::~IMAPConnectionWorker()
549 {
550 	puts("worker quit");
551 	delete_sem(fPendingCommandsSemaphore);
552 	_Disconnect();
553 }
554 
555 
556 bool
557 IMAPConnectionWorker::HasMailboxes() const
558 {
559 	BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
560 	return !fMailboxes.empty();
561 }
562 
563 
564 uint32
565 IMAPConnectionWorker::CountMailboxes() const
566 {
567 	BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
568 	return fMailboxes.size();
569 }
570 
571 
572 void
573 IMAPConnectionWorker::AddMailbox(IMAPFolder* folder)
574 {
575 	BAutolock locker(fLocker);
576 
577 	fMailboxes.insert(std::make_pair(folder, (IMAPMailbox*)NULL));
578 
579 	// Prefer to have the INBOX in idle mode over other mail boxes
580 	if (fIdleBox == NULL || folder->MailboxName().ICompare("INBOX") == 0)
581 		fIdleBox = folder;
582 }
583 
584 
585 void
586 IMAPConnectionWorker::RemoveAllMailboxes()
587 {
588 	BAutolock locker(fLocker);
589 
590 	// Reset listeners, and delete the mailboxes
591 	MailboxMap::iterator iterator = fMailboxes.begin();
592 	for (; iterator != fMailboxes.end(); iterator++) {
593 		iterator->first->SetListener(NULL);
594 		delete iterator->second;
595 	}
596 
597 	fIdleBox = NULL;
598 	fMailboxes.clear();
599 }
600 
601 
602 status_t
603 IMAPConnectionWorker::Run()
604 {
605 	fPendingCommandsSemaphore = create_sem(0, "imap pending commands");
606 	if (fPendingCommandsSemaphore < 0)
607 		return fPendingCommandsSemaphore;
608 
609 	fThread = spawn_thread(&_Worker, "imap connection worker",
610 		B_NORMAL_PRIORITY, this);
611 	if (fThread < 0)
612 		return fThread;
613 
614 	resume_thread(fThread);
615 	return B_OK;
616 }
617 
618 
619 void
620 IMAPConnectionWorker::Quit()
621 {
622 	printf("IMAP: worker %p: enqueue quit\n", this);
623 	_EnqueueCommand(new QuitCommand());
624 }
625 
626 
627 status_t
628 IMAPConnectionWorker::EnqueueCheckSubscribedFolders()
629 {
630 	printf("IMAP: worker %p: enqueue check subscribed folders\n", this);
631 	return _EnqueueCommand(new CheckSubscribedFoldersCommand());
632 }
633 
634 
635 status_t
636 IMAPConnectionWorker::EnqueueCheckMailboxes()
637 {
638 	// Do not schedule checking mailboxes again if we're still working on
639 	// those.
640 	if (fSyncPending > 0)
641 		return B_OK;
642 
643 	printf("IMAP: worker %p: enqueue check mailboxes\n", this);
644 	return _EnqueueCommand(new CheckMailboxesCommand(*this));
645 }
646 
647 
648 status_t
649 IMAPConnectionWorker::EnqueueFetchBody(IMAPFolder& folder, uint32 uid,
650 	const BMessenger& replyTo)
651 {
652 	IMAPMailbox* mailbox = _MailboxFor(folder);
653 	if (mailbox == NULL)
654 		return B_ENTRY_NOT_FOUND;
655 
656 	std::vector<uint32> uids;
657 	uids.push_back(uid);
658 
659 	return _EnqueueCommand(new FetchBodiesCommand(folder, *mailbox, uids,
660 		&replyTo));
661 }
662 
663 
664 status_t
665 IMAPConnectionWorker::EnqueueUpdateFlags(IMAPFolder& folder, uint32 uid,
666 	uint32 flags)
667 {
668 	IMAPMailbox* mailbox = _MailboxFor(folder);
669 	if (mailbox == NULL)
670 		return B_ENTRY_NOT_FOUND;
671 
672 	std::vector<uint32> uids;
673 	uids.push_back(uid);
674 
675 	return _EnqueueCommand(new UpdateFlagsCommand(folder, *mailbox, uids,
676 		flags));
677 }
678 
679 
680 // #pragma mark - Handler listener
681 
682 
683 void
684 IMAPConnectionWorker::MessageExistsReceived(uint32 count)
685 {
686 	printf("Message exists: %" B_PRIu32 "\n", count);
687 	fMessagesExist = count;
688 
689 	// TODO: We might want to trigger another check even during sync
690 	// (but only one), if this isn't the result of a SELECT
691 	EnqueueCheckMailboxes();
692 }
693 
694 
695 void
696 IMAPConnectionWorker::MessageExpungeReceived(uint32 index)
697 {
698 	printf("Message expunge: %" B_PRIu32 "\n", index);
699 	if (fSelectedBox == NULL)
700 		return;
701 
702 	BAutolock locker(fLocker);
703 
704 	IMAPMailbox* mailbox = _MailboxFor(*fSelectedBox);
705 	if (mailbox != NULL) {
706 		mailbox->RemoveMessageEntry(index);
707 		// TODO: remove message from folder
708 	}
709 }
710 
711 
712 // #pragma mark - private
713 
714 
715 status_t
716 IMAPConnectionWorker::_Worker()
717 {
718 	while (!fStopped) {
719 		BAutolock locker(fLocker);
720 
721 		if (fPendingCommands.IsEmpty()) {
722 			if (!fIdle)
723 				_Disconnect();
724 			locker.Unlock();
725 
726 			// TODO: in idle mode, we'd need to parse any incoming message here
727 			_WaitForCommands();
728 			continue;
729 		}
730 
731 		WorkerCommand* command = fPendingCommands.RemoveItemAt(0);
732 		if (command == NULL)
733 			continue;
734 
735 		CommandDeleter deleter(*this, command);
736 
737 		status_t status = _Connect();
738 		if (status != B_OK)
739 			return status;
740 
741 		status = command->Process(*this);
742 		if (status != B_OK)
743 			return status;
744 
745 		if (!command->IsDone()) {
746 			deleter.Detach();
747 			command->SetContinuation();
748 			_EnqueueCommand(command);
749 		}
750 	}
751 
752 	fOwner.WorkerQuit(this);
753 	return B_OK;
754 }
755 
756 
757 /*!	Enqueues the given command to the worker queue. This method will take
758 	over ownership of the given command even in the error case.
759 */
760 status_t
761 IMAPConnectionWorker::_EnqueueCommand(WorkerCommand* command)
762 {
763 	BAutolock locker(fLocker);
764 
765 	if (!fPendingCommands.AddItem(command)) {
766 		delete command;
767 		return B_NO_MEMORY;
768 	}
769 
770 	if (dynamic_cast<SyncCommand*>(command) != NULL
771 		&& !command->IsContinuation())
772 		fSyncPending++;
773 
774 	locker.Unlock();
775 	release_sem(fPendingCommandsSemaphore);
776 	return B_OK;
777 }
778 
779 
780 void
781 IMAPConnectionWorker::_WaitForCommands()
782 {
783 	int32 count = 1;
784 	get_sem_count(fPendingCommandsSemaphore, &count);
785 	if (count < 1)
786 		count = 1;
787 
788 	while (acquire_sem_etc(fPendingCommandsSemaphore, count, 0,
789 			B_INFINITE_TIMEOUT) == B_INTERRUPTED);
790 }
791 
792 
793 status_t
794 IMAPConnectionWorker::_SelectMailbox(IMAPFolder& folder, uint32* _nextUID)
795 {
796 	if (fSelectedBox == &folder && _nextUID == NULL)
797 		return B_OK;
798 
799 	IMAP::SelectCommand select(folder.MailboxName().String());
800 
801 	status_t status = fProtocol.ProcessCommand(select);
802 	if (status == B_OK) {
803 		folder.SetUIDValidity(select.UIDValidity());
804 		if (_nextUID != NULL)
805 			*_nextUID = select.NextUID();
806 		fSelectedBox = &folder;
807 	}
808 
809 	return status;
810 }
811 
812 
813 IMAPMailbox*
814 IMAPConnectionWorker::_MailboxFor(IMAPFolder& folder)
815 {
816 	MailboxMap::iterator found = fMailboxes.find(&folder);
817 	if (found == fMailboxes.end())
818 		return NULL;
819 
820 	IMAPMailbox* mailbox = found->second;
821 	if (mailbox == NULL) {
822 		mailbox = new IMAPMailbox(fProtocol, folder.MailboxName());
823 		folder.SetListener(mailbox);
824 		found->second = mailbox;
825 	}
826 	return mailbox;
827 }
828 
829 
830 void
831 IMAPConnectionWorker::_SyncCommandDone()
832 {
833 	fSyncPending--;
834 }
835 
836 
837 status_t
838 IMAPConnectionWorker::_Connect()
839 {
840 	if (fProtocol.IsConnected())
841 		return B_OK;
842 
843 	status_t status;
844 	int tries = 6;
845 	while (tries-- > 0) {
846 		status = fProtocol.Connect(fSettings.ServerAddress(),
847 			fSettings.Username(), fSettings.Password(), fSettings.UseSSL());
848 		if (status == B_OK)
849 			break;
850 
851 		// Wait for 10 seconds, and try again
852 		snooze(10000000);
853 	}
854 	// TODO: if other workers are connected, but it fails for us, we need to
855 	// remove this worker, and reduce the number of concurrent connections
856 	if (status != B_OK)
857 		return status;
858 
859 	//fIdle = fSettings.IdleMode() && fProtocol.Capabilities().Contains("IDLE");
860 	// TODO: Idle mode is not yet implemented!
861 	fIdle = false;
862 	return B_OK;
863 }
864 
865 
866 void
867 IMAPConnectionWorker::_Disconnect()
868 {
869 	fProtocol.Disconnect();
870 }
871 
872 
873 /*static*/ status_t
874 IMAPConnectionWorker::_Worker(void* _self)
875 {
876 	IMAPConnectionWorker* self = (IMAPConnectionWorker*)_self;
877 	status_t status = self->_Worker();
878 
879 	delete self;
880 	return status;
881 }
882