GCC Code Coverage Report


Directory: ./
File: panels/background/cc-background-xml.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 293 0.0%
Functions: 0 24 0.0%
Branches: 0 193 0.0%

Line Branch Exec Source
1 /*
2 * Authors: Rodney Dawes <dobey@ximian.com>
3 * Bastien Nocera <hadess@hadess.net>
4 *
5 * Copyright 2003-2006 Novell, Inc. (www.novell.com)
6 * Copyright 2011 Red Hat Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <gio/gio.h>
23 #include <string.h>
24 #include <libxml/parser.h>
25 #include <gdesktop-enums.h>
26
27 #include "gdesktop-enums-types.h"
28 #include "cc-background-item.h"
29 #include "cc-background-xml.h"
30
31 /* The number of items we signal as "added" before
32 * returning to the main loop */
33 #define NUM_ITEMS_PER_BATCH 1
34
35 struct _CcBackgroundXml
36 {
37 GObject parent_instance;
38
39 GHashTable *wp_hash;
40 GAsyncQueue *item_added_queue;
41 guint item_added_id;
42 GSList *monitors; /* GSList of GFileMonitor */
43 };
44
45 enum {
46 ADDED,
47 LAST_SIGNAL
48 };
49
50 static guint signals[LAST_SIGNAL] = { 0 };
51
52 G_DEFINE_TYPE (CcBackgroundXml, cc_background_xml, G_TYPE_OBJECT)
53
54 static gboolean
55 cc_background_xml_get_bool (const xmlNode *parent,
56 const gchar *prop_name)
57 {
58 xmlChar *prop;
59 gboolean ret_val = FALSE;
60
61 g_return_val_if_fail (parent != NULL, FALSE);
62 g_return_val_if_fail (prop_name != NULL, FALSE);
63
64 prop = xmlGetProp ((xmlNode *) parent, (xmlChar*)prop_name);
65 if (prop != NULL) {
66 if (!g_ascii_strcasecmp ((gchar *)prop, "true") || !g_ascii_strcasecmp ((gchar *)prop, "1")) {
67 ret_val = TRUE;
68 } else {
69 ret_val = FALSE;
70 }
71 xmlFree (prop);
72 }
73
74 return ret_val;
75 }
76
77 static struct {
78 int value;
79 const char *string;
80 } lookups[] = {
81 { G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL, "horizontal-gradient" },
82 { G_DESKTOP_BACKGROUND_SHADING_VERTICAL, "vertical-gradient" },
83 };
84
85 static int
86 enum_string_to_value (GType type,
87 const char *string)
88 {
89 GEnumClass *eclass;
90 GEnumValue *value;
91
92 eclass = G_ENUM_CLASS (g_type_class_peek (type));
93 value = g_enum_get_value_by_nick (eclass, string);
94
95 /* Here's a bit of hand-made parsing, bad bad */
96 if (value == NULL) {
97 guint i;
98 for (i = 0; i < G_N_ELEMENTS (lookups); i++) {
99 if (g_str_equal (lookups[i].string, string))
100 return lookups[i].value;
101 }
102 g_warning ("Unhandled value '%s' for enum '%s'",
103 string, G_FLAGS_CLASS_TYPE_NAME (eclass));
104 return 0;
105 }
106
107 return value->value;
108 }
109
110 static gboolean
111 idle_emit (CcBackgroundXml *xml)
112 {
113 gint i;
114
115 g_async_queue_lock (xml->item_added_queue);
116
117 for (i = 0; i < NUM_ITEMS_PER_BATCH; i++) {
118 g_autoptr(GObject) item = NULL;
119
120 item = g_async_queue_try_pop_unlocked (xml->item_added_queue);
121 if (item == NULL)
122 break;
123 g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item);
124 }
125
126 g_async_queue_unlock (xml->item_added_queue);
127
128 if (g_async_queue_length (xml->item_added_queue) > 0) {
129 return TRUE;
130 } else {
131 xml->item_added_id = 0;
132 return FALSE;
133 }
134 }
135
136 static void
137 emit_added_in_idle (CcBackgroundXml *xml,
138 GObject *object)
139 {
140 g_async_queue_lock (xml->item_added_queue);
141 g_async_queue_push_unlocked (xml->item_added_queue, object);
142 if (xml->item_added_id == 0)
143 xml->item_added_id = g_idle_add ((GSourceFunc) idle_emit, xml);
144 g_async_queue_unlock (xml->item_added_queue);
145 }
146
147 #define NONE "(none)"
148
149 static gboolean
150 cc_background_xml_load_xml_internal (CcBackgroundXml *xml,
151 const gchar *filename,
152 gboolean in_thread)
153 {
154 xmlDoc * wplist;
155 xmlNode * root, * list, * wpa;
156 xmlChar * nodelang;
157 const gchar * const * syslangs;
158 gint i;
159 gboolean retval;
160
161 wplist = xmlParseFile (filename);
162 retval = FALSE;
163
164 if (!wplist)
165 return retval;
166
167 syslangs = g_get_language_names ();
168
169 root = xmlDocGetRootElement (wplist);
170
171 for (list = root->children; list != NULL; list = list->next) {
172 if (!strcmp ((gchar *)list->name, "wallpaper")) {
173 g_autoptr(CcBackgroundItem) item = NULL;
174 g_autofree gchar *uri = NULL;
175 g_autofree gchar *cname = NULL;
176 g_autofree gchar *id = NULL;
177 g_autofree gchar *bg_uri = NULL;
178 g_autofree gchar *bg_uri_dark = NULL;
179
180 item = cc_background_item_new (NULL);
181
182 g_object_set (G_OBJECT (item),
183 "is-deleted", cc_background_xml_get_bool (list, "deleted"),
184 "source-xml", filename,
185 NULL);
186
187 for (wpa = list->children; wpa != NULL; wpa = wpa->next) {
188 if (wpa->type == XML_COMMENT_NODE) {
189 continue;
190 } else if (!strcmp ((gchar *)wpa->name, "filename")) {
191 if (wpa->last != NULL && wpa->last->content != NULL) {
192 gchar *content = g_strstrip ((gchar *)wpa->last->content);
193
194 /* FIXME same rubbish as in other parts of the code */
195 if (strcmp (content, NONE) == 0) {
196 bg_uri = NULL;
197 } else {
198 g_autoptr(GFile) file = NULL;
199 g_autofree gchar *dirname = NULL;
200
201 dirname = g_path_get_dirname (filename);
202 file = g_file_new_for_commandline_arg_and_cwd (content, dirname);
203 bg_uri = g_file_get_uri (file);
204 }
205 g_object_set (G_OBJECT (item), "uri", bg_uri, NULL);
206 } else {
207 break;
208 }
209 } else if (!strcmp ((gchar *)wpa->name, "filename-dark")) {
210 if (wpa->last != NULL && wpa->last->content != NULL) {
211 gchar *content = g_strstrip ((gchar *)wpa->last->content);
212
213 /* FIXME same rubbish as in other parts of the code */
214 if (strcmp (content, NONE) == 0) {
215 bg_uri_dark = NULL;
216 } else {
217 g_autoptr(GFile) file = NULL;
218 g_autofree gchar *dirname = NULL;
219
220 dirname = g_path_get_dirname (filename);
221 file = g_file_new_for_commandline_arg_and_cwd (content, dirname);
222 bg_uri_dark = g_file_get_uri (file);
223 }
224 g_object_set (G_OBJECT (item), "uri-dark", bg_uri_dark, NULL);
225 } else {
226 break;
227 }
228 } else if (!strcmp ((gchar *)wpa->name, "name")) {
229 if (wpa->last != NULL && wpa->last->content != NULL) {
230 g_autofree gchar *name = NULL;
231 nodelang = xmlNodeGetLang (wpa->last);
232
233 g_object_get (G_OBJECT (item), "name", &name, NULL);
234
235 if (name == NULL && nodelang == NULL) {
236 g_free (cname);
237 cname = g_strdup (g_strstrip ((gchar *)wpa->last->content));
238 g_object_set (G_OBJECT (item), "name", cname, NULL);
239 } else {
240 for (i = 0; syslangs[i] != NULL; i++) {
241 if (!strcmp (syslangs[i], (gchar *)nodelang)) {
242 g_object_set (G_OBJECT (item), "name",
243 g_strstrip ((gchar *)wpa->last->content), NULL);
244 break;
245 }
246 }
247 }
248
249 xmlFree (nodelang);
250 } else {
251 break;
252 }
253 } else if (!strcmp ((gchar *)wpa->name, "options")) {
254 if (wpa->last != NULL) {
255 g_object_set (G_OBJECT (item), "placement",
256 enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE,
257 g_strstrip ((gchar *)wpa->last->content)), NULL);
258 }
259 } else if (!strcmp ((gchar *)wpa->name, "shade_type")) {
260 if (wpa->last != NULL) {
261 g_object_set (G_OBJECT (item), "shading",
262 enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING,
263 g_strstrip ((gchar *)wpa->last->content)), NULL);
264 }
265 } else if (!strcmp ((gchar *)wpa->name, "pcolor")) {
266 if (wpa->last != NULL) {
267 g_object_set (G_OBJECT (item), "primary-color",
268 g_strstrip ((gchar *)wpa->last->content), NULL);
269 }
270 } else if (!strcmp ((gchar *)wpa->name, "scolor")) {
271 if (wpa->last != NULL) {
272 g_object_set (G_OBJECT (item), "secondary-color",
273 g_strstrip ((gchar *)wpa->last->content), NULL);
274 }
275 } else if (!strcmp ((gchar *)wpa->name, "source_url")) {
276 if (wpa->last != NULL) {
277 g_object_set (G_OBJECT (item),
278 "source-url", g_strstrip ((gchar *)wpa->last->content),
279 "needs-download", FALSE,
280 NULL);
281 }
282 } else if (!strcmp ((gchar *)wpa->name, "text")) {
283 /* Do nothing here, libxml2 is being weird */
284 } else {
285 g_debug ("Unknown Tag in %s: %s", filename, wpa->name);
286 }
287 }
288
289 /* Check whether the target file exists */
290 {
291 const char *uri;
292
293 uri = cc_background_item_get_uri (item);
294 if (uri != NULL)
295 {
296 g_autoptr(GFile) file = NULL;
297
298 file = g_file_new_for_uri (uri);
299 if (g_file_query_exists (file, NULL) == FALSE)
300 {
301 g_clear_pointer (&cname, g_free);
302 g_clear_object (&item);
303 continue;
304 }
305 }
306 }
307
308 /* FIXME, this is a broken way of doing,
309 * need to use proper code here */
310 uri = g_filename_to_uri (filename, NULL, NULL);
311 if (bg_uri || bg_uri_dark)
312 id = g_strdup_printf ("%s#%s#%s", uri, cname, bg_uri ? bg_uri : bg_uri_dark);
313 else
314 id = g_strdup_printf ("%s#%s", uri, cname);
315
316 /* Make sure we don't already have this one and that filename exists */
317 if (g_hash_table_lookup (xml->wp_hash, id) != NULL) {
318 continue;
319 }
320
321 g_hash_table_insert (xml->wp_hash,
322 g_strdup (id),
323 g_object_ref (item));
324 if (in_thread)
325 emit_added_in_idle (xml, g_object_ref (G_OBJECT (item)));
326 else
327 g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item);
328 retval = TRUE;
329 }
330 }
331 xmlFreeDoc (wplist);
332
333 return retval;
334 }
335
336 static void
337 gnome_wp_file_changed (CcBackgroundXml *xml,
338 GFile *file,
339 GFile *other_file,
340 GFileMonitorEvent event_type)
341 {
342 g_autofree gchar *filename = NULL;
343
344 switch (event_type) {
345 case G_FILE_MONITOR_EVENT_CHANGED:
346 case G_FILE_MONITOR_EVENT_CREATED:
347 filename = g_file_get_path (file);
348 cc_background_xml_load_xml_internal (xml, filename, FALSE);
349 break;
350 default:
351 break;
352 }
353 }
354
355 static void
356 cc_background_xml_add_monitor (GFile *directory,
357 CcBackgroundXml *data)
358 {
359 GFileMonitor *monitor;
360 g_autoptr(GError) error = NULL;
361
362 monitor = g_file_monitor_directory (directory,
363 G_FILE_MONITOR_NONE,
364 NULL,
365 &error);
366 if (error != NULL) {
367 g_autofree gchar *path = NULL;
368
369 path = g_file_get_parse_name (directory);
370 g_warning ("Unable to monitor directory %s: %s",
371 path, error->message);
372 return;
373 }
374
375 g_signal_connect_swapped (monitor, "changed",
376 G_CALLBACK (gnome_wp_file_changed),
377 data);
378
379 data->monitors = g_slist_prepend (data->monitors, monitor);
380 }
381
382 static void
383 cc_background_xml_load_from_dir (const gchar *path,
384 CcBackgroundXml *data,
385 gboolean in_thread)
386 {
387 g_autoptr(GFile) directory = NULL;
388 g_autoptr(GFileEnumerator) enumerator = NULL;
389 g_autoptr(GError) error = NULL;
390
391 if (!g_file_test (path, G_FILE_TEST_IS_DIR)) {
392 return;
393 }
394
395 directory = g_file_new_for_path (path);
396 enumerator = g_file_enumerate_children (directory,
397 G_FILE_ATTRIBUTE_STANDARD_NAME,
398 G_FILE_QUERY_INFO_NONE,
399 NULL,
400 &error);
401 if (error != NULL) {
402 g_warning ("Unable to check directory %s: %s", path, error->message);
403 return;
404 }
405
406 while (TRUE) {
407 g_autoptr(GFileInfo) info = NULL;
408 const gchar *filename;
409 g_autofree gchar *fullpath = NULL;
410
411 info = g_file_enumerator_next_file (enumerator, NULL, NULL);
412 if (info == NULL) {
413 g_file_enumerator_close (enumerator, NULL, NULL);
414 cc_background_xml_add_monitor (directory, data);
415 return;
416 }
417
418 filename = g_file_info_get_name (info);
419 fullpath = g_build_filename (path, filename, NULL);
420
421 cc_background_xml_load_xml_internal (data, fullpath, in_thread);
422 }
423 }
424
425 static void
426 cc_background_xml_load_list (CcBackgroundXml *data,
427 gboolean in_thread)
428 {
429 const char * const *system_data_dirs;
430 g_autofree gchar *datadir = NULL;
431 gint i;
432
433 datadir = g_build_filename (g_get_user_data_dir (),
434 "gnome-background-properties",
435 NULL);
436 cc_background_xml_load_from_dir (datadir, data, in_thread);
437
438 system_data_dirs = g_get_system_data_dirs ();
439 for (i = 0; system_data_dirs[i]; i++) {
440 g_autofree gchar *sdatadir = NULL;
441 sdatadir = g_build_filename (system_data_dirs[i],
442 "gnome-background-properties",
443 NULL);
444 cc_background_xml_load_from_dir (sdatadir, data, in_thread);
445 }
446 }
447
448 gboolean
449 cc_background_xml_load_list_finish (CcBackgroundXml *xml,
450 GAsyncResult *result,
451 GError **error)
452 {
453 g_return_val_if_fail (g_task_is_valid (result, xml), FALSE);
454 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
455 return g_task_propagate_boolean (G_TASK (result), error);
456 }
457
458 static void
459 load_list_thread (GTask *task,
460 gpointer source_object,
461 gpointer task_data,
462 GCancellable *cancellable)
463 {
464 CcBackgroundXml *xml = CC_BACKGROUND_XML (source_object);
465 cc_background_xml_load_list (xml, TRUE);
466 g_task_return_boolean (task, TRUE);
467 }
468
469 void
470 cc_background_xml_load_list_async (CcBackgroundXml *xml,
471 GCancellable *cancellable,
472 GAsyncReadyCallback callback,
473 gpointer user_data)
474 {
475 g_autoptr(GTask) task = NULL;
476
477 g_return_if_fail (CC_IS_BACKGROUND_XML (xml));
478
479 task = g_task_new (xml, cancellable, callback, user_data);
480 g_task_run_in_thread (task, load_list_thread);
481 }
482
483 gboolean
484 cc_background_xml_load_xml (CcBackgroundXml *xml,
485 const gchar *filename)
486 {
487 g_return_val_if_fail (CC_IS_BACKGROUND_XML (xml), FALSE);
488
489 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE)
490 return FALSE;
491
492 return cc_background_xml_load_xml_internal (xml, filename, FALSE);
493 }
494
495 static void
496 single_xml_added (CcBackgroundXml *xml,
497 CcBackgroundItem *item,
498 CcBackgroundItem **ret)
499 {
500 g_assert (*ret == NULL);
501 *ret = g_object_ref (item);
502 }
503
504 CcBackgroundItem *
505 cc_background_xml_get_item (const char *filename)
506 {
507 g_autoptr(CcBackgroundXml) xml = NULL;
508 CcBackgroundItem *item = NULL;
509
510 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE)
511 return NULL;
512
513 xml = cc_background_xml_new ();
514 g_signal_connect (G_OBJECT (xml), "added",
515 G_CALLBACK (single_xml_added), &item);
516 if (cc_background_xml_load_xml (xml, filename) == FALSE)
517 return NULL;
518
519 return item;
520 }
521
522 static const char *
523 enum_to_str (GType type,
524 int v)
525 {
526 GEnumClass *eclass;
527 GEnumValue *value;
528
529 eclass = G_ENUM_CLASS (g_type_class_peek (type));
530 value = g_enum_get_value (eclass, v);
531
532 g_assert (value);
533
534 return value->value_nick;
535 }
536
537 void
538 cc_background_xml_save (CcBackgroundItem *item,
539 const char *filename)
540 {
541 xmlDoc *wp;
542 xmlNode *root, *wallpaper;
543 xmlNode *xml_item G_GNUC_UNUSED;
544 const char * none = "(none)";
545 const char *placement_str, *shading_str;
546 g_autofree gchar *name = NULL;
547 g_autofree gchar *pcolor = NULL;
548 g_autofree gchar *scolor = NULL;
549 g_autofree gchar *uri = NULL;
550 g_autofree gchar *source_url = NULL;
551 CcBackgroundItemFlags flags;
552 GDesktopBackgroundStyle placement;
553 GDesktopBackgroundShading shading;
554
555 xmlKeepBlanksDefault (0);
556
557 wp = xmlNewDoc ((xmlChar *)"1.0");
558 xmlCreateIntSubset (wp, (xmlChar *)"wallpapers", NULL, (xmlChar *)"gnome-wp-list.dtd");
559 root = xmlNewNode (NULL, (xmlChar *)"wallpapers");
560 xmlDocSetRootElement (wp, root);
561
562 g_object_get (G_OBJECT (item),
563 "name", &name,
564 "uri", &uri,
565 "shading", &shading,
566 "placement", &placement,
567 "primary-color", &pcolor,
568 "secondary-color", &scolor,
569 "source-url", &source_url,
570 "flags", &flags,
571 NULL);
572
573 placement_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, placement);
574 shading_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, shading);
575
576 wallpaper = xmlNewChild (root, NULL, (xmlChar *)"wallpaper", NULL);
577 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"name", (xmlChar *)name);
578 if (flags & CC_BACKGROUND_ITEM_HAS_URI &&
579 uri != NULL)
580 {
581 g_autoptr(GFile) file = NULL;
582 g_autofree gchar *fname = NULL;
583
584 file = g_file_new_for_commandline_arg (uri);
585 fname = g_file_get_path (file);
586 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)fname);
587 }
588 else if (flags & CC_BACKGROUND_ITEM_HAS_URI)
589 {
590 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)none);
591 }
592
593 if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT)
594 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"options", (xmlChar *)placement_str);
595 if (flags & CC_BACKGROUND_ITEM_HAS_SHADING)
596 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"shade_type", (xmlChar *)shading_str);
597 if (flags & CC_BACKGROUND_ITEM_HAS_PCOLOR)
598 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"pcolor", (xmlChar *)pcolor);
599 if (flags & CC_BACKGROUND_ITEM_HAS_SCOLOR)
600 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"scolor", (xmlChar *)scolor);
601 if (source_url != NULL)
602 xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"source_url", (xmlChar *)source_url);
603
604 xmlSaveFormatFile (filename, wp, 1);
605 xmlFreeDoc (wp);
606 }
607
608 static void
609 cc_background_xml_finalize (GObject *object)
610 {
611 CcBackgroundXml *xml;
612
613 g_return_if_fail (object != NULL);
614 g_return_if_fail (CC_IS_BACKGROUND_XML (object));
615
616 xml = CC_BACKGROUND_XML (object);
617
618 g_slist_free_full (xml->monitors, g_object_unref);
619
620 g_clear_pointer (&xml->wp_hash, g_hash_table_destroy);
621 g_clear_handle_id (&xml->item_added_id, g_source_remove);
622 g_clear_pointer (&xml->item_added_queue, g_async_queue_unref);
623
624 G_OBJECT_CLASS (cc_background_xml_parent_class)->finalize (object);
625 }
626
627 static void
628 cc_background_xml_class_init (CcBackgroundXmlClass *klass)
629 {
630 GObjectClass *object_class = G_OBJECT_CLASS (klass);
631
632 object_class->finalize = cc_background_xml_finalize;
633
634 signals[ADDED] = g_signal_new ("added",
635 G_OBJECT_CLASS_TYPE (object_class),
636 G_SIGNAL_RUN_LAST,
637 0,
638 NULL, NULL,
639 g_cclosure_marshal_VOID__OBJECT,
640 G_TYPE_NONE, 1, CC_TYPE_BACKGROUND_ITEM);
641 }
642
643 static void
644 cc_background_xml_init (CcBackgroundXml *xml)
645 {
646 xml->wp_hash = g_hash_table_new_full (g_str_hash,
647 g_str_equal,
648 (GDestroyNotify) g_free,
649 (GDestroyNotify) g_object_unref);
650 xml->item_added_queue = g_async_queue_new_full ((GDestroyNotify) g_object_unref);
651 }
652
653 CcBackgroundXml *
654 cc_background_xml_new (void)
655 {
656 return CC_BACKGROUND_XML (g_object_new (CC_TYPE_BACKGROUND_XML, NULL));
657 }
658