haiku/src/servers/mail/MailDaemonApplication.cpp

877 lines
20 KiB
C++

/*
* Copyright 2007-2016, Haiku, Inc. All rights reserved.
* Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved.
* Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de>
* Distributed under the terms of the MIT License.
*/
#include "MailDaemonApplication.h"
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <Beep.h>
#include <Catalog.h>
#include <Deskbar.h>
#include <Directory.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <fs_index.h>
#include <IconUtils.h>
#include <NodeMonitor.h>
#include <Notification.h>
#include <Path.h>
#include <Roster.h>
#include <StringList.h>
#include <StringFormat.h>
#include <VolumeRoster.h>
#include <E-mail.h>
#include <MailDaemon.h>
#include <MailMessage.h>
#include <MailSettings.h>
#include <MailPrivate.h>
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "MailDaemon"
static const uint32 kMsgStartAutoCheck = 'stAC';
static const uint32 kMsgAutoCheck = 'moto';
static const bigtime_t kStartAutoCheckDelay = 30000000;
// Wait 30 seconds before the first auto check - this usually lets the
// boot process settle down, and give the network a chance to come up.
struct send_mails_info {
send_mails_info()
{
bytes = 0;
}
BMessage files;
off_t bytes;
};
class InboundMessenger : public BMessenger {
public:
InboundMessenger(BInboundMailProtocol* protocol)
:
BMessenger(protocol)
{
}
status_t MarkAsRead(const entry_ref& ref, read_flags flag)
{
BMessage message(kMsgMarkMessageAsRead);
message.AddRef("ref", &ref);
message.AddInt32("read", flag);
return SendMessage(&message);
}
status_t SynchronizeMessages()
{
BMessage message(kMsgSyncMessages);
return SendMessage(&message);
}
};
// #pragma mark -
static void
makeIndices()
{
const char* stringIndices[] = {
B_MAIL_ATTR_CC, B_MAIL_ATTR_FROM, B_MAIL_ATTR_NAME,
B_MAIL_ATTR_PRIORITY, B_MAIL_ATTR_REPLY, B_MAIL_ATTR_STATUS,
B_MAIL_ATTR_SUBJECT, B_MAIL_ATTR_TO, B_MAIL_ATTR_THREAD,
B_MAIL_ATTR_ACCOUNT, NULL
};
// add mail indices for all devices capable of querying
int32 cookie = 0;
dev_t device;
while ((device = next_dev(&cookie)) >= B_OK) {
fs_info info;
if (fs_stat_dev(device, &info) < 0
|| (info.flags & B_FS_HAS_QUERY) == 0)
continue;
for (int32 i = 0; stringIndices[i]; i++)
fs_create_index(device, stringIndices[i], B_STRING_TYPE, 0);
fs_create_index(device, "MAIL:draft", B_INT32_TYPE, 0);
fs_create_index(device, B_MAIL_ATTR_WHEN, B_INT32_TYPE, 0);
fs_create_index(device, B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0);
fs_create_index(device, B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0);
fs_create_index(device, B_MAIL_ATTR_READ, B_INT32_TYPE, 0);
}
}
static void
addAttribute(BMessage& msg, const char* name, const char* publicName,
int32 type = B_STRING_TYPE, bool viewable = true, bool editable = false,
int32 width = 200)
{
msg.AddString("attr:name", name);
msg.AddString("attr:public_name", publicName);
msg.AddInt32("attr:type", type);
msg.AddBool("attr:viewable", viewable);
msg.AddBool("attr:editable", editable);
msg.AddInt32("attr:width", width);
msg.AddInt32("attr:alignment", B_ALIGN_LEFT);
}
// #pragma mark -
account_protocols::account_protocols()
:
inboundImage(-1),
inboundProtocol(NULL),
outboundImage(-1),
outboundProtocol(NULL)
{
}
// #pragma mark -
MailDaemonApplication::MailDaemonApplication()
:
BServer(B_MAIL_DAEMON_SIGNATURE, true, NULL),
fAutoCheckRunner(NULL)
{
fErrorLogWindow = new ErrorLogWindow(BRect(200, 200, 500, 250),
B_TRANSLATE("Mail daemon status log"), B_TITLED_WINDOW);
// install MimeTypes, attributes, indices, and the
// system beep add startup
MakeMimeTypes();
makeIndices();
add_system_beep_event("New E-mail");
}
MailDaemonApplication::~MailDaemonApplication()
{
delete fAutoCheckRunner;
for (int32 i = 0; i < fQueries.CountItems(); i++)
delete fQueries.ItemAt(i);
while (!fAccounts.empty()) {
_RemoveAccount(fAccounts.begin()->second);
fAccounts.erase(fAccounts.begin());
}
delete fLEDAnimation;
delete fNotification;
}
void
MailDaemonApplication::ReadyToRun()
{
InstallDeskbarIcon();
_InitAccounts();
// Start auto mail check with a delay
BMessage startAutoCheck(kMsgStartAutoCheck);
BMessageRunner::StartSending(this, &startAutoCheck,
kStartAutoCheckDelay, 1);
_InitNewMessagesCount();
fCentralBeep = false;
fNotification = new BNotification(B_INFORMATION_NOTIFICATION);
fNotification->SetGroup(B_TRANSLATE("Mail status"));
fNotification->SetMessageID("daemon_status");
_UpdateNewMessagesNotification();
app_info info;
be_roster->GetAppInfo(B_MAIL_DAEMON_SIGNATURE, &info);
BBitmap icon(BRect(0, 0, 32, 32), B_RGBA32);
BNode node(&info.ref);
BIconUtils::GetVectorIcon(&node, "BEOS:ICON", &icon);
fNotification->SetIcon(&icon);
fLEDAnimation = new LEDAnimation();
SetPulseRate(1000000);
}
void
MailDaemonApplication::RefsReceived(BMessage* message)
{
entry_ref ref;
for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
BNode node(&ref);
if (node.InitCheck() != B_OK)
continue;
int32 account;
if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &account,
sizeof(account)) < 0)
continue;
BInboundMailProtocol* protocol = _InboundProtocol(account);
if (protocol == NULL)
continue;
BMessenger target;
BMessenger* replyTo = &target;
if (message->FindMessenger("target", &target) != B_OK)
replyTo = NULL;
protocol->FetchBody(ref, replyTo);
}
}
void
MailDaemonApplication::MessageReceived(BMessage* msg)
{
switch (msg->what) {
case kMsgStartAutoCheck:
_UpdateAutoCheckRunner();
break;
case kMsgAutoCheck:
// TODO: check whether internet is up and running!
// supposed to fall through
case kMsgCheckAndSend: // check & send messages
msg->what = kMsgSendMessages;
PostMessage(msg);
// supposed to fall trough
case kMsgCheckMessage: // check messages
GetNewMessages(msg);
break;
case kMsgSendMessages: // send messages
SendPendingMessages(msg);
break;
case kMsgSettingsUpdated:
fSettingsFile.Reload();
_UpdateAutoCheckRunner();
break;
case kMsgAccountsChanged:
_ReloadAccounts(msg);
break;
case kMsgMarkMessageAsRead:
{
int32 account = msg->FindInt32("account");
entry_ref ref;
if (msg->FindRef("ref", &ref) != B_OK)
break;
read_flags read = (read_flags)msg->FindInt32("read");
BInboundMailProtocol* protocol = _InboundProtocol(account);
if (protocol != NULL)
InboundMessenger(protocol).MarkAsRead(ref, read);
break;
}
case kMsgFetchBody:
RefsReceived(msg);
break;
case 'stwg': // Status window gone
{
BMessage reply('mnuc');
reply.AddInt32("num_new_messages", fNewMessages);
while ((msg = fFetchDoneRespondents.RemoveItemAt(0))) {
msg->SendReply(&reply);
delete msg;
}
if (fAlertString != B_EMPTY_STRING) {
fAlertString.Truncate(fAlertString.Length() - 1);
BAlert* alert = new BAlert(B_TRANSLATE("New Messages"),
fAlertString.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL);
alert->SetFeel(B_NORMAL_WINDOW_FEEL);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go(NULL);
fAlertString = B_EMPTY_STRING;
}
if (fCentralBeep) {
system_beep("New E-mail");
fCentralBeep = false;
}
break;
}
case 'mcbp':
if (fNewMessages > 0)
fCentralBeep = true;
break;
case kMsgCountNewMessages: // Number of new messages
{
BMessage reply('mnuc'); // Mail New message Count
if (msg->FindBool("wait_for_fetch_done")) {
fFetchDoneRespondents.AddItem(DetachCurrentMessage());
break;
}
reply.AddInt32("num_new_messages", fNewMessages);
msg->SendReply(&reply);
break;
}
case 'mblk': // Mail Blink
if (fNewMessages > 0)
fLEDAnimation->Start();
break;
case 'enda': // End Auto Check
delete fAutoCheckRunner;
fAutoCheckRunner = NULL;
break;
case 'numg':
{
static BStringFormat format(B_TRANSLATE("{0, plural, "
"one{# new message} other{# new messages}} for %name\n"));
int32 numMessages = msg->FindInt32("num_messages");
fAlertString.Truncate(0);
format.Format(fAlertString, numMessages);
fAlertString.ReplaceFirst("%name", msg->FindString("name"));
break;
}
case B_QUERY_UPDATE:
{
int32 previousCount = fNewMessages;
int32 opcode = msg->GetInt32("opcode", -1);
switch (opcode) {
case B_ENTRY_CREATED:
fNewMessages++;
break;
case B_ENTRY_REMOVED:
fNewMessages--;
break;
default:
return;
}
_UpdateNewMessagesNotification();
if (fSettingsFile.ShowStatusWindow()
!= B_MAIL_SHOW_STATUS_WINDOW_NEVER
&& previousCount < fNewMessages) {
fNotification->Send();
}
break;
}
default:
BApplication::MessageReceived(msg);
break;
}
}
void
MailDaemonApplication::Pulse()
{
bigtime_t idle = idle_time();
if (fLEDAnimation->IsRunning() && idle < 100000)
fLEDAnimation->Stop();
}
bool
MailDaemonApplication::QuitRequested()
{
RemoveDeskbarIcon();
return true;
}
void
MailDaemonApplication::InstallDeskbarIcon()
{
BDeskbar deskbar;
if (!deskbar.HasItem("mail_daemon")) {
BRoster roster;
entry_ref ref;
status_t status = roster.FindApp(B_MAIL_DAEMON_SIGNATURE, &ref);
if (status < B_OK) {
fprintf(stderr, "Can't find application to tell deskbar: %s\n",
strerror(status));
return;
}
status = deskbar.AddItem(&ref);
if (status < B_OK) {
fprintf(stderr, "Can't add deskbar replicant: %s\n",
strerror(status));
return;
}
}
}
void
MailDaemonApplication::RemoveDeskbarIcon()
{
BDeskbar deskbar;
if (deskbar.HasItem("mail_daemon"))
deskbar.RemoveItem("mail_daemon");
}
void
MailDaemonApplication::GetNewMessages(BMessage* msg)
{
int32 account = -1;
if (msg->FindInt32("account", &account) == B_OK && account >= 0) {
// Check the single requested account
BInboundMailProtocol* protocol = _InboundProtocol(account);
if (protocol != NULL)
InboundMessenger(protocol).SynchronizeMessages();
return;
}
// Check all accounts
AccountMap::const_iterator iterator = fAccounts.begin();
for (; iterator != fAccounts.end(); iterator++) {
BInboundMailProtocol* protocol = iterator->second.inboundProtocol;
if (protocol != NULL)
InboundMessenger(protocol).SynchronizeMessages();
}
}
void
MailDaemonApplication::SendPendingMessages(BMessage* msg)
{
BVolumeRoster roster;
BVolume volume;
std::map<int32, send_mails_info> messages;
int32 account = msg->GetInt32("account", -1);
if (!msg->HasString("message_path")) {
while (roster.GetNextVolume(&volume) == B_OK) {
BQuery query;
query.SetVolume(&volume);
query.PushAttr(B_MAIL_ATTR_FLAGS);
query.PushInt32(B_MAIL_PENDING);
query.PushOp(B_EQ);
query.PushAttr(B_MAIL_ATTR_FLAGS);
query.PushInt32(B_MAIL_PENDING | B_MAIL_SAVE);
query.PushOp(B_EQ);
if (account >= 0) {
query.PushAttr(B_MAIL_ATTR_ACCOUNT_ID);
query.PushInt32(account);
query.PushOp(B_EQ);
query.PushOp(B_AND);
}
query.PushOp(B_OR);
query.Fetch();
BEntry entry;
while (query.GetNextEntry(&entry) == B_OK) {
if (_IsEntryInTrash(entry))
continue;
BNode node;
while (node.SetTo(&entry) == B_BUSY)
snooze(1000);
if (!_IsPending(node))
continue;
if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0,
&account, sizeof(int32)) < 0)
account = -1;
_AddMessage(messages[account], entry, node);
}
}
} else {
// Send the requested message only
const char* path;
if (msg->FindString("message_path", &path) != B_OK)
return;
BEntry entry(path);
_AddMessage(messages[account], entry, BNode(&entry));
}
std::map<int32, send_mails_info>::iterator iterator = messages.begin();
for (; iterator != messages.end(); iterator++) {
BOutboundMailProtocol* protocol = _OutboundProtocol(iterator->first);
if (protocol == NULL)
continue;
send_mails_info& info = iterator->second;
if (info.bytes == 0)
continue;
protocol->SendMessages(info.files, info.bytes);
}
}
void
MailDaemonApplication::MakeMimeTypes(bool remakeMIMETypes)
{
// Add MIME database entries for the e-mail file types we handle. Either
// do a full rebuild from nothing, or just add on the new attributes that
// we support which the regular BeOS mail daemon didn't have.
const uint8 kNTypes = 2;
const char* types[kNTypes] = {"text/x-email", "text/x-partial-email"};
for (size_t i = 0; i < kNTypes; i++) {
BMessage info;
BMimeType mime(types[i]);
if (mime.InitCheck() != B_OK) {
fputs("could not init mime type.\n", stderr);
return;
}
if (!mime.IsInstalled() || remakeMIMETypes) {
// install the full mime type
mime.Delete();
mime.Install();
// Set up the list of e-mail related attributes that Tracker will
// let you display in columns for e-mail messages.
addAttribute(info, B_MAIL_ATTR_NAME, "Name");
addAttribute(info, B_MAIL_ATTR_SUBJECT, "Subject");
addAttribute(info, B_MAIL_ATTR_TO, "To");
addAttribute(info, B_MAIL_ATTR_CC, "Cc");
addAttribute(info, B_MAIL_ATTR_FROM, "From");
addAttribute(info, B_MAIL_ATTR_REPLY, "Reply To");
addAttribute(info, B_MAIL_ATTR_STATUS, "Status");
addAttribute(info, B_MAIL_ATTR_PRIORITY, "Priority", B_STRING_TYPE,
true, true, 40);
addAttribute(info, B_MAIL_ATTR_WHEN, "When", B_TIME_TYPE, true,
false, 150);
addAttribute(info, B_MAIL_ATTR_THREAD, "Thread");
addAttribute(info, B_MAIL_ATTR_ACCOUNT, "Account", B_STRING_TYPE,
true, false, 100);
addAttribute(info, B_MAIL_ATTR_READ, "Read", B_INT32_TYPE,
true, false, 70);
mime.SetAttrInfo(&info);
if (i == 0) {
mime.SetShortDescription("E-mail");
mime.SetLongDescription("Electronic Mail Message");
mime.SetPreferredApp("application/x-vnd.Be-MAIL");
} else {
mime.SetShortDescription("Partial E-mail");
mime.SetLongDescription("A Partially Downloaded E-mail");
mime.SetPreferredApp("application/x-vnd.Be-MAIL");
}
}
}
}
void
MailDaemonApplication::_InitAccounts()
{
BMailAccounts accounts;
for (int i = 0; i < accounts.CountAccounts(); i++)
_InitAccount(*accounts.AccountAt(i));
}
void
MailDaemonApplication::_InitAccount(BMailAccountSettings& settings)
{
account_protocols account;
// inbound
if (settings.IsInboundEnabled()) {
account.inboundProtocol = _CreateInboundProtocol(settings,
account.inboundImage);
}
if (account.inboundProtocol != NULL) {
DefaultNotifier* notifier = new DefaultNotifier(settings.Name(), true,
fErrorLogWindow, fSettingsFile.ShowStatusWindow());
account.inboundProtocol->SetMailNotifier(notifier);
account.inboundProtocol->Run();
}
// outbound
if (settings.IsOutboundEnabled()) {
account.outboundProtocol = _CreateOutboundProtocol(settings,
account.outboundImage);
}
if (account.outboundProtocol != NULL) {
DefaultNotifier* notifier = new DefaultNotifier(settings.Name(), false,
fErrorLogWindow, fSettingsFile.ShowStatusWindow());
account.outboundProtocol->SetMailNotifier(notifier);
account.outboundProtocol->Run();
}
printf("account name %s, id %i, in %p, out %p\n", settings.Name(),
(int)settings.AccountID(), account.inboundProtocol,
account.outboundProtocol);
if (account.inboundProtocol != NULL || account.outboundProtocol != NULL)
fAccounts[settings.AccountID()] = account;
}
void
MailDaemonApplication::_ReloadAccounts(BMessage* message)
{
type_code typeFound;
int32 countFound;
message->GetInfo("account", &typeFound, &countFound);
if (typeFound != B_INT32_TYPE)
return;
// reload accounts
BMailAccounts accounts;
for (int i = 0; i < countFound; i++) {
int32 account = message->FindInt32("account", i);
AccountMap::iterator found = fAccounts.find(account);
if (found != fAccounts.end()) {
_RemoveAccount(found->second);
fAccounts.erase(found);
}
BMailAccountSettings* settings = accounts.AccountByID(account);
if (settings != NULL)
_InitAccount(*settings);
}
}
void
MailDaemonApplication::_RemoveAccount(const account_protocols& account)
{
if (account.inboundProtocol != NULL) {
account.inboundProtocol->Lock();
account.inboundProtocol->Quit();
unload_add_on(account.inboundImage);
}
if (account.outboundProtocol != NULL) {
account.outboundProtocol->Lock();
account.outboundProtocol->Quit();
unload_add_on(account.outboundImage);
}
}
BInboundMailProtocol*
MailDaemonApplication::_CreateInboundProtocol(BMailAccountSettings& settings,
image_id& image)
{
const entry_ref& entry = settings.InboundAddOnRef();
BInboundMailProtocol* (*instantiateProtocol)(BMailAccountSettings*);
BPath path(&entry);
image = load_add_on(path.Path());
if (image < 0)
return NULL;
if (get_image_symbol(image, "instantiate_inbound_protocol",
B_SYMBOL_TYPE_TEXT, (void**)&instantiateProtocol) != B_OK) {
unload_add_on(image);
image = -1;
return NULL;
}
return instantiateProtocol(&settings);
}
BOutboundMailProtocol*
MailDaemonApplication::_CreateOutboundProtocol(BMailAccountSettings& settings,
image_id& image)
{
const entry_ref& entry = settings.OutboundAddOnRef();
BOutboundMailProtocol* (*instantiateProtocol)(BMailAccountSettings*);
BPath path(&entry);
image = load_add_on(path.Path());
if (image < 0)
return NULL;
if (get_image_symbol(image, "instantiate_outbound_protocol",
B_SYMBOL_TYPE_TEXT, (void**)&instantiateProtocol) != B_OK) {
unload_add_on(image);
image = -1;
return NULL;
}
return instantiateProtocol(&settings);
}
BInboundMailProtocol*
MailDaemonApplication::_InboundProtocol(int32 account)
{
AccountMap::iterator found = fAccounts.find(account);
if (found == fAccounts.end())
return NULL;
return found->second.inboundProtocol;
}
BOutboundMailProtocol*
MailDaemonApplication::_OutboundProtocol(int32 account)
{
if (account < 0)
account = BMailSettings().DefaultOutboundAccount();
AccountMap::iterator found = fAccounts.find(account);
if (found == fAccounts.end())
return NULL;
return found->second.outboundProtocol;
}
void
MailDaemonApplication::_InitNewMessagesCount()
{
BVolume volume;
BVolumeRoster roster;
fNewMessages = 0;
while (roster.GetNextVolume(&volume) == B_OK) {
BQuery* query = new BQuery;
query->SetTarget(this);
query->SetVolume(&volume);
query->PushAttr(B_MAIL_ATTR_STATUS);
query->PushString("New");
query->PushOp(B_EQ);
query->PushAttr("BEOS:TYPE");
query->PushString("text/x-email");
query->PushOp(B_EQ);
query->PushAttr("BEOS:TYPE");
query->PushString("text/x-partial-email");
query->PushOp(B_EQ);
query->PushOp(B_OR);
query->PushOp(B_AND);
query->Fetch();
BEntry entry;
while (query->GetNextEntry(&entry) == B_OK)
fNewMessages++;
fQueries.AddItem(query);
}
}
void
MailDaemonApplication::_UpdateNewMessagesNotification()
{
BString title;
if (fNewMessages > 0) {
BStringFormat format(B_TRANSLATE(
"{0, plural, one{One new message} other{# new messages}}"));
format.Format(title, fNewMessages);
} else
title = B_TRANSLATE("No new messages");
fNotification->SetTitle(title.String());
}
void
MailDaemonApplication::_UpdateAutoCheckRunner()
{
bigtime_t interval = fSettingsFile.AutoCheckInterval();
if (interval > 0) {
if (fAutoCheckRunner != NULL) {
fAutoCheckRunner->SetInterval(interval);
fAutoCheckRunner->SetCount(-1);
} else {
BMessage update(kMsgAutoCheck);
fAutoCheckRunner = new BMessageRunner(be_app_messenger, &update,
interval);
// Send one right away -- the message runner will wait until the
// first interval has passed before sending a message
PostMessage(&update);
}
} else {
delete fAutoCheckRunner;
fAutoCheckRunner = NULL;
}
}
void
MailDaemonApplication::_AddMessage(send_mails_info& info, const BEntry& entry,
const BNode& node)
{
entry_ref ref;
off_t size;
if (node.GetSize(&size) == B_OK && entry.GetRef(&ref) == B_OK) {
info.files.AddRef("ref", &ref);
info.bytes += size;
}
}
/*! Work-around for a broken index that contains out-of-date information.
*/
/*static*/ bool
MailDaemonApplication::_IsPending(BNode& node)
{
int32 flags;
if (node.ReadAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, sizeof(int32))
!= (ssize_t)sizeof(int32))
return false;
return (flags & B_MAIL_PENDING) != 0;
}
/*static*/ bool
MailDaemonApplication::_IsEntryInTrash(BEntry& entry)
{
entry_ref ref;
entry.GetRef(&ref);
BVolume volume(ref.device);
BPath path;
if (volume.InitCheck() != B_OK
|| find_directory(B_TRASH_DIRECTORY, &path, false, &volume) != B_OK)
return false;
BDirectory trash(path.Path());
return trash.Contains(&entry);
}