xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/IMAPProtocol.cpp (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
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 		fFolders.insert(std::make_pair(mailbox,
123 			_CreateFolder(mailbox, separator)));
124 	}
125 
126 	// Distribute the mailboxes evenly to the workers
127 	FolderMap::iterator iterator = fFolders.begin();
128 	int32 index = 0;
129 	for (; iterator != fFolders.end(); iterator++) {
130 		IMAPConnectionWorker* worker = fWorkers.ItemAt(index);
131 		IMAPFolder* folder = iterator->second;
132 		worker->AddMailbox(folder);
133 		fWorkerMap.insert(std::make_pair(folder, worker));
134 
135 		index = (index + 1) % fWorkers.CountItems();
136 	}
137 
138 	// Start waiting workers
139 	return _EnqueueCheckMailboxes();
140 }
141 
142 
143 void
144 IMAPProtocol::WorkerQuit(IMAPConnectionWorker* worker)
145 {
146 	MutexLocker locker(fWorkerLock);
147 	fWorkers.RemoveItem(worker);
148 
149 	WorkerMap::iterator iterator = fWorkerMap.begin();
150 	while (iterator != fWorkerMap.end()) {
151 		WorkerMap::iterator removed = iterator++;
152 		if (removed->second == worker)
153 			fWorkerMap.erase(removed);
154 	}
155 }
156 
157 
158 void
159 IMAPProtocol::MessageStored(IMAPFolder& folder, entry_ref& ref,
160 	BFile& stream, uint32 fetchFlags, BMessage& attributes)
161 {
162 	if ((fetchFlags & (IMAP::kFetchHeader | IMAP::kFetchBody))
163 			== (IMAP::kFetchHeader | IMAP::kFetchBody)) {
164 		ProcessMessageFetched(ref, stream, attributes);
165 	} else if ((fetchFlags & IMAP::kFetchHeader) != 0) {
166 		ProcessHeaderFetched(ref, stream, attributes);
167 	} else if ((fetchFlags & IMAP::kFetchBody) != 0) {
168 		NotifyBodyFetched(ref, stream, attributes);
169 	}
170 }
171 
172 
173 status_t
174 IMAPProtocol::UpdateMessageFlags(IMAPFolder& folder, uint32 uid, uint32 flags)
175 {
176 	MutexLocker locker(fWorkerLock);
177 
178 	WorkerMap::const_iterator found = fWorkerMap.find(&folder);
179 	if (found == fWorkerMap.end())
180 		return B_ERROR;
181 
182 	IMAPConnectionWorker* worker = found->second;
183 	return worker->EnqueueUpdateFlags(folder, uid, flags);
184 }
185 
186 
187 status_t
188 IMAPProtocol::SyncMessages()
189 {
190 	puts("IMAP: sync");
191 
192 	MutexLocker locker(fWorkerLock);
193 	if (fWorkers.IsEmpty()) {
194 		// Create main (and possibly initial) connection worker
195 		IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this,
196 			fSettings, true);
197 		if (!fWorkers.AddItem(worker)) {
198 			delete worker;
199 			return B_NO_MEMORY;
200 		}
201 
202 		worker->EnqueueCheckSubscribedFolders();
203 		return worker->Run();
204 	}
205 	fWorkers.ItemAt(0)->EnqueueCheckSubscribedFolders();
206 	return B_OK;
207 }
208 
209 
210 status_t
211 IMAPProtocol::MarkMessageAsRead(const entry_ref& ref, read_flags flags)
212 {
213 	printf("IMAP: mark as read %s: %d\n", ref.name, flags);
214 	return B_ERROR;
215 }
216 
217 
218 void
219 IMAPProtocol::MessageReceived(BMessage* message)
220 {
221 	switch (message->what) {
222 		case B_READY_TO_RUN:
223 			ReadyToRun();
224 			break;
225 
226 		default:
227 			BInboundMailProtocol::MessageReceived(message);
228 			break;
229 	}
230 }
231 
232 
233 status_t
234 IMAPProtocol::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo)
235 {
236 	printf("IMAP: fetch body %s\n", ref.name);
237 	MutexLocker locker(fWorkerLock);
238 
239 	IMAPFolder* folder = _FolderFor(ref.directory);
240 	if (folder == NULL)
241 		return B_ENTRY_NOT_FOUND;
242 
243 	uint32 uid;
244 	status_t status = folder->GetMessageUID(ref, uid);
245 	if (status != B_OK)
246 		return status;
247 
248 	WorkerMap::const_iterator found = fWorkerMap.find(folder);
249 	if (found == fWorkerMap.end())
250 		return B_ERROR;
251 
252 	IMAPConnectionWorker* worker = found->second;
253 	return worker->EnqueueFetchBody(*folder, uid, replyTo);
254 }
255 
256 
257 void
258 IMAPProtocol::ReadyToRun()
259 {
260 	puts("IMAP: ready to run!");
261 	if (fSettings.IdleMode())
262 		SyncMessages();
263 }
264 
265 
266 IMAPFolder*
267 IMAPProtocol::_CreateFolder(const BString& mailbox, const BString& separator)
268 {
269 	BString name = MailboxToFolderName(mailbox, separator);
270 
271 	BPath path(fSettings.Destination());
272 	if (path.Append(name.String()) != B_OK) {
273 		fprintf(stderr, "Could not append path: %s\n", name.String());
274 		return NULL;
275 	}
276 
277 	status_t status = create_directory(path.Path(), 0755);
278 	if (status != B_OK) {
279 		fprintf(stderr, "Could not create path %s: %s\n", path.Path(),
280 			strerror(status));
281 		return NULL;
282 	}
283 
284 	CopyMailFolderAttributes(path.Path());
285 
286 	entry_ref ref;
287 	status = get_ref_for_path(path.Path(), &ref);
288 	if (status != B_OK) {
289 		fprintf(stderr, "Could not get ref for %s: %s\n", path.Path(),
290 			strerror(status));
291 		return NULL;
292 	}
293 
294 	IMAPFolder* folder = new IMAPFolder(*this, mailbox, ref);
295 	status = folder->Init();
296 	if (status != B_OK) {
297 		fprintf(stderr, "Initializing folder %s failed: %s\n", path.Path(),
298 			strerror(status));
299 		delete folder;
300 		return NULL;
301 	}
302 
303 	fFolderNodeMap.insert(std::make_pair(folder->NodeID(), folder));
304 	return folder;
305 }
306 
307 
308 IMAPFolder*
309 IMAPProtocol::_FolderFor(ino_t directory)
310 {
311 	FolderNodeMap::const_iterator found = fFolderNodeMap.find(directory);
312 	if (found != fFolderNodeMap.end())
313 		return found->second;
314 
315 	return NULL;
316 }
317 
318 
319 status_t
320 IMAPProtocol::_EnqueueCheckMailboxes()
321 {
322 	for (int32 i = 0; i < fWorkers.CountItems(); i++) {
323 		fWorkers.ItemAt(i)->EnqueueCheckMailboxes();
324 	}
325 
326 	return B_OK;
327 }
328 
329 
330 // #pragma mark -
331 
332 
333 extern "C" BInboundMailProtocol*
334 instantiate_inbound_protocol(const BMailAccountSettings& settings)
335 {
336 	return new IMAPProtocol(settings);
337 }
338