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