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