IMAP: work in progress of downloading the mail body.

* Most things are in place now, we just try to download the body to the
  wrong file, as the final location is currently unknown.
* Added local only kPartialMessage flag for mails, but it's not being
  used yet.
This commit is contained in:
Axel Dörfler
2015-01-06 15:26:28 +01:00
parent dcb85ffa51
commit 1052525dc5
8 changed files with 282 additions and 50 deletions
@@ -59,6 +59,11 @@ public:
return fWorker._MailboxFor(folder);
}
int32 BodyFetchLimit()
{
return fWorker.fSettings.BodyFetchLimit();
}
status_t EnqueueCommand(WorkerCommand* command)
{
return fWorker._EnqueueCommand(command);
@@ -125,15 +130,78 @@ public:
};
class FetchBodiesCommand : public SyncCommand, public IMAP::FetchListener {
public:
FetchBodiesCommand(IMAPFolder& folder, IMAPMailbox& mailbox,
std::vector<uint32>& entries)
:
fFolder(folder),
fMailbox(mailbox),
fEntries(entries)
{
}
virtual status_t Process(IMAPConnectionWorker& worker)
{
IMAP::Protocol& protocol = WorkerPrivate(worker).Protocol();
if (fEntries.empty())
return B_OK;
fUID = *fEntries.begin();
fEntries.erase(fEntries.begin());
// TODO: check nextUID if we get one
status_t status = WorkerPrivate(worker).SelectMailbox(fFolder);
if (status != B_OK)
return status;
printf("IMAP: fetch body for %lu\n", fUID);
// TODO: combine smaller messages together for faster retrieval
IMAP::FetchCommand fetch(fUID, fUID, IMAP::kFetchBody);
fetch.SetListener(this);
return protocol.ProcessCommand(fetch);
}
virtual bool IsDone() const
{
return fEntries.empty();
}
virtual bool FetchData(uint32 fetchFlags, BDataIO& stream, size_t& length)
{
fFetchStatus = fFolder.StoreBody(fUID, stream, length, fRef, fFile);
return true;
}
virtual void FetchedData(uint32 fetchFlags, uint32 uid, uint32 flags)
{
if (fFetchStatus == B_OK)
fFolder.BodyStored(fRef, fFile, uid);
}
private:
IMAPFolder& fFolder;
IMAPMailbox& fMailbox;
std::vector<uint32> fEntries;
uint32 fUID;
entry_ref fRef;
BFile fFile;
status_t fFetchStatus;
};
class FetchHeadersCommand : public SyncCommand, public IMAP::FetchListener {
public:
FetchHeadersCommand(IMAPFolder& folder, IMAPMailbox& mailbox,
uint32 from, uint32 to)
uint32 from, uint32 to, int32 bodyFetchLimit)
:
fFolder(folder),
fMailbox(mailbox),
fFrom(from),
fTo(to)
fTo(to),
fBodyFetchLimit(bodyFetchLimit)
{
}
@@ -152,8 +220,6 @@ public:
if (to - fFrom >= kMaxFetchEntries)
to = fFrom + kMaxFetchEntries - 1;
// TODO: trigger download of mails for all messages below the
// body fetch limit
printf("IMAP: fetch headers from %lu to %lu\n", fFrom, to);
IMAP::FetchCommand fetch(fFrom, to,
IMAP::kFetchHeader | IMAP::kFetchFlags);
@@ -164,6 +230,13 @@ public:
return status;
fFrom = to + 1;
if (IsDone() && !fFetchBodies.empty()) {
// Enqueue command to fetch the message bodies
WorkerPrivate(worker).EnqueueCommand(new FetchBodiesCommand(fFolder,
fMailbox, fFetchBodies));
}
return B_OK;
}
@@ -174,15 +247,20 @@ public:
virtual bool FetchData(uint32 fetchFlags, BDataIO& stream, size_t& length)
{
fFetchStatus = fFolder.StoreMessage(fFile, fetchFlags, stream,
length, fRef);
fFetchStatus = fFolder.StoreMessage(fetchFlags, stream, length,
fRef, fFile);
return true;
}
virtual void FetchedData(uint32 fetchFlags, uint32 uid, uint32 flags)
{
if (fFetchStatus == B_OK)
if (fFetchStatus == B_OK) {
fFolder.MessageStored(fRef, fFile, fetchFlags, uid, flags);
uint32 size = fMailbox.MessageSize(uid);
if (fBodyFetchLimit < 0 || size < fBodyFetchLimit)
fFetchBodies.push_back(uid);
}
}
private:
@@ -190,6 +268,8 @@ private:
IMAPMailbox& fMailbox;
uint32 fFrom;
uint32 fTo;
uint32 fBodyFetchLimit;
std::vector<uint32> fFetchBodies;
entry_ref fRef;
BFile fFile;
status_t fFetchStatus;
@@ -269,9 +349,13 @@ public:
return status;
// Determine how much we need to download
// TODO: also retrieve the header size, and only take the body
// size into account if it's below the limit
for (size_t i = 0; i < entries.size(); i++) {
printf("%10lu %8lu bytes, flags: %#lx\n", entries[i].uid,
entries[i].size, entries[i].flags);
fMailbox->AddMessageEntry(entries[i].uid, entries[i].flags,
entries[i].size);
fTotalBytes += entries[i].size;
}
fTotalEntries += entries.size();
@@ -283,7 +367,8 @@ public:
if (fMailboxEntries > 0) {
// Add pending command to fetch the message headers
WorkerCommand* command = new FetchHeadersCommand(*fFolder,
*fMailbox, fFirstUID, fNextUID);
*fMailbox, fFirstUID, fNextUID,
WorkerPrivate(worker).BodyFetchLimit());
if (!fFetchCommands.AddItem(command))
delete command;
}
@@ -132,7 +132,7 @@ IMAPFolder::IMAPFolder(IMAPProtocol& protocol, const BString& mailboxName,
uint32 uid = B_BENDIAN_TO_HOST_INT32(entries[i].uid);
uint32 flags = B_BENDIAN_TO_HOST_INT32(entries[i].flags);
fUIDMap.insert(std::make_pair(uid, flags));
fFlagsMap.insert(std::make_pair(uid, flags));
}
}
@@ -167,6 +167,29 @@ IMAPFolder::SetUIDValidity(uint32 uidValidity)
}
status_t
IMAPFolder::GetMessageEntryRef(uint32 uid, entry_ref& ref) const
{
UIDToRefMap::const_iterator found = fRefMap.find(uid);
if (found == fRefMap.end())
return B_ENTRY_NOT_FOUND;
ref = found->second;
return B_OK;
}
uint32
IMAPFolder::MessageFlags(uint32 uid) const
{
UIDToFlagsMap::const_iterator found = fFlagsMap.find(uid);
if (found == fFlagsMap.end())
return 0;
return found->second;
}
/*! Stores the given \a stream into a temporary file using the provided
BFile object. A new file will be created, and the \a ref object will
point to it. The file will remain open when this method exits without
@@ -176,8 +199,8 @@ IMAPFolder::SetUIDValidity(uint32 uidValidity)
were an error.
*/
status_t
IMAPFolder::StoreMessage(BFile& file, uint32 fetchFlags, BDataIO& stream,
size_t& length, entry_ref& ref)
IMAPFolder::StoreMessage(uint32 fetchFlags, BDataIO& stream,
size_t& length, entry_ref& ref, BFile& file)
{
BPath path;
status_t status = path.SetTo(&fRef);
@@ -189,26 +212,11 @@ IMAPFolder::StoreMessage(BFile& file, uint32 fetchFlags, BDataIO& stream,
if (status != B_OK)
return status;
char buffer[65535];
while (length > 0) {
ssize_t bytesRead = stream.Read(buffer,
std::min(sizeof(buffer), length));
if (bytesRead < 0)
return bytesRead;
if (bytesRead <= 0)
break;
status = _WriteStream(file, stream, length);
if (status == B_OK)
temporaryFile.KeepFile();
length -= bytesRead;
ssize_t bytesWritten = file.Write(buffer, bytesRead);
if (bytesWritten < 0)
return bytesWritten;
if (bytesWritten != bytesRead)
return B_IO_ERROR;
}
temporaryFile.KeepFile();
return B_OK;
return status;
}
@@ -223,29 +231,73 @@ IMAPFolder::MessageStored(entry_ref& ref, BFile& file, uint32 fetchFlags,
if ((fetchFlags & IMAP::kFetchFlags) != 0)
_WriteFlags(file, flags);
// TODO: the call below may move/rename the file
fProtocol.MessageStored(ref, file, fetchFlags);
// TODO: the call below may move/rename the file - this prevents downloading
// the body to the correct file!
fProtocol.MessageStored(*this, ref, file, fetchFlags);
file.Unset();
fRefMap.insert(std::make_pair(uid, ref));
if (uid > fLastUID) {
// Update last known UID
fLastUID = uid;
BNode directory(&fRef);
status_t status = _WriteUInt32(directory, kLastUIDAttribute, uid);
if (status != B_OK)
if (status != B_OK) {
fprintf(stderr, "IMAP: Could not write last UID for mailbox "
"%s: %s\n", fMailboxName.String(), strerror(status));
}
}
}
/*! Appends the given \a stream as body to the message file for the
specified unique ID. The file will remain open when this method exits
without an error.
\a length will reflect how many bytes are left to read in case there
were an error.
*/
status_t
IMAPFolder::StoreBody(uint32 uid, BDataIO& stream, size_t& length,
entry_ref& ref, BFile& file)
{
status_t status = GetMessageEntryRef(uid, ref);
if (status != B_OK)
return status;
status = file.SetTo(&ref, B_OPEN_AT_END | B_WRITE_ONLY);
if (status != B_OK)
return status;
BPath path(&ref);
printf("IMAP: write body to %s\n", path.Path());
return _WriteStream(file, stream, length);
}
/*! Notifies the protocol that a body has been fetched.
This method also closes the \a file passed in.
*/
void
IMAPFolder::BodyStored(entry_ref& ref, BFile& file, uint32 uid)
{
fProtocol.MessageStored(*this, ref, file, IMAP::kFetchBody);
file.Unset();
}
void
IMAPFolder::DeleteMessage(uint32 uid)
{
}
/*! Called when the flags of a message changed on the server. This will update
the flags for the local file.
*/
void
IMAPFolder::SetMessageFlags(uint32 uid, uint32 flags)
{
@@ -264,8 +316,8 @@ IMAPFolder::_InitializeFolderState()
// Create set of the last known UID state - if an entry is found, it
// is being removed from the list. The remaining entries were deleted.
std::set<uint32> lastUIDs;
UIDToFlagsMap::iterator iterator = fUIDMap.begin();
for (; iterator != fUIDMap.end(); iterator++)
UIDToFlagsMap::iterator iterator = fFlagsMap.begin();
for (; iterator != fFlagsMap.end(); iterator++)
lastUIDs.insert(iterator->first);
BDirectory directory(&fRef);
@@ -286,12 +338,11 @@ IMAPFolder::_InitializeFolderState()
// The message is still around
lastUIDs.erase(found);
UIDToFlagsMap::iterator flagsFound = fUIDMap.find(uid);
ASSERT(flagsFound != fUIDMap.end());
if (flagsFound->second != flags) {
uint32 flagsFound = MessageFlags(uid);
if (flagsFound != flags) {
// Its flags have changed locally, and need to be updated
fListener->MessageFlagsChanged(_Token(uid), ref,
flagsFound->second, flags);
flagsFound, flags);
}
} else {
// This is a new message
@@ -376,3 +427,28 @@ IMAPFolder::_WriteUInt32(BNode& node, const char* attribute, uint32 value)
return bytesWritten < 0 ? bytesWritten : B_IO_ERROR;
}
status_t
IMAPFolder::_WriteStream(BFile& file, BDataIO& stream, size_t& length)
{
char buffer[65535];
while (length > 0) {
ssize_t bytesRead = stream.Read(buffer,
std::min(sizeof(buffer), length));
if (bytesRead < 0)
return bytesRead;
if (bytesRead <= 0)
break;
length -= bytesRead;
ssize_t bytesWritten = file.Write(buffer, bytesRead);
if (bytesWritten < 0)
return bytesWritten;
if (bytesWritten != bytesRead)
return B_IO_ERROR;
}
return B_OK;
}
@@ -24,6 +24,11 @@ struct MessageToken {
uint32 uid;
};
// Additional local only message flags
enum FolderMessageFlags {
kPartialMessage = 0x00010000,
};
class FolderListener {
public:
@@ -51,13 +56,23 @@ public:
uint32 LastUID() const { return fLastUID; }
status_t StoreMessage(BFile& file, uint32 fetchFlags,
BDataIO& stream, size_t& length,
entry_ref& ref);
status_t GetMessageEntryRef(uint32 uid,
entry_ref& ref) const;
uint32 MessageFlags(uint32 uid) const;
status_t StoreMessage(uint32 fetchFlags, BDataIO& stream,
size_t& length, entry_ref& ref,
BFile& file);
void MessageStored(entry_ref& ref, BFile& file,
uint32 fetchFlags, uint32 uid,
uint32 flags);
status_t StoreBody(uint32 uid, BDataIO& stream,
size_t& length, entry_ref& ref,
BFile& file);
void BodyStored(entry_ref& ref, BFile& file,
uint32 uid);
void DeleteMessage(uint32 uid);
void SetMessageFlags(uint32 uid, uint32 flags);
@@ -75,6 +90,9 @@ private:
status_t _WriteUInt32(BNode& node,
const char* attribute, uint32 value);
status_t _WriteStream(BFile& file, BDataIO& stream,
size_t& length);
private:
typedef std::hash_map<uint32, uint32> UIDToFlagsMap;
typedef std::hash_map<uint32, entry_ref> UIDToRefMap;
@@ -86,7 +104,7 @@ private:
uint32 fLastUID;
FolderListener* fListener;
UIDToRefMap fRefMap;
UIDToFlagsMap fUIDMap;
UIDToFlagsMap fFlagsMap;
};
@@ -24,6 +24,36 @@ IMAPMailbox::~IMAPMailbox()
}
void
IMAPMailbox::AddMessageEntry(uint32 uid, uint32 flags, uint32 size)
{
fMessageEntries.insert(
std::make_pair(uid, MessageFlagsAndSize(flags, size)));
}
uint32
IMAPMailbox::MessageFlags(uint32 uid) const
{
MessageEntryMap::const_iterator found = fMessageEntries.find(uid);
if (found == fMessageEntries.end())
return 0;
return found->second.flags;
}
uint32
IMAPMailbox::MessageSize(uint32 uid) const
{
MessageEntryMap::const_iterator found = fMessageEntries.find(uid);
if (found == fMessageEntries.end())
return 0;
return found->second.size;
}
uint32
IMAPMailbox::MessageAdded(const MessageToken& fromToken, const entry_ref& ref)
{
@@ -22,6 +22,11 @@ public:
const BString& MailboxName() const { return fMailboxName; }
void AddMessageEntry(uint32 uid, uint32 flags,
uint32 size);
uint32 MessageFlags(uint32 uid) const;
uint32 MessageSize(uint32 uid) const;
// FolderListener interface
virtual uint32 MessageAdded(const MessageToken& fromToken,
const entry_ref& ref);
@@ -32,8 +37,22 @@ public:
uint32 newFlags);
protected:
struct MessageFlagsAndSize {
MessageFlagsAndSize(uint32 _flags, uint32 _size)
:
flags(_flags),
size(_size)
{
}
uint32 flags;
uint32 size;
};
typedef std::hash_map<uint32, MessageFlagsAndSize> MessageEntryMap;
IMAP::Protocol& fProtocol;
BString fMailboxName;
MessageEntryMap fMessageEntries;
};
@@ -123,7 +123,8 @@ IMAPProtocol::WorkerQuit(IMAPConnectionWorker* worker)
void
IMAPProtocol::MessageStored(entry_ref& ref, BFile& stream, uint32 fetchFlags)
IMAPProtocol::MessageStored(IMAPFolder& folder, entry_ref& ref, BFile& stream,
uint32 fetchFlags)
{
if ((fetchFlags & IMAP::kFetchHeader) != 0)
NotifyHeaderFetched(ref, &stream);
@@ -34,7 +34,8 @@ public:
IMAP::Protocol& protocol, bool idle);
void WorkerQuit(IMAPConnectionWorker* worker);
void MessageStored(entry_ref& ref, BFile& stream,
void MessageStored(IMAPFolder& folder,
entry_ref& ref, BFile& stream,
uint32 fetchFlags);
virtual status_t SyncMessages();
@@ -34,12 +34,14 @@ struct MessageEntry {
typedef std::vector<MessageEntry> MessageEntryList;
enum MessageFlags {
kSeen = 0x01,
kAnswered = 0x02,
kFlagged = 0x04,
kDeleted = 0x08,
kDraft = 0x10
kSeen = 0x01,
kAnswered = 0x02,
kFlagged = 0x04,
kDeleted = 0x08,
kDraft = 0x10,
// \Recent doesn't really have any useful meaning, so we just ignore it
kServerFlagsMask = 0x0000ffff
};