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 ®istry->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 (®istry->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 (®istry->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