Enforce changing type in config_setting_add() if such setting exists.
[lxde/lxpanel.git] / src / conf.c
CommitLineData
0260eac5
AG
1/*
2 * Copyright (c) 2014 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#ifdef HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include "conf.h"
944a8264 24#include "private.h"
0260eac5 25
17fab6e5
AG
26#include <string.h>
27#include <stdlib.h>
28
0260eac5
AG
29struct _config_setting_t
30{
31 config_setting_t *next;
32 config_setting_t *parent;
33 PanelConfType type;
34 PanelConfSaveHook hook;
17fab6e5 35 gpointer hook_data;
0260eac5
AG
36 char *name;
37 union {
38 gint num; /* for integer or boolean */
39 gchar *str; /* for string */
40 config_setting_t *first; /* for group or list */
41 };
42};
43
44struct _PanelConf
45{
46 config_setting_t *root;
47};
48
49static config_setting_t *_config_setting_t_new(config_setting_t *parent, int index,
50 const char *name, PanelConfType type)
51{
52 config_setting_t *s;
53 s = g_slice_new0(config_setting_t);
54 s->type = type;
55 s->name = g_strdup(name);
56 if (parent == NULL || (parent->type != PANEL_CONF_TYPE_GROUP && parent->type != PANEL_CONF_TYPE_LIST))
57 return s;
58 s->parent = parent;
59 if (parent->first == NULL || index == 0)
60 {
61 s->next = parent->first;
62 parent->first = s;
63 }
64 else
65 {
66 for (parent = parent->first; parent->next && index != 1; parent = parent->next)
67 index--;
68 /* FIXME: check if index is out of range? */
69 s->next = parent->next;
70 parent->next = s;
71 }
72 return s;
73}
74
75/* frees data, not removes from parent */
76static void _config_setting_t_free(config_setting_t *setting)
77{
78 g_free(setting->name);
79 switch (setting->type)
80 {
81 case PANEL_CONF_TYPE_STRING:
82 g_free(setting->str);
83 break;
84 case PANEL_CONF_TYPE_GROUP:
85 case PANEL_CONF_TYPE_LIST:
86 while (setting->first)
87 {
88 config_setting_t *s = setting->first;
89 setting->first = s->next;
90 _config_setting_t_free(s);
91 }
92 break;
93 case PANEL_CONF_TYPE_INT:
94 break;
95 }
96 g_slice_free(config_setting_t, setting);
97}
98
99/* the same as above but removes from parent */
100static void _config_setting_t_remove(config_setting_t *setting)
101{
102 g_return_if_fail(setting->parent);
103 g_return_if_fail(setting->parent->type == PANEL_CONF_TYPE_GROUP || setting->parent->type == PANEL_CONF_TYPE_LIST);
104 /* remove from parent */
105 if (setting->parent->first == setting)
106 setting->parent->first = setting->next;
107 else
108 {
109 config_setting_t *s = setting->parent->first;
110 while (s->next != NULL && s->next != setting)
111 s = s->next;
112 g_assert(s->next != NULL);
113 s->next = setting->next;
114 }
115 /* free the data */
116 _config_setting_t_free(setting);
117}
118
e018d9ed
AG
119static config_setting_t * _config_setting_get_member(const config_setting_t * setting, const char * name)
120{
121 config_setting_t *s;
122 for (s = setting->first; s; s = s->next)
123 if (g_strcmp0(s->name, name) == 0)
124 break;
125 return s;
126}
127
128/* returns either new or existing setting struct, NULL on error or conflict */
129static config_setting_t * _config_setting_try_add(config_setting_t * parent,
130 const char * name,
131 PanelConfType type)
132{
133 config_setting_t *s;
134 if (parent == NULL)
135 return NULL;
136 if (name[0] == '\0')
137 return NULL;
138 if (parent->type == PANEL_CONF_TYPE_GROUP &&
139 (s = _config_setting_get_member(parent, name)))
140 return (s->type == type) ? s : NULL;
141 return _config_setting_t_new(parent, -1, name, type);
142}
143
0260eac5
AG
144PanelConf *config_new(void)
145{
146 PanelConf *c = g_slice_new(PanelConf);
147 c->root = _config_setting_t_new(NULL, -1, NULL, PANEL_CONF_TYPE_GROUP);
17fab6e5 148 return c;
0260eac5
AG
149}
150
151void config_destroy(PanelConf * config)
152{
153 _config_setting_t_free(config->root);
154 g_slice_free(PanelConf, config);
155}
156
157gboolean config_read_file(PanelConf * config, const char * filename)
158{
159 FILE *f = fopen(filename, "r");
050bf5bb 160 size_t size;
0260eac5
AG
161 char *buff, *c, *name, *end;
162 config_setting_t *s, *parent;
163
164 if (f == NULL)
165 return FALSE;
166 fseek(f, 0L, SEEK_END);
167 size = ftell(f);
168 rewind(f);
169 buff = g_malloc(size + 1);
050bf5bb 170 size = fread(buff, 1, size, f);
0260eac5
AG
171 fclose(f);
172 buff[size] = '\0';
173 name = NULL;
174 parent = config->root;
175 for (c = buff; *c; )
176 {
177 switch(*c)
178 {
179 case '#':
180_skip_all:
181 while (*c && *c != '\n')
182 c++;
183 if (!*c)
184 break;
185 /* continue with EOL */
186 case '\n':
187 name = NULL;
188 c++;
189 break;
190 case ' ':
191 case '\t':
192 if (name)
193 *c = '\0';
194 c++;
195 break;
196 case '=': /* scalar value follows */
197 if (name)
198 *c++ = '\0';
199 else
200 {
17fab6e5 201 g_warning("config: invalid scalar definition");
0260eac5
AG
202 goto _skip_all;
203 }
17fab6e5 204 while (*c == ' ' || *c == '\t')
0260eac5 205 c++; /* skip spaces after '=' */
17fab6e5 206 if (name == NULL || *c == '\0' || *c == '\n') /* invalid statement */
0260eac5
AG
207 break;
208 size = strtol(c, &end, 10);
17fab6e5 209 while (*end == ' ' || *end == '\t')
60826154 210 end++; /* skip trailing spaces */
0260eac5
AG
211 if (*end == '\0' || *end == '\n')
212 {
e018d9ed 213 s = _config_setting_try_add(parent, name, PANEL_CONF_TYPE_INT);
0260eac5 214 if (s)
17fab6e5 215 {
0260eac5 216 s->num = (int)size;
17fab6e5
AG
217 /* g_debug("config loader: got new int %s: %d", name, s->num); */
218 }
0260eac5 219 else
17fab6e5 220 g_warning("config: duplicate setting '%s' conflicts, ignored", name);
0260eac5
AG
221 }
222 else
223 {
224 for (end = c; *end && *end != '\n'; )
225 end++;
e018d9ed 226 s = _config_setting_try_add(parent, name, PANEL_CONF_TYPE_STRING);
0260eac5
AG
227 if (s)
228 {
229 g_free(s->str);
230 s->str = g_strndup(c, end - c);
17fab6e5 231 /* g_debug("config loader: got new string %s: %s", name, s->str); */
0260eac5
AG
232 }
233 else
17fab6e5 234 g_warning("config: duplicate setting '%s' conflicts, ignored", name);
0260eac5
AG
235 }
236 c = end;
237 break;
238 case '{':
239 parent = config_setting_add(parent, "", PANEL_CONF_TYPE_LIST);
240 if (name)
241 {
242 *c = '\0';
243 s = config_setting_add(parent, name, PANEL_CONF_TYPE_GROUP);
244 }
245 else
246 s = NULL;
247 c++;
248 if (s)
249 {
250 parent = s;
17fab6e5 251 /* g_debug("config loader: group '%s' added", name); */
0260eac5
AG
252 }
253 else
17fab6e5 254 g_warning("config: invalid group '%s' in config file ignored", name);
0260eac5
AG
255 name = NULL;
256 break;
257 case '}':
258 c++;
259 if (parent->parent)
260 parent = parent->parent; /* go up, to anonymous list */
261 if (parent->type == PANEL_CONF_TYPE_LIST)
262 parent = parent->parent; /* go to upper group */
263 name = NULL;
264 break;
265 default:
266 if (name == NULL)
267 name = c;
268 c++;
269 }
270 }
271 g_free(buff);
272 return TRUE;
273}
274
275#define SETTING_INDENT " "
276
277static void _config_write_setting(const config_setting_t *setting, GString *buf,
278 GString *out, FILE *f)
279{
280 gint indent = buf->len;
281 config_setting_t *s;
282
283 switch (setting->type)
284 {
285 case PANEL_CONF_TYPE_INT:
286 g_string_append_printf(buf, "%s=%d\n", setting->name, setting->num);
287 break;
288 case PANEL_CONF_TYPE_STRING:
096e155f
AG
289 if (!setting->str) /* don't save NULL strings */
290 return;
291 g_string_append_printf(buf, "%s=%s\n", setting->name, setting->str);
0260eac5
AG
292 break;
293 case PANEL_CONF_TYPE_GROUP:
0260eac5 294 if (!out && setting->hook) /* plugin does not support settings */
944a8264
AG
295 {
296 lxpanel_put_line(f, "%s%s {", buf->str, setting->name);
17fab6e5 297 setting->hook(setting, f, setting->hook_data);
944a8264
AG
298 lxpanel_put_line(f, "%s}", buf->str);
299 /* old settings ways are kinda weird... */
300 }
0260eac5
AG
301 else
302 {
944a8264
AG
303 if (out)
304 {
305 g_string_append(out, buf->str);
306 g_string_append(out, setting->name);
307 g_string_append(out, " {\n");
308 }
309 else
310 fprintf(f, "%s%s {\n", buf->str, setting->name);
0260eac5
AG
311 g_string_append(buf, SETTING_INDENT);
312 for (s = setting->first; s; s = s->next)
313 _config_write_setting(s, buf, out, f);
314 g_string_truncate(buf, indent);
315 if (out)
316 {
317 g_string_append(out, buf->str);
318 g_string_append(out, "}\n");
319 }
320 else
321 fprintf(f, "%s}\n", buf->str);
322 }
323 return;
324 case PANEL_CONF_TYPE_LIST:
325 if (setting->name[0] != '\0')
326 {
327 g_warning("only anonymous lists are supported in panel config, got \"%s\"",
328 setting->name);
329 return;
330 }
0260eac5
AG
331 for (s = setting->first; s; s = s->next)
332 _config_write_setting(s, buf, out, f);
0260eac5
AG
333 return;
334 }
335 if (out)
336 g_string_append(out, buf->str);
337 else
338 fputs(buf->str, f);
339 g_string_truncate(buf, indent);
340}
341
342gboolean config_write_file(PanelConf * config, const char * filename)
343{
344 FILE *f = fopen(filename, "w");
345 GString *str;
346 if (f == NULL)
347 return FALSE;
944a8264
AG
348 fputs("# lxpanel <profile> config file. Manually editing is not recommended.\n"
349 "# Use preference dialog in lxpanel to adjust config when you can.\n\n", f);
350 str = g_string_sized_new(128);
351 _config_write_setting(config_setting_get_member(config->root, ""), str, NULL, f);
0260eac5
AG
352 /* FIXME: handle errors */
353 fclose(f);
354 g_string_free(str, TRUE);
355 return TRUE;
356}
357
358/* it is used for old plugins only */
359char * config_setting_to_string(const config_setting_t * setting)
360{
361 GString *val, *buf;
362 g_return_val_if_fail(setting, NULL);
363 val = g_string_sized_new(128);
364 buf = g_string_sized_new(128);
365 _config_write_setting(setting, val, buf, NULL);
366 g_string_free(val, TRUE);
367 return g_string_free(buf, FALSE);
368}
369
370config_setting_t * config_root_setting(const PanelConf * config)
371{
372 return config->root;
373}
374
17fab6e5
AG
375config_setting_t * config_setting_get_member(const config_setting_t * setting, const char * name)
376{
377 g_return_val_if_fail(name && setting, NULL);
378 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, NULL);
379 return _config_setting_get_member(setting, name);
380}
381
0260eac5
AG
382config_setting_t * config_setting_get_elem(const config_setting_t * setting, unsigned int index)
383{
384 config_setting_t *s;
17fab6e5
AG
385 g_return_val_if_fail(setting, NULL);
386 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_LIST || setting->type == PANEL_CONF_TYPE_GROUP, NULL);
0260eac5
AG
387 for (s = setting->first; s && index > 0; s = s->next)
388 index--;
389 return s;
390}
391
17fab6e5
AG
392const char * config_setting_get_name(const config_setting_t * setting)
393{
394 return setting->name;
395}
396
397config_setting_t * config_setting_get_parent(const config_setting_t * setting)
398{
399 return setting->parent;
400}
401
0260eac5
AG
402int config_setting_get_int(const config_setting_t * setting)
403{
404 if (!setting || setting->type != PANEL_CONF_TYPE_INT)
405 return 0;
406 return setting->num;
407}
408
409const char * config_setting_get_string(const config_setting_t * setting)
410{
411 if (!setting || setting->type != PANEL_CONF_TYPE_STRING)
17fab6e5 412 return NULL;
0260eac5
AG
413 return setting->str;
414}
4bca3e51
AG
415
416gboolean config_setting_lookup_int(const config_setting_t * setting,
417 const char * name, int * value)
418{
419 config_setting_t *sub;
420
421 g_return_val_if_fail(name && setting && value, FALSE);
422 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, FALSE);
423 sub = _config_setting_get_member(setting, name);
424 if (!sub || sub->type != PANEL_CONF_TYPE_INT)
425 return FALSE;
2b363b78 426 *value = sub->num;
4bca3e51
AG
427 return TRUE;
428}
429
430gboolean config_setting_lookup_string(const config_setting_t * setting,
431 const char * name, const char ** value)
432{
433 config_setting_t *sub;
434
435 g_return_val_if_fail(name && setting && value, FALSE);
436 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, FALSE);
437 sub = _config_setting_get_member(setting, name);
438 if (!sub || sub->type != PANEL_CONF_TYPE_STRING)
439 return FALSE;
2b363b78 440 *value = sub->str;
4bca3e51
AG
441 return TRUE;
442}
0260eac5 443
e018d9ed
AG
444/* returns either new or existing setting struct, NULL on args error,
445 removes old setting on conflict */
0260eac5
AG
446config_setting_t * config_setting_add(config_setting_t * parent, const char * name, PanelConfType type)
447{
448 config_setting_t *s;
449 if (parent == NULL || (parent->type != PANEL_CONF_TYPE_GROUP && parent->type != PANEL_CONF_TYPE_LIST))
450 return NULL;
451 if (type == PANEL_CONF_TYPE_LIST)
452 {
453 if (!name || name[0])
454 /* only anonymous lists are supported */
455 return NULL;
456 }
457 else if (name == NULL || name[0] == '\0')
458 /* other types should be not anonymous */
459 return NULL;
17fab6e5
AG
460 if (parent->type == PANEL_CONF_TYPE_GROUP &&
461 (s = _config_setting_get_member(parent, name)))
e018d9ed
AG
462 {
463 if (s->type == type)
464 return s;
465 _config_setting_t_remove(s);
466 }
0260eac5
AG
467 return _config_setting_t_new(parent, -1, name, type);
468}
469
94f56022
HG
470
471static void remove_from_parent(config_setting_t * setting)
472{
473 config_setting_t *s;
474
475 if (setting->parent->first == setting) {
476 setting->parent->first = setting->next;
477 goto _isolate_setting;
478 }
479
480 for (s = setting->parent->first; s->next; s = s->next)
481 if (s->next == setting)
482 break;
483 g_assert(s->next);
484 s->next = setting->next;
485
486_isolate_setting:
487 setting->next = NULL;
488 setting->parent = NULL;
489}
490
491static void append_to_parent(config_setting_t * setting, config_setting_t * parent)
0260eac5
AG
492{
493 config_setting_t *s;
494
94f56022
HG
495 setting->parent = parent;
496 if (parent->first == NULL) {
497 parent->first = setting;
498 return;
499 }
500
501 s = parent->first;
502 while (s->next)
503 s = s->next;
504 s->next = setting;
505}
506
507static void insert_after(config_setting_t * setting, config_setting_t * parent,
508 config_setting_t * prev)
509{
510 setting->parent = parent;
511 if (prev == NULL) {
512 setting->next = parent->first;
513 parent->first = setting;
514 } else {
515 setting->next = prev->next;
516 prev->next = setting;
517 }
518}
519
520gboolean config_setting_move_member(config_setting_t * setting, config_setting_t * parent, const char * name)
521{
537ec037 522 config_setting_t *s;
94f56022 523
0260eac5
AG
524 g_return_val_if_fail(setting && setting->parent, FALSE);
525 if (parent == NULL || name == NULL || parent->type != PANEL_CONF_TYPE_GROUP)
526 return FALSE;
17fab6e5 527 s = _config_setting_get_member(parent, name);
0260eac5
AG
528 if (s) /* we cannot rename/move to this name, it exists already */
529 return (s == setting);
530 if (setting->parent == parent) /* it's just renaming thing */
531 goto _rename;
94f56022
HG
532 remove_from_parent(setting); /* remove from old parent */
533 append_to_parent(setting, parent); /* add to new parent */
0260eac5
AG
534 /* rename if need */
535 if (g_strcmp0(setting->name, name) != 0)
536 {
537_rename:
538 g_free(setting->name);
539 setting->name = g_strdup(name);
540 }
541 return TRUE;
542}
543
544gboolean config_setting_move_elem(config_setting_t * setting, config_setting_t * parent, int index)
545{
537ec037 546 config_setting_t *prev = NULL;
0260eac5
AG
547
548 g_return_val_if_fail(setting && setting->parent, FALSE);
549 if (parent == NULL || parent->type != PANEL_CONF_TYPE_LIST)
550 return FALSE;
551 if (setting->type != PANEL_CONF_TYPE_GROUP) /* we support only list of groups now */
552 return FALSE;
553 /* let check the place */
554 if (index != 0)
555 {
94f56022
HG
556 prev = parent->first;
557 if (prev)
0260eac5
AG
558 for ( ; index != 1 && prev->next; prev = prev->next)
559 index--;
560 if (index > 1) /* too few elements yet */
561 {
3d6ee560 562_out_of_range:
0260eac5
AG
563 g_warning("config_setting_move_elem: index out of range");
564 return FALSE;
565 }
566 if (prev && prev->next == setting) /* it is already there */
567 return TRUE;
761af06c
AG
568 if (prev == setting) /* special case: we moving it +1, swapping with next */
569 {
570 if (prev->next == NULL)
571 goto _out_of_range;
572 prev = prev->next;
573 }
0260eac5
AG
574 }
575 else if (parent->first == setting) /* it is already there */
576 return TRUE;
94f56022 577 remove_from_parent(setting); /* remove from old parent */
0260eac5 578 /* add to new parent */
0260eac5 579 if (index == 0)
94f56022
HG
580 g_assert(prev == NULL);
581 insert_after(setting, parent, prev);
0260eac5
AG
582 /* don't rename */
583 return TRUE;
584}
585
586gboolean config_setting_set_int(config_setting_t * setting, int value)
587{
588 if (!setting || setting->type != PANEL_CONF_TYPE_INT)
589 return FALSE;
590 setting->num = value;
591 return TRUE;
592}
593
594gboolean config_setting_set_string(config_setting_t * setting, const char * value)
595{
596 if (!setting || setting->type != PANEL_CONF_TYPE_STRING)
597 return FALSE;
598 g_free(setting->str);
599 setting->str = g_strdup(value);
600 return TRUE;
601}
602
603gboolean config_setting_remove(config_setting_t * parent, const char * name)
604{
605 config_setting_t *s = config_setting_get_member(parent, name);
606 if (s == NULL)
607 return FALSE;
608 _config_setting_t_remove(s);
609 return TRUE;
610}
611
612gboolean config_setting_remove_elem(config_setting_t * parent, unsigned int index)
613{
614 config_setting_t *s = config_setting_get_elem(parent, index);
615 if (s == NULL)
616 return FALSE;
617 _config_setting_t_remove(s);
618 return TRUE;
619}
620
17fab6e5
AG
621gboolean config_setting_destroy(config_setting_t * setting)
622{
623 if (setting == NULL || setting->parent == NULL)
624 return FALSE;
625 _config_setting_t_remove(setting);
626 return TRUE;
627}
628
0260eac5
AG
629PanelConfType config_setting_type(const config_setting_t * setting)
630{
631 return setting->type;
632}
633
17fab6e5
AG
634void config_setting_set_save_hook(config_setting_t * setting, PanelConfSaveHook hook,
635 gpointer user_data)
0260eac5
AG
636{
637 setting->hook = hook;
17fab6e5 638 setting->hook_data = user_data;
0260eac5 639}