xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/IMAPConnectionWorker.cpp (revision 99d1318ec02694fc520a0dc38ae38565db7e8c3c)
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*>(Get()) != 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 qlocker(fQueueLocker);
622 	while (!fPendingCommands.IsEmpty())
623 		delete(fPendingCommands.RemoveItemAt(0));
624 	_EnqueueCommand(new QuitCommand());
625 }
626 
627 
628 status_t
629 IMAPConnectionWorker::EnqueueCheckSubscribedFolders()
630 {
631 	printf("IMAP: worker %p: enqueue check subscribed folders\n", this);
632 	return _EnqueueCommand(new CheckSubscribedFoldersCommand());
633 }
634 
635 
636 status_t
637 IMAPConnectionWorker::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 
649 status_t
650 IMAPConnectionWorker::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 
665 status_t
666 IMAPConnectionWorker::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 
684 void
685 IMAPConnectionWorker::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 
696 void
697 IMAPConnectionWorker::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 
716 status_t
717 IMAPConnectionWorker::_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 */
768 status_t
769 IMAPConnectionWorker::_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 
788 void
789 IMAPConnectionWorker::_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 
801 status_t
802 IMAPConnectionWorker::_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 
821 IMAPMailbox*
822 IMAPConnectionWorker::_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 
838 void
839 IMAPConnectionWorker::_SyncCommandDone()
840 {
841 	fSyncPending--;
842 }
843 
844 
845 bool
846 IMAPConnectionWorker::_IsQuitPending()
847 {
848 	BAutolock locker(fQueueLocker);
849 	WorkerCommand* nextCommand = fPendingCommands.ItemAt(0);
850 	return dynamic_cast<QuitCommand*>(nextCommand) != NULL;
851 }
852 
853 
854 status_t
855 IMAPConnectionWorker::_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 
885 void
886 IMAPConnectionWorker::_Disconnect()
887 {
888 	fProtocol.Disconnect();
889 	fSelectedBox = NULL;
890 }
891 
892 
893 /*static*/ status_t
894 IMAPConnectionWorker::_Worker(void* _self)
895 {
896 	IMAPConnectionWorker* self = (IMAPConnectionWorker*)_self;
897 	status_t status = self->_Worker();
898 
899 	delete self;
900 	return status;
901 }
902