xref: /haiku/src/kits/mail/MailProtocol.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
1 /* BMailProtocol - the base class for protocol filters
2 **
3 ** Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved.
4 */
5 
6 
7 #include <String.h>
8 #include <Alert.h>
9 #include <Query.h>
10 #include <VolumeRoster.h>
11 #include <StringList.h>
12 #include <E-mail.h>
13 #include <NodeInfo.h>
14 #include <Directory.h>
15 #include <NodeMonitor.h>
16 #include <Node.h>
17 
18 #include <stdio.h>
19 #include <fs_attr.h>
20 #include <stdlib.h>
21 #include <assert.h>
22 
23 #include <MDRLanguage.h>
24 
25 class BMailProtocol;
26 
27 #include <MailProtocol.h>
28 #include <ChainRunner.h>
29 #include <status.h>
30 
31 namespace {
32 
33 class ManifestAdder : public BMailChainCallback {
34 	public:
35 		ManifestAdder(BStringList *list,BStringList **list2, const char *id) : manifest(list), uids_on_disk(list2), uid(id) {}
36 		virtual void Callback(status_t result) {
37 			if (result == B_OK) {
38 				(*manifest) += uid;
39 				if (*uids_on_disk != NULL)
40 					(**uids_on_disk) += uid;
41 			}
42 		}
43 
44 	private:
45 		BStringList *manifest,**uids_on_disk;
46 		const char *uid;
47 };
48 
49 class MessageDeletion : public BMailChainCallback {
50 	public:
51 		MessageDeletion(BMailProtocol *home, const char *uid, BEntry *io_entry, bool delete_anyway);
52 		virtual void Callback(status_t result);
53 
54 	private:
55 		BMailProtocol *us;
56 		bool always;
57 		const char *message_id;
58 		BEntry *entry;
59 };
60 
61 } // unnamed namspace
62 
63 inline void
64 BMailProtocol::error_alert(const char *process, status_t error)
65 {
66 	BString string;
67 	MDR_DIALECT_CHOICE (
68 		string << "Error while " << process << ": " << strerror(error);
69 		runner->ShowError(string.String());
70 	,
71 		string << process << "中にエラーが発生しました: " << strerror(error);
72 		runner->ShowError(string.String());
73 	)
74 }
75 
76 
77 namespace {
78 
79 class DeleteHandler : public BHandler {
80 	public:
81 		DeleteHandler(BMailProtocol *a)
82 			: us(a)
83 		{
84 		}
85 
86 		void MessageReceived(BMessage *msg)
87 		{
88 			if ((msg->what == 'DELE') && (us->InitCheck() == B_OK)) {
89 				us->CheckForDeletedMessages();
90 				Looper()->RemoveHandler(this);
91 				delete this;
92 			}
93 		}
94 
95 	private:
96 		BMailProtocol *us;
97 };
98 
99 class TrashMonitor : public BHandler {
100 	public:
101 		TrashMonitor(BMailProtocol *a, int32 chain_id)
102 			: us(a), trash("/boot/home/Desktop/Trash"), messages_for_us(0), id(chain_id)
103 		{
104 		}
105 
106 		void MessageReceived(BMessage *msg)
107 		{
108 			if (msg->what == 'INIT') {
109 				node_ref to_watch;
110 				trash.GetNodeRef(&to_watch);
111 				watch_node(&to_watch,B_WATCH_DIRECTORY,this);
112 				return;
113 			}
114 			if ((msg->what == B_NODE_MONITOR) && (us->InitCheck() == B_OK)) {
115 			 	int32 opcode;
116 				if (msg->FindInt32("opcode",&opcode) < B_OK)
117 					return;
118 
119 				if (opcode == B_ENTRY_MOVED) {
120 					int64 node(msg->FindInt64("to directory"));
121 					dev_t device(msg->FindInt32("device"));
122 					node_ref item_ref;
123 					item_ref.node = node;
124 					item_ref.device = device;
125 
126 					BDirectory moved_to(&item_ref);
127 
128 					BNode trash_item(&moved_to,msg->FindString("name"));
129 					int32 chain;
130 					if (trash_item.ReadAttr("MAIL:chain",B_INT32_TYPE,0,&chain,sizeof(chain)) < B_OK)
131 						return;
132 
133 					if (chain == id)
134 						messages_for_us += (moved_to == trash) ? 1 : -1;
135 				}
136 
137 				if (messages_for_us < 0)
138 					messages_for_us = 0; // Guard against weirdness
139 
140 			 	if (trash.CountEntries() == 0) {
141 			 		if (messages_for_us > 0)
142 						us->CheckForDeletedMessages();
143 
144 					messages_for_us = 0;
145 				}
146 			}
147 		}
148 
149 	private:
150 		BMailProtocol *us;
151 		BDirectory trash;
152 		int32 messages_for_us;
153 		int32 id;
154 };
155 
156 } // unnamed namspace
157 
158 BMailProtocol::BMailProtocol(BMessage *settings, BMailChainRunner *run)
159 	: BMailFilter(settings),
160 	runner(run), trash_monitor(NULL), uids_on_disk(NULL)
161 {
162 	unique_ids = new BStringList;
163 	BMailProtocol::settings = settings;
164 
165 	manifest = new BStringList;
166 
167 	{
168 		BString attr_name = "MAIL:";
169 		attr_name << runner->Chain()->ID() << ":manifest"; //--- In case someone puts multiple accounts in the same directory
170 
171 		if (runner->Chain()->MetaData()->HasString("path")) {
172 			BNode node(runner->Chain()->MetaData()->FindString("path"));
173 			if (node.InitCheck() >= B_OK) {
174 				// We already have a directory so we can try to read metadata
175 				// from it. Note that it is normal for this directory not to
176 				// be founf on the first run as it will be later created by
177 				// the INBOX system filter.
178 				attr_info info;
179 				if (node.GetAttrInfo(attr_name.String(),&info) < B_OK) {
180 					if (runner->Chain()->MetaData()->FindFlat("manifest", manifest) == B_OK) {
181 						runner->Chain()->MetaData()->RemoveName("manifest");
182 						runner->Chain()->Save(); //--- Not having this code made an earlier version of MDR delete all my *(&(*& mail
183 					}
184 				} else {
185 					void *flatmanifest = malloc(info.size);
186 					node.ReadAttr(attr_name.String(),manifest->TypeCode(),0,flatmanifest,info.size);
187 					manifest->Unflatten(manifest->TypeCode(),flatmanifest,info.size);
188 					free(flatmanifest);
189 				}
190 			}
191 		} else runner->ShowError("Error while reading account manifest: no destination directory exists.");
192 	}
193 
194 	uids_on_disk = new BStringList;
195 	BVolumeRoster volumes;
196 	BVolume volume;
197 	while (volumes.GetNextVolume(&volume) == B_OK) {
198 		BQuery fido;
199 		entry_ref entry;
200 
201 		fido.SetVolume(&volume);
202 		fido.PushAttr("MAIL:chain");
203 		fido.PushInt32(settings->FindInt32("chain"));
204 		fido.PushOp(B_EQ);
205 		fido.PushAttr("MAIL:pending_chain");
206 		fido.PushInt32(settings->FindInt32("chain"));
207 		fido.PushOp(B_EQ);
208 		fido.PushOp(B_OR);
209 		if (!settings->FindBool("leave_mail_on_server")) {
210 			fido.PushAttr("BEOS:type");
211 			fido.PushString("text/x-partial-email");
212 			fido.PushOp(B_EQ);
213 			fido.PushOp(B_AND);
214 		}
215 		fido.Fetch();
216 
217 		BString uid;
218 		while (fido.GetNextRef(&entry) == B_OK) {
219 			BNode(&entry).ReadAttrString("MAIL:unique_id",&uid);
220 			uids_on_disk->AddItem(uid.String());
221 		}
222 	}
223 
224 	(*manifest) |= (*uids_on_disk);
225 
226 	if (!settings->FindBool("login_and_do_nothing_else_of_any_importance")) {
227 		DeleteHandler *h = new DeleteHandler(this);
228 		runner->AddHandler(h);
229 		runner->PostMessage('DELE',h);
230 
231 		trash_monitor = new TrashMonitor(this,runner->Chain()->ID());
232 		runner->AddHandler(trash_monitor);
233 		runner->PostMessage('INIT',trash_monitor);
234 	}
235 }
236 
237 
238 BMailProtocol::~BMailProtocol()
239 {
240 	if (manifest != NULL) {
241 		BMessage *meta_data = runner->Chain()->MetaData();
242 		meta_data->RemoveName("manifest");
243 		BString attr_name = "MAIL:";
244 		attr_name << runner->Chain()->ID() << ":manifest"; //--- In case someone puts multiple accounts in the same directory
245 		if (meta_data->HasString("path")) {
246 			BNode node(meta_data->FindString("path"));
247 			if (node.InitCheck() >= B_OK) {
248 				node.RemoveAttr(attr_name.String());
249 				ssize_t manifestsize = manifest->FlattenedSize();
250 				void *flatmanifest = malloc(manifestsize);
251 				manifest->Flatten(flatmanifest,manifestsize);
252 				if (status_t err = node.WriteAttr(attr_name.String(),manifest->TypeCode(),0,flatmanifest,manifestsize) < B_OK) {
253 					BString error = "Error while saving account manifest: ";
254 					error << strerror(err);
255 					runner->ShowError(error.String());
256 				}
257 				free(flatmanifest);
258 			} else runner->ShowError("Error while saving account manifest: cannot use destination directory.");
259 		} else runner->ShowError("Error while saving account manifest: no destination directory exists.");
260 	}
261 	delete unique_ids;
262 	delete manifest;
263 	delete trash_monitor;
264 	delete uids_on_disk;
265 }
266 
267 
268 #define dump_stringlist(a) printf("BStringList %s:\n",#a); \
269 							for (int32 i = 0; i < (a)->CountItems(); i++)\
270 								puts((a)->ItemAt(i)); \
271 							puts("Done\n");
272 
273 status_t
274 BMailProtocol::ProcessMailMessage(BPositionIO **io_message, BEntry *io_entry,
275 	BMessage *io_headers, BPath *io_folder, const char *io_uid)
276 {
277 	status_t error;
278 
279 	if (io_uid == NULL)
280 		return B_ERROR;
281 
282 	error = GetMessage(io_uid, io_message, io_headers, io_folder);
283 	if (error < B_OK) {
284 		if (error != B_MAIL_END_FETCH) {
285 			MDR_DIALECT_CHOICE (
286 				error_alert("getting a message",error);,
287 				error_alert("新しいメッセージヲ取得中にエラーが発生しました",error);
288 			);
289 		}
290 		return B_MAIL_END_FETCH;
291 	}
292 
293 	runner->RegisterMessageCallback(new ManifestAdder(manifest, &uids_on_disk, io_uid));
294 	runner->RegisterMessageCallback(new MessageDeletion(this, io_uid, io_entry, !settings->FindBool("leave_mail_on_server")));
295 
296 	return B_OK;
297 }
298 
299 void BMailProtocol::CheckForDeletedMessages() {
300 	{
301 		//---Delete things from the manifest no longer on the server
302 		BStringList temp;
303 		manifest->NotThere(*unique_ids, &temp);
304 		(*manifest) -= temp;
305 	}
306 
307 	if (((settings->FindBool("delete_remote_when_local")) || !(settings->FindBool("leave_mail_on_server"))) && (manifest->CountItems() > 0)) {
308 		BStringList to_delete;
309 
310 		if (uids_on_disk == NULL) {
311 			BStringList query_contents;
312 			BVolumeRoster volumes;
313 			BVolume volume;
314 
315 			while (volumes.GetNextVolume(&volume) == B_OK) {
316 				BQuery fido;
317 				entry_ref entry;
318 
319 				fido.SetVolume(&volume);
320 				fido.PushAttr("MAIL:chain");
321 				fido.PushInt32(settings->FindInt32("chain"));
322 				fido.PushOp(B_EQ);
323 				fido.PushAttr("MAIL:pending_chain");
324 				fido.PushInt32(settings->FindInt32("chain"));
325 				fido.PushOp(B_EQ);
326 				fido.PushOp(B_OR);
327 				fido.Fetch();
328 
329 				BString uid;
330 				while (fido.GetNextRef(&entry) == B_OK) {
331 					BNode(&entry).ReadAttrString("MAIL:unique_id",&uid);
332 					query_contents.AddItem(uid.String());
333 				}
334 			}
335 
336 			query_contents.NotHere(*manifest,&to_delete);
337 		} else {
338 			uids_on_disk->NotHere(*manifest,&to_delete);
339 			delete uids_on_disk;
340 			uids_on_disk = NULL;
341 		}
342 
343 		for (int32 i = 0; i < to_delete.CountItems(); i++)
344 			DeleteMessage(to_delete[i]);
345 
346 		//*(unique_ids) -= to_delete; --- This line causes bad things to
347 		// happen (POP3 client uses the wrong indices to retrieve
348 		// messages).  Without it, bad things don't happen.
349 		*(manifest) -= to_delete;
350 	}
351 }
352 
353 void BMailProtocol::_ReservedProtocol1() {}
354 void BMailProtocol::_ReservedProtocol2() {}
355 void BMailProtocol::_ReservedProtocol3() {}
356 void BMailProtocol::_ReservedProtocol4() {}
357 void BMailProtocol::_ReservedProtocol5() {}
358 
359 
360 //	#pragma mark -
361 
362 
363 MessageDeletion::MessageDeletion(BMailProtocol *home, const char *uid,
364 	BEntry *io_entry, bool delete_anyway)
365 	:
366 	us(home),
367 	always(delete_anyway),
368 	message_id(uid), entry(io_entry)
369 {
370 }
371 
372 
373 void
374 MessageDeletion::Callback(status_t result)
375 {
376 	#if DEBUG
377 	 printf("Deleting %s\n", message_id);
378 	#endif
379 	BNode node(entry);
380 	BNodeInfo info(&node);
381 	char type[255];
382 	info.GetType(type);
383 	if ((always && strcmp(B_MAIL_TYPE,type) == 0) || result == B_MAIL_DISCARD)
384 		us->DeleteMessage(message_id);
385 }
386