xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/IMAPProtocol.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
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 
19 IMAPProtocol::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 
39 IMAPProtocol::~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 
59 status_t
60 IMAPProtocol::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 
144 void
145 IMAPProtocol::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 
159 void
160 IMAPProtocol::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 
174 status_t
175 IMAPProtocol::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 
188 status_t
189 IMAPProtocol::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 
211 status_t
212 IMAPProtocol::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 
219 void
220 IMAPProtocol::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 
234 status_t
235 IMAPProtocol::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 
258 void
259 IMAPProtocol::ReadyToRun()
260 {
261 	puts("IMAP: ready to run!");
262 	if (fSettings.IdleMode())
263 		SyncMessages();
264 }
265 
266 
267 IMAPFolder*
268 IMAPProtocol::_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 
319 IMAPFolder*
320 IMAPProtocol::_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 
330 status_t
331 IMAPProtocol::_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 
344 extern "C" BInboundMailProtocol*
345 instantiate_inbound_protocol(const BMailAccountSettings& settings)
346 {
347 	return new IMAPProtocol(settings);
348 }
349