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