1/*
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.
6 */
7
8
9#include "MailDaemonApplication.h"
10
11#include <stdio.h>
12#include <stdlib.h>
13#include <vector>
14
15#include <Beep.h>
16#include <Catalog.h>
17#include <Deskbar.h>
18#include <Directory.h>
19#include <Entry.h>
20#include <FindDirectory.h>
21#include <fs_index.h>
22#include <IconUtils.h>
23#include <NodeInfo.h>
24#include <NodeMonitor.h>
25#include <Notification.h>
26#include <Path.h>
27#include <Roster.h>
28#include <StringList.h>
29#include <StringFormat.h>
30#include <VolumeRoster.h>
31
32#include <E-mail.h>
33#include <MailDaemon.h>
34#include <MailMessage.h>
35#include <MailSettings.h>
36
37#include <MailPrivate.h>
38
39
40#undef B_TRANSLATION_CONTEXT
41#define B_TRANSLATION_CONTEXT "MailDaemon"
42
43
44static const uint32 kMsgStartAutoCheck = 'stAC';
45static const uint32 kMsgAutoCheck = 'moto';
46
47static const bigtime_t kStartAutoCheckDelay = 30000000;
48	// Wait 30 seconds before the first auto check - this usually lets the
49	// boot process settle down, and give the network a chance to come up.
50
51
52struct send_mails_info {
53	send_mails_info()
54	{
55		bytes = 0;
56	}
57
58	BMessage	files;
59	off_t		bytes;
60};
61
62
63class InboundMessenger : public BMessenger {
64public:
65	InboundMessenger(BInboundMailProtocol* protocol)
66		:
67		BMessenger(protocol)
68	{
69	}
70
71	status_t MarkAsRead(const entry_ref& ref, read_flags flag)
72	{
73		BMessage message(kMsgMarkMessageAsRead);
74		message.AddRef("ref", &ref);
75		message.AddInt32("read", flag);
76
77		return SendMessage(&message);
78	}
79
80	status_t SynchronizeMessages()
81	{
82		BMessage message(kMsgSyncMessages);
83		return SendMessage(&message);
84	}
85};
86
87
88// #pragma mark -
89
90
91static void
92makeIndices()
93{
94	const char* stringIndices[] = {
95		B_MAIL_ATTR_CC, B_MAIL_ATTR_FROM, B_MAIL_ATTR_NAME,
96		B_MAIL_ATTR_PRIORITY, B_MAIL_ATTR_REPLY, B_MAIL_ATTR_STATUS,
97		B_MAIL_ATTR_SUBJECT, B_MAIL_ATTR_TO, B_MAIL_ATTR_THREAD,
98		B_MAIL_ATTR_ACCOUNT, NULL
99	};
100
101	// add mail indices for all devices capable of querying
102
103	int32 cookie = 0;
104	dev_t device;
105	while ((device = next_dev(&cookie)) >= B_OK) {
106		fs_info info;
107		if (fs_stat_dev(device, &info) < 0
108			|| (info.flags & B_FS_HAS_QUERY) == 0)
109			continue;
110
111		for (int32 i = 0; stringIndices[i]; i++)
112			fs_create_index(device, stringIndices[i], B_STRING_TYPE, 0);
113
114		fs_create_index(device, "MAIL:draft", B_INT32_TYPE, 0);
115		fs_create_index(device, B_MAIL_ATTR_WHEN, B_INT32_TYPE, 0);
116		fs_create_index(device, B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0);
117		fs_create_index(device, B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0);
118		fs_create_index(device, B_MAIL_ATTR_READ, B_INT32_TYPE, 0);
119	}
120}
121
122
123static void
124addAttribute(BMessage& msg, const char* name, const char* publicName,
125	int32 type = B_STRING_TYPE, bool viewable = true, bool editable = false,
126	int32 width = 200)
127{
128	msg.AddString("attr:name", name);
129	msg.AddString("attr:public_name", publicName);
130	msg.AddInt32("attr:type", type);
131	msg.AddBool("attr:viewable", viewable);
132	msg.AddBool("attr:editable", editable);
133	msg.AddInt32("attr:width", width);
134	msg.AddInt32("attr:alignment", B_ALIGN_LEFT);
135}
136
137
138// #pragma mark -
139
140
141account_protocols::account_protocols()
142	:
143	inboundImage(-1),
144	inboundProtocol(NULL),
145	outboundImage(-1),
146	outboundProtocol(NULL)
147{
148}
149
150
151// #pragma mark -
152
153
154MailDaemonApplication::MailDaemonApplication()
155	:
156	BServer(B_MAIL_DAEMON_SIGNATURE, true, NULL),
157	fAutoCheckRunner(NULL)
158{
159	fErrorLogWindow = new ErrorLogWindow(BRect(200, 200, 500, 250),
160		B_TRANSLATE("Mail daemon status log"), B_TITLED_WINDOW);
161	// install MimeTypes, attributes, indices, and the
162	// system beep add startup
163	MakeMimeTypes();
164	makeIndices();
165	add_system_beep_event("New E-mail");
166}
167
168
169MailDaemonApplication::~MailDaemonApplication()
170{
171	delete fAutoCheckRunner;
172
173	for (int32 i = 0; i < fQueries.CountItems(); i++)
174		delete fQueries.ItemAt(i);
175
176	while (!fAccounts.empty()) {
177		_RemoveAccount(fAccounts.begin()->second);
178		fAccounts.erase(fAccounts.begin());
179	}
180
181	delete fLEDAnimation;
182	delete fNotification;
183}
184
185
186void
187MailDaemonApplication::ReadyToRun()
188{
189	InstallDeskbarIcon();
190
191	_InitAccounts();
192
193	// Start auto mail check with a delay
194	BMessage startAutoCheck(kMsgStartAutoCheck);
195	BMessageRunner::StartSending(this, &startAutoCheck,
196		kStartAutoCheckDelay, 1);
197
198	_InitNewMessagesCount();
199
200	fCentralBeep = false;
201
202	fNotification = new BNotification(B_INFORMATION_NOTIFICATION);
203	fNotification->SetGroup(B_TRANSLATE("Mail status"));
204	fNotification->SetMessageID("daemon_status");
205
206	BPath path;
207	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
208		path.Append("Mail/New E-mail");
209
210		entry_ref ref;
211		if (get_ref_for_path(path.Path(), &ref) == B_OK) {
212			fNotification->SetOnClickApp("application/x-vnd.Be-TRAK");
213			fNotification->AddOnClickRef(&ref);
214		}
215	}
216
217	_UpdateNewMessagesNotification();
218
219	app_info info;
220	be_roster->GetAppInfo(B_MAIL_DAEMON_SIGNATURE, &info);
221	BBitmap icon(BRect(0, 0, 32, 32), B_RGBA32);
222	BNode node(&info.ref);
223	BIconUtils::GetVectorIcon(&node, "BEOS:ICON", &icon);
224	fNotification->SetIcon(&icon);
225
226	fLEDAnimation = new LEDAnimation();
227	SetPulseRate(1000000);
228}
229
230
231void
232MailDaemonApplication::RefsReceived(BMessage* message)
233{
234	entry_ref ref;
235	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
236		BNode node(&ref);
237		if (node.InitCheck() != B_OK)
238			continue;
239
240		int32 account;
241		if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &account,
242				sizeof(account)) < 0)
243			continue;
244
245		BInboundMailProtocol* protocol = _InboundProtocol(account);
246		if (protocol == NULL)
247			continue;
248
249		BMessenger target;
250		BMessenger* replyTo = &target;
251		if (message->FindMessenger("target", &target) != B_OK)
252			replyTo = NULL;
253
254		protocol->FetchBody(ref, replyTo);
255	}
256}
257
258
259void
260MailDaemonApplication::MessageReceived(BMessage* msg)
261{
262	switch (msg->what) {
263		case kMsgStartAutoCheck:
264			_UpdateAutoCheckRunner();
265			break;
266
267		case kMsgAutoCheck:
268			// TODO: check whether internet is up and running!
269			// supposed to fall through
270		case kMsgCheckAndSend:	// check & send messages
271			msg->what = kMsgSendMessages;
272			PostMessage(msg);
273			// supposed to fall trough
274		case kMsgCheckMessage:	// check messages
275			GetNewMessages(msg);
276			break;
277
278		case kMsgSendMessages:	// send messages
279			SendPendingMessages(msg);
280			break;
281
282		case kMsgSettingsUpdated:
283			fSettingsFile.Reload();
284			_UpdateAutoCheckRunner();
285			break;
286
287		case kMsgAccountsChanged:
288			_ReloadAccounts(msg);
289			break;
290
291		case kMsgMarkMessageAsRead:
292		{
293			int32 account = msg->FindInt32("account");
294			entry_ref ref;
295			if (msg->FindRef("ref", &ref) != B_OK)
296				break;
297			read_flags read = (read_flags)msg->FindInt32("read");
298
299			BInboundMailProtocol* protocol = _InboundProtocol(account);
300			if (protocol != NULL)
301				InboundMessenger(protocol).MarkAsRead(ref, read);
302			break;
303		}
304
305		case kMsgFetchBody:
306			RefsReceived(msg);
307			break;
308
309		case 'stwg':	// Status window gone
310		{
311			BMessage reply('mnuc');
312			reply.AddInt32("num_new_messages", fNewMessages);
313
314			while ((msg = fFetchDoneRespondents.RemoveItemAt(0))) {
315				msg->SendReply(&reply);
316				delete msg;
317			}
318
319			if (fAlertString != B_EMPTY_STRING) {
320				fAlertString.Truncate(fAlertString.Length() - 1);
321				BAlert* alert = new BAlert(B_TRANSLATE("New Messages"),
322					fAlertString.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL);
323				alert->SetFeel(B_NORMAL_WINDOW_FEEL);
324				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
325				alert->Go(NULL);
326				fAlertString = B_EMPTY_STRING;
327			}
328
329			if (fCentralBeep) {
330				system_beep("New E-mail");
331				fCentralBeep = false;
332			}
333			break;
334		}
335
336		case 'mcbp':
337			if (fNewMessages > 0)
338				fCentralBeep = true;
339			break;
340
341		case kMsgCountNewMessages:	// Number of new messages
342		{
343			BMessage reply('mnuc');	// Mail New message Count
344			if (msg->FindBool("wait_for_fetch_done")) {
345				fFetchDoneRespondents.AddItem(DetachCurrentMessage());
346				break;
347			}
348
349			reply.AddInt32("num_new_messages", fNewMessages);
350			msg->SendReply(&reply);
351			break;
352		}
353
354		case 'mblk':	// Mail Blink
355			if (fNewMessages > 0)
356				fLEDAnimation->Start();
357			break;
358
359		case 'enda':	// End Auto Check
360			delete fAutoCheckRunner;
361			fAutoCheckRunner = NULL;
362			break;
363
364		case 'numg':
365		{
366			static BStringFormat format(B_TRANSLATE("{0, plural, "
367				"one{# new message} other{# new messages}} for %name\n"));
368
369			int32 numMessages = msg->FindInt32("num_messages");
370			fAlertString.Truncate(0);
371			format.Format(fAlertString, numMessages);
372			fAlertString.ReplaceFirst("%name", msg->FindString("name"));
373			break;
374		}
375
376		case B_QUERY_UPDATE:
377		{
378			int32 previousCount = fNewMessages;
379
380			int32 opcode;
381			msg->FindInt32("opcode", &opcode);
382
383			switch (opcode) {
384				case B_ENTRY_CREATED:
385				case B_ENTRY_REMOVED:
386				{
387					entry_ref ref;
388					msg->FindInt32("device", &ref.device);
389					msg->FindInt64("directory", &ref.directory);
390					BEntry entry(&ref);
391
392					if (!_IsEntryInTrash(entry)) {
393						if (opcode == B_ENTRY_CREATED)
394							fNewMessages++;
395						else
396							fNewMessages--;
397					}
398					break;
399				}
400				default:
401					return;
402			}
403
404			_UpdateNewMessagesNotification();
405
406			if (fSettingsFile.ShowStatusWindow()
407					!= B_MAIL_SHOW_STATUS_WINDOW_NEVER
408				&& previousCount < fNewMessages) {
409				fNotification->Send();
410			}
411			break;
412		}
413
414		default:
415			BApplication::MessageReceived(msg);
416			break;
417	}
418}
419
420
421void
422MailDaemonApplication::Pulse()
423{
424	bigtime_t idle = idle_time();
425	if (fLEDAnimation->IsRunning() && idle < 100000)
426		fLEDAnimation->Stop();
427}
428
429
430bool
431MailDaemonApplication::QuitRequested()
432{
433	RemoveDeskbarIcon();
434	return true;
435}
436
437
438void
439MailDaemonApplication::InstallDeskbarIcon()
440{
441	BDeskbar deskbar;
442
443	if (!deskbar.HasItem("mail_daemon")) {
444		BRoster roster;
445		entry_ref ref;
446
447		status_t status = roster.FindApp(B_MAIL_DAEMON_SIGNATURE, &ref);
448		if (status < B_OK) {
449			fprintf(stderr, "Can't find application to tell deskbar: %s\n",
450				strerror(status));
451			return;
452		}
453
454		status = deskbar.AddItem(&ref);
455		if (status < B_OK) {
456			fprintf(stderr, "Can't add deskbar replicant: %s\n",
457				strerror(status));
458			return;
459		}
460	}
461}
462
463
464void
465MailDaemonApplication::RemoveDeskbarIcon()
466{
467	BDeskbar deskbar;
468	if (deskbar.HasItem("mail_daemon"))
469		deskbar.RemoveItem("mail_daemon");
470}
471
472
473void
474MailDaemonApplication::GetNewMessages(BMessage* msg)
475{
476	int32 account = -1;
477	if (msg->FindInt32("account", &account) == B_OK && account >= 0) {
478		// Check the single requested account
479		BInboundMailProtocol* protocol = _InboundProtocol(account);
480		if (protocol != NULL)
481			InboundMessenger(protocol).SynchronizeMessages();
482		return;
483	}
484
485	// Check all accounts
486
487	AccountMap::const_iterator iterator = fAccounts.begin();
488	for (; iterator != fAccounts.end(); iterator++) {
489		BInboundMailProtocol* protocol = iterator->second.inboundProtocol;
490		if (protocol != NULL)
491			InboundMessenger(protocol).SynchronizeMessages();
492	}
493}
494
495
496void
497MailDaemonApplication::SendPendingMessages(BMessage* msg)
498{
499	BVolumeRoster roster;
500	BVolume volume;
501	std::map<int32, send_mails_info> messages;
502	int32 account = msg->GetInt32("account", -1);
503
504	if (!msg->HasString("message_path")) {
505		while (roster.GetNextVolume(&volume) == B_OK) {
506			BQuery query;
507			query.SetVolume(&volume);
508			query.PushAttr(B_MAIL_ATTR_FLAGS);
509			query.PushInt32(B_MAIL_PENDING);
510			query.PushOp(B_EQ);
511
512			query.PushAttr(B_MAIL_ATTR_FLAGS);
513			query.PushInt32(B_MAIL_PENDING | B_MAIL_SAVE);
514			query.PushOp(B_EQ);
515
516			if (account >= 0) {
517				query.PushAttr(B_MAIL_ATTR_ACCOUNT_ID);
518				query.PushInt32(account);
519				query.PushOp(B_EQ);
520				query.PushOp(B_AND);
521			}
522
523			query.PushOp(B_OR);
524			query.Fetch();
525			BEntry entry;
526			while (query.GetNextEntry(&entry) == B_OK) {
527				if (_IsEntryInTrash(entry))
528					continue;
529
530				BNode node;
531				while (node.SetTo(&entry) == B_BUSY)
532					snooze(1000);
533				if (!_IsPending(node))
534					continue;
535
536				if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0,
537						&account, sizeof(int32)) < 0)
538					account = -1;
539
540				_AddMessage(messages[account], entry, node);
541			}
542		}
543	} else {
544		// Send the requested message only
545		const char* path;
546		if (msg->FindString("message_path", &path) != B_OK)
547			return;
548
549		BEntry entry(path);
550		_AddMessage(messages[account], entry, BNode(&entry));
551	}
552
553	std::map<int32, send_mails_info>::iterator iterator = messages.begin();
554	for (; iterator != messages.end(); iterator++) {
555		BOutboundMailProtocol* protocol = _OutboundProtocol(iterator->first);
556		if (protocol == NULL)
557			continue;
558
559		send_mails_info& info = iterator->second;
560		if (info.bytes == 0)
561			continue;
562
563		protocol->SendMessages(info.files, info.bytes);
564	}
565}
566
567
568void
569MailDaemonApplication::MakeMimeTypes(bool remakeMIMETypes)
570{
571	// Add MIME database entries for the e-mail file types we handle.  Either
572	// do a full rebuild from nothing, or just add on the new attributes that
573	// we support which the regular BeOS mail daemon didn't have.
574
575	const uint8 kNTypes = 2;
576	const char* types[kNTypes] = {"text/x-email", "text/x-partial-email"};
577
578	for (size_t i = 0; i < kNTypes; i++) {
579		BMessage info;
580		BMimeType mime(types[i]);
581		if (mime.InitCheck() != B_OK) {
582			fputs("could not init mime type.\n", stderr);
583			return;
584		}
585
586		if (!mime.IsInstalled() || remakeMIMETypes) {
587			// install the full mime type
588			mime.Delete();
589			mime.Install();
590
591			// Set up the list of e-mail related attributes that Tracker will
592			// let you display in columns for e-mail messages.
593			addAttribute(info, B_MAIL_ATTR_NAME, "Name");
594			addAttribute(info, B_MAIL_ATTR_SUBJECT, "Subject");
595			addAttribute(info, B_MAIL_ATTR_TO, "To");
596			addAttribute(info, B_MAIL_ATTR_CC, "Cc");
597			addAttribute(info, B_MAIL_ATTR_FROM, "From");
598			addAttribute(info, B_MAIL_ATTR_REPLY, "Reply To");
599			addAttribute(info, B_MAIL_ATTR_STATUS, "Status");
600			addAttribute(info, B_MAIL_ATTR_PRIORITY, "Priority", B_STRING_TYPE,
601				true, true, 40);
602			addAttribute(info, B_MAIL_ATTR_WHEN, "When", B_TIME_TYPE, true,
603				false, 150);
604			addAttribute(info, B_MAIL_ATTR_THREAD, "Thread");
605			addAttribute(info, B_MAIL_ATTR_ACCOUNT, "Account", B_STRING_TYPE,
606				true, false, 100);
607			addAttribute(info, B_MAIL_ATTR_READ, "Read", B_INT32_TYPE,
608				true, false, 70);
609			mime.SetAttrInfo(&info);
610
611			if (i == 0) {
612				mime.SetShortDescription("E-mail");
613				mime.SetLongDescription("Electronic Mail Message");
614				mime.SetPreferredApp("application/x-vnd.Be-MAIL");
615			} else {
616				mime.SetShortDescription("Partial E-mail");
617				mime.SetLongDescription("A Partially Downloaded E-mail");
618				mime.SetPreferredApp("application/x-vnd.Be-MAIL");
619			}
620		}
621	}
622}
623
624
625void
626MailDaemonApplication::_InitAccounts()
627{
628	BMailAccounts accounts;
629	for (int i = 0; i < accounts.CountAccounts(); i++)
630		_InitAccount(*accounts.AccountAt(i));
631}
632
633
634void
635MailDaemonApplication::_InitAccount(BMailAccountSettings& settings)
636{
637	account_protocols account;
638
639	// inbound
640	if (settings.IsInboundEnabled()) {
641		account.inboundProtocol = _CreateInboundProtocol(settings,
642			account.inboundImage);
643	}
644	if (account.inboundProtocol != NULL) {
645		DefaultNotifier* notifier = new DefaultNotifier(settings.Name(), true,
646			fErrorLogWindow, fSettingsFile.ShowStatusWindow());
647		account.inboundProtocol->SetMailNotifier(notifier);
648		account.inboundProtocol->Run();
649	}
650
651	// outbound
652	if (settings.IsOutboundEnabled()) {
653		account.outboundProtocol = _CreateOutboundProtocol(settings,
654			account.outboundImage);
655	}
656	if (account.outboundProtocol != NULL) {
657		DefaultNotifier* notifier = new DefaultNotifier(settings.Name(), false,
658			fErrorLogWindow, fSettingsFile.ShowStatusWindow());
659		account.outboundProtocol->SetMailNotifier(notifier);
660		account.outboundProtocol->Run();
661	}
662
663	printf("account name %s, id %i, in %p, out %p\n", settings.Name(),
664		(int)settings.AccountID(), account.inboundProtocol,
665		account.outboundProtocol);
666
667	if (account.inboundProtocol != NULL || account.outboundProtocol != NULL)
668		fAccounts[settings.AccountID()] = account;
669}
670
671
672void
673MailDaemonApplication::_ReloadAccounts(BMessage* message)
674{
675	type_code typeFound;
676	int32 countFound;
677	message->GetInfo("account", &typeFound, &countFound);
678	if (typeFound != B_INT32_TYPE)
679		return;
680
681	// reload accounts
682	BMailAccounts accounts;
683
684	for (int i = 0; i < countFound; i++) {
685		int32 account = message->FindInt32("account", i);
686		AccountMap::iterator found = fAccounts.find(account);
687		if (found != fAccounts.end()) {
688			_RemoveAccount(found->second);
689			fAccounts.erase(found);
690		}
691
692		BMailAccountSettings* settings = accounts.AccountByID(account);
693		if (settings != NULL)
694			_InitAccount(*settings);
695	}
696}
697
698
699void
700MailDaemonApplication::_RemoveAccount(const account_protocols& account)
701{
702	if (account.inboundProtocol != NULL) {
703		account.inboundProtocol->Lock();
704		account.inboundProtocol->Quit();
705
706		unload_add_on(account.inboundImage);
707	}
708
709	if (account.outboundProtocol != NULL) {
710		account.outboundProtocol->Lock();
711		account.outboundProtocol->Quit();
712
713		unload_add_on(account.outboundImage);
714	}
715}
716
717
718BInboundMailProtocol*
719MailDaemonApplication::_CreateInboundProtocol(BMailAccountSettings& settings,
720	image_id& image)
721{
722	const entry_ref& entry = settings.InboundAddOnRef();
723	BInboundMailProtocol* (*instantiateProtocol)(BMailAccountSettings*);
724
725	BPath path(&entry);
726	image = load_add_on(path.Path());
727	if (image < 0)
728		return NULL;
729
730	if (get_image_symbol(image, "instantiate_inbound_protocol",
731			B_SYMBOL_TYPE_TEXT, (void**)&instantiateProtocol) != B_OK) {
732		unload_add_on(image);
733		image = -1;
734		return NULL;
735	}
736	return instantiateProtocol(&settings);
737}
738
739
740BOutboundMailProtocol*
741MailDaemonApplication::_CreateOutboundProtocol(BMailAccountSettings& settings,
742	image_id& image)
743{
744	const entry_ref& entry = settings.OutboundAddOnRef();
745	BOutboundMailProtocol* (*instantiateProtocol)(BMailAccountSettings*);
746
747	BPath path(&entry);
748	image = load_add_on(path.Path());
749	if (image < 0)
750		return NULL;
751
752	if (get_image_symbol(image, "instantiate_outbound_protocol",
753			B_SYMBOL_TYPE_TEXT, (void**)&instantiateProtocol) != B_OK) {
754		unload_add_on(image);
755		image = -1;
756		return NULL;
757	}
758	return instantiateProtocol(&settings);
759}
760
761
762BInboundMailProtocol*
763MailDaemonApplication::_InboundProtocol(int32 account)
764{
765	AccountMap::iterator found = fAccounts.find(account);
766	if (found == fAccounts.end())
767		return NULL;
768	return found->second.inboundProtocol;
769}
770
771
772BOutboundMailProtocol*
773MailDaemonApplication::_OutboundProtocol(int32 account)
774{
775	if (account < 0)
776		account = BMailSettings().DefaultOutboundAccount();
777
778	AccountMap::iterator found = fAccounts.find(account);
779	if (found == fAccounts.end())
780		return NULL;
781	return found->second.outboundProtocol;
782}
783
784
785void
786MailDaemonApplication::_InitNewMessagesCount()
787{
788	BVolume volume;
789	BVolumeRoster roster;
790
791	fNewMessages = 0;
792
793	while (roster.GetNextVolume(&volume) == B_OK) {
794		BQuery* query = new BQuery;
795
796		query->SetTarget(this);
797		query->SetVolume(&volume);
798		query->PushAttr(B_MAIL_ATTR_STATUS);
799		query->PushString("New");
800		query->PushOp(B_EQ);
801		query->PushAttr("BEOS:TYPE");
802		query->PushString("text/x-email");
803		query->PushOp(B_EQ);
804		query->PushAttr("BEOS:TYPE");
805		query->PushString("text/x-partial-email");
806		query->PushOp(B_EQ);
807		query->PushOp(B_OR);
808		query->PushOp(B_AND);
809		query->Fetch();
810
811		BEntry entry;
812		while (query->GetNextEntry(&entry) == B_OK) {
813			if (!_IsEntryInTrash(entry))
814				fNewMessages++;
815		}
816
817		fQueries.AddItem(query);
818	}
819}
820
821
822void
823MailDaemonApplication::_UpdateNewMessagesNotification()
824{
825	BString title;
826	if (fNewMessages > 0) {
827		BStringFormat format(B_TRANSLATE(
828			"{0, plural, one{One new message} other{# new messages}}"));
829
830		format.Format(title, fNewMessages);
831	} else
832		title = B_TRANSLATE("No new messages");
833
834	fNotification->SetTitle(title.String());
835}
836
837
838void
839MailDaemonApplication::_UpdateAutoCheckRunner()
840{
841	bigtime_t interval = fSettingsFile.AutoCheckInterval();
842	if (interval > 0) {
843		if (fAutoCheckRunner != NULL) {
844			fAutoCheckRunner->SetInterval(interval);
845			fAutoCheckRunner->SetCount(-1);
846		} else {
847			BMessage update(kMsgAutoCheck);
848			fAutoCheckRunner = new BMessageRunner(be_app_messenger, &update,
849				interval);
850
851			// Send one right away -- the message runner will wait until the
852			// first interval has passed before sending a message
853			PostMessage(&update);
854		}
855	} else {
856		delete fAutoCheckRunner;
857		fAutoCheckRunner = NULL;
858	}
859}
860
861
862void
863MailDaemonApplication::_AddMessage(send_mails_info& info, const BEntry& entry,
864	const BNode& node)
865{
866	entry_ref ref;
867	off_t size;
868	if (node.GetSize(&size) == B_OK && entry.GetRef(&ref) == B_OK) {
869		info.files.AddRef("ref", &ref);
870		info.bytes += size;
871	}
872}
873
874
875/*!	Work-around for a broken index that contains out-of-date information.
876*/
877/*static*/ bool
878MailDaemonApplication::_IsPending(BNode& node)
879{
880	int32 flags;
881	if (node.ReadAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, sizeof(int32))
882			!= (ssize_t)sizeof(int32))
883		return false;
884
885	return (flags & B_MAIL_PENDING) != 0;
886}
887
888
889/*static*/ bool
890MailDaemonApplication::_IsEntryInTrash(BEntry& entry)
891{
892	entry_ref ref;
893	entry.GetRef(&ref);
894
895	BVolume volume(ref.device);
896	BPath path;
897	if (volume.InitCheck() != B_OK
898		|| find_directory(B_TRASH_DIRECTORY, &path, false, &volume) != B_OK)
899		return false;
900
901	BDirectory trash(path.Path());
902	return trash.Contains(&entry);
903}
904