2 * Copyright 2007-2016, Haiku, Inc. All rights reserved.
3 * Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved.
4 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de>
5 * Distributed under the terms of the MIT License.
9 #include "MailDaemonApplication.h"
18 #include <Directory.h>
20 #include <FindDirectory.h>
22 #include <IconUtils.h>
23 #include <MessageFormat.h>
24 #include <NodeMonitor.h>
25 #include <Notification.h>
28 #include <StringList.h>
29 #include <VolumeRoster.h>
32 #include <MailDaemon.h>
33 #include <MailMessage.h>
34 #include <MailSettings.h>
36 #include <MailPrivate.h>
39 #undef B_TRANSLATION_CONTEXT
40 #define B_TRANSLATION_CONTEXT "MailDaemon"
43 static const uint32 kMsgStartAutoCheck
= 'stAC';
44 static const uint32 kMsgAutoCheck
= 'moto';
46 static const bigtime_t kStartAutoCheckDelay
= 30000000;
47 // Wait 30 seconds before the first auto check - this usually lets the
48 // boot process settle down, and give the network a chance to come up.
51 struct send_mails_info
{
62 class InboundMessenger
: public BMessenger
{
64 InboundMessenger(BInboundMailProtocol
* protocol
)
70 status_t
MarkAsRead(const entry_ref
& ref
, read_flags flag
)
72 BMessage
message(kMsgMarkMessageAsRead
);
73 message
.AddRef("ref", &ref
);
74 message
.AddInt32("read", flag
);
76 return SendMessage(&message
);
79 status_t
SynchronizeMessages()
81 BMessage
message(kMsgSyncMessages
);
82 return SendMessage(&message
);
93 const char* stringIndices
[] = {
94 B_MAIL_ATTR_CC
, B_MAIL_ATTR_FROM
, B_MAIL_ATTR_NAME
,
95 B_MAIL_ATTR_PRIORITY
, B_MAIL_ATTR_REPLY
, B_MAIL_ATTR_STATUS
,
96 B_MAIL_ATTR_SUBJECT
, B_MAIL_ATTR_TO
, B_MAIL_ATTR_THREAD
,
97 B_MAIL_ATTR_ACCOUNT
, NULL
100 // add mail indices for all devices capable of querying
104 while ((device
= next_dev(&cookie
)) >= B_OK
) {
106 if (fs_stat_dev(device
, &info
) < 0
107 || (info
.flags
& B_FS_HAS_QUERY
) == 0)
110 for (int32 i
= 0; stringIndices
[i
]; i
++)
111 fs_create_index(device
, stringIndices
[i
], B_STRING_TYPE
, 0);
113 fs_create_index(device
, "MAIL:draft", B_INT32_TYPE
, 0);
114 fs_create_index(device
, B_MAIL_ATTR_WHEN
, B_INT32_TYPE
, 0);
115 fs_create_index(device
, B_MAIL_ATTR_FLAGS
, B_INT32_TYPE
, 0);
116 fs_create_index(device
, B_MAIL_ATTR_ACCOUNT_ID
, B_INT32_TYPE
, 0);
117 fs_create_index(device
, B_MAIL_ATTR_READ
, B_INT32_TYPE
, 0);
123 addAttribute(BMessage
& msg
, const char* name
, const char* publicName
,
124 int32 type
= B_STRING_TYPE
, bool viewable
= true, bool editable
= false,
127 msg
.AddString("attr:name", name
);
128 msg
.AddString("attr:public_name", publicName
);
129 msg
.AddInt32("attr:type", type
);
130 msg
.AddBool("attr:viewable", viewable
);
131 msg
.AddBool("attr:editable", editable
);
132 msg
.AddInt32("attr:width", width
);
133 msg
.AddInt32("attr:alignment", B_ALIGN_LEFT
);
140 account_protocols::account_protocols()
143 inboundProtocol(NULL
),
145 outboundProtocol(NULL
)
153 MailDaemonApplication::MailDaemonApplication()
155 BServer(B_MAIL_DAEMON_SIGNATURE
, true, NULL
),
156 fAutoCheckRunner(NULL
)
158 fErrorLogWindow
= new ErrorLogWindow(BRect(200, 200, 500, 250),
159 B_TRANSLATE("Mail daemon status log"), B_TITLED_WINDOW
);
160 // install MimeTypes, attributes, indices, and the
161 // system beep add startup
164 add_system_beep_event("New E-mail");
168 MailDaemonApplication::~MailDaemonApplication()
170 delete fAutoCheckRunner
;
172 for (int32 i
= 0; i
< fQueries
.CountItems(); i
++)
173 delete fQueries
.ItemAt(i
);
175 while (!fAccounts
.empty()) {
176 _RemoveAccount(fAccounts
.begin()->second
);
177 fAccounts
.erase(fAccounts
.begin());
180 delete fLEDAnimation
;
181 delete fNotification
;
186 MailDaemonApplication::ReadyToRun()
188 InstallDeskbarIcon();
192 // Start auto mail check with a delay
193 BMessage
startAutoCheck(kMsgStartAutoCheck
);
194 BMessageRunner::StartSending(this, &startAutoCheck
,
195 kStartAutoCheckDelay
, 1);
197 _InitNewMessagesCount();
199 fCentralBeep
= false;
201 fNotification
= new BNotification(B_INFORMATION_NOTIFICATION
);
202 fNotification
->SetGroup(B_TRANSLATE("Mail status"));
203 fNotification
->SetMessageID("daemon_status");
204 _UpdateNewMessagesNotification();
207 be_roster
->GetAppInfo(B_MAIL_DAEMON_SIGNATURE
, &info
);
208 BBitmap
icon(BRect(0, 0, 32, 32), B_RGBA32
);
209 BNode
node(&info
.ref
);
210 BIconUtils::GetVectorIcon(&node
, "BEOS:ICON", &icon
);
211 fNotification
->SetIcon(&icon
);
213 fLEDAnimation
= new LEDAnimation();
214 SetPulseRate(1000000);
219 MailDaemonApplication::RefsReceived(BMessage
* message
)
222 for (int32 i
= 0; message
->FindRef("refs", i
, &ref
) == B_OK
; i
++) {
224 if (node
.InitCheck() != B_OK
)
228 if (node
.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID
, B_INT32_TYPE
, 0, &account
,
229 sizeof(account
)) < 0)
232 BInboundMailProtocol
* protocol
= _InboundProtocol(account
);
233 if (protocol
== NULL
)
237 BMessenger
* replyTo
= &target
;
238 if (message
->FindMessenger("target", &target
) != B_OK
)
241 protocol
->FetchBody(ref
, replyTo
);
247 MailDaemonApplication::MessageReceived(BMessage
* msg
)
250 case kMsgStartAutoCheck
:
251 _UpdateAutoCheckRunner();
255 // TODO: check whether internet is up and running!
256 // supposed to fall through
257 case kMsgCheckAndSend
: // check & send messages
258 msg
->what
= kMsgSendMessages
;
260 // supposed to fall trough
261 case kMsgCheckMessage
: // check messages
265 case kMsgSendMessages
: // send messages
266 SendPendingMessages(msg
);
269 case kMsgSettingsUpdated
:
270 fSettingsFile
.Reload();
271 _UpdateAutoCheckRunner();
274 case kMsgAccountsChanged
:
275 _ReloadAccounts(msg
);
278 case kMsgMarkMessageAsRead
:
280 int32 account
= msg
->FindInt32("account");
282 if (msg
->FindRef("ref", &ref
) != B_OK
)
284 read_flags read
= (read_flags
)msg
->FindInt32("read");
286 BInboundMailProtocol
* protocol
= _InboundProtocol(account
);
287 if (protocol
!= NULL
)
288 InboundMessenger(protocol
).MarkAsRead(ref
, read
);
296 case 'stwg': // Status window gone
298 BMessage
reply('mnuc');
299 reply
.AddInt32("num_new_messages", fNewMessages
);
301 while ((msg
= fFetchDoneRespondents
.RemoveItemAt(0))) {
302 msg
->SendReply(&reply
);
306 if (fAlertString
!= B_EMPTY_STRING
) {
307 fAlertString
.Truncate(fAlertString
.Length() - 1);
308 BAlert
* alert
= new BAlert(B_TRANSLATE("New Messages"),
309 fAlertString
.String(), "OK", NULL
, NULL
, B_WIDTH_AS_USUAL
);
310 alert
->SetFeel(B_NORMAL_WINDOW_FEEL
);
311 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
313 fAlertString
= B_EMPTY_STRING
;
317 system_beep("New E-mail");
318 fCentralBeep
= false;
324 if (fNewMessages
> 0)
328 case kMsgCountNewMessages
: // Number of new messages
330 BMessage
reply('mnuc'); // Mail New message Count
331 if (msg
->FindBool("wait_for_fetch_done")) {
332 fFetchDoneRespondents
.AddItem(DetachCurrentMessage());
336 reply
.AddInt32("num_new_messages", fNewMessages
);
337 msg
->SendReply(&reply
);
341 case 'mblk': // Mail Blink
342 if (fNewMessages
> 0)
343 fLEDAnimation
->Start();
346 case 'enda': // End Auto Check
347 delete fAutoCheckRunner
;
348 fAutoCheckRunner
= NULL
;
353 static BMessageFormat
format(B_TRANSLATE("{0, plural, "
354 "one{# new message} other{# new messages}} for %name\n"));
356 int32 numMessages
= msg
->FindInt32("num_messages");
357 fAlertString
.Truncate(0);
358 format
.Format(fAlertString
, numMessages
);
359 fAlertString
.ReplaceFirst("%name", msg
->FindString("name"));
365 int32 previousCount
= fNewMessages
;
367 int32 opcode
= msg
->GetInt32("opcode", -1);
369 case B_ENTRY_CREATED
:
372 case B_ENTRY_REMOVED
:
379 _UpdateNewMessagesNotification();
381 if (fSettingsFile
.ShowStatusWindow()
382 != B_MAIL_SHOW_STATUS_WINDOW_NEVER
383 && previousCount
< fNewMessages
) {
384 fNotification
->Send();
390 BApplication::MessageReceived(msg
);
397 MailDaemonApplication::Pulse()
399 bigtime_t idle
= idle_time();
400 if (fLEDAnimation
->IsRunning() && idle
< 100000)
401 fLEDAnimation
->Stop();
406 MailDaemonApplication::QuitRequested()
414 MailDaemonApplication::InstallDeskbarIcon()
418 if (!deskbar
.HasItem("mail_daemon")) {
422 status_t status
= roster
.FindApp(B_MAIL_DAEMON_SIGNATURE
, &ref
);
424 fprintf(stderr
, "Can't find application to tell deskbar: %s\n",
429 status
= deskbar
.AddItem(&ref
);
431 fprintf(stderr
, "Can't add deskbar replicant: %s\n",
440 MailDaemonApplication::RemoveDeskbarIcon()
443 if (deskbar
.HasItem("mail_daemon"))
444 deskbar
.RemoveItem("mail_daemon");
449 MailDaemonApplication::GetNewMessages(BMessage
* msg
)
452 if (msg
->FindInt32("account", &account
) == B_OK
&& account
>= 0) {
453 // Check the single requested account
454 BInboundMailProtocol
* protocol
= _InboundProtocol(account
);
455 if (protocol
!= NULL
)
456 InboundMessenger(protocol
).SynchronizeMessages();
460 // Check all accounts
462 AccountMap::const_iterator iterator
= fAccounts
.begin();
463 for (; iterator
!= fAccounts
.end(); iterator
++) {
464 BInboundMailProtocol
* protocol
= iterator
->second
.inboundProtocol
;
465 if (protocol
!= NULL
)
466 InboundMessenger(protocol
).SynchronizeMessages();
472 MailDaemonApplication::SendPendingMessages(BMessage
* msg
)
474 BVolumeRoster roster
;
476 std::map
<int32
, send_mails_info
> messages
;
477 int32 account
= msg
->GetInt32("account", -1);
479 if (!msg
->HasString("message_path")) {
480 while (roster
.GetNextVolume(&volume
) == B_OK
) {
482 query
.SetVolume(&volume
);
483 query
.PushAttr(B_MAIL_ATTR_FLAGS
);
484 query
.PushInt32(B_MAIL_PENDING
);
487 query
.PushAttr(B_MAIL_ATTR_FLAGS
);
488 query
.PushInt32(B_MAIL_PENDING
| B_MAIL_SAVE
);
492 query
.PushAttr(B_MAIL_ATTR_ACCOUNT_ID
);
493 query
.PushInt32(account
);
501 while (query
.GetNextEntry(&entry
) == B_OK
) {
502 if (_IsEntryInTrash(entry
))
506 while (node
.SetTo(&entry
) == B_BUSY
)
508 if (!_IsPending(node
))
511 if (node
.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID
, B_INT32_TYPE
, 0,
512 &account
, sizeof(int32
)) < 0)
515 _AddMessage(messages
[account
], entry
, node
);
519 // Send the requested message only
521 if (msg
->FindString("message_path", &path
) != B_OK
)
525 _AddMessage(messages
[account
], entry
, BNode(&entry
));
528 std::map
<int32
, send_mails_info
>::iterator iterator
= messages
.begin();
529 for (; iterator
!= messages
.end(); iterator
++) {
530 BOutboundMailProtocol
* protocol
= _OutboundProtocol(iterator
->first
);
531 if (protocol
== NULL
)
534 send_mails_info
& info
= iterator
->second
;
538 protocol
->SendMessages(info
.files
, info
.bytes
);
544 MailDaemonApplication::MakeMimeTypes(bool remakeMIMETypes
)
546 // Add MIME database entries for the e-mail file types we handle. Either
547 // do a full rebuild from nothing, or just add on the new attributes that
548 // we support which the regular BeOS mail daemon didn't have.
550 const uint8 kNTypes
= 2;
551 const char* types
[kNTypes
] = {"text/x-email", "text/x-partial-email"};
553 for (size_t i
= 0; i
< kNTypes
; i
++) {
555 BMimeType
mime(types
[i
]);
556 if (mime
.InitCheck() != B_OK
) {
557 fputs("could not init mime type.\n", stderr
);
561 if (!mime
.IsInstalled() || remakeMIMETypes
) {
562 // install the full mime type
566 // Set up the list of e-mail related attributes that Tracker will
567 // let you display in columns for e-mail messages.
568 addAttribute(info
, B_MAIL_ATTR_NAME
, "Name");
569 addAttribute(info
, B_MAIL_ATTR_SUBJECT
, "Subject");
570 addAttribute(info
, B_MAIL_ATTR_TO
, "To");
571 addAttribute(info
, B_MAIL_ATTR_CC
, "Cc");
572 addAttribute(info
, B_MAIL_ATTR_FROM
, "From");
573 addAttribute(info
, B_MAIL_ATTR_REPLY
, "Reply To");
574 addAttribute(info
, B_MAIL_ATTR_STATUS
, "Status");
575 addAttribute(info
, B_MAIL_ATTR_PRIORITY
, "Priority", B_STRING_TYPE
,
577 addAttribute(info
, B_MAIL_ATTR_WHEN
, "When", B_TIME_TYPE
, true,
579 addAttribute(info
, B_MAIL_ATTR_THREAD
, "Thread");
580 addAttribute(info
, B_MAIL_ATTR_ACCOUNT
, "Account", B_STRING_TYPE
,
582 addAttribute(info
, B_MAIL_ATTR_READ
, "Read", B_INT32_TYPE
,
584 mime
.SetAttrInfo(&info
);
587 mime
.SetShortDescription("E-mail");
588 mime
.SetLongDescription("Electronic Mail Message");
589 mime
.SetPreferredApp("application/x-vnd.Be-MAIL");
591 mime
.SetShortDescription("Partial E-mail");
592 mime
.SetLongDescription("A Partially Downloaded E-mail");
593 mime
.SetPreferredApp("application/x-vnd.Be-MAIL");
601 MailDaemonApplication::_InitAccounts()
603 BMailAccounts accounts
;
604 for (int i
= 0; i
< accounts
.CountAccounts(); i
++)
605 _InitAccount(*accounts
.AccountAt(i
));
610 MailDaemonApplication::_InitAccount(BMailAccountSettings
& settings
)
612 account_protocols account
;
615 if (settings
.IsInboundEnabled()) {
616 account
.inboundProtocol
= _CreateInboundProtocol(settings
,
617 account
.inboundImage
);
619 if (account
.inboundProtocol
!= NULL
) {
620 DefaultNotifier
* notifier
= new DefaultNotifier(settings
.Name(), true,
621 fErrorLogWindow
, fSettingsFile
.ShowStatusWindow());
622 account
.inboundProtocol
->SetMailNotifier(notifier
);
623 account
.inboundProtocol
->Run();
627 if (settings
.IsOutboundEnabled()) {
628 account
.outboundProtocol
= _CreateOutboundProtocol(settings
,
629 account
.outboundImage
);
631 if (account
.outboundProtocol
!= NULL
) {
632 DefaultNotifier
* notifier
= new DefaultNotifier(settings
.Name(), false,
633 fErrorLogWindow
, fSettingsFile
.ShowStatusWindow());
634 account
.outboundProtocol
->SetMailNotifier(notifier
);
635 account
.outboundProtocol
->Run();
638 printf("account name %s, id %i, in %p, out %p\n", settings
.Name(),
639 (int)settings
.AccountID(), account
.inboundProtocol
,
640 account
.outboundProtocol
);
642 if (account
.inboundProtocol
!= NULL
|| account
.outboundProtocol
!= NULL
)
643 fAccounts
[settings
.AccountID()] = account
;
648 MailDaemonApplication::_ReloadAccounts(BMessage
* message
)
652 message
->GetInfo("account", &typeFound
, &countFound
);
653 if (typeFound
!= B_INT32_TYPE
)
657 BMailAccounts accounts
;
659 for (int i
= 0; i
< countFound
; i
++) {
660 int32 account
= message
->FindInt32("account", i
);
661 AccountMap::iterator found
= fAccounts
.find(account
);
662 if (found
!= fAccounts
.end()) {
663 _RemoveAccount(found
->second
);
664 fAccounts
.erase(found
);
667 BMailAccountSettings
* settings
= accounts
.AccountByID(account
);
668 if (settings
!= NULL
)
669 _InitAccount(*settings
);
675 MailDaemonApplication::_RemoveAccount(const account_protocols
& account
)
677 if (account
.inboundProtocol
!= NULL
) {
678 account
.inboundProtocol
->Lock();
679 account
.inboundProtocol
->Quit();
681 unload_add_on(account
.inboundImage
);
684 if (account
.outboundProtocol
!= NULL
) {
685 account
.outboundProtocol
->Lock();
686 account
.outboundProtocol
->Quit();
688 unload_add_on(account
.outboundImage
);
693 BInboundMailProtocol
*
694 MailDaemonApplication::_CreateInboundProtocol(BMailAccountSettings
& settings
,
697 const entry_ref
& entry
= settings
.InboundAddOnRef();
698 BInboundMailProtocol
* (*instantiateProtocol
)(BMailAccountSettings
*);
701 image
= load_add_on(path
.Path());
705 if (get_image_symbol(image
, "instantiate_inbound_protocol",
706 B_SYMBOL_TYPE_TEXT
, (void**)&instantiateProtocol
) != B_OK
) {
707 unload_add_on(image
);
711 return instantiateProtocol(&settings
);
715 BOutboundMailProtocol
*
716 MailDaemonApplication::_CreateOutboundProtocol(BMailAccountSettings
& settings
,
719 const entry_ref
& entry
= settings
.OutboundAddOnRef();
720 BOutboundMailProtocol
* (*instantiateProtocol
)(BMailAccountSettings
*);
723 image
= load_add_on(path
.Path());
727 if (get_image_symbol(image
, "instantiate_outbound_protocol",
728 B_SYMBOL_TYPE_TEXT
, (void**)&instantiateProtocol
) != B_OK
) {
729 unload_add_on(image
);
733 return instantiateProtocol(&settings
);
737 BInboundMailProtocol
*
738 MailDaemonApplication::_InboundProtocol(int32 account
)
740 AccountMap::iterator found
= fAccounts
.find(account
);
741 if (found
== fAccounts
.end())
743 return found
->second
.inboundProtocol
;
747 BOutboundMailProtocol
*
748 MailDaemonApplication::_OutboundProtocol(int32 account
)
751 account
= BMailSettings().DefaultOutboundAccount();
753 AccountMap::iterator found
= fAccounts
.find(account
);
754 if (found
== fAccounts
.end())
756 return found
->second
.outboundProtocol
;
761 MailDaemonApplication::_InitNewMessagesCount()
764 BVolumeRoster roster
;
768 while (roster
.GetNextVolume(&volume
) == B_OK
) {
769 BQuery
* query
= new BQuery
;
771 query
->SetTarget(this);
772 query
->SetVolume(&volume
);
773 query
->PushAttr(B_MAIL_ATTR_STATUS
);
774 query
->PushString("New");
776 query
->PushAttr("BEOS:TYPE");
777 query
->PushString("text/x-email");
779 query
->PushAttr("BEOS:TYPE");
780 query
->PushString("text/x-partial-email");
783 query
->PushOp(B_AND
);
787 while (query
->GetNextEntry(&entry
) == B_OK
)
790 fQueries
.AddItem(query
);
796 MailDaemonApplication::_UpdateNewMessagesNotification()
799 if (fNewMessages
> 0) {
800 BMessageFormat
format(B_TRANSLATE(
801 "{0, plural, one{One new message} other{# new messages}}"));
803 format
.Format(title
, fNewMessages
);
805 title
= B_TRANSLATE("No new messages");
807 fNotification
->SetTitle(title
.String());
812 MailDaemonApplication::_UpdateAutoCheckRunner()
814 bigtime_t interval
= fSettingsFile
.AutoCheckInterval();
816 if (fAutoCheckRunner
!= NULL
) {
817 fAutoCheckRunner
->SetInterval(interval
);
818 fAutoCheckRunner
->SetCount(-1);
820 BMessage
update(kMsgAutoCheck
);
821 fAutoCheckRunner
= new BMessageRunner(be_app_messenger
, &update
,
824 // Send one right away -- the message runner will wait until the
825 // first interval has passed before sending a message
826 PostMessage(&update
);
829 delete fAutoCheckRunner
;
830 fAutoCheckRunner
= NULL
;
836 MailDaemonApplication::_AddMessage(send_mails_info
& info
, const BEntry
& entry
,
841 if (node
.GetSize(&size
) == B_OK
&& entry
.GetRef(&ref
) == B_OK
) {
842 info
.files
.AddRef("ref", &ref
);
848 /*! Work-around for a broken index that contains out-of-date information.
851 MailDaemonApplication::_IsPending(BNode
& node
)
854 if (node
.ReadAttr(B_MAIL_ATTR_FLAGS
, B_INT32_TYPE
, 0, &flags
, sizeof(int32
))
855 != (ssize_t
)sizeof(int32
))
858 return (flags
& B_MAIL_PENDING
) != 0;
863 MailDaemonApplication::_IsEntryInTrash(BEntry
& entry
)
868 BVolume
volume(ref
.device
);
870 if (volume
.InitCheck() != B_OK
871 || find_directory(B_TRASH_DIRECTORY
, &path
, false, &volume
) != B_OK
)
874 BDirectory
trash(path
.Path());
875 return trash
.Contains(&entry
);