1 /*
2 * e-composer-autosave.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-composer-autosave.h"
21
22 #include <composer/e-msg-composer.h>
23
24 #include "e-autosave-utils.h"
25
26 #define E_COMPOSER_AUTOSAVE_GET_PRIVATE(obj) \
27 (G_TYPE_INSTANCE_GET_PRIVATE \
28 ((obj), E_TYPE_COMPOSER_AUTOSAVE, EComposerAutosavePrivate))
29
30 #define AUTOSAVE_INTERVAL 60 /* seconds */
31
32 struct _EComposerAutosavePrivate {
33 GCancellable *cancellable;
34 guint timeout_id;
35
36 GFile *malfunction_snapshot_file;
37 gboolean editor_is_malfunction;
38 };
39
40 G_DEFINE_DYNAMIC_TYPE (
41 EComposerAutosave,
42 e_composer_autosave,
43 E_TYPE_EXTENSION)
44
45 static void composer_autosave_changed_cb (EComposerAutosave *autosave);
46
47 static void
composer_autosave_finished_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)48 composer_autosave_finished_cb (GObject *source_object,
49 GAsyncResult *result,
50 gpointer user_data)
51 {
52 EMsgComposer *composer;
53 EComposerAutosave *autosave;
54 GFile *snapshot_file;
55 GError *local_error = NULL;
56
57 composer = E_MSG_COMPOSER (source_object);
58 autosave = E_COMPOSER_AUTOSAVE (user_data);
59
60 snapshot_file = e_composer_get_snapshot_file (composer);
61 e_composer_save_snapshot_finish (composer, result, &local_error);
62
63 /* Return silently if we were cancelled. */
64 if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
65 g_error_free (local_error);
66
67 else if (local_error != NULL) {
68 EHTMLEditor *editor;
69 gchar *basename;
70
71 if (G_IS_FILE (snapshot_file))
72 basename = g_file_get_basename (snapshot_file);
73 else
74 basename = g_strdup (" ");
75
76 editor = e_msg_composer_get_editor (composer);
77
78 if (editor) {
79 e_alert_submit (
80 E_ALERT_SINK (editor),
81 "mail-composer:no-autosave",
82 basename, local_error->message, NULL);
83 } else
84 g_warning ("%s: %s", basename, local_error->message);
85
86 g_free (basename);
87 g_error_free (local_error);
88
89 /* Re-schedule on error, maybe it'll work a bit later */
90 composer_autosave_changed_cb (autosave);
91 }
92
93 g_object_unref (autosave);
94 }
95
96 static gboolean
composer_autosave_timeout_cb(gpointer user_data)97 composer_autosave_timeout_cb (gpointer user_data)
98 {
99 EComposerAutosave *autosave;
100 EExtensible *extensible;
101 EMsgComposer *composer;
102
103 autosave = E_COMPOSER_AUTOSAVE (user_data);
104
105 if (autosave->priv->editor_is_malfunction) {
106 autosave->priv->timeout_id = 0;
107 return FALSE;
108 }
109
110 extensible = e_extension_get_extensible (E_EXTENSION (autosave));
111 composer = E_MSG_COMPOSER (extensible);
112
113 /* Do not do anything when it's busy */
114 if (e_msg_composer_is_soft_busy (composer))
115 return TRUE;
116
117 /* Cancel the previous snapshot if it's still in
118 * progress and start a new snapshot operation. */
119 g_cancellable_cancel (autosave->priv->cancellable);
120 g_object_unref (autosave->priv->cancellable);
121 autosave->priv->cancellable = g_cancellable_new ();
122
123 autosave->priv->timeout_id = 0;
124
125 e_composer_save_snapshot (
126 composer,
127 autosave->priv->cancellable,
128 composer_autosave_finished_cb,
129 g_object_ref (autosave));
130
131 return FALSE;
132 }
133
134 static void
composer_autosave_changed_cb(EComposerAutosave * autosave)135 composer_autosave_changed_cb (EComposerAutosave *autosave)
136 {
137 EHTMLEditor *editor;
138 EContentEditor *cnt_editor;
139 EExtensible *extensible;
140
141 extensible = e_extension_get_extensible (E_EXTENSION (autosave));
142
143 editor = e_msg_composer_get_editor (E_MSG_COMPOSER (extensible));
144 cnt_editor = e_html_editor_get_content_editor (editor);
145
146 if (autosave->priv->timeout_id == 0 &&
147 !autosave->priv->editor_is_malfunction &&
148 e_content_editor_get_changed (cnt_editor)) {
149 autosave->priv->timeout_id = e_named_timeout_add_seconds (
150 AUTOSAVE_INTERVAL,
151 composer_autosave_timeout_cb, autosave);
152 }
153 }
154
155 static void
composer_autosave_editor_is_malfunction_cb(EComposerAutosave * autosave)156 composer_autosave_editor_is_malfunction_cb (EComposerAutosave *autosave)
157 {
158 EHTMLEditor *editor;
159 EContentEditor *cnt_editor;
160 EExtensible *extensible;
161
162 extensible = e_extension_get_extensible (E_EXTENSION (autosave));
163
164 editor = e_msg_composer_get_editor (E_MSG_COMPOSER (extensible));
165 cnt_editor = e_html_editor_get_content_editor (editor);
166
167 g_clear_object (&autosave->priv->malfunction_snapshot_file);
168 autosave->priv->editor_is_malfunction = e_content_editor_is_malfunction (cnt_editor);
169
170 if (autosave->priv->editor_is_malfunction) {
171 e_composer_prevent_snapshot_file_delete (E_MSG_COMPOSER (extensible));
172 autosave->priv->malfunction_snapshot_file = e_composer_get_snapshot_file (E_MSG_COMPOSER (extensible));
173 if (autosave->priv->malfunction_snapshot_file)
174 g_object_ref (autosave->priv->malfunction_snapshot_file);
175 } else {
176 e_composer_allow_snapshot_file_delete (E_MSG_COMPOSER (extensible));
177 composer_autosave_changed_cb (autosave);
178 }
179 }
180
181 static void
composer_autosave_recovered_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)182 composer_autosave_recovered_cb (GObject *source_object,
183 GAsyncResult *result,
184 gpointer user_data)
185 {
186 EMsgComposer *composer;
187 GError *local_error = NULL;
188
189 composer = e_composer_load_snapshot_finish (E_SHELL (source_object), result, &local_error);
190
191 if (local_error != NULL) {
192 /* FIXME Show an alert dialog here explaining
193 * why we could not recover the message.
194 * Will need a new error XML entry. */
195 g_warn_if_fail (composer == NULL);
196 g_warning ("%s: %s", G_STRFUNC, local_error->message);
197 g_error_free (local_error);
198 } else {
199 gtk_widget_show (GTK_WIDGET (composer));
200 g_object_unref (composer);
201 }
202 }
203
204 static void
composer_autosave_msg_composer_before_destroy_cb(EMsgComposer * composer,gpointer user_data)205 composer_autosave_msg_composer_before_destroy_cb (EMsgComposer *composer,
206 gpointer user_data)
207 {
208 EComposerAutosave *autosave = user_data;
209
210 g_return_if_fail (autosave != NULL);
211
212 /* Cancel any snapshots in progress, composer is going to destroy its content. */
213 g_cancellable_cancel (autosave->priv->cancellable);
214
215 if (autosave->priv->malfunction_snapshot_file) {
216 if (e_alert_run_dialog_for_args (GTK_WINDOW (composer), "mail-composer:recover-autosave-malfunction", NULL) == GTK_RESPONSE_YES) {
217 e_composer_load_snapshot (
218 e_msg_composer_get_shell (composer), autosave->priv->malfunction_snapshot_file, NULL,
219 composer_autosave_recovered_cb, NULL);
220 } else {
221 g_file_delete (autosave->priv->malfunction_snapshot_file, NULL, NULL);
222 }
223 }
224 }
225
226 static void
composer_autosave_dispose(GObject * object)227 composer_autosave_dispose (GObject *object)
228 {
229 EComposerAutosavePrivate *priv;
230
231 priv = E_COMPOSER_AUTOSAVE_GET_PRIVATE (object);
232
233 /* Cancel any snapshots in progress. */
234 g_cancellable_cancel (priv->cancellable);
235
236 if (priv->timeout_id > 0) {
237 g_source_remove (priv->timeout_id);
238 priv->timeout_id = 0;
239 }
240
241 g_clear_object (&priv->cancellable);
242
243 g_clear_object (&priv->malfunction_snapshot_file);
244
245 /* Chain up to parent's dispose() method. */
246 G_OBJECT_CLASS (e_composer_autosave_parent_class)->dispose (object);
247 }
248
249 static void
composer_autosave_constructed(GObject * object)250 composer_autosave_constructed (GObject *object)
251 {
252 EHTMLEditor *editor;
253 EContentEditor *cnt_editor;
254 EExtensible *extensible;
255
256 /* Chain up to parent's constructed() method. */
257 G_OBJECT_CLASS (e_composer_autosave_parent_class)->constructed (object);
258
259 extensible = e_extension_get_extensible (E_EXTENSION (object));
260 editor = e_msg_composer_get_editor (E_MSG_COMPOSER (extensible));
261 cnt_editor = e_html_editor_get_content_editor (editor);
262
263 g_signal_connect (
264 extensible, "before-destroy",
265 G_CALLBACK (composer_autosave_msg_composer_before_destroy_cb), object);
266
267 e_signal_connect_notify_swapped (
268 cnt_editor, "notify::is-malfunction",
269 G_CALLBACK (composer_autosave_editor_is_malfunction_cb), object);
270
271 /* Do not use e_signal_connect_notify_swapped() here,
272 this module relies on "false" change notifications. */
273 g_signal_connect_swapped (
274 cnt_editor, "notify::changed",
275 G_CALLBACK (composer_autosave_changed_cb), object);
276
277 g_signal_connect_swapped (
278 cnt_editor, "content-changed",
279 G_CALLBACK (composer_autosave_changed_cb), object);
280 }
281
282 static void
e_composer_autosave_class_init(EComposerAutosaveClass * class)283 e_composer_autosave_class_init (EComposerAutosaveClass *class)
284 {
285 GObjectClass *object_class;
286 EExtensionClass *extension_class;
287
288 g_type_class_add_private (class, sizeof (EComposerAutosavePrivate));
289
290 object_class = G_OBJECT_CLASS (class);
291 object_class->dispose = composer_autosave_dispose;
292 object_class->constructed = composer_autosave_constructed;
293
294 extension_class = E_EXTENSION_CLASS (class);
295 extension_class->extensible_type = E_TYPE_MSG_COMPOSER;
296 }
297
298 static void
e_composer_autosave_class_finalize(EComposerAutosaveClass * class)299 e_composer_autosave_class_finalize (EComposerAutosaveClass *class)
300 {
301 }
302
303 static void
e_composer_autosave_init(EComposerAutosave * autosave)304 e_composer_autosave_init (EComposerAutosave *autosave)
305 {
306 autosave->priv = E_COMPOSER_AUTOSAVE_GET_PRIVATE (autosave);
307 autosave->priv->cancellable = g_cancellable_new ();
308 autosave->priv->malfunction_snapshot_file = NULL;
309 autosave->priv->editor_is_malfunction = FALSE;
310 }
311
312 void
e_composer_autosave_type_register(GTypeModule * type_module)313 e_composer_autosave_type_register (GTypeModule *type_module)
314 {
315 /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
316 * function, so we have to wrap it with a public function in
317 * order to register types from a separate compilation unit. */
318 e_composer_autosave_register_type (type_module);
319 }
320