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