1 /*
2  * e-autosave-utils.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include "e-autosave-utils.h"
21 
22 #include <errno.h>
23 #include <glib/gstdio.h>
24 #include <camel/camel.h>
25 
26 #include <e-util/e-util.h>
27 
28 #define SNAPSHOT_FILE_KEY	"e-composer-snapshot-file"
29 #define SNAPSHOT_FILE_PREFIX	".evolution-composer.autosave"
30 #define SNAPSHOT_FILE_SEED	SNAPSHOT_FILE_PREFIX "-XXXXXX"
31 
32 typedef struct _LoadContext LoadContext;
33 typedef struct _SaveContext SaveContext;
34 
35 struct _LoadContext {
36 	EMsgComposer *composer;
37 };
38 
39 struct _SaveContext {
40 	GCancellable *cancellable;
41 	GFile *snapshot_file;
42 };
43 
44 static void
load_context_free(LoadContext * context)45 load_context_free (LoadContext *context)
46 {
47 	if (context->composer != NULL)
48 		g_object_unref (context->composer);
49 
50 	g_slice_free (LoadContext, context);
51 }
52 
53 static void
save_context_free(SaveContext * context)54 save_context_free (SaveContext *context)
55 {
56 	g_clear_object (&context->cancellable);
57 	g_clear_object (&context->snapshot_file);
58 
59 	g_slice_free (SaveContext, context);
60 }
61 
62 static void
delete_snapshot_file(GFile * snapshot_file)63 delete_snapshot_file (GFile *snapshot_file)
64 {
65 	g_file_delete (snapshot_file, NULL, NULL);
66 	g_object_unref (snapshot_file);
67 }
68 
69 static GFile *
create_snapshot_file(EMsgComposer * composer,GError ** error)70 create_snapshot_file (EMsgComposer *composer,
71                       GError **error)
72 {
73 	GFile *snapshot_file;
74 	const gchar *user_data_dir;
75 	gchar *path;
76 	gint fd;
77 
78 	snapshot_file = e_composer_get_snapshot_file (composer);
79 
80 	if (G_IS_FILE (snapshot_file))
81 		return snapshot_file;
82 
83 	user_data_dir = e_get_user_data_dir ();
84 	path = g_build_filename (user_data_dir, SNAPSHOT_FILE_SEED, NULL);
85 
86 	/* g_mkstemp() modifies the XXXXXX part of the
87 	 * template string to form the actual filename. */
88 	errno = 0;
89 	fd = g_mkstemp (path);
90 	if (fd == -1) {
91 		g_set_error (
92 			error, G_FILE_ERROR,
93 			g_file_error_from_errno (errno),
94 			"%s", g_strerror (errno));
95 		g_free (path);
96 		return NULL;
97 	}
98 
99 	close (fd);
100 
101 	snapshot_file = g_file_new_for_path (path);
102 
103 	/* Save the GFile for subsequent snapshots. */
104 	g_object_set_data_full (
105 		G_OBJECT (composer),
106 		SNAPSHOT_FILE_KEY, snapshot_file,
107 		(GDestroyNotify) delete_snapshot_file);
108 
109 	g_free (path);
110 
111 	return snapshot_file;
112 }
113 
114 typedef struct _CreateComposerData {
115 	GSimpleAsyncResult *simple;
116 	LoadContext *context;
117 	CamelMimeMessage *message;
118 	GFile *snapshot_file;
119 } CreateComposerData;
120 
121 static void
autosave_composer_created_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)122 autosave_composer_created_cb (GObject *source_object,
123 			      GAsyncResult *result,
124 			      gpointer user_data)
125 {
126 	CreateComposerData *ccd = user_data;
127 	EMsgComposer *composer;
128 	GError *error = NULL;
129 
130 	composer = e_msg_composer_new_finish (result, &error);
131 	if (error) {
132 		g_warning ("%s: Failed to create msg composer: %s", G_STRFUNC, error->message);
133 		g_simple_async_result_take_error (ccd->simple, error);
134 	} else {
135 		e_msg_composer_setup_with_message (composer, ccd->message, TRUE, NULL, NULL, NULL, NULL);
136 		g_object_set_data_full (
137 			G_OBJECT (composer),
138 			SNAPSHOT_FILE_KEY, g_object_ref (ccd->snapshot_file),
139 			(GDestroyNotify) delete_snapshot_file);
140 		ccd->context->composer = g_object_ref_sink (composer);
141 	}
142 
143 	g_simple_async_result_complete (ccd->simple);
144 
145 	g_clear_object (&ccd->simple);
146 	g_clear_object (&ccd->message);
147 	g_clear_object (&ccd->snapshot_file);
148 	g_slice_free (CreateComposerData, ccd);
149 }
150 
151 static void
load_snapshot_loaded_cb(GFile * snapshot_file,GAsyncResult * result,GSimpleAsyncResult * simple)152 load_snapshot_loaded_cb (GFile *snapshot_file,
153                          GAsyncResult *result,
154                          GSimpleAsyncResult *simple)
155 {
156 	EShell *shell;
157 	GObject *object;
158 	LoadContext *context;
159 	CamelMimeMessage *message;
160 	CamelStream *camel_stream;
161 	gchar *contents = NULL;
162 	gsize length;
163 	CreateComposerData *ccd;
164 	GError *local_error = NULL;
165 
166 	context = g_simple_async_result_get_op_res_gpointer (simple);
167 
168 	g_file_load_contents_finish (
169 		snapshot_file, result, &contents, &length, NULL, &local_error);
170 
171 	if (local_error != NULL) {
172 		g_warn_if_fail (contents == NULL);
173 		g_simple_async_result_take_error (simple, local_error);
174 		g_simple_async_result_complete (simple);
175 		g_object_unref (simple);
176 		return;
177 	}
178 
179 	/* Create an in-memory buffer for the MIME parser to read from.
180 	 * We have to do this because CamelStreams are syncrhonous-only,
181 	 * and feeding the parser a direct file stream would block. */
182 	message = camel_mime_message_new ();
183 	camel_stream = camel_stream_mem_new_with_buffer (contents, length);
184 	camel_data_wrapper_construct_from_stream_sync (
185 		CAMEL_DATA_WRAPPER (message), camel_stream, NULL, &local_error);
186 	g_object_unref (camel_stream);
187 	g_free (contents);
188 
189 	if (local_error != NULL) {
190 		g_simple_async_result_take_error (simple, local_error);
191 		g_simple_async_result_complete (simple);
192 		g_object_unref (message);
193 		g_object_unref (simple);
194 		return;
195 	}
196 
197 	/* g_async_result_get_source_object() returns a new reference. */
198 	object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
199 
200 	/* Create a new composer window from the loaded message and
201 	 * restore its snapshot file so it continues auto-saving to
202 	 * the same file. */
203 	shell = E_SHELL (object);
204 
205 	ccd = g_slice_new0 (CreateComposerData);
206 	ccd->simple = simple;
207 	ccd->context = context;
208 	ccd->message = message;
209 	ccd->snapshot_file = g_object_ref (snapshot_file);
210 
211 	e_msg_composer_new (shell, autosave_composer_created_cb, ccd);
212 
213 	g_object_unref (object);
214 }
215 
216 static void
save_snapshot_splice_cb(CamelDataWrapper * data_wrapper,GAsyncResult * result,GSimpleAsyncResult * simple)217 save_snapshot_splice_cb (CamelDataWrapper *data_wrapper,
218                          GAsyncResult *result,
219                          GSimpleAsyncResult *simple)
220 {
221 	GError *local_error = NULL;
222 
223 	g_return_if_fail (CAMEL_IS_DATA_WRAPPER (data_wrapper));
224 	g_return_if_fail (g_task_is_valid (result, data_wrapper));
225 
226 	g_task_propagate_int (G_TASK (result), &local_error);
227 
228 	if (local_error != NULL)
229 		g_simple_async_result_take_error (simple, local_error);
230 
231 	g_simple_async_result_complete (simple);
232 	g_object_unref (simple);
233 }
234 
235 static void
write_message_to_stream_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)236 write_message_to_stream_thread (GTask *task,
237 				gpointer source_object,
238 				gpointer task_data,
239 				GCancellable *cancellable)
240 {
241 	GFileOutputStream *file_output_stream;
242 	GOutputStream *output_stream;
243 	GFile *snapshot_file;
244 	gssize bytes_written;
245 	GError *local_error = NULL;
246 
247 	snapshot_file = task_data;
248 
249 	file_output_stream = g_file_replace (snapshot_file, NULL, FALSE,
250 		G_FILE_CREATE_PRIVATE, cancellable, &local_error);
251 
252 	if (!file_output_stream) {
253 		if (local_error)
254 			g_task_return_error (task, local_error);
255 		else
256 			g_task_return_int (task, 0);
257 
258 		return;
259 	}
260 
261 	output_stream = G_OUTPUT_STREAM (file_output_stream);
262 
263 	bytes_written = camel_data_wrapper_decode_to_output_stream_sync (
264 		CAMEL_DATA_WRAPPER (source_object),
265 		output_stream, cancellable, &local_error);
266 
267 	g_output_stream_close (output_stream, cancellable, local_error ? NULL : &local_error);
268 
269 	g_object_unref (file_output_stream);
270 
271 	if (local_error != NULL) {
272 		g_task_return_error (task, local_error);
273 	} else {
274 		g_task_return_int (task, bytes_written);
275 	}
276 }
277 
278 static void
save_snapshot_get_message_cb(EMsgComposer * composer,GAsyncResult * result,GSimpleAsyncResult * simple)279 save_snapshot_get_message_cb (EMsgComposer *composer,
280                               GAsyncResult *result,
281                               GSimpleAsyncResult *simple)
282 {
283 	SaveContext *context;
284 	CamelMimeMessage *message;
285 	GTask *task;
286 	GError *local_error = NULL;
287 
288 	context = g_simple_async_result_get_op_res_gpointer (simple);
289 
290 	message = e_msg_composer_get_message_draft_finish (
291 		composer, result, &local_error);
292 
293 	if (local_error != NULL) {
294 		g_warn_if_fail (message == NULL);
295 		g_simple_async_result_take_error (simple, local_error);
296 		g_simple_async_result_complete (simple);
297 		g_object_unref (simple);
298 		return;
299 	}
300 
301 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
302 
303 	task = g_task_new (message, context->cancellable, (GAsyncReadyCallback) save_snapshot_splice_cb, simple);
304 
305 	g_task_set_task_data (task, g_object_ref (context->snapshot_file), g_object_unref);
306 
307 	g_task_run_in_thread (task, write_message_to_stream_thread);
308 
309 	g_object_unref (task);
310 	g_object_unref (message);
311 }
312 
313 static EMsgComposer *
composer_registry_lookup(GQueue * registry,const gchar * basename)314 composer_registry_lookup (GQueue *registry,
315                           const gchar *basename)
316 {
317 	GList *iter;
318 
319 	/* Find the composer with the given snapshot filename. */
320 	for (iter = registry->head; iter != NULL; iter = iter->next) {
321 		EMsgComposer *composer;
322 		GFile *snapshot_file;
323 		gchar *snapshot_name;
324 
325 		composer = E_MSG_COMPOSER (iter->data);
326 		snapshot_file = e_composer_get_snapshot_file (composer);
327 
328 		if (!G_IS_FILE (snapshot_file))
329 			continue;
330 
331 		snapshot_name = g_file_get_basename (snapshot_file);
332 		if (g_strcmp0 (basename, snapshot_name) == 0) {
333 			g_free (snapshot_name);
334 			return composer;
335 		}
336 
337 		g_free (snapshot_name);
338 	}
339 
340 	return NULL;
341 }
342 
343 GList *
e_composer_find_orphans(GQueue * registry,GError ** error)344 e_composer_find_orphans (GQueue *registry,
345                          GError **error)
346 {
347 	GDir *dir;
348 	const gchar *dirname;
349 	const gchar *basename;
350 	GList *orphans = NULL;
351 
352 	g_return_val_if_fail (registry != NULL, NULL);
353 
354 	dirname = e_get_user_data_dir ();
355 	dir = g_dir_open (dirname, 0, error);
356 	if (dir == NULL)
357 		return NULL;
358 
359 	/* Scan the user data directory for snapshot files. */
360 	while ((basename = g_dir_read_name (dir)) != NULL) {
361 		const gchar *errmsg;
362 		gchar *filename;
363 		struct stat st;
364 
365 		/* Is this a snapshot file? */
366 		if (!g_str_has_prefix (basename, SNAPSHOT_FILE_PREFIX))
367 			continue;
368 
369 		/* Is this an orphaned snapshot file? */
370 		if (composer_registry_lookup (registry, basename) != NULL)
371 			continue;
372 
373 		filename = g_build_filename (dirname, basename, NULL);
374 
375 		/* Try to examine the snapshot file.  Failure here
376 		 * is non-fatal; just emit a warning and move on. */
377 		errno = 0;
378 		if (g_stat (filename, &st) < 0) {
379 			errmsg = g_strerror (errno);
380 			g_warning ("%s: %s", filename, errmsg);
381 			g_free (filename);
382 			continue;
383 		}
384 
385 		/* If the file is empty, delete it.  Failure here
386 		 * is non-fatal; just emit a warning and move on. */
387 		if (st.st_size == 0) {
388 			errno = 0;
389 			if (g_unlink (filename) < 0) {
390 				errmsg = g_strerror (errno);
391 				g_warning ("%s: %s", filename, errmsg);
392 			}
393 			g_free (filename);
394 			continue;
395 		}
396 
397 		orphans = g_list_prepend (
398 			orphans, g_file_new_for_path (filename));
399 
400 		g_free (filename);
401 	}
402 
403 	g_dir_close (dir);
404 
405 	return g_list_reverse (orphans);
406 }
407 
408 void
e_composer_load_snapshot(EShell * shell,GFile * snapshot_file,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)409 e_composer_load_snapshot (EShell *shell,
410                           GFile *snapshot_file,
411                           GCancellable *cancellable,
412                           GAsyncReadyCallback callback,
413                           gpointer user_data)
414 {
415 	GSimpleAsyncResult *simple;
416 	LoadContext *context;
417 
418 	g_return_if_fail (E_IS_SHELL (shell));
419 	g_return_if_fail (G_IS_FILE (snapshot_file));
420 
421 	context = g_slice_new0 (LoadContext);
422 
423 	simple = g_simple_async_result_new (
424 		G_OBJECT (shell), callback, user_data,
425 		e_composer_load_snapshot);
426 
427 	g_simple_async_result_set_check_cancellable (simple, cancellable);
428 
429 	g_simple_async_result_set_op_res_gpointer (
430 		simple, context, (GDestroyNotify) load_context_free);
431 
432 	g_file_load_contents_async (
433 		snapshot_file, cancellable, (GAsyncReadyCallback)
434 		load_snapshot_loaded_cb, simple);
435 }
436 
437 EMsgComposer *
e_composer_load_snapshot_finish(EShell * shell,GAsyncResult * result,GError ** error)438 e_composer_load_snapshot_finish (EShell *shell,
439                                  GAsyncResult *result,
440                                  GError **error)
441 {
442 	GSimpleAsyncResult *simple;
443 	LoadContext *context;
444 
445 	g_return_val_if_fail (
446 		g_simple_async_result_is_valid (
447 			result, G_OBJECT (shell),
448 			e_composer_load_snapshot), NULL);
449 
450 	simple = G_SIMPLE_ASYNC_RESULT (result);
451 	context = g_simple_async_result_get_op_res_gpointer (simple);
452 
453 	if (g_simple_async_result_propagate_error (simple, error))
454 		return NULL;
455 
456 	g_return_val_if_fail (E_IS_MSG_COMPOSER (context->composer), NULL);
457 
458 	return g_object_ref (context->composer);
459 }
460 
461 void
e_composer_save_snapshot(EMsgComposer * composer,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)462 e_composer_save_snapshot (EMsgComposer *composer,
463                           GCancellable *cancellable,
464                           GAsyncReadyCallback callback,
465                           gpointer user_data)
466 {
467 	GSimpleAsyncResult *simple;
468 	SaveContext *context;
469 	GFile *snapshot_file;
470 	GError *local_error = NULL;
471 
472 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
473 
474 	context = g_slice_new0 (SaveContext);
475 
476 	if (G_IS_CANCELLABLE (cancellable))
477 		context->cancellable = g_object_ref (cancellable);
478 
479 	simple = g_simple_async_result_new (
480 		G_OBJECT (composer), callback, user_data,
481 		e_composer_save_snapshot);
482 
483 	g_simple_async_result_set_check_cancellable (simple, cancellable);
484 
485 	g_simple_async_result_set_op_res_gpointer (
486 		simple, context, (GDestroyNotify) save_context_free);
487 
488 	snapshot_file = e_composer_get_snapshot_file (composer);
489 
490 	if (!G_IS_FILE (snapshot_file))
491 		snapshot_file = create_snapshot_file (composer, &local_error);
492 
493 	if (local_error != NULL) {
494 		g_warn_if_fail (snapshot_file == NULL);
495 		g_simple_async_result_take_error (simple, local_error);
496 		g_simple_async_result_complete (simple);
497 		g_object_unref (simple);
498 		return;
499 	}
500 
501 	g_return_if_fail (G_IS_FILE (snapshot_file));
502 
503 	context->snapshot_file = g_object_ref (snapshot_file);
504 
505 	e_msg_composer_get_message_draft (
506 		composer, G_PRIORITY_DEFAULT,
507 		context->cancellable, (GAsyncReadyCallback)
508 		save_snapshot_get_message_cb, simple);
509 }
510 
511 gboolean
e_composer_save_snapshot_finish(EMsgComposer * composer,GAsyncResult * result,GError ** error)512 e_composer_save_snapshot_finish (EMsgComposer *composer,
513                                  GAsyncResult *result,
514                                  GError **error)
515 {
516 	GSimpleAsyncResult *simple;
517 
518 	g_return_val_if_fail (
519 		g_simple_async_result_is_valid (
520 			result, G_OBJECT (composer),
521 			e_composer_save_snapshot), FALSE);
522 
523 	simple = G_SIMPLE_ASYNC_RESULT (result);
524 
525 	/* Success is assumed unless a GError is set. */
526 	return !g_simple_async_result_propagate_error (simple, error);
527 }
528 
529 GFile *
e_composer_get_snapshot_file(EMsgComposer * composer)530 e_composer_get_snapshot_file (EMsgComposer *composer)
531 {
532 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
533 
534 	return g_object_get_data (G_OBJECT (composer), SNAPSHOT_FILE_KEY);
535 }
536 
537 void
e_composer_prevent_snapshot_file_delete(EMsgComposer * composer)538 e_composer_prevent_snapshot_file_delete (EMsgComposer *composer)
539 {
540 	GFile *snapshot_file;
541 
542 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
543 
544 	snapshot_file = g_object_steal_data (G_OBJECT (composer), SNAPSHOT_FILE_KEY);
545 	if (snapshot_file) {
546 		g_object_set_data_full (G_OBJECT (composer), SNAPSHOT_FILE_KEY,
547 			snapshot_file, g_object_unref);
548 	}
549 }
550 
551 void
e_composer_allow_snapshot_file_delete(EMsgComposer * composer)552 e_composer_allow_snapshot_file_delete (EMsgComposer *composer)
553 {
554 	GFile *snapshot_file;
555 
556 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
557 
558 	snapshot_file = g_object_steal_data (G_OBJECT (composer), SNAPSHOT_FILE_KEY);
559 	if (snapshot_file) {
560 		g_object_set_data_full (G_OBJECT (composer), SNAPSHOT_FILE_KEY,
561 			snapshot_file, (GDestroyNotify) delete_snapshot_file);
562 	}
563 }
564