xref: /haiku/src/kits/mail/MailProtocol.cpp (revision 16d5c24e533eb14b7b8a99ee9f3ec9ba66335b1e)
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 				attr_info info;
175 				if (node.GetAttrInfo(attr_name.String(),&info) < B_OK) {
176 					if (runner->Chain()->MetaData()->FindFlat("manifest", manifest) == B_OK) {
177 						runner->Chain()->MetaData()->RemoveName("manifest");
178 						runner->Chain()->Save(); //--- Not having this code made an earlier version of MDR delete all my *(&(*& mail
179 					}
180 				} else {
181 					void *flatmanifest = malloc(info.size);
182 					node.ReadAttr(attr_name.String(),manifest->TypeCode(),0,flatmanifest,info.size);
183 					manifest->Unflatten(manifest->TypeCode(),flatmanifest,info.size);
184 					free(flatmanifest);
185 				}
186 			} else runner->ShowError("Error while reading account manifest: cannot use destination directory.");
187 		} else runner->ShowError("Error while reading account manifest: no destination directory exists.");
188 	}
189 
190 	uids_on_disk = new BStringList;
191 	BVolumeRoster volumes;
192 	BVolume volume;
193 	while (volumes.GetNextVolume(&volume) == B_OK) {
194 		BQuery fido;
195 		entry_ref entry;
196 
197 		fido.SetVolume(&volume);
198 		fido.PushAttr("MAIL:chain");
199 		fido.PushInt32(settings->FindInt32("chain"));
200 		fido.PushOp(B_EQ);
201 		fido.PushAttr("MAIL:pending_chain");
202 		fido.PushInt32(settings->FindInt32("chain"));
203 		fido.PushOp(B_EQ);
204 		fido.PushOp(B_OR);
205 		if (!settings->FindBool("leave_mail_on_server")) {
206 			fido.PushAttr("BEOS:type");
207 			fido.PushString("text/x-partial-email");
208 			fido.PushOp(B_EQ);
209 			fido.PushOp(B_AND);
210 		}
211 		fido.Fetch();
212 
213 		BString uid;
214 		while (fido.GetNextRef(&entry) == B_OK) {
215 			BNode(&entry).ReadAttrString("MAIL:unique_id",&uid);
216 			uids_on_disk->AddItem(uid.String());
217 		}
218 	}
219 
220 	(*manifest) |= (*uids_on_disk);
221 
222 	if (!settings->FindBool("login_and_do_nothing_else_of_any_importance")) {
223 		DeleteHandler *h = new DeleteHandler(this);
224 		runner->AddHandler(h);
225 		runner->PostMessage('DELE',h);
226 
227 		trash_monitor = new TrashMonitor(this,runner->Chain()->ID());
228 		runner->AddHandler(trash_monitor);
229 		runner->PostMessage('INIT',trash_monitor);
230 	}
231 }
232 
233 
234 BMailProtocol::~BMailProtocol()
235 {
236 	if (manifest != NULL) {
237 		BMessage *meta_data = runner->Chain()->MetaData();
238 		meta_data->RemoveName("manifest");
239 		BString attr_name = "MAIL:";
240 		attr_name << runner->Chain()->ID() << ":manifest"; //--- In case someone puts multiple accounts in the same directory
241 		if (meta_data->HasString("path")) {
242 			BNode node(meta_data->FindString("path"));
243 			if (node.InitCheck() >= B_OK) {
244 				node.RemoveAttr(attr_name.String());
245 				ssize_t manifestsize = manifest->FlattenedSize();
246 				void *flatmanifest = malloc(manifestsize);
247 				manifest->Flatten(flatmanifest,manifestsize);
248 				if (status_t err = node.WriteAttr(attr_name.String(),manifest->TypeCode(),0,flatmanifest,manifestsize) < B_OK) {
249 					BString error = "Error while saving account manifest: ";
250 					error << strerror(err);
251 					runner->ShowError(error.String());
252 				}
253 				free(flatmanifest);
254 			} else runner->ShowError("Error while saving account manifest: cannot use destination directory.");
255 		} else runner->ShowError("Error while saving account manifest: no destination directory exists.");
256 	}
257 	delete unique_ids;
258 	delete manifest;
259 	delete trash_monitor;
260 	delete uids_on_disk;
261 }
262 
263 
264 #define dump_stringlist(a) printf("BStringList %s:\n",#a); \
265 							for (int32 i = 0; i < (a)->CountItems(); i++)\
266 								puts((a)->ItemAt(i)); \
267 							puts("Done\n");
268 
269 status_t
270 BMailProtocol::ProcessMailMessage(BPositionIO **io_message, BEntry *io_entry,
271 	BMessage *io_headers, BPath *io_folder, const char *io_uid)
272 {
273 	status_t error;
274 
275 	if (io_uid == NULL)
276 		return B_ERROR;
277 
278 	error = GetMessage(io_uid, io_message, io_headers, io_folder);
279 	if (error < B_OK) {
280 		if (error != B_MAIL_END_FETCH) {
281 			MDR_DIALECT_CHOICE (
282 				error_alert("getting a message",error);,
283 				error_alert("新しいメッセージヲ取得中にエラーが発生しました",error);
284 			);
285 		}
286 		return B_MAIL_END_FETCH;
287 	}
288 
289 	runner->RegisterMessageCallback(new ManifestAdder(manifest, &uids_on_disk, io_uid));
290 	runner->RegisterMessageCallback(new MessageDeletion(this, io_uid, io_entry, !settings->FindBool("leave_mail_on_server")));
291 
292 	return B_OK;
293 }
294 
295 void BMailProtocol::CheckForDeletedMessages() {
296 	{
297 		//---Delete things from the manifest no longer on the server
298 		BStringList temp;
299 		manifest->NotThere(*unique_ids, &temp);
300 		(*manifest) -= temp;
301 	}
302 
303 	if (((settings->FindBool("delete_remote_when_local")) || !(settings->FindBool("leave_mail_on_server"))) && (manifest->CountItems() > 0)) {
304 		BStringList to_delete;
305 
306 		if (uids_on_disk == NULL) {
307 			BStringList query_contents;
308 			BVolumeRoster volumes;
309 			BVolume volume;
310 
311 			while (volumes.GetNextVolume(&volume) == B_OK) {
312 				BQuery fido;
313 				entry_ref entry;
314 
315 				fido.SetVolume(&volume);
316 				fido.PushAttr("MAIL:chain");
317 				fido.PushInt32(settings->FindInt32("chain"));
318 				fido.PushOp(B_EQ);
319 				fido.PushAttr("MAIL:pending_chain");
320 				fido.PushInt32(settings->FindInt32("chain"));
321 				fido.PushOp(B_EQ);
322 				fido.PushOp(B_OR);
323 				fido.Fetch();
324 
325 				BString uid;
326 				while (fido.GetNextRef(&entry) == B_OK) {
327 					BNode(&entry).ReadAttrString("MAIL:unique_id",&uid);
328 					query_contents.AddItem(uid.String());
329 				}
330 			}
331 
332 			query_contents.NotHere(*manifest,&to_delete);
333 		} else {
334 			uids_on_disk->NotHere(*manifest,&to_delete);
335 			delete uids_on_disk;
336 			uids_on_disk = NULL;
337 		}
338 
339 		for (int32 i = 0; i < to_delete.CountItems(); i++)
340 			DeleteMessage(to_delete[i]);
341 
342 		//*(unique_ids) -= to_delete; --- This line causes bad things to
343 		// happen (POP3 client uses the wrong indices to retrieve
344 		// messages).  Without it, bad things don't happen.
345 		*(manifest) -= to_delete;
346 	}
347 }
348 
349 void BMailProtocol::_ReservedProtocol1() {}
350 void BMailProtocol::_ReservedProtocol2() {}
351 void BMailProtocol::_ReservedProtocol3() {}
352 void BMailProtocol::_ReservedProtocol4() {}
353 void BMailProtocol::_ReservedProtocol5() {}
354 
355 
356 //	#pragma mark -
357 
358 
359 MessageDeletion::MessageDeletion(BMailProtocol *home, const char *uid,
360 	BEntry *io_entry, bool delete_anyway)
361 	:
362 	us(home),
363 	always(delete_anyway),
364 	message_id(uid), entry(io_entry)
365 {
366 }
367 
368 
369 void
370 MessageDeletion::Callback(status_t result)
371 {
372 	#if DEBUG
373 	 printf("Deleting %s\n", message_id);
374 	#endif
375 	BNode node(entry);
376 	BNodeInfo info(&node);
377 	char type[255];
378 	info.GetType(type);
379 	if ((always && strcmp(B_MAIL_TYPE,type) == 0) || result == B_MAIL_DISCARD)
380 		us->DeleteMessage(message_id);
381 }
382