1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * Caja
4 *
5 * Copyright (C) 2011 Red Hat, Inc.
6 * 2012 Stefano Karapetsas
7 * Copyright (C) 2012-2021 The MATE developers
8 *
9 * Caja is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
13 *
14 * Caja is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public
20 * License along with this program; see the file COPYING. If not,
21 * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 * Authors: Cosimo Cecchi <cosimoc@redhat.com>
25 * Stefano Karapetsas <stefano@karapetsas.com>
26 */
27
28 #include <config.h>
29
30 #include "caja-desktop-metadata.h"
31
32 #include "caja-directory-notify.h"
33 #include "caja-file-private.h"
34 #include "caja-file-utilities.h"
35
36 #include <glib/gstdio.h>
37
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41
42 static guint save_in_idle_source_id = 0;
43
44 static gchar *
get_keyfile_path(void)45 get_keyfile_path (void)
46 {
47 gchar *xdg_dir, *retval;
48
49 xdg_dir = caja_get_user_directory ();
50 retval = g_build_filename (xdg_dir, "desktop-metadata", NULL);
51
52 g_free (xdg_dir);
53
54 return retval;
55 }
56
57 static gboolean
save_in_idle_cb(gpointer data)58 save_in_idle_cb (gpointer data)
59 {
60 GKeyFile *keyfile = data;
61 gchar *contents, *filename;
62 gsize length;
63 GError *error = NULL;
64
65 save_in_idle_source_id = 0;
66
67 contents = g_key_file_to_data (keyfile, &length, NULL);
68 filename = get_keyfile_path ();
69
70 if (contents != NULL) {
71 g_file_set_contents (filename,
72 contents, length,
73 &error);
74 g_free (contents);
75 }
76
77 if (error != NULL) {
78 g_warning ("Couldn't save the desktop metadata keyfile to disk: %s",
79 error->message);
80 g_error_free (error);
81 }
82
83 g_free (filename);
84
85 return FALSE;
86 }
87
88 static void
save_in_idle(GKeyFile * keyfile)89 save_in_idle (GKeyFile *keyfile)
90 {
91 if (save_in_idle_source_id != 0) {
92 g_source_remove (save_in_idle_source_id);
93 }
94
95 save_in_idle_source_id = g_idle_add (save_in_idle_cb, keyfile);
96 }
97
98 static GKeyFile *
load_metadata_keyfile(void)99 load_metadata_keyfile (void)
100 {
101 GKeyFile *retval;
102 GError *error = NULL;
103 gchar *filename;
104
105 retval = g_key_file_new ();
106 filename = get_keyfile_path ();
107
108 g_key_file_load_from_file (retval,
109 filename,
110 G_KEY_FILE_NONE,
111 &error);
112
113 if (error != NULL) {
114 if (!g_error_matches (error,
115 G_FILE_ERROR,
116 G_FILE_ERROR_NOENT)) {
117 g_print ("Unable to open the desktop metadata keyfile: %s\n",
118 error->message);
119 }
120
121 g_error_free (error);
122 }
123
124 g_free (filename);
125
126 return retval;
127 }
128
129 static GKeyFile *
get_keyfile(void)130 get_keyfile (void)
131 {
132 static gboolean keyfile_loaded = FALSE;
133 static GKeyFile *keyfile = NULL;
134
135 if (!keyfile_loaded) {
136 keyfile = load_metadata_keyfile ();
137 keyfile_loaded = TRUE;
138 }
139
140 return keyfile;
141 }
142
143 void
caja_desktop_set_metadata_string(CajaFile * file,const gchar * name,const gchar * key,const gchar * string)144 caja_desktop_set_metadata_string (CajaFile *file,
145 const gchar *name,
146 const gchar *key,
147 const gchar *string)
148 {
149 GKeyFile *keyfile;
150 GError *error = NULL;
151
152 keyfile = get_keyfile ();
153
154 if (string != NULL) {
155 g_key_file_set_string (keyfile,
156 name,
157 key,
158 string);
159 } else {
160 /* NULL as value is taken to mean that we want to remove the key */
161
162 g_key_file_remove_key (keyfile,
163 name,
164 key,
165 &error);
166
167 if (error != NULL) {
168 g_warning ("Couldn't remove the key '%s' from '%s' in the keyfile: %s",
169 key,
170 name,
171 error->message);
172 g_error_free (error);
173 }
174 }
175
176 save_in_idle (keyfile);
177
178 if (caja_desktop_update_metadata_from_keyfile (file, name)) {
179 caja_file_changed (file);
180 }
181 }
182
183 #define STRV_TERMINATOR "@x-caja-desktop-metadata-term@"
184
185 void
caja_desktop_set_metadata_stringv(CajaFile * file,const char * name,const char * key,const char * const * stringv)186 caja_desktop_set_metadata_stringv (CajaFile *file,
187 const char *name,
188 const char *key,
189 const char * const *stringv)
190 {
191 GKeyFile *keyfile;
192 guint length;
193 gchar **actual_stringv = NULL;
194 gboolean free_strv = FALSE;
195
196 keyfile = get_keyfile ();
197
198 /* if we would be setting a single-length strv, append a fake
199 * terminator to the array, to be able to differentiate it later from
200 * the single string case
201 */
202 length = g_strv_length ((gchar **) stringv);
203
204 if (length == 1) {
205 actual_stringv = g_malloc0 (3 * sizeof (gchar *));
206 actual_stringv[0] = (gchar *) stringv[0];
207 actual_stringv[1] = STRV_TERMINATOR;
208 actual_stringv[2] = NULL;
209
210 length = 2;
211 free_strv = TRUE;
212 } else {
213 actual_stringv = (gchar **) stringv;
214 }
215
216 g_key_file_set_string_list (keyfile,
217 name,
218 key,
219 (const gchar **) actual_stringv,
220 length);
221
222 save_in_idle (keyfile);
223
224 if (caja_desktop_update_metadata_from_keyfile (file, name)) {
225 caja_file_changed (file);
226 }
227
228 if (free_strv) {
229 g_free (actual_stringv);
230 }
231 }
232
233 gboolean
caja_desktop_update_metadata_from_keyfile(CajaFile * file,const gchar * name)234 caja_desktop_update_metadata_from_keyfile (CajaFile *file,
235 const gchar *name)
236 {
237 gchar **keys, **values;
238 const gchar *actual_values[2];
239 const gchar *value;
240 gsize length, values_length;
241 GKeyFile *keyfile;
242 GFileInfo *info;
243 gint idx;
244 gboolean res;
245
246 keyfile = get_keyfile ();
247
248 keys = g_key_file_get_keys (keyfile,
249 name,
250 &length,
251 NULL);
252
253 if (keys == NULL) {
254 return FALSE;
255 }
256
257 info = g_file_info_new ();
258
259 for (idx = 0; idx < length; idx++) {
260 const gchar *key;
261 gchar *gio_key;
262
263 key = keys[idx];
264 values = g_key_file_get_string_list (keyfile,
265 name,
266 key,
267 &values_length,
268 NULL);
269
270 gio_key = g_strconcat ("metadata::", key, NULL);
271
272 if (values_length < 1) {
273 continue;
274 } else if (values_length == 1) {
275 g_file_info_set_attribute_string (info,
276 gio_key,
277 values[0]);
278 } else if (values_length == 2) {
279 /* deal with the fact that single-length strv are stored
280 * with an additional terminator in the keyfile string, to differentiate
281 * them from the regular string case.
282 */
283 value = values[1];
284
285 if (g_strcmp0 (value, STRV_TERMINATOR) == 0) {
286 /* if the 2nd value is the terminator, remove it */
287 actual_values[0] = values[0];
288 actual_values[1] = NULL;
289
290 g_file_info_set_attribute_stringv (info,
291 gio_key,
292 (gchar **) actual_values);
293 } else {
294 /* otherwise, set it as a regular strv */
295 g_file_info_set_attribute_stringv (info,
296 gio_key,
297 values);
298 }
299 } else {
300 g_file_info_set_attribute_stringv (info,
301 gio_key,
302 values);
303 }
304
305 g_free (gio_key);
306 g_strfreev (values);
307 }
308
309 res = caja_file_update_metadata_from_info (file, info);
310
311 g_strfreev (keys);
312 g_object_unref (info);
313
314 return res;
315 }
316