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:
WorkerPrivate(IMAPConnectionWorker & worker)28 WorkerPrivate(IMAPConnectionWorker& worker)
29 :
30 fWorker(worker)
31 {
32 }
33
Protocol()34 IMAP::Protocol& Protocol()
35 {
36 return fWorker.fProtocol;
37 }
38
AddFolders(BObjectList<IMAPFolder> & folders)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
SelectMailbox(IMAPFolder & folder)51 status_t SelectMailbox(IMAPFolder& folder)
52 {
53 return fWorker._SelectMailbox(folder, NULL);
54 }
55
SelectMailbox(IMAPFolder & folder,uint32 & nextUID)56 status_t SelectMailbox(IMAPFolder& folder, uint32& nextUID)
57 {
58 return fWorker._SelectMailbox(folder, &nextUID);
59 }
60
MailboxFor(IMAPFolder & folder)61 IMAPMailbox* MailboxFor(IMAPFolder& folder)
62 {
63 return fWorker._MailboxFor(folder);
64 }
65
BodyFetchLimit() const66 int32 BodyFetchLimit() const
67 {
68 return fWorker.fSettings.BodyFetchLimit();
69 }
70
MessagesExist() const71 uint32 MessagesExist() const
72 {
73 return fWorker._MessagesExist();
74 }
75
EnqueueCommand(WorkerCommand * command)76 status_t EnqueueCommand(WorkerCommand* command)
77 {
78 return fWorker._EnqueueCommand(command);
79 }
80
SyncCommandDone()81 void SyncCommandDone()
82 {
83 fWorker._SyncCommandDone();
84 }
85
Quit()86 void Quit()
87 {
88 fWorker.fStopped = true;
89 }
90
91 private:
92 IMAPConnectionWorker& fWorker;
93 };
94
95
96 class WorkerCommand {
97 public:
WorkerCommand()98 WorkerCommand()
99 :
100 fContinuation(false)
101 {
102 }
103
~WorkerCommand()104 virtual ~WorkerCommand()
105 {
106 }
107
108 virtual status_t Process(IMAPConnectionWorker& worker) = 0;
109
IsDone() const110 virtual bool IsDone() const
111 {
112 return true;
113 }
114
IsContinuation() const115 bool IsContinuation() const
116 {
117 return fContinuation;
118 }
119
SetContinuation()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:
QuitCommand()140 QuitCommand()
141 {
142 }
143
Process(IMAPConnectionWorker & worker)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:
Process(IMAPConnectionWorker & worker)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:
FetchBodiesCommand(IMAPFolder & folder,IMAPMailbox & mailbox,MessageUIDList & entries,const BMessenger * replyTo=NULL)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
Process(IMAPConnectionWorker & worker)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
IsDone() const208 virtual bool IsDone() const
209 {
210 return fEntries.empty();
211 }
212
FetchData(uint32 fetchFlags,BDataIO & stream,size_t & length)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
FetchedData(uint32 fetchFlags,uint32 uid,uint32 flags)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:
FetchHeadersCommand(IMAPFolder & folder,IMAPMailbox & mailbox,MessageUIDList & uids,int32 bodyFetchLimit)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
Process(IMAPConnectionWorker & worker)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
IsDone() const275 virtual bool IsDone() const
276 {
277 return fUIDs.empty();
278 }
279
FetchData(uint32 fetchFlags,BDataIO & stream,size_t & length)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
FetchedData(uint32 fetchFlags,uint32 uid,uint32 flags)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:
CheckMailboxesCommand(IMAPConnectionWorker & worker)312 CheckMailboxesCommand(IMAPConnectionWorker& worker)
313 :
314 fWorker(worker),
315 fFolders(5, false),
316 fState(INIT),
317 fFolder(NULL),
318 fMailbox(NULL)
319 {
320 }
321
Process(IMAPConnectionWorker & worker)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
IsDone() const422 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:
UpdateFlagsCommand(IMAPFolder & folder,IMAPMailbox & mailbox,MessageUIDList & entries,uint32 flags)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
Process(IMAPConnectionWorker & worker)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
IsDone() const479 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 {
operator ()CommandDelete495 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 {
CommandDeleterCommandDeleter507 CommandDeleter(IMAPConnectionWorker& worker, WorkerCommand* command)
508 :
509 BPrivate::AutoDeleter<WorkerCommand, CommandDelete>(command),
510 fWorker(worker)
511 {
512 }
513
~CommandDeleterCommandDeleter514 ~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
IMAPConnectionWorker(IMAPProtocol & owner,const Settings & settings,bool main)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
~IMAPConnectionWorker()546 IMAPConnectionWorker::~IMAPConnectionWorker()
547 {
548 puts("worker quit");
549 delete_sem(fPendingCommandsSemaphore);
550 _Disconnect();
551 }
552
553
554 bool
HasMailboxes() const555 IMAPConnectionWorker::HasMailboxes() const
556 {
557 BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
558 return !fMailboxes.empty();
559 }
560
561
562 uint32
CountMailboxes() const563 IMAPConnectionWorker::CountMailboxes() const
564 {
565 BAutolock locker(const_cast<IMAPConnectionWorker*>(this)->fLocker);
566 return fMailboxes.size();
567 }
568
569
570 void
AddMailbox(IMAPFolder * folder)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
RemoveAllMailboxes()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
Run()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
Quit()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
EnqueueCheckSubscribedFolders()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
EnqueueCheckMailboxes()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
EnqueueFetchBody(IMAPFolder & folder,uint32 uid,const BMessenger & replyTo)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
EnqueueUpdateFlags(IMAPFolder & folder,uint32 uid,uint32 flags)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
MessageExistsReceived(uint32 count)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
MessageExpungeReceived(uint32 index)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
_Worker()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
_EnqueueCommand(WorkerCommand * command)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
_WaitForCommands()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
_SelectMailbox(IMAPFolder & folder,uint32 * _nextUID)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*
_MailboxFor(IMAPFolder & folder)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
_SyncCommandDone()839 IMAPConnectionWorker::_SyncCommandDone()
840 {
841 fSyncPending--;
842 }
843
844
845 bool
_IsQuitPending()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
_Connect()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
_Disconnect()886 IMAPConnectionWorker::_Disconnect()
887 {
888 fProtocol.Disconnect();
889 fSelectedBox = NULL;
890 }
891
892
893 /*static*/ status_t
_Worker(void * _self)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