1 /*
2  * e-composer-registry.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 <glib/gstdio.h>
21 #include <libebackend/libebackend.h>
22 
23 #include <shell/e-shell.h>
24 #include <shell/e-shell-window.h>
25 #include <composer/e-msg-composer.h>
26 
27 #include "e-autosave-utils.h"
28 
29 #include "e-composer-registry.h"
30 
31 #define E_COMPOSER_REGISTRY_GET_PRIVATE(obj) \
32 	(G_TYPE_INSTANCE_GET_PRIVATE \
33 	((obj), E_TYPE_COMPOSER_REGISTRY, EComposerRegistryPrivate))
34 
35 struct _EComposerRegistryPrivate {
36 	GQueue composers;
37 	gboolean orphans_restored;
38 	gulong map_event_handler_id;
39 };
40 
G_DEFINE_DYNAMIC_TYPE(EComposerRegistry,e_composer_registry,E_TYPE_EXTENSION)41 G_DEFINE_DYNAMIC_TYPE (
42 	EComposerRegistry,
43 	e_composer_registry,
44 	E_TYPE_EXTENSION)
45 
46 static void
47 composer_registry_recovered_cb (GObject *source_object,
48                                 GAsyncResult *result,
49                                 gpointer user_data)
50 {
51 	EMsgComposer *composer;
52 	EComposerRegistry *registry;
53 	GError *local_error = NULL;
54 
55 	registry = E_COMPOSER_REGISTRY (user_data);
56 
57 	composer = e_composer_load_snapshot_finish (
58 		E_SHELL (source_object), result, &local_error);
59 
60 	if (local_error != NULL) {
61 		/* FIXME Show an alert dialog here explaining
62 		 *       why we could not recover the message.
63 		 *       Will need a new error XML entry. */
64 		g_warn_if_fail (composer == NULL);
65 		g_warning ("%s: %s", G_STRFUNC, local_error->message);
66 		g_error_free (local_error);
67 		goto exit;
68 	}
69 
70 	gtk_widget_show (GTK_WIDGET (composer));
71 
72 	g_object_unref (composer);
73 
74 exit:
75 	g_object_unref (registry);
76 }
77 
78 static void
composer_registry_restore_orphans(EComposerRegistry * registry,GtkWindow * parent)79 composer_registry_restore_orphans (EComposerRegistry *registry,
80                                    GtkWindow *parent)
81 {
82 	EExtensible *extensible;
83 	GList *orphans;
84 	gint response;
85 	GError *local_error = NULL;
86 
87 	extensible = e_extension_get_extensible (E_EXTENSION (registry));
88 
89 	/* Look for orphaned auto-save files. */
90 	orphans = e_composer_find_orphans (
91 		&registry->priv->composers, &local_error);
92 	if (orphans == NULL) {
93 		if (local_error != NULL) {
94 			g_warning ("%s", local_error->message);
95 			g_error_free (local_error);
96 		}
97 		return;
98 	}
99 
100 	/* Ask if the user wants to recover the orphaned files. */
101 	response = e_alert_run_dialog_for_args (
102 		parent, "mail-composer:recover-autosave", NULL);
103 
104 	/* Based on the user's reponse, recover or delete them. */
105 	while (orphans != NULL) {
106 		GFile *file = orphans->data;
107 
108 		if (response == GTK_RESPONSE_YES)
109 			e_composer_load_snapshot (
110 				E_SHELL (extensible),
111 				file, NULL,
112 				composer_registry_recovered_cb,
113 				g_object_ref (registry));
114 		else
115 			g_file_delete (file, NULL, NULL);
116 
117 		g_object_unref (file);
118 
119 		orphans = g_list_delete_link (orphans, orphans);
120 	}
121 }
122 
123 static gboolean
composer_registry_map_event_cb(GtkWindow * parent,GdkEvent * event,EComposerRegistry * registry)124 composer_registry_map_event_cb (GtkWindow *parent,
125                                 GdkEvent *event,
126                                 EComposerRegistry *registry)
127 {
128 	composer_registry_restore_orphans (registry, parent);
129 	registry->priv->orphans_restored = TRUE;
130 
131 	/* This is a one-time-only signal handler.
132 	 * Disconnect from subsequent map events. */
133 	g_signal_handler_disconnect (
134 		parent, registry->priv->map_event_handler_id);
135 	registry->priv->map_event_handler_id = 0;
136 
137 	return FALSE;
138 }
139 
140 static void
composer_registry_notify_cb(EComposerRegistry * registry,GObject * where_the_object_was)141 composer_registry_notify_cb (EComposerRegistry *registry,
142                              GObject *where_the_object_was)
143 {
144 	/* Remove the finalized composer from the registry. */
145 	g_queue_remove (&registry->priv->composers, where_the_object_was);
146 
147 	g_object_unref (registry);
148 }
149 
150 static void
composer_registry_window_added_cb(GtkApplication * application,GtkWindow * window,EComposerRegistry * registry)151 composer_registry_window_added_cb (GtkApplication *application,
152                                    GtkWindow *window,
153                                    EComposerRegistry *registry)
154 {
155 	/* Offer to restore any orphaned auto-save files from the
156 	 * previous session once the first EShellWindow is mapped. */
157 	if (E_IS_SHELL_WINDOW (window) && !registry->priv->orphans_restored) {
158 		gulong handler_id;
159 
160 		handler_id = g_signal_connect (
161 			window, "map-event",
162 			G_CALLBACK (composer_registry_map_event_cb),
163 			registry);
164 		registry->priv->map_event_handler_id = handler_id;
165 
166 	/* Track the new composer window. */
167 	} else if (E_IS_MSG_COMPOSER (window)) {
168 		g_queue_push_tail (&registry->priv->composers, window);
169 		g_object_weak_ref (
170 			G_OBJECT (window), (GWeakNotify)
171 			composer_registry_notify_cb,
172 			g_object_ref (registry));
173 	}
174 }
175 
176 static void
composer_registry_finalize(GObject * object)177 composer_registry_finalize (GObject *object)
178 {
179 	EComposerRegistryPrivate *priv;
180 
181 	priv = E_COMPOSER_REGISTRY_GET_PRIVATE (object);
182 
183 	/* All composers should have been finalized by now. */
184 	g_warn_if_fail (g_queue_is_empty (&priv->composers));
185 
186 	/* Chain up to parent's finalize() method. */
187 	G_OBJECT_CLASS (e_composer_registry_parent_class)->finalize (object);
188 }
189 
190 static void
composer_registry_constructed(GObject * object)191 composer_registry_constructed (GObject *object)
192 {
193 	EExtensible *extensible;
194 
195 	/* Chain up to parent's constructed() method. */
196 	G_OBJECT_CLASS (e_composer_registry_parent_class)->constructed (object);
197 
198 	extensible = e_extension_get_extensible (E_EXTENSION (object));
199 
200 	/* Listen for new watched windows. */
201 	g_signal_connect (
202 		extensible, "window-added",
203 		G_CALLBACK (composer_registry_window_added_cb),
204 		object);
205 }
206 
207 static void
e_composer_registry_class_init(EComposerRegistryClass * class)208 e_composer_registry_class_init (EComposerRegistryClass *class)
209 {
210 	GObjectClass *object_class;
211 	EExtensionClass *extension_class;
212 
213 	g_type_class_add_private (class, sizeof (EComposerRegistryPrivate));
214 
215 	object_class = G_OBJECT_CLASS (class);
216 	object_class->finalize = composer_registry_finalize;
217 	object_class->constructed = composer_registry_constructed;
218 
219 	extension_class = E_EXTENSION_CLASS (class);
220 	extension_class->extensible_type = E_TYPE_SHELL;
221 }
222 
223 static void
e_composer_registry_class_finalize(EComposerRegistryClass * class)224 e_composer_registry_class_finalize (EComposerRegistryClass *class)
225 {
226 }
227 
228 static void
e_composer_registry_init(EComposerRegistry * registry)229 e_composer_registry_init (EComposerRegistry *registry)
230 {
231 	registry->priv = E_COMPOSER_REGISTRY_GET_PRIVATE (registry);
232 }
233 
234 void
e_composer_registry_type_register(GTypeModule * type_module)235 e_composer_registry_type_register (GTypeModule *type_module)
236 {
237 	/* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
238 	 *     function, so we have to wrap it with a public function in
239 	 *     order to register types from a separate compilation unit. */
240 	e_composer_registry_register_type (type_module);
241 }
242