xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/IMAPFolder.cpp (revision f82119e9101f24cde5fd93b900a918ccecbc022a)
1 /*
2  * Copyright 2012-2016, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "IMAPFolder.h"
8 
9 #include <set>
10 
11 #include <ByteOrder.h>
12 #include <Debug.h>
13 #include <Directory.h>
14 #include <File.h>
15 #include <fs_attr.h>
16 #include <Messenger.h>
17 #include <Node.h>
18 #include <Path.h>
19 
20 #include <NodeMessage.h>
21 
22 #include "IMAPProtocol.h"
23 
24 
25 static const char* kMailboxNameAttribute = "IMAP:mailbox";
26 static const char* kUIDValidityAttribute = "IMAP:uidvalidity";
27 static const char* kLastUIDAttribute = "IMAP:lastuid";
28 static const char* kStateAttribute = "IMAP:state";
29 static const char* kFlagsAttribute = "IMAP:flags";
30 static const char* kUIDAttribute = "MAIL:unique_id";
31 
32 
33 class TemporaryFile : public BFile {
34 public:
TemporaryFile(BFile & file)35 	TemporaryFile(BFile& file)
36 		:
37 		fFile(file),
38 		fDeleteFile(false)
39 	{
40 	}
41 
~TemporaryFile()42 	~TemporaryFile()
43 	{
44 		if (fDeleteFile) {
45 			fFile.Unset();
46 			BEntry(fPath.Path()).Remove();
47 		}
48 	}
49 
Init(const BPath & path,entry_ref & ref)50 	status_t Init(const BPath& path, entry_ref& ref)
51 	{
52 		int32 tries = 53;
53 		while (true) {
54 			BString name("temp-mail-");
55 			name << system_time();
56 
57 			fPath = path;
58 			fPath.Append(name.String());
59 
60 			status_t status = fFile.SetTo(fPath.Path(),
61 				B_CREATE_FILE | B_FAIL_IF_EXISTS | B_READ_WRITE);
62 			if (status == B_FILE_EXISTS && tries-- > 0)
63 				continue;
64 			if (status != B_OK)
65 				return status;
66 
67 			fDeleteFile = true;
68 			return get_ref_for_path(fPath.Path(), &ref);
69 		}
70 	}
71 
KeepFile()72 	void KeepFile()
73 	{
74 		fDeleteFile = false;
75 	}
76 
77 private:
78 			BFile&				fFile;
79 			BPath				fPath;
80 			bool				fDeleteFile;
81 };
82 
83 
84 // #pragma mark -
85 
86 
IMAPFolder(IMAPProtocol & protocol,const BString & mailboxName,const entry_ref & ref)87 IMAPFolder::IMAPFolder(IMAPProtocol& protocol, const BString& mailboxName,
88 	const entry_ref& ref)
89 	:
90 	BHandler(mailboxName.String()),
91 	fProtocol(protocol),
92 	fRef(ref),
93 	fMailboxName(mailboxName),
94 	fUIDValidity(UINT32_MAX),
95 	fLastUID(0),
96 	fListener(NULL),
97 	fFolderStateInitialized(false),
98 	fQuitFolderState(false)
99 {
100 	mutex_init(&fLock, "imap folder lock");
101 	mutex_init(&fFolderStateLock, "imap folder state lock");
102 }
103 
104 
~IMAPFolder()105 IMAPFolder::~IMAPFolder()
106 {
107 	MutexLocker locker(fLock);
108 	if (!fFolderStateInitialized && fListener != NULL) {
109 		fQuitFolderState = true;
110 		locker.Unlock();
111 		wait_for_thread(fReadFolderStateThread, NULL);
112 	}
113 }
114 
115 
116 status_t
Init()117 IMAPFolder::Init()
118 {
119 	// Initialize from folder attributes
120 	BNode node(&fRef);
121 	status_t status = node.InitCheck();
122 	if (status != B_OK)
123 		return status;
124 
125 	node_ref nodeRef;
126 	status = node.GetNodeRef(&nodeRef);
127 	if (status != B_OK)
128 		return status;
129 
130 	fNodeID = nodeRef.node;
131 
132 	BString originalMailboxName;
133 	if (node.ReadAttrString(kMailboxNameAttribute, &originalMailboxName) == B_OK
134 		&& originalMailboxName != fMailboxName) {
135 		// TODO: mailbox name has changed
136 	}
137 
138 	fUIDValidity = _ReadUInt32(node, kUIDValidityAttribute);
139 	fLastUID = _ReadUInt32(node, kLastUIDAttribute);
140 	printf("IMAP: %s, last UID %" B_PRIu32 "\n", fMailboxName.String(),
141 		fLastUID);
142 
143 	attr_info info;
144 	status = node.GetAttrInfo(kStateAttribute, &info);
145 	if (status == B_OK) {
146 		struct entry {
147 			uint32	uid;
148 			uint32	flags;
149 		} _PACKED;
150 		struct entry* entries = (struct entry*)malloc(info.size);
151 		if (entries == NULL)
152 			return B_NO_MEMORY;
153 
154 		ssize_t bytesRead = node.ReadAttr(kStateAttribute, B_RAW_TYPE, 0,
155 			entries, info.size);
156 		if (bytesRead != info.size)
157 			return B_BAD_DATA;
158 
159 		for (size_t i = 0; i < info.size / sizeof(entry); i++) {
160 			uint32 uid = B_BENDIAN_TO_HOST_INT32(entries[i].uid);
161 			uint32 flags = B_BENDIAN_TO_HOST_INT32(entries[i].flags);
162 
163 			fFlagsMap.insert(std::make_pair(uid, flags));
164 		}
165 	}
166 
167 	return B_OK;
168 }
169 
170 
171 void
SetListener(FolderListener * listener)172 IMAPFolder::SetListener(FolderListener* listener)
173 {
174 	ASSERT(fListener == NULL);
175 
176 	fListener = listener;
177 
178 	// Initialize current state from actual folder
179 	// TODO: this should be done in another thread
180 	_InitializeFolderState();
181 }
182 
183 
184 void
SetUIDValidity(uint32 uidValidity)185 IMAPFolder::SetUIDValidity(uint32 uidValidity)
186 {
187 	if (fUIDValidity == uidValidity)
188 		return;
189 
190 	// TODO: delete all mails that have the same UID validity value we had
191 	fUIDValidity = uidValidity;
192 
193 	BNode node(&fRef);
194 	_WriteUInt32(node, kUIDValidityAttribute, uidValidity);
195 }
196 
197 
198 status_t
GetMessageEntryRef(uint32 uid,entry_ref & ref)199 IMAPFolder::GetMessageEntryRef(uint32 uid, entry_ref& ref)
200 {
201 	MutexLocker locker(fLock);
202 	return _GetMessageEntryRef(uid, ref);
203 }
204 
205 
206 status_t
GetMessageUID(const entry_ref & ref,uint32 & uid) const207 IMAPFolder::GetMessageUID(const entry_ref& ref, uint32& uid) const
208 {
209 	BNode node(&ref);
210 	status_t status = node.InitCheck();
211 	if (status != B_OK)
212 		return status;
213 
214 	uid = _ReadUniqueID(node);
215 	if (uid == 0)
216 		return B_ENTRY_NOT_FOUND;
217 
218 	return B_OK;
219 }
220 
221 
222 uint32
MessageFlags(uint32 uid)223 IMAPFolder::MessageFlags(uint32 uid)
224 {
225 	MutexLocker locker(fLock);
226 	UIDToFlagsMap::const_iterator found = fFlagsMap.find(uid);
227 	if (found == fFlagsMap.end())
228 		return 0;
229 
230 	return found->second;
231 }
232 
233 
234 /*!	Synchronizes the message flags/state from the server with the local
235 	one.
236 */
237 void
SyncMessageFlags(uint32 uid,uint32 mailboxFlags)238 IMAPFolder::SyncMessageFlags(uint32 uid, uint32 mailboxFlags)
239 {
240 	if (uid > LastUID())
241 		return;
242 
243 	entry_ref ref;
244 	BNode node;
245 
246 	while (true) {
247 		status_t status = GetMessageEntryRef(uid, ref);
248 		if (status == B_ENTRY_NOT_FOUND) {
249 			// The message does not exist anymore locally, delete it on the
250 			// server
251 			// TODO: copy it to the trash directory first!
252 			if (fProtocol.Settings()->DeleteRemoteWhenLocal())
253 				fProtocol.UpdateMessageFlags(*this, uid, IMAP::kDeleted);
254 			return;
255 		}
256 		if (status == B_OK)
257 			status = node.SetTo(&ref);
258 		if (status == B_TIMED_OUT) {
259 			// We don't know the message state yet
260 			fPendingFlagsMap.insert(std::make_pair(uid, mailboxFlags));
261 		}
262 		if (status != B_OK)
263 			return;
264 
265 		break;
266 	}
267 	fSynchronizedUIDsSet.insert(uid);
268 
269 	uint32 previousFlags = MessageFlags(uid);
270 	uint32 currentFlags = previousFlags;
271 	if (_MailToIMAPFlags(node, currentFlags) != B_OK)
272 		return;
273 
274 	// Compare flags to previous/current flags, and update either the
275 	// message on the server, or the message locally (or even both)
276 
277 	uint32 nextFlags = mailboxFlags;
278 	_TestMessageFlags(previousFlags, mailboxFlags, currentFlags,
279 		IMAP::kSeen, nextFlags);
280 	_TestMessageFlags(previousFlags, mailboxFlags, currentFlags,
281 		IMAP::kAnswered, nextFlags);
282 
283 	if (nextFlags != previousFlags)
284 		_WriteFlags(node, nextFlags);
285 	if (currentFlags != nextFlags) {
286 		// Update mail message attributes
287 		BMessage attributes;
288 		_IMAPToMailFlags(nextFlags, attributes);
289 		node << attributes;
290 
291 		fFlagsMap[uid] = nextFlags;
292 	}
293 	if (mailboxFlags != nextFlags) {
294 		// Update server flags
295 		fProtocol.UpdateMessageFlags(*this, uid, nextFlags);
296 	}
297 }
298 
299 
300 void
MessageEntriesFetched()301 IMAPFolder::MessageEntriesFetched()
302 {
303 	_WaitForFolderState();
304 
305 	// Synchronize all pending flags first
306 	UIDToFlagsMap::const_iterator pendingIterator = fPendingFlagsMap.begin();
307 	for (; pendingIterator != fPendingFlagsMap.end(); pendingIterator++)
308 		SyncMessageFlags(pendingIterator->first, pendingIterator->second);
309 
310 	fPendingFlagsMap.clear();
311 
312 	// Delete all local messages that are no longer found on the server
313 
314 	MutexLocker locker(fLock);
315 	UIDSet deleteUIDs;
316 	UIDToRefMap::const_iterator iterator = fRefMap.begin();
317 	for (; iterator != fRefMap.end(); iterator++) {
318 		uint32 uid = iterator->first;
319 		if (fSynchronizedUIDsSet.find(uid) == fSynchronizedUIDsSet.end())
320 			deleteUIDs.insert(uid);
321 	}
322 
323 	fSynchronizedUIDsSet.clear();
324 	locker.Unlock();
325 
326 	UIDSet::const_iterator deleteIterator = deleteUIDs.begin();
327 	for (; deleteIterator != deleteUIDs.end(); deleteIterator++)
328 		_DeleteLocalMessage(*deleteIterator);
329 }
330 
331 
332 /*!	Stores the given \a stream into a temporary file using the provided
333 	BFile object. A new file will be created, and the \a ref object will
334 	point to it. The file will remain open when this method exits without
335 	an error.
336 
337 	\a length will reflect how many bytes are left to read in case there
338 	was an error.
339 */
340 status_t
StoreMessage(uint32 fetchFlags,BDataIO & stream,size_t & length,entry_ref & ref,BFile & file)341 IMAPFolder::StoreMessage(uint32 fetchFlags, BDataIO& stream,
342 	size_t& length, entry_ref& ref, BFile& file)
343 {
344 	BPath path;
345 	status_t status = path.SetTo(&fRef);
346 	if (status != B_OK)
347 		return status;
348 
349 	TemporaryFile temporaryFile(file);
350 	status = temporaryFile.Init(path, ref);
351 	if (status != B_OK)
352 		return status;
353 
354 	status = _WriteStream(file, stream, length);
355 	if (status == B_OK)
356 		temporaryFile.KeepFile();
357 
358 	return status;
359 }
360 
361 
362 /*!	Writes UID, and flags to the message, and notifies the protocol that a
363 	message has been fetched. This method also closes the \a file passed in.
364 */
365 void
MessageStored(entry_ref & ref,BFile & file,uint32 fetchFlags,uint32 uid,uint32 flags)366 IMAPFolder::MessageStored(entry_ref& ref, BFile& file, uint32 fetchFlags,
367 	uint32 uid, uint32 flags)
368 {
369 	_WriteUniqueIDValidity(file);
370 	_WriteUniqueID(file, uid);
371 	if ((fetchFlags & IMAP::kFetchFlags) != 0)
372 		_WriteFlags(file, flags);
373 
374 	BMessage attributes;
375 	_IMAPToMailFlags(flags, attributes);
376 
377 	fProtocol.MessageStored(*this, ref, file, fetchFlags, attributes);
378 	file.Unset();
379 
380 	fRefMap.insert(std::make_pair(uid, ref));
381 
382 	if (uid > fLastUID) {
383 		// Update last known UID
384 		fLastUID = uid;
385 
386 		BNode directory(&fRef);
387 		status_t status = _WriteUInt32(directory, kLastUIDAttribute, uid);
388 		if (status != B_OK) {
389 			fprintf(stderr, "IMAP: Could not write last UID for mailbox "
390 				"%s: %s\n", fMailboxName.String(), strerror(status));
391 		}
392 	}
393 }
394 
395 
396 /*!	Pushes the refs for the pending bodies to the pending bodies list.
397 	This allows to prevent retrieving bodies more than once.
398 */
399 void
RegisterPendingBodies(IMAP::MessageUIDList & uids,const BMessenger * replyTo)400 IMAPFolder::RegisterPendingBodies(IMAP::MessageUIDList& uids,
401 	const BMessenger* replyTo)
402 {
403 	MutexLocker locker(fLock);
404 
405 	MessengerList messengers;
406 	if (replyTo != NULL)
407 		messengers.push_back(*replyTo);
408 
409 	IMAP::MessageUIDList::const_iterator iterator = uids.begin();
410 	for (; iterator != uids.end(); iterator++) {
411 		if (replyTo != NULL) {
412 			fPendingBodies[*iterator].push_back(*replyTo);
413 		} else {
414 			// Note: GCC 13 or later warns about the unused result of the statement below.
415 			//       This code should be reviewed as part of #18478.
416 			#if __GNUC__ >= 13
417 			#  pragma GCC diagnostic push
418 			#  pragma GCC diagnostic warning "-Wunused-result"
419 			#endif
420 			fPendingBodies[*iterator].begin();
421 			#if __GNUC__ >= 13
422 			#  pragma GCC diagnostic pop
423 			#endif
424 		}
425 	}
426 }
427 
428 
429 /*!	Appends the given \a stream as body to the message file for the
430 	specified unique ID. The file will remain open when this method exits
431 	without an error.
432 
433 	\a length will reflect how many bytes are left to read in case there
434 	were an error.
435 */
436 status_t
StoreBody(uint32 uid,BDataIO & stream,size_t & length,entry_ref & ref,BFile & file)437 IMAPFolder::StoreBody(uint32 uid, BDataIO& stream, size_t& length,
438 	entry_ref& ref, BFile& file)
439 {
440 	status_t status = GetMessageEntryRef(uid, ref);
441 	if (status != B_OK)
442 		return status;
443 
444 	status = file.SetTo(&ref, B_OPEN_AT_END | B_WRITE_ONLY);
445 	if (status != B_OK)
446 		return status;
447 
448 	BPath path(&ref);
449 	printf("IMAP: write body to %s\n", path.Path());
450 
451 	return _WriteStream(file, stream, length);
452 }
453 
454 
455 /*!	Notifies the protocol that a body has been fetched.
456 	This method also closes the \a file passed in.
457 */
458 void
BodyStored(entry_ref & ref,BFile & file,uint32 uid)459 IMAPFolder::BodyStored(entry_ref& ref, BFile& file, uint32 uid)
460 {
461 	BMessage attributes;
462 	fProtocol.MessageStored(*this, ref, file, IMAP::kFetchBody, attributes);
463 	file.Unset();
464 
465 	_NotifyStoredBody(ref, uid, B_OK);
466 }
467 
468 
469 void
StoringBodyFailed(const entry_ref & ref,uint32 uid,status_t error)470 IMAPFolder::StoringBodyFailed(const entry_ref& ref, uint32 uid, status_t error)
471 {
472 	_NotifyStoredBody(ref, uid, error);
473 }
474 
475 
476 void
DeleteMessage(uint32 uid)477 IMAPFolder::DeleteMessage(uint32 uid)
478 {
479 	// TODO: move message to trash (server side)
480 
481 	_DeleteLocalMessage(uid);
482 }
483 
484 
485 void
MessageReceived(BMessage * message)486 IMAPFolder::MessageReceived(BMessage* message)
487 {
488 	switch (message->what) {
489 		default:
490 			BHandler::MessageReceived(message);
491 			break;
492 	}
493 }
494 
495 
496 void
_WaitForFolderState()497 IMAPFolder::_WaitForFolderState()
498 {
499 	while (true) {
500 		MutexLocker locker(fFolderStateLock);
501 		if (fFolderStateInitialized)
502 			return;
503 	}
504 }
505 
506 
507 void
_InitializeFolderState()508 IMAPFolder::_InitializeFolderState()
509 {
510 	mutex_lock(&fFolderStateLock);
511 
512 	fReadFolderStateThread = spawn_thread(&IMAPFolder::_ReadFolderState,
513 		"IMAP folder state", B_NORMAL_PRIORITY, this);
514 	if (fReadFolderStateThread >= 0)
515 		resume_thread(fReadFolderStateThread);
516 	else
517 		mutex_unlock(&fFolderStateLock);
518 }
519 
520 
521 void
_ReadFolderState()522 IMAPFolder::_ReadFolderState()
523 {
524 	BDirectory directory(&fRef);
525 	BEntry entry;
526 	while (directory.GetNextEntry(&entry) == B_OK) {
527 		entry_ref ref;
528 		BNode node;
529 		if (!entry.IsFile() || entry.GetRef(&ref) != B_OK
530 			|| node.SetTo(&entry) != B_OK)
531 			continue;
532 
533 		uint32 uidValidity = _ReadUniqueIDValidity(node);
534 		if (uidValidity != fUIDValidity) {
535 			// TODO: add file to mailbox
536 			continue;
537 		}
538 		uint32 uid = _ReadUniqueID(node);
539 		uint32 flags = _ReadFlags(node);
540 
541 		MutexLocker locker(fLock);
542 		if (fQuitFolderState)
543 			return;
544 
545 		fRefMap.insert(std::make_pair(uid, ref));
546 		fFlagsMap.insert(std::make_pair(uid, flags));
547 
548 //		// TODO: make sure a listener exists at this point!
549 //		std::set<uint32>::iterator found = lastUIDs.find(uid);
550 //		if (found != lastUIDs.end()) {
551 //			// The message is still around
552 //			lastUIDs.erase(found);
553 //
554 //			uint32 flagsFound = MessageFlags(uid);
555 //			if (flagsFound != flags) {
556 //				// Its flags have changed locally, and need to be updated
557 //				fListener->MessageFlagsChanged(_Token(uid), ref,
558 //					flagsFound, flags);
559 //			}
560 //		} else {
561 //			// This is a new message
562 //			// TODO: the token must be the originating token!
563 //			uid = fListener->MessageAdded(_Token(uid), ref);
564 //			_WriteUniqueID(node, uid);
565 //		}
566 //
567 	}
568 
569 	fFolderStateInitialized = true;
570 	mutex_unlock(&fFolderStateLock);
571 }
572 
573 
574 /*static*/ status_t
_ReadFolderState(void * self)575 IMAPFolder::_ReadFolderState(void* self)
576 {
577 	((IMAPFolder*)self)->_ReadFolderState();
578 	return B_OK;
579 }
580 
581 
582 const MessageToken
_Token(uint32 uid) const583 IMAPFolder::_Token(uint32 uid) const
584 {
585 	MessageToken token;
586 	token.mailboxName = fMailboxName;
587 	token.uidValidity = fUIDValidity;
588 	token.uid = uid;
589 
590 	return token;
591 }
592 
593 
594 void
_NotifyStoredBody(const entry_ref & ref,uint32 uid,status_t status)595 IMAPFolder::_NotifyStoredBody(const entry_ref& ref, uint32 uid, status_t status)
596 {
597 	MutexLocker locker(fLock);
598 	MessengerMap::iterator found = fPendingBodies.find(uid);
599 	if (found != fPendingBodies.end()) {
600 		MessengerList messengers = found->second;
601 		fPendingBodies.erase(found);
602 		locker.Unlock();
603 
604 		MessengerList::iterator iterator = messengers.begin();
605 		for (; iterator != messengers.end(); iterator++)
606 			BInboundMailProtocol::ReplyBodyFetched(*iterator, ref, status);
607 	}
608 }
609 
610 
611 status_t
_GetMessageEntryRef(uint32 uid,entry_ref & ref) const612 IMAPFolder::_GetMessageEntryRef(uint32 uid, entry_ref& ref) const
613 {
614 	UIDToRefMap::const_iterator found = fRefMap.find(uid);
615 	if (found == fRefMap.end())
616 		return !fFolderStateInitialized ? B_TIMED_OUT : B_ENTRY_NOT_FOUND;
617 
618 	ref = found->second;
619 	return B_OK;
620 }
621 
622 
623 status_t
_DeleteLocalMessage(uint32 uid)624 IMAPFolder::_DeleteLocalMessage(uint32 uid)
625 {
626 	entry_ref ref;
627 	status_t status = GetMessageEntryRef(uid, ref);
628 	if (status != B_OK)
629 		return status;
630 
631 	fRefMap.erase(uid);
632 	fFlagsMap.erase(uid);
633 
634 	BEntry entry(&ref);
635 	return entry.Remove();
636 }
637 
638 
639 void
_IMAPToMailFlags(uint32 flags,BMessage & attributes)640 IMAPFolder::_IMAPToMailFlags(uint32 flags, BMessage& attributes)
641 {
642 	// TODO: add some utility function for this in libmail.so
643 	if ((flags & IMAP::kAnswered) != 0)
644 		attributes.AddString(B_MAIL_ATTR_STATUS, "Answered");
645 	else if ((flags & IMAP::kFlagged) != 0)
646 		attributes.AddString(B_MAIL_ATTR_STATUS, "Starred");
647 	else if ((flags & IMAP::kSeen) != 0)
648 		attributes.AddString(B_MAIL_ATTR_STATUS, "Read");
649 }
650 
651 
652 status_t
_MailToIMAPFlags(BNode & node,uint32 & flags)653 IMAPFolder::_MailToIMAPFlags(BNode& node, uint32& flags)
654 {
655 	BString mailStatus;
656 	status_t status = node.ReadAttrString(B_MAIL_ATTR_STATUS, &mailStatus);
657 	if (status != B_OK)
658 		return status;
659 
660 	flags &= ~(IMAP::kAnswered | IMAP::kSeen);
661 
662 	// TODO: add some utility function for this in libmail.so
663 	if (mailStatus == "Answered")
664 		flags |= IMAP::kAnswered | IMAP::kSeen;
665 	else if (mailStatus == "Read")
666 		flags |= IMAP::kSeen;
667 	else if (mailStatus == "Starred")
668 		flags |= IMAP::kFlagged | IMAP::kSeen;
669 
670 	return B_OK;
671 }
672 
673 
674 void
_TestMessageFlags(uint32 previousFlags,uint32 mailboxFlags,uint32 currentFlags,uint32 testFlag,uint32 & nextFlags)675 IMAPFolder::_TestMessageFlags(uint32 previousFlags, uint32 mailboxFlags,
676 	uint32 currentFlags, uint32 testFlag, uint32& nextFlags)
677 {
678 	if ((previousFlags & testFlag) != (mailboxFlags & testFlag)) {
679 		if ((previousFlags & testFlag) == (currentFlags & testFlag)) {
680 			// The flags on the mailbox changed, update local flags
681 			nextFlags &= ~testFlag;
682 			nextFlags |= mailboxFlags & testFlag;
683 		} else {
684 			// Both flags changed. Since we don't have the means to do
685 			// conflict resolution, we use a best effort mechanism
686 			nextFlags |= testFlag;
687 		}
688 		return;
689 	}
690 
691 	// Previous message flags, and mailbox flags are identical, see
692 	// if the user changed the flag locally
693 	if ((currentFlags & testFlag) != (previousFlags & testFlag)) {
694 		// Flag changed, update mailbox
695 		nextFlags &= ~testFlag;
696 		nextFlags |= currentFlags & testFlag;
697 	}
698 }
699 
700 
701 uint32
_ReadUniqueID(BNode & node) const702 IMAPFolder::_ReadUniqueID(BNode& node) const
703 {
704 	// For compatibility we must assume that the UID is stored as a string
705 	BString string;
706 	if (node.ReadAttrString(kUIDAttribute, &string) != B_OK)
707 		return 0;
708 
709 	return strtoul(string.String(), NULL, 0);
710 }
711 
712 
713 status_t
_WriteUniqueID(BNode & node,uint32 uid) const714 IMAPFolder::_WriteUniqueID(BNode& node, uint32 uid) const
715 {
716 	// For compatibility we must assume that the UID is stored as a string
717 	BString string;
718 	string << uid;
719 
720 	return node.WriteAttrString(kUIDAttribute, &string);
721 }
722 
723 
724 uint32
_ReadUniqueIDValidity(BNode & node) const725 IMAPFolder::_ReadUniqueIDValidity(BNode& node) const
726 {
727 
728 	return _ReadUInt32(node, kUIDValidityAttribute);
729 }
730 
731 
732 status_t
_WriteUniqueIDValidity(BNode & node) const733 IMAPFolder::_WriteUniqueIDValidity(BNode& node) const
734 {
735 	return _WriteUInt32(node, kUIDValidityAttribute, fUIDValidity);
736 }
737 
738 
739 uint32
_ReadFlags(BNode & node) const740 IMAPFolder::_ReadFlags(BNode& node) const
741 {
742 	return _ReadUInt32(node, kFlagsAttribute);
743 }
744 
745 
746 status_t
_WriteFlags(BNode & node,uint32 flags) const747 IMAPFolder::_WriteFlags(BNode& node, uint32 flags) const
748 {
749 	return _WriteUInt32(node, kFlagsAttribute, flags);
750 }
751 
752 
753 uint32
_ReadUInt32(BNode & node,const char * attribute) const754 IMAPFolder::_ReadUInt32(BNode& node, const char* attribute) const
755 {
756 	uint32 value;
757 	ssize_t bytesRead = node.ReadAttr(attribute, B_UINT32_TYPE, 0,
758 		&value, sizeof(uint32));
759 	if (bytesRead == (ssize_t)sizeof(uint32))
760 		return value;
761 
762 	return 0;
763 }
764 
765 
766 status_t
_WriteUInt32(BNode & node,const char * attribute,uint32 value) const767 IMAPFolder::_WriteUInt32(BNode& node, const char* attribute, uint32 value) const
768 {
769 	ssize_t bytesWritten = node.WriteAttr(attribute, B_UINT32_TYPE, 0,
770 		&value, sizeof(uint32));
771 	if (bytesWritten == (ssize_t)sizeof(uint32))
772 		return B_OK;
773 
774 	return bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
775 }
776 
777 
778 status_t
_WriteStream(BFile & file,BDataIO & stream,size_t & length) const779 IMAPFolder::_WriteStream(BFile& file, BDataIO& stream, size_t& length) const
780 {
781 	char buffer[65535];
782 	while (length > 0) {
783 		ssize_t bytesRead = stream.Read(buffer,
784 			std::min(sizeof(buffer), length));
785 		if (bytesRead < 0)
786 			return bytesRead;
787 		if (bytesRead <= 0)
788 			break;
789 
790 		length -= bytesRead;
791 
792 		ssize_t bytesWritten = file.Write(buffer, bytesRead);
793 		if (bytesWritten < 0)
794 			return bytesWritten;
795 		if (bytesWritten != bytesRead)
796 			return B_IO_ERROR;
797 	}
798 
799 	return B_OK;
800 }
801