/*
* Copyright 2013-2016, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include "IMAPProtocol.h"
#include <Directory.h>
#include <Messenger.h>
#include "IMAPConnectionWorker.h"
#include "IMAPFolder.h"
#include "Utilities.h"
#include <mail_util.h>
IMAPProtocol::IMAPProtocol(const BMailAccountSettings& settings)
:
BInboundMailProtocol("IMAP", settings),
fSettings(settings.Name(), settings.InboundSettings()),
fWorkers(5, false)
{
BPath destination = fSettings.Destination();
status_t status = create_directory(destination.Path(), 0755);
if (status != B_OK) {
fprintf(stderr, "IMAP: Could not create destination directory %s: %s\n",
destination.Path(), strerror(status));
}
mutex_init(&fWorkerLock, "imap worker lock");
PostMessage(B_READY_TO_RUN);
}
IMAPProtocol::~IMAPProtocol()
{
MutexLocker locker(fWorkerLock);
std::vector<thread_id> threads;
for (int32 i = 0; i < fWorkers.CountItems(); i++) {
threads.push_back(fWorkers.ItemAt(i)->Thread());
fWorkers.ItemAt(i)->Quit();
}
locker.Unlock();
for (uint32 i = 0; i < threads.size(); i++)
wait_for_thread(threads[i], NULL);
FolderMap::iterator iterator = fFolders.begin();
for (; iterator != fFolders.end(); iterator++) {
IMAPFolder* folder = iterator->second;
delete folder; // to stop thread
}
}
status_t
IMAPProtocol::CheckSubscribedFolders(IMAP::Protocol& protocol, bool idle)
{
// Get list of subscribed folders
BStringList newFolders;
BString separator;
status_t status = protocol.GetSubscribedFolders(newFolders, separator);
if (status != B_OK)
return status;
// Determine how many new mailboxes we have
for (int32 i = 0; i < newFolders.CountStrings();) {
if (fFolders.find(newFolders.StringAt(i)) != fFolders.end())
newFolders.Remove(i);
else
i++;
}
int32 totalMailboxes = fFolders.size() + newFolders.CountStrings();
int32 workersWanted = 1;
if (idle)
workersWanted = std::min(fSettings.MaxConnections(), totalMailboxes);
MutexLocker locker(fWorkerLock);
if (newFolders.IsEmpty() && fWorkers.CountItems() == workersWanted) {
// Nothing to do - we've already distributed everything
return _EnqueueCheckMailboxes();
}
// Remove mailboxes from workers
for (int32 i = 0; i < fWorkers.CountItems(); i++) {
fWorkers.ItemAt(i)->RemoveAllMailboxes();
}
fWorkerMap.clear();
// Create/remove connection workers as allowed and needed
while (fWorkers.CountItems() < workersWanted) {
IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this,
fSettings);
if (!fWorkers.AddItem(worker)) {
delete worker;
break;
}
status = worker->Run();
if (status != B_OK) {
fWorkers.RemoveItem(worker);
delete worker;
}
}
while (fWorkers.CountItems() > workersWanted) {
IMAPConnectionWorker* worker
= fWorkers.RemoveItemAt(fWorkers.CountItems() - 1);
worker->Quit();
}
// Update known mailboxes
for (int32 i = 0; i < newFolders.CountStrings(); i++) {
const BString& mailbox = newFolders.StringAt(i);
fFolders.insert(std::make_pair(mailbox,
_CreateFolder(mailbox, separator)));
}
// Distribute the mailboxes evenly to the workers
FolderMap::iterator iterator = fFolders.begin();
int32 index = 0;
for (; iterator != fFolders.end(); iterator++) {
IMAPConnectionWorker* worker = fWorkers.ItemAt(index);
IMAPFolder* folder = iterator->second;
worker->AddMailbox(folder);
fWorkerMap.insert(std::make_pair(folder, worker));
index = (index + 1) % fWorkers.CountItems();
}
// Start waiting workers
return _EnqueueCheckMailboxes();
}
void
IMAPProtocol::WorkerQuit(IMAPConnectionWorker* worker)
{
MutexLocker locker(fWorkerLock);
fWorkers.RemoveItem(worker);
WorkerMap::iterator iterator = fWorkerMap.begin();
while (iterator != fWorkerMap.end()) {
WorkerMap::iterator removed = iterator++;
if (removed->second == worker)
fWorkerMap.erase(removed);
}
}
void
IMAPProtocol::MessageStored(IMAPFolder& folder, entry_ref& ref,
BFile& stream, uint32 fetchFlags, BMessage& attributes)
{
if ((fetchFlags & (IMAP::kFetchHeader | IMAP::kFetchBody))
== (IMAP::kFetchHeader | IMAP::kFetchBody)) {
ProcessMessageFetched(ref, stream, attributes);
} else if ((fetchFlags & IMAP::kFetchHeader) != 0) {
ProcessHeaderFetched(ref, stream, attributes);
} else if ((fetchFlags & IMAP::kFetchBody) != 0) {
NotifyBodyFetched(ref, stream, attributes);
}
}
status_t
IMAPProtocol::UpdateMessageFlags(IMAPFolder& folder, uint32 uid, uint32 flags)
{
MutexLocker locker(fWorkerLock);
WorkerMap::const_iterator found = fWorkerMap.find(&folder);
if (found == fWorkerMap.end())
return B_ERROR;
IMAPConnectionWorker* worker = found->second;
return worker->EnqueueUpdateFlags(folder, uid, flags);
}
status_t
IMAPProtocol::SyncMessages()
{
puts("IMAP: sync");
MutexLocker locker(fWorkerLock);
if (fWorkers.IsEmpty()) {
// Create main (and possibly initial) connection worker
IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this,
fSettings, true);
if (!fWorkers.AddItem(worker)) {
delete worker;
return B_NO_MEMORY;
}
worker->EnqueueCheckSubscribedFolders();
return worker->Run();
}
fWorkers.ItemAt(0)->EnqueueCheckSubscribedFolders();
return B_OK;
}
status_t
IMAPProtocol::MarkMessageAsRead(const entry_ref& ref, read_flags flags)
{
printf("IMAP: mark as read %s: %d\n", ref.name, flags);
return B_ERROR;
}
void
IMAPProtocol::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_READY_TO_RUN:
ReadyToRun();
break;
default:
BInboundMailProtocol::MessageReceived(message);
break;
}
}
status_t
IMAPProtocol::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo)
{
printf("IMAP: fetch body %s\n", ref.name);
MutexLocker locker(fWorkerLock);
IMAPFolder* folder = _FolderFor(ref.directory);
if (folder == NULL)
return B_ENTRY_NOT_FOUND;
uint32 uid;
status_t status = folder->GetMessageUID(ref, uid);
if (status != B_OK)
return status;
WorkerMap::const_iterator found = fWorkerMap.find(folder);
if (found == fWorkerMap.end())
return B_ERROR;
IMAPConnectionWorker* worker = found->second;
return worker->EnqueueFetchBody(*folder, uid, replyTo);
}
void
IMAPProtocol::ReadyToRun()
{
puts("IMAP: ready to run!");
if (fSettings.IdleMode())
SyncMessages();
}
IMAPFolder*
IMAPProtocol::_CreateFolder(const BString& mailbox, const BString& separator)
{
BString name = MailboxToFolderName(mailbox, separator);
BPath path(fSettings.Destination());
if (path.Append(name.String()) != B_OK) {
fprintf(stderr, "Could not append path: %s\n", name.String());
return NULL;
}
status_t status = create_directory(path.Path(), 0755);
if (status != B_OK) {
fprintf(stderr, "Could not create path %s: %s\n", path.Path(),
strerror(status));
return NULL;
}
CopyMailFolderAttributes(path.Path());
entry_ref ref;
status = get_ref_for_path(path.Path(), &ref);
if (status != B_OK) {
fprintf(stderr, "Could not get ref for %s: %s\n", path.Path(),
strerror(status));
return NULL;
}
IMAPFolder* folder = new IMAPFolder(*this, mailbox, ref);
status = folder->Init();
if (status != B_OK) {
fprintf(stderr, "Initializing folder %s failed: %s\n", path.Path(),
strerror(status));
return NULL;
}
fFolderNodeMap.insert(std::make_pair(folder->NodeID(), folder));
return folder;
}
IMAPFolder*
IMAPProtocol::_FolderFor(ino_t directory)
{
FolderNodeMap::const_iterator found = fFolderNodeMap.find(directory);
if (found != fFolderNodeMap.end())
return found->second;
return NULL;
}
status_t
IMAPProtocol::_EnqueueCheckMailboxes()
{
for (int32 i = 0; i < fWorkers.CountItems(); i++) {
fWorkers.ItemAt(i)->EnqueueCheckMailboxes();
}
return B_OK;
}
// #pragma mark -
extern "C" BInboundMailProtocol*
instantiate_inbound_protocol(const BMailAccountSettings& settings)
{
return new IMAPProtocol(settings);
}
↑ V773 The function was exited without releasing the 'folder' pointer. A memory leak is possible.