/* * Copyright (C) 2010-2018 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "settings_types.h" /* * Described in header */ kv_t *settings_kv_create(char *key, char *value) { kv_t *this; INIT(this, .key = key, .value = value, ); return this; } /* * Described in header */ void settings_kv_destroy(kv_t *this, array_t *contents) { free(this->key); if (contents && this->value) { array_insert(contents, ARRAY_TAIL, this->value); } else { free(this->value); } free(this); } /* * Described in header */ section_t *settings_section_create(char *name) { section_t *this; INIT(this, .name = name, ); return this; } static void section_destroy(section_t *section, int idx, array_t *contents) { settings_section_destroy(section, contents); } static void kv_destroy(kv_t *kv, int idx, array_t *contents) { settings_kv_destroy(kv, contents); } static void ref_destroy(section_ref_t *ref, int idx, void *ctx) { free(ref->name); free(ref); } /* * Described in header */ void settings_section_destroy(section_t *this, array_t *contents) { array_destroy_function(this->sections, (void*)section_destroy, contents); array_destroy(this->sections_order); array_destroy_function(this->kv, (void*)kv_destroy, contents); array_destroy(this->kv_order); array_destroy_function(this->references, (void*)ref_destroy, NULL); free(this->name); free(this); } /* * Described in header */ void settings_kv_set(kv_t *kv, char *value, array_t *contents) { if (value && kv->value && streq(value, kv->value)) { /* no update required */ free(value); return; } /* if the new value was shorter we could overwrite the existing one but that * could lead to reads of partially updated values from other threads that * have a pointer to the existing value, so we replace it anyway */ if (kv->value && contents) { array_insert(contents, ARRAY_TAIL, kv->value); } else { free(kv->value); } kv->value = value; } /* * Described in header */ void settings_kv_add(section_t *section, kv_t *kv, array_t *contents) { kv_t *found; if (array_bsearch(section->kv, kv->key, settings_kv_find, &found) == -1) { array_insert_create(§ion->kv, ARRAY_TAIL, kv); array_sort(section->kv, settings_kv_sort, NULL); array_insert_create(§ion->kv_order, ARRAY_TAIL, kv); } else { settings_kv_set(found, kv->value, contents); kv->value = NULL; settings_kv_destroy(kv, NULL); } } /* * Described in header */ void settings_reference_add(section_t *section, char *name, bool permanent) { section_ref_t *ref; int i; for (i = 0; i < array_count(section->references); i++) { array_get(section->references, i, &ref); if (ref->permanent && !permanent) { /* add it before any permanent references */ break; } if (ref->permanent == permanent && streq(name, ref->name)) { free(name); return; } } INIT(ref, .name = name, .permanent = permanent, ); array_insert_create(§ion->references, i, ref); } /* * Add a section to the given parent, optionally remove settings/subsections * not found when extending an existing section */ static void add_section(section_t *parent, section_t *section, array_t *contents, bool purge) { section_t *found; if (array_bsearch(parent->sections, section->name, settings_section_find, &found) == -1) { array_insert_create(&parent->sections, ARRAY_TAIL, section); array_sort(parent->sections, settings_section_sort, NULL); array_insert_create(&parent->sections_order, ARRAY_TAIL, section); } else { settings_section_extend(found, section, contents, purge); settings_section_destroy(section, contents); } } /* * Described in header */ void settings_section_add(section_t *parent, section_t *section, array_t *contents) { add_section(parent, section, contents, FALSE); } /** * Purge contents of a section, returns TRUE if section can be safely removed. */ static bool section_purge(section_t *this, array_t *contents) { section_t *current; section_ref_t *ref; int i, idx; array_destroy_function(this->kv, (void*)kv_destroy, contents); this->kv = NULL; array_destroy(this->kv_order); this->kv_order = NULL; /* remove non-permanent references */ for (i = array_count(this->references) - 1; i >= 0; i--) { array_get(this->references, i, &ref); if (!ref->permanent) { array_remove(this->references, i, NULL); ref_destroy(ref, 0, NULL); } } if (!array_count(this->references)) { array_destroy(this->references); this->references = NULL; } for (i = array_count(this->sections_order) - 1; i >= 0; i--) { array_get(this->sections_order, i, ¤t); if (section_purge(current, contents)) { array_remove(this->sections_order, i, NULL); idx = array_bsearch(this->sections, current->name, settings_section_find, NULL); array_remove(this->sections, idx, NULL); settings_section_destroy(current, contents); } } /* we ensure sections configured with permanent references (or having any * such subsections) are not removed */ return !this->references && !array_count(this->sections); } /* * Described in header */ void settings_section_extend(section_t *base, section_t *extension, array_t *contents, bool purge) { enumerator_t *enumerator; section_t *section; section_ref_t *ref; kv_t *kv; array_t *sections = NULL, *kvs = NULL; int idx; if (purge) { /* remove sections, settings in base not found in extension, the others * are removed too (from the _order list) so they can be inserted in the * order found in extension, non-permanent references are removed */ enumerator = array_create_enumerator(base->sections_order); while (enumerator->enumerate(enumerator, (void**)§ion)) { if (array_bsearch(extension->sections, section->name, settings_section_find, NULL) == -1) { idx = array_bsearch(base->sections, section->name, settings_section_find, NULL); if (section_purge(section, contents)) { /* only remove them if we can purge them */ array_remove(base->sections, idx, NULL); array_remove_at(base->sections_order, enumerator); settings_section_destroy(section, contents); } } else { array_remove_at(base->sections_order, enumerator); array_insert_create(§ions, ARRAY_TAIL, section); array_sort(sections, settings_section_sort, NULL); } } enumerator->destroy(enumerator); while (array_remove(base->kv_order, 0, &kv)) { if (array_bsearch(extension->kv, kv->key, settings_kv_find, NULL) == -1) { idx = array_bsearch(base->kv, kv->key, settings_kv_find, NULL); array_remove(base->kv, idx, NULL); settings_kv_destroy(kv, contents); } else { array_insert_create(&kvs, ARRAY_TAIL, kv); array_sort(kvs, settings_kv_sort, NULL); } } enumerator = array_create_enumerator(base->references); while (enumerator->enumerate(enumerator, (void**)&ref)) { if (ref->permanent) { /* permanent references are ignored */ continue; } array_remove_at(base->references, enumerator); ref_destroy(ref, 0, NULL); } enumerator->destroy(enumerator); } while (array_remove(extension->sections_order, 0, §ion)) { idx = array_bsearch(sections, section->name, settings_section_find, NULL); if (idx != -1) { section_t *existing; array_remove(sections, idx, &existing); array_insert(base->sections_order, ARRAY_TAIL, existing); } idx = array_bsearch(extension->sections, section->name, settings_section_find, NULL); array_remove(extension->sections, idx, NULL); add_section(base, section, contents, purge); } while (array_remove(extension->kv_order, 0, &kv)) { idx = array_bsearch(kvs, kv->key, settings_kv_find, NULL); if (idx != -1) { kv_t *existing; array_remove(kvs, idx, &existing); array_insert(base->kv_order, ARRAY_TAIL, existing); } idx = array_bsearch(extension->kv, kv->key, settings_kv_find, NULL); array_remove(extension->kv, idx, NULL); settings_kv_add(base, kv, contents); } while (array_remove(extension->references, 0, &ref)) { if (ref->permanent) { /* ignore permanent references in the extension */ continue; } settings_reference_add(base, strdup(ref->name), FALSE); ref_destroy(ref, 0, NULL); } array_destroy(sections); array_destroy(kvs); } /* * Described in header */ int settings_section_find(const void *a, const void *b) { const char *key = a; const section_t *item = b; return strcmp(key, item->name); } /* * Described in header */ int settings_section_sort(const void *a, const void *b, void *user) { const section_t *sa = a, *sb = b; return strcmp(sa->name, sb->name); } /* * Described in header */ int settings_kv_find(const void *a, const void *b) { const char *key = a; const kv_t *item = b; return strcmp(key, item->key); } /* * Described in header */ int settings_kv_sort(const void *a, const void *b, void *user) { const kv_t *kva = a, *kvb = b; return strcmp(kva->key, kvb->key); }