1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  * gsm-session-save.c
3  * Copyright (C) 2008 Lucas Rocha.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 
21 #include <glib.h>
22 #include <glib/gstdio.h>
23 #include <gio/gio.h>
24 
25 #include "gsm-app.h"
26 #include "gsm-util.h"
27 #include "gsm-autostart-app.h"
28 #include "gsm-client.h"
29 
30 #include "gsm-session-save.h"
31 
32 #define GSM_MANAGER_SCHEMA        "org.gnome.SessionManager"
33 #define KEY_AUTOSAVE_ONE_SHOT     "auto-save-session-one-shot"
34 
35 
36 static gboolean gsm_session_clear_saved_session (const char *directory,
37                                                  GHashTable *discard_hash);
38 
39 typedef struct {
40         const char  *dir;
41         GHashTable  *discard_hash;
42         GsmStore    *app_store;
43         GError     **error;
44 } SessionSaveData;
45 
46 static gboolean
_app_has_app_id(const char * id,GsmApp * app,const char * app_id_a)47 _app_has_app_id (const char   *id,
48                  GsmApp       *app,
49                  const char   *app_id_a)
50 {
51         const char *app_id_b;
52 
53         app_id_b = gsm_app_peek_app_id (app);
54         return g_strcmp0 (app_id_a, app_id_b) == 0;
55 }
56 
57 static gboolean
save_one_client(char * id,GObject * object,SessionSaveData * data)58 save_one_client (char            *id,
59                  GObject         *object,
60                  SessionSaveData *data)
61 {
62         GsmClient  *client;
63         GKeyFile   *keyfile;
64         GsmApp     *app = NULL;
65         const char *app_id;
66         char       *path = NULL;
67         char       *filename = NULL;
68         char       *contents = NULL;
69         gsize       length = 0;
70         char       *discard_exec;
71         GError     *local_error;
72 
73         client = GSM_CLIENT (object);
74 
75         local_error = NULL;
76 
77         app_id = gsm_client_peek_app_id (client);
78         if (!IS_STRING_EMPTY (app_id)) {
79                 if (g_str_has_suffix (app_id, ".desktop"))
80                         filename = g_strdup (app_id);
81                 else
82                         filename = g_strdup_printf ("%s.desktop", app_id);
83 
84                 path = g_build_filename (data->dir, filename, NULL);
85 
86                 app = (GsmApp *)gsm_store_find (data->app_store,
87                                                 (GsmStoreFunc)_app_has_app_id,
88                                                 (char *)app_id);
89         }
90         keyfile = gsm_client_save (client, app, &local_error);
91 
92         if (keyfile == NULL || local_error) {
93                 goto out;
94         }
95 
96         contents = g_key_file_to_data (keyfile, &length, &local_error);
97 
98         if (local_error) {
99                 goto out;
100         }
101 
102         if (!path || g_file_test (path, G_FILE_TEST_EXISTS)) {
103                 if (filename)
104                         g_free (filename);
105                 if (path)
106                         g_free (path);
107 
108                 filename = g_strdup_printf ("%s.desktop",
109                                             gsm_client_peek_startup_id (client));
110                 path = g_build_filename (data->dir, filename, NULL);
111         }
112 
113         g_file_set_contents (path,
114                              contents,
115                              length,
116                              &local_error);
117 
118         if (local_error) {
119                 goto out;
120         }
121 
122         discard_exec = g_key_file_get_string (keyfile,
123                                               G_KEY_FILE_DESKTOP_GROUP,
124                                               GSM_AUTOSTART_APP_DISCARD_KEY,
125                                               NULL);
126         if (discard_exec) {
127                 g_hash_table_insert (data->discard_hash,
128                                      discard_exec, discard_exec);
129         }
130 
131         g_debug ("GsmSessionSave: saved client %s to %s", id, filename);
132 
133 out:
134         if (keyfile != NULL) {
135                 g_key_file_free (keyfile);
136         }
137 
138         g_free (contents);
139         g_free (filename);
140         g_free (path);
141 
142         /* in case of any error, stop saving session */
143         if (local_error) {
144                 g_propagate_error (data->error, local_error);
145                 g_error_free (local_error);
146 
147                 return TRUE;
148         }
149 
150         return FALSE;
151 }
152 
153 void
gsm_session_save(GsmStore * client_store,GsmStore * app_store,GError ** error)154 gsm_session_save (GsmStore  *client_store,
155                   GsmStore  *app_store,
156                   GError   **error)
157 {
158         GSettings       *settings;
159         const char      *save_dir;
160         SessionSaveData  data;
161 
162         g_debug ("GsmSessionSave: Saving session");
163 
164         /* Clear one shot key autosave in the event its set (so that it's actually
165          * one shot only)
166          */
167         settings = g_settings_new (GSM_MANAGER_SCHEMA);
168         g_settings_set_boolean (settings, KEY_AUTOSAVE_ONE_SHOT, FALSE);
169         g_object_unref (settings);
170 
171         save_dir = gsm_util_get_saved_session_dir ();
172         if (save_dir == NULL) {
173                 g_warning ("GsmSessionSave: cannot create saved session directory");
174                 return;
175         }
176 
177         data.dir = save_dir;
178         data.discard_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
179                                                    g_free, NULL);
180         data.app_store = app_store;
181 
182         /* remove old saved session */
183         gsm_session_clear_saved_session (save_dir, data.discard_hash);
184         data.error = error;
185 
186         gsm_store_foreach (client_store,
187                            (GsmStoreFunc) save_one_client,
188                            &data);
189 
190         g_hash_table_destroy (data.discard_hash);
191 }
192 
193 static gboolean
gsm_session_clear_one_client(const char * filename,GHashTable * discard_hash)194 gsm_session_clear_one_client (const char *filename,
195                               GHashTable *discard_hash)
196 {
197         gboolean  result = TRUE;
198         GKeyFile *key_file;
199         char     *discard_exec = NULL;
200         char    **envp;
201 
202         g_debug ("GsmSessionSave: removing '%s' from saved session", filename);
203 
204         envp = (char **) gsm_util_listenv ();
205         key_file = g_key_file_new ();
206         if (g_key_file_load_from_file (key_file, filename,
207                                        G_KEY_FILE_NONE, NULL)) {
208                 char **argv;
209                 int    argc;
210 
211                 discard_exec = g_key_file_get_string (key_file,
212                                                       G_KEY_FILE_DESKTOP_GROUP,
213                                                       GSM_AUTOSTART_APP_DISCARD_KEY,
214                                                       NULL);
215                 if (!discard_exec)
216                         goto out;
217 
218                 if (discard_hash && g_hash_table_lookup (discard_hash, discard_exec))
219                         goto out;
220 
221                 if (!g_shell_parse_argv (discard_exec, &argc, &argv, NULL))
222                         goto out;
223 
224                 result = g_spawn_async (NULL, argv, envp, G_SPAWN_SEARCH_PATH,
225                                         NULL, NULL, NULL, NULL) && result;
226 
227                 g_strfreev (argv);
228         } else {
229                 result = FALSE;
230         }
231 
232 out:
233         if (key_file)
234                 g_key_file_free (key_file);
235         if (discard_exec)
236                 g_free (discard_exec);
237 
238         result = (g_unlink (filename) == 0) && result;
239 
240         return result;
241 }
242 
243 static gboolean
gsm_session_clear_saved_session(const char * directory,GHashTable * discard_hash)244 gsm_session_clear_saved_session (const char *directory,
245                                  GHashTable *discard_hash)
246 {
247         GDir       *dir;
248         const char *filename;
249         gboolean    result = TRUE;
250         GError     *error;
251 
252         g_debug ("GsmSessionSave: clearing currently saved session at %s",
253                  directory);
254 
255         if (directory == NULL) {
256                 return FALSE;
257         }
258 
259         error = NULL;
260         dir = g_dir_open (directory, 0, &error);
261         if (error) {
262                 g_warning ("GsmSessionSave: error loading saved session directory: %s", error->message);
263                 g_error_free (error);
264                 return FALSE;
265         }
266 
267         while ((filename = g_dir_read_name (dir))) {
268                 char *path = g_build_filename (directory,
269                                                filename, NULL);
270 
271                 result = gsm_session_clear_one_client (path, discard_hash)
272                          && result;
273 
274                 g_free (path);
275         }
276 
277         g_dir_close (dir);
278 
279         return result;
280 }
281 
282 void
gsm_session_save_clear(void)283 gsm_session_save_clear (void)
284 {
285         const char *save_dir;
286 
287         g_debug ("GsmSessionSave: Clearing saved session");
288 
289         save_dir = gsm_util_get_saved_session_dir ();
290         if (save_dir == NULL) {
291                 g_warning ("GsmSessionSave: cannot create saved session directory");
292                 return;
293         }
294 
295         gsm_session_clear_saved_session (save_dir, NULL);
296 }
297