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