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