evolution-3.6.4/modules/composer-autosave/e-autosave-utils.c

No issues found

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