1/*
2 * Copyright 2013-2016, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "IMAPProtocol.h"
8
9#include <Directory.h>
10#include <Messenger.h>
11
12#include "IMAPConnectionWorker.h"
13#include "IMAPFolder.h"
14#include "Utilities.h"
15
16#include <mail_util.h>
17
18
19IMAPProtocol::IMAPProtocol(const BMailAccountSettings& settings)
20	:
21	BInboundMailProtocol("IMAP", settings),
22	fSettings(settings.Name(), settings.InboundSettings()),
23	fWorkers(5, false)
24{
25	BPath destination = fSettings.Destination();
26
27	status_t status = create_directory(destination.Path(), 0755);
28	if (status != B_OK) {
29		fprintf(stderr, "IMAP: Could not create destination directory %s: %s\n",
30			destination.Path(), strerror(status));
31	}
32
33	mutex_init(&fWorkerLock, "imap worker lock");
34
35	PostMessage(B_READY_TO_RUN);
36}
37
38
39IMAPProtocol::~IMAPProtocol()
40{
41	MutexLocker locker(fWorkerLock);
42	std::vector<thread_id> threads;
43	for (int32 i = 0; i < fWorkers.CountItems(); i++) {
44		threads.push_back(fWorkers.ItemAt(i)->Thread());
45		fWorkers.ItemAt(i)->Quit();
46	}
47	locker.Unlock();
48
49	for (uint32 i = 0; i < threads.size(); i++)
50		wait_for_thread(threads[i], NULL);
51
52	FolderMap::iterator iterator = fFolders.begin();
53	for (; iterator != fFolders.end(); iterator++) {
54		IMAPFolder* folder = iterator->second;
55		delete folder; // to stop thread
56	}
57}
58
59status_t
60IMAPProtocol::CheckSubscribedFolders(IMAP::Protocol& protocol, bool idle)
61{
62	// Get list of subscribed folders
63
64	BStringList newFolders;
65	BString separator;
66	status_t status = protocol.GetSubscribedFolders(newFolders, separator);
67	if (status != B_OK)
68		return status;
69
70	// Determine how many new mailboxes we have
71
72	for (int32 i = 0; i < newFolders.CountStrings();) {
73		if (fFolders.find(newFolders.StringAt(i)) != fFolders.end())
74			newFolders.Remove(i);
75		else
76			i++;
77	}
78
79	int32 totalMailboxes = fFolders.size() + newFolders.CountStrings();
80	int32 workersWanted = 1;
81	if (idle)
82		workersWanted = std::min(fSettings.MaxConnections(), totalMailboxes);
83
84	MutexLocker locker(fWorkerLock);
85
86	if (newFolders.IsEmpty() && fWorkers.CountItems() == workersWanted) {
87		// Nothing to do - we've already distributed everything
88		return _EnqueueCheckMailboxes();
89	}
90
91	// Remove mailboxes from workers
92	for (int32 i = 0; i < fWorkers.CountItems(); i++) {
93		fWorkers.ItemAt(i)->RemoveAllMailboxes();
94	}
95	fWorkerMap.clear();
96
97	// Create/remove connection workers as allowed and needed
98	while (fWorkers.CountItems() < workersWanted) {
99		IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this,
100			fSettings);
101		if (!fWorkers.AddItem(worker)) {
102			delete worker;
103			break;
104		}
105
106		status = worker->Run();
107		if (status != B_OK) {
108			fWorkers.RemoveItem(worker);
109			delete worker;
110		}
111	}
112
113	while (fWorkers.CountItems() > workersWanted) {
114		IMAPConnectionWorker* worker
115			= fWorkers.RemoveItemAt(fWorkers.CountItems() - 1);
116		worker->Quit();
117	}
118
119	// Update known mailboxes
120	for (int32 i = 0; i < newFolders.CountStrings(); i++) {
121		const BString& mailbox = newFolders.StringAt(i);
122		IMAPFolder* folder = _CreateFolder(mailbox, separator);
123		if (folder != NULL)
124			fFolders.insert(std::make_pair(mailbox, folder));
125	}
126
127	// Distribute the mailboxes evenly to the workers
128	FolderMap::iterator iterator = fFolders.begin();
129	int32 index = 0;
130	for (; iterator != fFolders.end(); iterator++) {
131		IMAPConnectionWorker* worker = fWorkers.ItemAt(index);
132		IMAPFolder* folder = iterator->second;
133		worker->AddMailbox(folder);
134		fWorkerMap.insert(std::make_pair(folder, worker));
135
136		index = (index + 1) % fWorkers.CountItems();
137	}
138
139	// Start waiting workers
140	return _EnqueueCheckMailboxes();
141}
142
143
144void
145IMAPProtocol::WorkerQuit(IMAPConnectionWorker* worker)
146{
147	MutexLocker locker(fWorkerLock);
148	fWorkers.RemoveItem(worker);
149
150	WorkerMap::iterator iterator = fWorkerMap.begin();
151	while (iterator != fWorkerMap.end()) {
152		WorkerMap::iterator removed = iterator++;
153		if (removed->second == worker)
154			fWorkerMap.erase(removed);
155	}
156}
157
158
159void
160IMAPProtocol::MessageStored(IMAPFolder& folder, entry_ref& ref,
161	BFile& stream, uint32 fetchFlags, BMessage& attributes)
162{
163	if ((fetchFlags & (IMAP::kFetchHeader | IMAP::kFetchBody))
164			== (IMAP::kFetchHeader | IMAP::kFetchBody)) {
165		ProcessMessageFetched(ref, stream, attributes);
166	} else if ((fetchFlags & IMAP::kFetchHeader) != 0) {
167		ProcessHeaderFetched(ref, stream, attributes);
168	} else if ((fetchFlags & IMAP::kFetchBody) != 0) {
169		NotifyBodyFetched(ref, stream, attributes);
170	}
171}
172
173
174status_t
175IMAPProtocol::UpdateMessageFlags(IMAPFolder& folder, uint32 uid, uint32 flags)
176{
177	MutexLocker locker(fWorkerLock);
178
179	WorkerMap::const_iterator found = fWorkerMap.find(&folder);
180	if (found == fWorkerMap.end())
181		return B_ERROR;
182
183	IMAPConnectionWorker* worker = found->second;
184	return worker->EnqueueUpdateFlags(folder, uid, flags);
185}
186
187
188status_t
189IMAPProtocol::SyncMessages()
190{
191	puts("IMAP: sync");
192
193	MutexLocker locker(fWorkerLock);
194	if (fWorkers.IsEmpty()) {
195		// Create main (and possibly initial) connection worker
196		IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this,
197			fSettings, true);
198		if (!fWorkers.AddItem(worker)) {
199			delete worker;
200			return B_NO_MEMORY;
201		}
202
203		worker->EnqueueCheckSubscribedFolders();
204		return worker->Run();
205	}
206	fWorkers.ItemAt(0)->EnqueueCheckSubscribedFolders();
207	return B_OK;
208}
209
210
211status_t
212IMAPProtocol::MarkMessageAsRead(const entry_ref& ref, read_flags flags)
213{
214	printf("IMAP: mark as read %s: %d\n", ref.name, flags);
215	return B_ERROR;
216}
217
218
219void
220IMAPProtocol::MessageReceived(BMessage* message)
221{
222	switch (message->what) {
223		case B_READY_TO_RUN:
224			ReadyToRun();
225			break;
226
227		default:
228			BInboundMailProtocol::MessageReceived(message);
229			break;
230	}
231}
232
233
234status_t
235IMAPProtocol::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo)
236{
237	printf("IMAP: fetch body %s\n", ref.name);
238	MutexLocker locker(fWorkerLock);
239
240	IMAPFolder* folder = _FolderFor(ref.directory);
241	if (folder == NULL)
242		return B_ENTRY_NOT_FOUND;
243
244	uint32 uid;
245	status_t status = folder->GetMessageUID(ref, uid);
246	if (status != B_OK)
247		return status;
248
249	WorkerMap::const_iterator found = fWorkerMap.find(folder);
250	if (found == fWorkerMap.end())
251		return B_ERROR;
252
253	IMAPConnectionWorker* worker = found->second;
254	return worker->EnqueueFetchBody(*folder, uid, replyTo);
255}
256
257
258void
259IMAPProtocol::ReadyToRun()
260{
261	puts("IMAP: ready to run!");
262	if (fSettings.IdleMode())
263		SyncMessages();
264}
265
266
267IMAPFolder*
268IMAPProtocol::_CreateFolder(const BString& mailbox, const BString& separator)
269{
270	BString name = MailboxToFolderName(mailbox, separator);
271
272	BPath path(fSettings.Destination());
273	if (path.Append(name.String()) != B_OK) {
274		fprintf(stderr, "Could not append path: %s\n", name.String());
275		return NULL;
276	}
277
278	status_t status;
279	BNode node(path.Path());
280
281	if (node.InitCheck() == B_OK) {
282		if (!node.IsDirectory()) {
283			fprintf(stderr, "%s already exists and is not a directory\n",
284				path.Path());
285			return NULL;
286		}
287	} else {
288		status = create_directory(path.Path(), 0755);
289		if (status != B_OK) {
290			fprintf(stderr, "Could not create path %s: %s\n", path.Path(),
291				strerror(status));
292			return NULL;
293		}
294		CopyMailFolderAttributes(path.Path());
295	}
296
297	entry_ref ref;
298	status = get_ref_for_path(path.Path(), &ref);
299	if (status != B_OK) {
300		fprintf(stderr, "Could not get ref for %s: %s\n", path.Path(),
301			strerror(status));
302		return NULL;
303	}
304
305	IMAPFolder* folder = new IMAPFolder(*this, mailbox, ref);
306	status = folder->Init();
307	if (status != B_OK) {
308		fprintf(stderr, "Initializing folder %s failed: %s\n", path.Path(),
309			strerror(status));
310		delete folder;
311		return NULL;
312	}
313
314	fFolderNodeMap.insert(std::make_pair(folder->NodeID(), folder));
315	return folder;
316}
317
318
319IMAPFolder*
320IMAPProtocol::_FolderFor(ino_t directory)
321{
322	FolderNodeMap::const_iterator found = fFolderNodeMap.find(directory);
323	if (found != fFolderNodeMap.end())
324		return found->second;
325
326	return NULL;
327}
328
329
330status_t
331IMAPProtocol::_EnqueueCheckMailboxes()
332{
333	for (int32 i = 0; i < fWorkers.CountItems(); i++) {
334		fWorkers.ItemAt(i)->EnqueueCheckMailboxes();
335	}
336
337	return B_OK;
338}
339
340
341// #pragma mark -
342
343
344extern "C" BInboundMailProtocol*
345instantiate_inbound_protocol(const BMailAccountSettings& settings)
346{
347	return new IMAPProtocol(settings);
348}
349