1/*
2 * Copyright 2010-2011, Haiku Inc. All Rights Reserved.
3 * Copyright 2010 Clemens Zeidler. All rights reserved.
4 *
5 * Distributed under the terms of the MIT License.
6 */
7
8
9#include "IMAPStorage.h"
10
11#include <stdlib.h>
12
13#include <Directory.h>
14#include <Entry.h>
15#include <NodeInfo.h>
16#include <OS.h>
17
18#include <mail_util.h>
19
20#include "IMAPMailbox.h"
21
22
23#define DEBUG_IMAP_STORAGE
24#ifdef DEBUG_IMAP_STORAGE
25#	include <stdio.h>
26#	define TRACE(x...) printf(x)
27#else
28#	define TRACE(x...) /* nothing */
29#endif
30
31
32status_t
33IMAPMailboxSync::Sync(IMAPStorage& storage, IMAPMailbox& mailbox)
34{
35	const MailEntryMap& files = storage.GetFiles();
36	const MinMessageList& messages = mailbox.GetMessageList();
37
38	for (MailEntryMap::const_iterator it = files.begin(); it != files.end();
39		it++) {
40		const StorageMailEntry& mailEntry = (*it).second;
41		bool found = false;
42		for (unsigned int a = 0; a < messages.size(); a++) {
43			const MinMessage& minMessage = messages[a];
44			if (mailEntry.uid == minMessage.uid) {
45				found = true;
46				if (mailEntry.flags != minMessage.flags)
47					storage.SetFlags(mailEntry.uid, minMessage.flags);
48				break;
49			}
50		}
51		if (!found)
52			storage.DeleteMessage(mailEntry.uid);
53	}
54
55	for (unsigned int i = 0; i < messages.size(); i++) {
56		const MinMessage& minMessage = messages[i];
57		bool found = false;
58		for (MailEntryMap::const_iterator it = files.begin(); it != files.end();
59			it++) {
60			const StorageMailEntry& mailEntry = (*it).second;
61
62			if (mailEntry.uid == minMessage.uid) {
63				found = true;
64				break;
65			}
66		}
67		if (!found)
68			fToFetchList.push_back(i + 1);
69	}
70
71	mailbox.Listener().NewMessagesToFetch(fToFetchList.size());
72
73	// fetch headers in big bunches if possible
74	int32 fetchCount = 0;
75	int32 start = -1;
76	int32 end = -1;
77	int32 lastId = -1;
78	for (unsigned int i = 0; i < fToFetchList.size(); i++) {
79		if (mailbox.StopNow())
80			return B_ERROR;
81
82		int32 current = fToFetchList[i];
83		fetchCount++;
84
85		if (start < 0) {
86			start = current;
87			lastId = current;
88			continue;
89		}
90		if (current - 1 == lastId) {
91			end = current;
92			lastId = current;
93			// limit to a fix number to make it interruptable see StopNow()
94			if (fetchCount < 250)
95				continue;
96		}
97
98		fetchCount = 0;
99		mailbox.FetchMessages(start, end);
100		start = -1;
101		end = -1;
102	}
103	if (start > 0)
104		mailbox.FetchMessages(start, end);
105
106	return B_OK;
107}
108
109
110// #pragma mark -
111
112
113IMAPStorage::IMAPStorage()
114{
115	fLoadDatabaseLock = create_sem(1, "sync lock");
116}
117
118
119IMAPStorage::~IMAPStorage()
120{
121	delete_sem(fLoadDatabaseLock);
122}
123
124
125void
126IMAPStorage::SetTo(const char* dir)
127{
128	fMailboxPath.SetTo(dir);
129}
130
131
132status_t
133IMAPStorage::StartReadDatabase()
134{
135	status_t status = create_directory(fMailboxPath.Path(), 0755);
136	if (status != B_OK)
137		return status;
138
139	thread_id id = spawn_thread(_ReadFilesThreadFunction, "read mailbox",
140		B_LOW_PRIORITY, this);
141	if (id < 0)
142		return id;
143
144	// will be unlocked from thread
145	acquire_sem(fLoadDatabaseLock);
146	status = resume_thread(id);
147	if (status != B_OK)
148		release_sem(fLoadDatabaseLock);
149	return status;
150}
151
152
153status_t
154IMAPStorage::WaitForDatabaseRead()
155{
156	// just wait for thread
157	if (acquire_sem(fLoadDatabaseLock) != B_OK)
158		return B_ERROR;
159	release_sem(fLoadDatabaseLock);
160	return B_OK;
161}
162
163
164status_t
165IMAPStorage::AddNewMessage(int32 uid, int32 flags, BPositionIO** file)
166{
167	if (file != NULL)
168		*file = NULL;
169	// TODO: make sure there is not a valid mail with this name
170	BString fileName = "Downloading file... uid: ";
171	fileName << uid;
172
173	BPath filePath = fMailboxPath;
174	filePath.Append(fileName);
175	TRACE("AddNewMessage %s\n", filePath.Path());
176	BFile* newFile = new BFile(filePath.Path(), B_READ_WRITE | B_CREATE_FILE
177		| B_ERASE_FILE);
178	if (newFile == NULL)
179		return B_NO_MEMORY;
180
181	StorageMailEntry storageEntry;
182	storageEntry.uid = uid;
183	storageEntry.flags = flags;
184	storageEntry.fileName = fileName;
185	newFile->GetNodeRef(&storageEntry.nodeRef);
186
187	if (_WriteUniqueID(*newFile, uid) != B_OK) {
188		delete newFile;
189		return B_ERROR;
190	}
191
192	status_t status = _WriteFlags(flags, *newFile);
193	if (status != B_OK) {
194		delete newFile;
195		return status;
196	}
197
198	if (file)
199		*file = newFile;
200	else
201		delete newFile;
202	fMailEntryMap[uid] = storageEntry;
203	return B_OK;
204}
205
206
207status_t
208IMAPStorage::OpenMessage(int32 uid, BPositionIO** file)
209{
210	*file = NULL;
211
212	MailEntryMap::const_iterator it = fMailEntryMap.find(uid);
213	if (it == fMailEntryMap.end())
214		return B_BAD_VALUE;
215
216	const StorageMailEntry& storageEntry = (*it).second;
217	BPath filePath = fMailboxPath;
218	filePath.Append(storageEntry.fileName);
219	BFile* ourFile = new BFile(filePath.Path(), B_READ_WRITE);
220	if (!ourFile)
221		return B_NO_MEMORY;
222	status_t status = ourFile->InitCheck();
223	if (status != B_OK) {
224		delete *file;
225		return status;
226	}
227	*file = ourFile;
228	return B_OK;
229}
230
231
232status_t
233IMAPStorage::DeleteMessage(int32 uid)
234{
235	MailEntryMap::iterator it = fMailEntryMap.find(uid);
236	if (it == fMailEntryMap.end())
237		return B_ENTRY_NOT_FOUND;
238	const StorageMailEntry& storageEntry = (*it).second;
239
240	BPath filePath = fMailboxPath;
241	filePath.Append(storageEntry.fileName);
242	BEntry entry(filePath.Path());
243	TRACE("IMAPStorage::DeleteMessage %s, %ld\n", filePath.Path(), uid);
244
245	status_t status = entry.Remove();
246	if (status != B_OK)
247		return status;
248	fMailEntryMap.erase(it);
249	return B_OK;
250}
251
252
253status_t
254IMAPStorage::SetFlags(int32 uid, int32 flags)
255{
256	MailEntryMap::iterator it = fMailEntryMap.find(uid);
257	if (it == fMailEntryMap.end())
258		return B_ENTRY_NOT_FOUND;
259	StorageMailEntry& entry = (*it).second;
260
261	BPath filePath = fMailboxPath;
262	filePath.Append(entry.fileName);
263	BNode node(filePath.Path());
264
265	status_t status = _WriteFlags(flags, node);
266	if (status != B_OK)
267		return status;
268
269	entry.flags = flags;
270	return B_OK;
271}
272
273
274int32
275IMAPStorage::GetFlags(int32 uid)
276{
277	MailEntryMap::const_iterator it = fMailEntryMap.find(uid);
278	if (it == fMailEntryMap.end())
279		return -1;
280	const StorageMailEntry& entry = (*it).second;
281	return entry.flags;
282}
283
284
285status_t
286IMAPStorage::SetFileName(int32 uid, const BString& name)
287{
288	MailEntryMap::iterator it = fMailEntryMap.find(uid);
289	if (it == fMailEntryMap.end())
290		return -1;
291	StorageMailEntry& storageEntry = (*it).second;
292	BPath filePath = fMailboxPath;
293	filePath.Append(storageEntry.fileName);
294	BEntry entry(filePath.Path());
295	status_t status = entry.Rename(name);
296	if (status != B_OK)
297		return status;
298	storageEntry.fileName = name;
299	return B_OK;
300}
301
302
303status_t
304IMAPStorage::FileRenamed(const entry_ref& from, const entry_ref& to)
305{
306	int32 uid = RefToUID(from);
307	if (uid < 0)
308		return B_BAD_VALUE;
309	fMailEntryMap[uid].fileName = to.name;
310	return B_OK;
311}
312
313
314bool
315IMAPStorage::HasFile(int32 uid)
316{
317	MailEntryMap::const_iterator it = fMailEntryMap.find(uid);
318	if (it == fMailEntryMap.end())
319		return false;
320	return true;
321}
322
323
324status_t
325IMAPStorage::SetCompleteMessageSize(int32 uid, int32 size)
326{
327	MailEntryMap::iterator it = fMailEntryMap.find(uid);
328	if (it == fMailEntryMap.end())
329		return false;
330	StorageMailEntry& storageEntry = (*it).second;
331	BPath filePath = fMailboxPath;
332	filePath.Append(storageEntry.fileName);
333	BNode node(filePath.Path());
334
335	if (node.WriteAttr("MAIL:size", B_INT32_TYPE, 0, &size, sizeof(int32)) < 0)
336		return B_ERROR;
337	return B_OK;
338}
339
340
341StorageMailEntry*
342IMAPStorage::GetEntryForRef(const node_ref& ref)
343{
344	for (MailEntryMap::iterator it = fMailEntryMap.begin();
345		it != fMailEntryMap.end(); it++) {
346		StorageMailEntry& mailEntry = (*it).second;
347		if (mailEntry.nodeRef == ref)
348			return &mailEntry;
349	}
350	return NULL;
351}
352
353
354bool
355IMAPStorage::BodyFetched(int32 uid)
356{
357	MailEntryMap::iterator it = fMailEntryMap.find(uid);
358	if (it == fMailEntryMap.end())
359		return false;
360	StorageMailEntry& storageEntry = (*it).second;
361	BPath filePath = fMailboxPath;
362	filePath.Append(storageEntry.fileName);
363	BNode node(filePath.Path());
364
365	char buffer[B_MIME_TYPE_LENGTH];
366	BNodeInfo info(&node);
367	info.GetType(buffer);
368	if (strcmp(buffer, B_MAIL_TYPE) == 0)
369		return true;
370	return false;
371}
372
373
374int32
375IMAPStorage::RefToUID(const entry_ref& ref)
376{
377	for (MailEntryMap::iterator it = fMailEntryMap.begin();
378		it != fMailEntryMap.end(); it++) {
379		StorageMailEntry& mailEntry = (*it).second;
380		if (mailEntry.fileName == ref.name)
381			return mailEntry.uid;
382	}
383
384	// not found try to fix internal name
385	BDirectory dir(fMailboxPath.Path());
386	if (!dir.Contains(ref.name))
387		return -1;
388
389	BNode node(&ref);
390	int32 uid;
391	if (ReadUniqueID(node, uid) != B_OK)
392		return -1;
393
394	MailEntryMap::iterator it = fMailEntryMap.find(uid);
395	if (it == fMailEntryMap.end())
396		return -1;
397	it->second.fileName = ref.name;
398
399	return uid;
400}
401
402
403bool
404IMAPStorage::UIDToRef(int32 uid, entry_ref& ref)
405{
406	MailEntryMap::iterator it = fMailEntryMap.find(uid);
407	if (it == fMailEntryMap.end())
408		return false;
409
410	StorageMailEntry& mailEntry = (*it).second;
411	BPath filePath = fMailboxPath;
412	filePath.Append(mailEntry.fileName);
413
414	BEntry entry(filePath.Path());
415	if (entry.GetRef(&ref) == B_OK)
416		return true;
417	return false;
418}
419
420
421status_t
422IMAPStorage::_ReadFilesThreadFunction(void* data)
423{
424	IMAPStorage* storage = (IMAPStorage*)data;
425	return storage->_ReadFiles();
426}
427
428
429status_t
430IMAPStorage::_ReadFiles()
431{
432	fMailEntryMap.clear();
433
434	BDirectory directory(fMailboxPath.Path());
435
436	entry_ref ref;
437	while (directory.GetNextRef(&ref) != B_ENTRY_NOT_FOUND) {
438		BNode node(&ref);
439		if (node.InitCheck() != B_OK || !node.IsFile())
440			continue;
441
442		char buffer[B_MIME_TYPE_LENGTH];
443		BNodeInfo info(&node);
444		info.GetType(buffer);
445
446		// maybe interrupted downloads ignore them
447		if (strcmp(buffer, "text/x-partial-email") != 0
448			&& strcmp(buffer, B_MAIL_TYPE) != 0)
449			continue;
450
451		StorageMailEntry entry;
452		entry.fileName = ref.name;
453		if (ReadUniqueID(node, entry.uid) != B_OK) {
454			TRACE("IMAPStorage::_ReadFilesThread() failed to read uid %s\n",
455				ref.name);
456			continue;
457		}
458
459		if (node.ReadAttr("MAIL:server_flags", B_INT32_TYPE, 0, &entry.flags,
460			sizeof(int32)) != sizeof(int32))
461			continue;
462
463		node.GetNodeRef(&entry.nodeRef);
464
465		fMailEntryMap[entry.uid] = entry;
466	}
467
468	release_sem(fLoadDatabaseLock);
469	return B_OK;
470}
471
472
473status_t
474IMAPStorage::_WriteFlags(int32 flags, BNode& node)
475{
476	if ((flags & kSeen) != 0)
477		write_read_attr(node, B_READ);
478	else
479		write_read_attr(node, B_UNREAD);
480
481	ssize_t writen = node.WriteAttr("MAIL:server_flags", B_INT32_TYPE, 0,
482		&flags, sizeof(int32));
483	if (writen != sizeof(int32))
484		return writen;
485
486	return B_OK;
487}
488
489
490status_t
491IMAPStorage::ReadUniqueID(BNode& node, int32& uid)
492{
493	const uint32 kMaxUniqueLength = 32;
494	char uidString[kMaxUniqueLength];
495	memset(uidString, 0, kMaxUniqueLength);
496	if (node.ReadAttr("MAIL:unique_id", B_STRING_TYPE, 0, uidString,
497		kMaxUniqueLength) < 0)
498		return B_ERROR;
499	uid = atoi(uidString);
500	return B_OK;
501}
502
503
504status_t
505IMAPStorage::_WriteUniqueID(BNode& node, int32 uid)
506{
507	BString uidString;
508	uidString << uid;
509	ssize_t written = node.WriteAttr("MAIL:unique_id", B_STRING_TYPE, 0,
510		uidString.String(), uidString.Length());
511	if (written < 0)
512		return written;
513	return B_OK;
514}
515