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