1186c96d5SAxel Dörfler/*
281805393SAxel Dörfler * Copyright 2013-2016, Axel D��rfler, axeld@pinc-software.de.
3186c96d5SAxel Dörfler * Distributed under the terms of the MIT License.
4186c96d5SAxel Dörfler */
5186c96d5SAxel Dörfler
6186c96d5SAxel Dörfler
7186c96d5SAxel Dörfler#include "IMAPProtocol.h"
8186c96d5SAxel Dörfler
9186c96d5SAxel Dörfler#include <Directory.h>
1081805393SAxel Dörfler#include <Messenger.h>
11186c96d5SAxel Dörfler
12186c96d5SAxel Dörfler#include "IMAPConnectionWorker.h"
13a4bdd26dSAxel Dörfler#include "IMAPFolder.h"
14a4bdd26dSAxel Dörfler#include "Utilities.h"
15186c96d5SAxel Dörfler
16618cc43bSAxel Dörfler#include <mail_util.h>
17618cc43bSAxel Dörfler
18186c96d5SAxel Dörfler
19186c96d5SAxel DörflerIMAPProtocol::IMAPProtocol(const BMailAccountSettings& settings)
20186c96d5SAxel Dörfler	:
2162eec600SAxel Dörfler	BInboundMailProtocol("IMAP", settings),
2228ee6c28SAxel Dörfler	fSettings(settings.Name(), settings.InboundSettings()),
237993ddfaSAxel Dörfler	fWorkers(5, false)
24186c96d5SAxel Dörfler{
25186c96d5SAxel Dörfler	BPath destination = fSettings.Destination();
26186c96d5SAxel Dörfler
27186c96d5SAxel Dörfler	status_t status = create_directory(destination.Path(), 0755);
28186c96d5SAxel Dörfler	if (status != B_OK) {
2928ee6c28SAxel Dörfler		fprintf(stderr, "IMAP: Could not create destination directory %s: %s\n",
30186c96d5SAxel Dörfler			destination.Path(), strerror(status));
31186c96d5SAxel Dörfler	}
32186c96d5SAxel Dörfler
3381805393SAxel Dörfler	mutex_init(&fWorkerLock, "imap worker lock");
3481805393SAxel Dörfler
35186c96d5SAxel Dörfler	PostMessage(B_READY_TO_RUN);
36186c96d5SAxel Dörfler}
37186c96d5SAxel Dörfler
38186c96d5SAxel Dörfler
39186c96d5SAxel DörflerIMAPProtocol::~IMAPProtocol()
40186c96d5SAxel Dörfler{
416fa27973SPeter Kosyh	MutexLocker locker(fWorkerLock);
426fa27973SPeter Kosyh	std::vector<thread_id> threads;
436fa27973SPeter Kosyh	for (int32 i = 0; i < fWorkers.CountItems(); i++) {
446fa27973SPeter Kosyh		threads.push_back(fWorkers.ItemAt(i)->Thread());
456fa27973SPeter Kosyh		fWorkers.ItemAt(i)->Quit();
466fa27973SPeter Kosyh	}
476fa27973SPeter Kosyh	locker.Unlock();
486fa27973SPeter Kosyh
496fa27973SPeter Kosyh	for (uint32 i = 0; i < threads.size(); i++)
506fa27973SPeter Kosyh		wait_for_thread(threads[i], NULL);
51186c96d5SAxel Dörfler
526fa27973SPeter Kosyh	FolderMap::iterator iterator = fFolders.begin();
536fa27973SPeter Kosyh	for (; iterator != fFolders.end(); iterator++) {
546fa27973SPeter Kosyh		IMAPFolder* folder = iterator->second;
556fa27973SPeter Kosyh		delete folder; // to stop thread
566fa27973SPeter Kosyh	}
576fa27973SPeter Kosyh}
58186c96d5SAxel Dörfler
59adbe8fc9SAxel Dörflerstatus_t
607993ddfaSAxel DörflerIMAPProtocol::CheckSubscribedFolders(IMAP::Protocol& protocol, bool idle)
61adbe8fc9SAxel Dörfler{
62adbe8fc9SAxel Dörfler	// Get list of subscribed folders
63adbe8fc9SAxel Dörfler
6429871039SAxel Dörfler	BStringList newFolders;
65a4bdd26dSAxel Dörfler	BString separator;
66a4bdd26dSAxel Dörfler	status_t status = protocol.GetSubscribedFolders(newFolders, separator);
67adbe8fc9SAxel Dörfler	if (status != B_OK)
68adbe8fc9SAxel Dörfler		return status;
69adbe8fc9SAxel Dörfler
70adbe8fc9SAxel Dörfler	// Determine how many new mailboxes we have
71adbe8fc9SAxel Dörfler
7229871039SAxel Dörfler	for (int32 i = 0; i < newFolders.CountStrings();) {
7329871039SAxel Dörfler		if (fFolders.find(newFolders.StringAt(i)) != fFolders.end())
7429871039SAxel Dörfler			newFolders.Remove(i);
75a4bdd26dSAxel Dörfler		else
7629871039SAxel Dörfler			i++;
77adbe8fc9SAxel Dörfler	}
78adbe8fc9SAxel Dörfler
7929871039SAxel Dörfler	int32 totalMailboxes = fFolders.size() + newFolders.CountStrings();
80a4bdd26dSAxel Dörfler	int32 workersWanted = 1;
817993ddfaSAxel Dörfler	if (idle)
82a4bdd26dSAxel Dörfler		workersWanted = std::min(fSettings.MaxConnections(), totalMailboxes);
83adbe8fc9SAxel Dörfler
8481805393SAxel Dörfler	MutexLocker locker(fWorkerLock);
8581805393SAxel Dörfler
8629871039SAxel Dörfler	if (newFolders.IsEmpty() && fWorkers.CountItems() == workersWanted) {
87a4bdd26dSAxel Dörfler		// Nothing to do - we've already distributed everything
886fa27973SPeter Kosyh		return _EnqueueCheckMailboxes();
89a4bdd26dSAxel Dörfler	}
90adbe8fc9SAxel Dörfler
91a4bdd26dSAxel Dörfler	// Remove mailboxes from workers
92a4bdd26dSAxel Dörfler	for (int32 i = 0; i < fWorkers.CountItems(); i++) {
93a4bdd26dSAxel Dörfler		fWorkers.ItemAt(i)->RemoveAllMailboxes();
94a4bdd26dSAxel Dörfler	}
9581805393SAxel Dörfler	fWorkerMap.clear();
96adbe8fc9SAxel Dörfler
97a4bdd26dSAxel Dörfler	// Create/remove connection workers as allowed and needed
98a4bdd26dSAxel Dörfler	while (fWorkers.CountItems() < workersWanted) {
99a4bdd26dSAxel Dörfler		IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this,
100a4bdd26dSAxel Dörfler			fSettings);
101a4bdd26dSAxel Dörfler		if (!fWorkers.AddItem(worker)) {
102a4bdd26dSAxel Dörfler			delete worker;
103a4bdd26dSAxel Dörfler			break;
104adbe8fc9SAxel Dörfler		}
105a4bdd26dSAxel Dörfler
1067993ddfaSAxel Dörfler		status = worker->Run();
1077993ddfaSAxel Dörfler		if (status != B_OK) {
1087993ddfaSAxel Dörfler			fWorkers.RemoveItem(worker);
1097993ddfaSAxel Dörfler			delete worker;
1107993ddfaSAxel Dörfler		}
111a4bdd26dSAxel Dörfler	}
1127993ddfaSAxel Dörfler
113a4bdd26dSAxel Dörfler	while (fWorkers.CountItems() > workersWanted) {
114a4bdd26dSAxel Dörfler		IMAPConnectionWorker* worker
115a4bdd26dSAxel Dörfler			= fWorkers.RemoveItemAt(fWorkers.CountItems() - 1);
116a4bdd26dSAxel Dörfler		worker->Quit();
117adbe8fc9SAxel Dörfler	}
118adbe8fc9SAxel Dörfler
119a4bdd26dSAxel Dörfler	// Update known mailboxes
12029871039SAxel Dörfler	for (int32 i = 0; i < newFolders.CountStrings(); i++) {
12129871039SAxel Dörfler		const BString& mailbox = newFolders.StringAt(i);
122a4bdd26dSAxel Dörfler		fFolders.insert(std::make_pair(mailbox,
123a4bdd26dSAxel Dörfler			_CreateFolder(mailbox, separator)));
124a4bdd26dSAxel Dörfler	}
125adbe8fc9SAxel Dörfler
126a4bdd26dSAxel Dörfler	// Distribute the mailboxes evenly to the workers
127a4bdd26dSAxel Dörfler	FolderMap::iterator iterator = fFolders.begin();
128adbe8fc9SAxel Dörfler	int32 index = 0;
129a4bdd26dSAxel Dörfler	for (; iterator != fFolders.end(); iterator++) {
13081805393SAxel Dörfler		IMAPConnectionWorker* worker = fWorkers.ItemAt(index);
13181805393SAxel Dörfler		IMAPFolder* folder = iterator->second;
13281805393SAxel Dörfler		worker->AddMailbox(folder);
13381805393SAxel Dörfler		fWorkerMap.insert(std::make_pair(folder, worker));
13481805393SAxel Dörfler
135adbe8fc9SAxel Dörfler		index = (index + 1) % fWorkers.CountItems();
136adbe8fc9SAxel Dörfler	}
137adbe8fc9SAxel Dörfler
1387993ddfaSAxel Dörfler	// Start waiting workers
1394b2c5571SAxel Dörfler	return _EnqueueCheckMailboxes();
140adbe8fc9SAxel Dörfler}
141adbe8fc9SAxel Dörfler
142adbe8fc9SAxel Dörfler
143229c7773SAxel Dörflervoid
144229c7773SAxel DörflerIMAPProtocol::WorkerQuit(IMAPConnectionWorker* worker)
145229c7773SAxel Dörfler{
14681805393SAxel Dörfler	MutexLocker locker(fWorkerLock);
147229c7773SAxel Dörfler	fWorkers.RemoveItem(worker);
14881805393SAxel Dörfler
14981805393SAxel Dörfler	WorkerMap::iterator iterator = fWorkerMap.begin();
15081805393SAxel Dörfler	while (iterator != fWorkerMap.end()) {
15181805393SAxel Dörfler		WorkerMap::iterator removed = iterator++;
15281805393SAxel Dörfler		if (removed->second == worker)
15381805393SAxel Dörfler			fWorkerMap.erase(removed);
15481805393SAxel Dörfler	}
155229c7773SAxel Dörfler}
156229c7773SAxel Dörfler
157229c7773SAxel Dörfler
158eba458b9SAxel Dörflervoid
15981805393SAxel DörflerIMAPProtocol::MessageStored(IMAPFolder& folder, entry_ref& ref,
16081805393SAxel Dörfler	BFile& stream, uint32 fetchFlags, BMessage& attributes)
161eba458b9SAxel Dörfler{
162d33e4744SAxel Dörfler	if ((fetchFlags & (IMAP::kFetchHeader | IMAP::kFetchBody))
1634fe2002bSAxel Dörfler			== (IMAP::kFetchHeader | IMAP::kFetchBody)) {
164d33e4744SAxel Dörfler		ProcessMessageFetched(ref, stream, attributes);
165d33e4744SAxel Dörfler	} else if ((fetchFlags & IMAP::kFetchHeader) != 0) {
166d33e4744SAxel Dörfler		ProcessHeaderFetched(ref, stream, attributes);
167d33e4744SAxel Dörfler	} else if ((fetchFlags & IMAP::kFetchBody) != 0) {
168d33e4744SAxel Dörfler		NotifyBodyFetched(ref, stream, attributes);
169549949b2SAxel Dörfler	}
170eba458b9SAxel Dörfler}
171eba458b9SAxel Dörfler
172eba458b9SAxel Dörfler
17381805393SAxel Dörflerstatus_t
17481805393SAxel DörflerIMAPProtocol::UpdateMessageFlags(IMAPFolder& folder, uint32 uid, uint32 flags)
17581805393SAxel Dörfler{
17681805393SAxel Dörfler	MutexLocker locker(fWorkerLock);
17781805393SAxel Dörfler
17881805393SAxel Dörfler	WorkerMap::const_iterator found = fWorkerMap.find(&folder);
17981805393SAxel Dörfler	if (found == fWorkerMap.end())
18081805393SAxel Dörfler		return B_ERROR;
18181805393SAxel Dörfler
18281805393SAxel Dörfler	IMAPConnectionWorker* worker = found->second;
18381805393SAxel Dörfler	return worker->EnqueueUpdateFlags(folder, uid, flags);
18481805393SAxel Dörfler}
18581805393SAxel Dörfler
18681805393SAxel Dörfler
187186c96d5SAxel Dörflerstatus_t
188186c96d5SAxel DörflerIMAPProtocol::SyncMessages()
189186c96d5SAxel Dörfler{
190186c96d5SAxel Dörfler	puts("IMAP: sync");
191adbe8fc9SAxel Dörfler
19281805393SAxel Dörfler	MutexLocker locker(fWorkerLock);
193adbe8fc9SAxel Dörfler	if (fWorkers.IsEmpty()) {
194adbe8fc9SAxel Dörfler		// Create main (and possibly initial) connection worker
195adbe8fc9SAxel Dörfler		IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this,
196adbe8fc9SAxel Dörfler			fSettings, true);
197adbe8fc9SAxel Dörfler		if (!fWorkers.AddItem(worker)) {
198adbe8fc9SAxel Dörfler			delete worker;
199adbe8fc9SAxel Dörfler			return B_NO_MEMORY;
200adbe8fc9SAxel Dörfler		}
201adbe8fc9SAxel Dörfler
202229c7773SAxel Dörfler		worker->EnqueueCheckSubscribedFolders();
203a4bdd26dSAxel Dörfler		return worker->Run();
204adbe8fc9SAxel Dörfler	}
2056fa27973SPeter Kosyh	fWorkers.ItemAt(0)->EnqueueCheckSubscribedFolders();
2066fa27973SPeter Kosyh	return B_OK;
207186c96d5SAxel Dörfler}
208186c96d5SAxel Dörfler
209186c96d5SAxel Dörfler
210186c96d5SAxel Dörflerstatus_t
211186c96d5SAxel DörflerIMAPProtocol::MarkMessageAsRead(const entry_ref& ref, read_flags flags)
212186c96d5SAxel Dörfler{
213186c96d5SAxel Dörfler	printf("IMAP: mark as read %s: %d\n", ref.name, flags);
214186c96d5SAxel Dörfler	return B_ERROR;
215186c96d5SAxel Dörfler}
216186c96d5SAxel Dörfler
217186c96d5SAxel Dörfler
218186c96d5SAxel Dörflervoid
219186c96d5SAxel DörflerIMAPProtocol::MessageReceived(BMessage* message)
220186c96d5SAxel Dörfler{
221186c96d5SAxel Dörfler	switch (message->what) {
222186c96d5SAxel Dörfler		case B_READY_TO_RUN:
223186c96d5SAxel Dörfler			ReadyToRun();
224186c96d5SAxel Dörfler			break;
225adbe8fc9SAxel Dörfler
226adbe8fc9SAxel Dörfler		default:
227adbe8fc9SAxel Dörfler			BInboundMailProtocol::MessageReceived(message);
228adbe8fc9SAxel Dörfler			break;
229186c96d5SAxel Dörfler	}
230186c96d5SAxel Dörfler}
231186c96d5SAxel Dörfler
232186c96d5SAxel Dörfler
23381805393SAxel Dörflerstatus_t
23481805393SAxel DörflerIMAPProtocol::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo)
23581805393SAxel Dörfler{
23681805393SAxel Dörfler	printf("IMAP: fetch body %s\n", ref.name);
23781805393SAxel Dörfler	MutexLocker locker(fWorkerLock);
23881805393SAxel Dörfler
23981805393SAxel Dörfler	IMAPFolder* folder = _FolderFor(ref.directory);
24081805393SAxel Dörfler	if (folder == NULL)
24181805393SAxel Dörfler		return B_ENTRY_NOT_FOUND;
24281805393SAxel Dörfler
24381805393SAxel Dörfler	uint32 uid;
24481805393SAxel Dörfler	status_t status = folder->GetMessageUID(ref, uid);
24581805393SAxel Dörfler	if (status != B_OK)
24681805393SAxel Dörfler		return status;
24781805393SAxel Dörfler
24881805393SAxel Dörfler	WorkerMap::const_iterator found = fWorkerMap.find(folder);
24981805393SAxel Dörfler	if (found == fWorkerMap.end())
25081805393SAxel Dörfler		return B_ERROR;
25181805393SAxel Dörfler
25281805393SAxel Dörfler	IMAPConnectionWorker* worker = found->second;
25381805393SAxel Dörfler	return worker->EnqueueFetchBody(*folder, uid, replyTo);
25481805393SAxel Dörfler}
25581805393SAxel Dörfler
25681805393SAxel Dörfler
257186c96d5SAxel Dörflervoid
258186c96d5SAxel DörflerIMAPProtocol::ReadyToRun()
259186c96d5SAxel Dörfler{
260adbe8fc9SAxel Dörfler	puts("IMAP: ready to run!");
261adbe8fc9SAxel Dörfler	if (fSettings.IdleMode())
262adbe8fc9SAxel Dörfler		SyncMessages();
263186c96d5SAxel Dörfler}
264186c96d5SAxel Dörfler
265186c96d5SAxel Dörfler
266a4bdd26dSAxel DörflerIMAPFolder*
267a4bdd26dSAxel DörflerIMAPProtocol::_CreateFolder(const BString& mailbox, const BString& separator)
268a4bdd26dSAxel Dörfler{
269a4bdd26dSAxel Dörfler	BString name = MailboxToFolderName(mailbox, separator);
270a4bdd26dSAxel Dörfler
271a4bdd26dSAxel Dörfler	BPath path(fSettings.Destination());
272a4bdd26dSAxel Dörfler	if (path.Append(name.String()) != B_OK) {
273a4bdd26dSAxel Dörfler		fprintf(stderr, "Could not append path: %s\n", name.String());
274a4bdd26dSAxel Dörfler		return NULL;
275a4bdd26dSAxel Dörfler	}
276a4bdd26dSAxel Dörfler
277a4bdd26dSAxel Dörfler	status_t status = create_directory(path.Path(), 0755);
278a4bdd26dSAxel Dörfler	if (status != B_OK) {
279a4bdd26dSAxel Dörfler		fprintf(stderr, "Could not create path %s: %s\n", path.Path(),
280a4bdd26dSAxel Dörfler			strerror(status));
281a4bdd26dSAxel Dörfler		return NULL;
282a4bdd26dSAxel Dörfler	}
283a4bdd26dSAxel Dörfler
284618cc43bSAxel Dörfler	CopyMailFolderAttributes(path.Path());
285618cc43bSAxel Dörfler
286a4bdd26dSAxel Dörfler	entry_ref ref;
287a4bdd26dSAxel Dörfler	status = get_ref_for_path(path.Path(), &ref);
288a4bdd26dSAxel Dörfler	if (status != B_OK) {
289a4bdd26dSAxel Dörfler		fprintf(stderr, "Could not get ref for %s: %s\n", path.Path(),
290a4bdd26dSAxel Dörfler			strerror(status));
291a4bdd26dSAxel Dörfler		return NULL;
292a4bdd26dSAxel Dörfler	}
293a4bdd26dSAxel Dörfler
29481805393SAxel Dörfler	IMAPFolder* folder = new IMAPFolder(*this, mailbox, ref);
29581805393SAxel Dörfler	status = folder->Init();
29681805393SAxel Dörfler	if (status != B_OK) {
29781805393SAxel Dörfler		fprintf(stderr, "Initializing folder %s failed: %s\n", path.Path(),
29881805393SAxel Dörfler			strerror(status));
29981805393SAxel Dörfler		return NULL;
30081805393SAxel Dörfler	}
30181805393SAxel Dörfler
30281805393SAxel Dörfler	fFolderNodeMap.insert(std::make_pair(folder->NodeID(), folder));
30381805393SAxel Dörfler	return folder;
30481805393SAxel Dörfler}
30581805393SAxel Dörfler
30681805393SAxel Dörfler
30781805393SAxel DörflerIMAPFolder*
30881805393SAxel DörflerIMAPProtocol::_FolderFor(ino_t directory)
30981805393SAxel Dörfler{
31081805393SAxel Dörfler	FolderNodeMap::const_iterator found = fFolderNodeMap.find(directory);
31181805393SAxel Dörfler	if (found != fFolderNodeMap.end())
31281805393SAxel Dörfler		return found->second;
31381805393SAxel Dörfler
31481805393SAxel Dörfler	return NULL;
315a4bdd26dSAxel Dörfler}
316a4bdd26dSAxel Dörfler
317a4bdd26dSAxel Dörfler
3184b2c5571SAxel Dörflerstatus_t
3194b2c5571SAxel DörflerIMAPProtocol::_EnqueueCheckMailboxes()
3204b2c5571SAxel Dörfler{
3214b2c5571SAxel Dörfler	for (int32 i = 0; i < fWorkers.CountItems(); i++) {
3224b2c5571SAxel Dörfler		fWorkers.ItemAt(i)->EnqueueCheckMailboxes();
3234b2c5571SAxel Dörfler	}
3244b2c5571SAxel Dörfler
3254b2c5571SAxel Dörfler	return B_OK;
3264b2c5571SAxel Dörfler}
3274b2c5571SAxel Dörfler
3284b2c5571SAxel Dörfler
329186c96d5SAxel Dörfler// #pragma mark -
330186c96d5SAxel Dörfler
331186c96d5SAxel Dörfler
332186c96d5SAxel Dörflerextern "C" BInboundMailProtocol*
333186c96d5SAxel Dörflerinstantiate_inbound_protocol(const BMailAccountSettings& settings)
334186c96d5SAxel Dörfler{
335186c96d5SAxel Dörfler	return new IMAPProtocol(settings);
336186c96d5SAxel Dörfler}
337