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