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