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 }