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