xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/IMAPConnectionWorker.cpp (revision df4074fbed092b09474695aa286752ca41625332)
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 			// 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 
427 private:
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 
449 class UpdateFlagsCommand : public WorkerCommand {
450 public:
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 
484 private:
485 	IMAPFolder&				fFolder;
486 	IMAPMailbox&			fMailbox;
487 	MessageUIDList			fEntries;
488 	uint32					fUID;
489 	uint32					fFlags;
490 };
491 
492 
493 struct 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 */
505 struct 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 
520 private:
521 	IMAPConnectionWorker&	fWorker;
522 };
523 
524 
525 // #pragma mark -
526 
527 
528 IMAPConnectionWorker::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 
546 IMAPConnectionWorker::~IMAPConnectionWorker()
547 {
548 	puts("worker quit");
549 	delete_sem(fPendingCommandsSemaphore);
550 	_Disconnect();
551 }
552 
553 
554 bool
555 IMAPConnectionWorker::HasMailboxes() const
556 {
557 	BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
558 	return !fMailboxes.empty();
559 }
560 
561 
562 uint32
563 IMAPConnectionWorker::CountMailboxes() const
564 {
565 	BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
566 	return fMailboxes.size();
567 }
568 
569 
570 void
571 IMAPConnectionWorker::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 
583 void
584 IMAPConnectionWorker::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 
600 status_t
601 IMAPConnectionWorker::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 
617 void
618 IMAPConnectionWorker::Quit()
619 {
620 	printf("IMAP: worker %p: enqueue quit\n", this);
621 	BAutolock locker(fLocker);
622 	while (!fPendingCommands.IsEmpty())
623 		delete(fPendingCommands.RemoveItemAt(0));
624 	locker.Unlock();
625 	_EnqueueCommand(new QuitCommand());
626 }
627 
628 
629 status_t
630 IMAPConnectionWorker::EnqueueCheckSubscribedFolders()
631 {
632 	printf("IMAP: worker %p: enqueue check subscribed folders\n", this);
633 	return _EnqueueCommand(new CheckSubscribedFoldersCommand());
634 }
635 
636 
637 status_t
638 IMAPConnectionWorker::EnqueueCheckMailboxes()
639 {
640 	// Do not schedule checking mailboxes again if we're still working on
641 	// those.
642 	if (fSyncPending > 0)
643 		return B_OK;
644 
645 	printf("IMAP: worker %p: enqueue check mailboxes\n", this);
646 	return _EnqueueCommand(new CheckMailboxesCommand(*this));
647 }
648 
649 
650 status_t
651 IMAPConnectionWorker::EnqueueFetchBody(IMAPFolder& folder, uint32 uid,
652 	const BMessenger& replyTo)
653 {
654 	IMAPMailbox* mailbox = _MailboxFor(folder);
655 	if (mailbox == NULL)
656 		return B_ENTRY_NOT_FOUND;
657 
658 	std::vector<uint32> uids;
659 	uids.push_back(uid);
660 
661 	return _EnqueueCommand(new FetchBodiesCommand(folder, *mailbox, uids,
662 		&replyTo));
663 }
664 
665 
666 status_t
667 IMAPConnectionWorker::EnqueueUpdateFlags(IMAPFolder& folder, uint32 uid,
668 	uint32 flags)
669 {
670 	IMAPMailbox* mailbox = _MailboxFor(folder);
671 	if (mailbox == NULL)
672 		return B_ENTRY_NOT_FOUND;
673 
674 	std::vector<uint32> uids;
675 	uids.push_back(uid);
676 
677 	return _EnqueueCommand(new UpdateFlagsCommand(folder, *mailbox, uids,
678 		flags));
679 }
680 
681 
682 // #pragma mark - Handler listener
683 
684 
685 void
686 IMAPConnectionWorker::MessageExistsReceived(uint32 count)
687 {
688 	printf("Message exists: %" B_PRIu32 "\n", count);
689 	fMessagesExist = count;
690 
691 	// TODO: We might want to trigger another check even during sync
692 	// (but only one), if this isn't the result of a SELECT
693 	EnqueueCheckMailboxes();
694 }
695 
696 
697 void
698 IMAPConnectionWorker::MessageExpungeReceived(uint32 index)
699 {
700 	printf("Message expunge: %" B_PRIu32 "\n", index);
701 	if (fSelectedBox == NULL)
702 		return;
703 
704 	BAutolock locker(fLocker);
705 
706 	IMAPMailbox* mailbox = _MailboxFor(*fSelectedBox);
707 	if (mailbox != NULL) {
708 		mailbox->RemoveMessageEntry(index);
709 		// TODO: remove message from folder
710 	}
711 }
712 
713 
714 // #pragma mark - private
715 
716 
717 status_t
718 IMAPConnectionWorker::_Worker()
719 {
720 	status_t status = B_OK;
721 
722 	while (!fStopped) {
723 		BAutolock locker(fLocker);
724 
725 		if (fPendingCommands.IsEmpty()) {
726 			if (!fIdle)
727 				_Disconnect();
728 			locker.Unlock();
729 
730 			// TODO: in idle mode, we'd need to parse any incoming message here
731 			_WaitForCommands();
732 			continue;
733 		}
734 
735 		WorkerCommand* command = fPendingCommands.RemoveItemAt(0);
736 		if (command == NULL)
737 			continue;
738 
739 		CommandDeleter deleter(*this, command);
740 
741 		if (dynamic_cast<QuitCommand*>(command) == NULL) { // do not connect on QuitCommand
742 			status = _Connect();
743 			if (status != B_OK)
744 				break;
745 		}
746 
747 		status = command->Process(*this);
748 		if (status != B_OK)
749 			break;
750 
751 		if (!command->IsDone()) {
752 			deleter.Detach();
753 			command->SetContinuation();
754 			_EnqueueCommand(command);
755 		}
756 	}
757 
758 	fOwner.WorkerQuit(this);
759 	return status;
760 }
761 
762 
763 /*!	Enqueues the given command to the worker queue. This method will take
764 	over ownership of the given command even in the error case.
765 */
766 status_t
767 IMAPConnectionWorker::_EnqueueCommand(WorkerCommand* command)
768 {
769 	BAutolock locker(fLocker);
770 
771 	if (!fPendingCommands.AddItem(command)) {
772 		delete command;
773 		return B_NO_MEMORY;
774 	}
775 
776 	if (dynamic_cast<SyncCommand*>(command) != NULL
777 		&& !command->IsContinuation())
778 		fSyncPending++;
779 
780 	locker.Unlock();
781 	release_sem(fPendingCommandsSemaphore);
782 	return B_OK;
783 }
784 
785 
786 void
787 IMAPConnectionWorker::_WaitForCommands()
788 {
789 	int32 count = 1;
790 	get_sem_count(fPendingCommandsSemaphore, &count);
791 	if (count < 1)
792 		count = 1;
793 
794 	while (acquire_sem_etc(fPendingCommandsSemaphore, count, 0,
795 			B_INFINITE_TIMEOUT) == B_INTERRUPTED);
796 }
797 
798 
799 status_t
800 IMAPConnectionWorker::_SelectMailbox(IMAPFolder& folder, uint32* _nextUID)
801 {
802 	if (fSelectedBox == &folder && _nextUID == NULL)
803 		return B_OK;
804 
805 	IMAP::SelectCommand select(folder.MailboxName().String());
806 
807 	status_t status = fProtocol.ProcessCommand(select);
808 	if (status == B_OK) {
809 		folder.SetUIDValidity(select.UIDValidity());
810 		if (_nextUID != NULL)
811 			*_nextUID = select.NextUID();
812 		fSelectedBox = &folder;
813 	}
814 
815 	return status;
816 }
817 
818 
819 IMAPMailbox*
820 IMAPConnectionWorker::_MailboxFor(IMAPFolder& folder)
821 {
822 	MailboxMap::iterator found = fMailboxes.find(&folder);
823 	if (found == fMailboxes.end())
824 		return NULL;
825 
826 	IMAPMailbox* mailbox = found->second;
827 	if (mailbox == NULL) {
828 		mailbox = new IMAPMailbox(fProtocol, folder.MailboxName());
829 		folder.SetListener(mailbox);
830 		found->second = mailbox;
831 	}
832 	return mailbox;
833 }
834 
835 
836 void
837 IMAPConnectionWorker::_SyncCommandDone()
838 {
839 	fSyncPending--;
840 }
841 
842 
843 status_t
844 IMAPConnectionWorker::_Connect()
845 {
846 	if (fProtocol.IsConnected())
847 		return B_OK;
848 
849 	status_t status;
850 	int tries = 6;
851 	while (tries-- > 0) {
852 		status = fProtocol.Connect(fSettings.ServerAddress(),
853 			fSettings.Username(), fSettings.Password(), fSettings.UseSSL());
854 		if (status == B_OK)
855 			break;
856 
857 		// Wait for 10 seconds, and try again
858 		snooze(10000000);
859 	}
860 	// TODO: if other workers are connected, but it fails for us, we need to
861 	// remove this worker, and reduce the number of concurrent connections
862 	if (status != B_OK)
863 		return status;
864 
865 	//fIdle = fSettings.IdleMode() && fProtocol.Capabilities().Contains("IDLE");
866 	// TODO: Idle mode is not yet implemented!
867 	fIdle = false;
868 	return B_OK;
869 }
870 
871 
872 void
873 IMAPConnectionWorker::_Disconnect()
874 {
875 	fProtocol.Disconnect();
876 	fSelectedBox = NULL;
877 }
878 
879 
880 /*static*/ status_t
881 IMAPConnectionWorker::_Worker(void* _self)
882 {
883 	IMAPConnectionWorker* self = (IMAPConnectionWorker*)_self;
884 	status_t status = self->_Worker();
885 
886 	delete self;
887 	return status;
888 }
889