application/settings.c

Wed, 13 Nov 2024 22:05:52 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 13 Nov 2024 22:05:52 +0100
changeset 81
42f66ebb9881
parent 76
641dcc79e0ef
child 92
43aee6c8c166
permissions
-rw-r--r--

change binary name to idav

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2024 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "settings.h"

#include <cx/list.h>
#include <cx/array_list.h>
#include <cx/printf.h>
#include <libidav/utils.h>



#define SETTINGS_STATE_REPOLIST_SELECTED 1
#define SETTINGS_STATE_REPO_ENCRYPTION 20
#define SETTINGS_STATE_CREDENTIALS_SELECTED 30
#define SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED 31
#define SETTINGS_STATE_KEYS_SELECTED 40

#define SETTINGS_STATE_DISABLED 9999


static void repolist_activate(UiEvent *event, void *userdata) {
    UiListSelection *selection = event->eventdata;
    SettingsWindow *settings = event->window;
    settings_edit_repository(settings, selection->rows[0]);
}

static void repolist_selection(UiEvent *event, void *userdata) {
    UiListSelection *selection = event->eventdata;
    SettingsWindow *settings = event->window;
    if(selection->count > 0) {
        ui_set_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED);
        settings->selected_repo = selection->rows[0];
    } else {
        ui_unset_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED);
    }
}

static void repolist_edit(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(settings->selected_repo >= 0) {
        settings_edit_repository(settings, settings->selected_repo);
    }
}

static void repolist_add(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    settings->repo_new = TRUE;
    settings->selected_repo = -1;
    settings_clear_repository(settings);
    // switch to editing tab
    ui_set(settings->repo_tabview, 1);
}

static void repolist_remove(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    DavCfgRepository *repo = ui_list_get(settings->repos, settings->selected_repo);
    if(!repo) {
        fprintf(stderr, "Error: cannot get repository at index %d\n", settings->selected_repo);
        return;
    }
    dav_repository_remove_and_free(settings->config, repo);
    settings_update_repolist(settings);
    settings->selected_repo = -1;
    ui_unset_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED);
}

static void editrepo_go_back(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    settings_store_repository(settings);
    ui_set(settings->repo_tabview, 0);
}

static void credentials_add(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(settings_credentials_save(settings)) {
        return;
    }
    if(settings->credentials_list_needs_update) {
        settings->credentials_ignore_selectionevent = TRUE;
        ui_list_update(settings->credentials_users);
        settings_reload_repo_credentials(settings);
        settings->credentials_ignore_selectionevent = FALSE;
        settings->credentials_list_needs_update = FALSE;
    }
    settings_credentials_clear(settings);
    settings->credentials_new = TRUE;
    PwdStore *pwd = settings->pwdstore;
    if(!pwd->isdecrypted) {
        settings_credentials_decrypt(settings);
        return;
    }
    settings_credentials_select(settings, NULL);
}

static void addcred_onclick(UiEvent *event, void *userdata) {
    SettingsNewCredentialsDialog *wdata = event->window;
    if(event->intval == 4) {
        char *id = ui_get(wdata->id);
        char *user = ui_get(wdata->user);
        char *password = ui_get(wdata->password);
        int addlocation = ui_get(wdata->addlocation);
        
        if(strlen(id) == 0) {
            ui_set(wdata->message, "Missing identifier!");
            return;
        }
        if(strlen(user) == 0) {
            ui_set(wdata->message, "Missing user!");
            return;
        } 
        if(strlen(password) == 0) {
            ui_set(wdata->message, "Missing password!");
            return;
        }
        
        if(pwdstore_has_id(wdata->settings->pwdstore, id)) {
            ui_set(wdata->message, "Identifier already in use!");
            return;
        }
        
        CxList *locations = NULL;
        if(addlocation) {
            locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
            cxListAdd(locations, strdup(wdata->url));
        }
        pwdstore_put(wdata->settings->pwdstore, id, user, password);
        pwdstore_put_index(wdata->settings->pwdstore, strdup(id), locations);
        
        settings_reload_credentials(wdata->settings);
        settings_reload_repo_credentials(wdata->settings);
        
        CxList *list = wdata->settings->repo_credentials->data;
        ssize_t index = cxListFind(list, id);
        if(index >= 0) {
            ui_list_setselection(wdata->settings->repo_credentials, index);
        }
    }
    
    ui_close(event->obj);
}

// called by repo_edit
// create new credentials in dialog
static void credentials_new(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    
    UiObject *dialog = ui_dialog_window(event->obj,
            .modal = UI_ON,
            .title = "New Credentials",
            .show_closebutton = UI_OFF,
            .lbutton1 = "Cancel",
            .rbutton4 = "Add",
            .onclick = addcred_onclick);
    
    SettingsNewCredentialsDialog *wdata = ui_malloc(dialog->ctx, sizeof(SettingsNewCredentialsDialog));
    wdata->settings = settings;
    wdata->id = ui_string_new(dialog->ctx, NULL);
    wdata->user = ui_string_new(dialog->ctx, NULL);
    wdata->password = ui_string_new(dialog->ctx, NULL);
    wdata->addlocation = ui_int_new(dialog->ctx, NULL);
    wdata->message = ui_string_new(dialog->ctx, NULL);
    dialog->window = wdata;
    
    char *url = ui_get(settings->repo_url);
    if(strlen(url) > 0) {
        wdata->url = ui_strdup(dialog->ctx, url);
    } else {
        wdata->url = NULL;
    }
    
    ui_grid(dialog, .margin = 16, .columnspacing = 40, .rowspacing = 10) {
        ui_llabel(dialog, .label = "Identifier");
        ui_textfield(dialog, .value = wdata->id,  .hexpand = TRUE);
        ui_newline(dialog);
        
        ui_llabel(dialog, .label = "User");
        ui_textfield(dialog, .value = wdata->user, .hexpand = TRUE);
        ui_newline(dialog);
        
        ui_llabel(dialog, .label = "Password");
        ui_passwordfield(dialog, .value = wdata->password, .hexpand = TRUE);
        ui_newline(dialog);
        
        if(wdata->url) {
            cxmutstr msg = cx_asprintf("Add URL %s to Credential Locations", url);
            ui_checkbox(dialog, .label = msg.ptr, .value = wdata->addlocation, .colspan = 2);
            ui_newline(dialog);
            free(msg.ptr);
        }
        
        ui_llabel(dialog, .value = wdata->message, .colspan = 2);
    }
    
    ui_show(dialog);
}

static void credentials_remove(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(settings->credentials_selected_id) {
        pwdstore_remove_entry(settings->pwdstore, settings->credentials_selected_id);
        ui_list_remove(settings->credentials_users, settings->credentials_selected_index);
        ui_list_update(settings->credentials_users);
        settings_reload_repo_credentials(settings);
    }
}

static void credentials_onselect(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(settings->credentials_ignore_selectionevent) {
        return;
    }
    UiListSelection *sel = event->eventdata;
    if(settings_credentials_save(settings)) {
        return;
    }
    if(sel->count > 0) {
        const char *id = ui_list_get(settings->credentials_users, sel->rows[0]);
        settings->credentials_selected_index = sel->rows[0];
        settings_credentials_select(settings, id);
        if(settings->credentials_list_needs_update) {
            settings->credentials_ignore_selectionevent = TRUE;
            ui_list_update(settings->credentials_users);
            settings_reload_repo_credentials(settings);
            ui_list_setselection(settings->credentials_users, sel->rows[0]);
            settings->credentials_ignore_selectionevent = FALSE;
            settings->credentials_list_needs_update = FALSE;
        }
    } else {
        settings_credentials_clear(settings);
    }
}

static void c_add_location(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(event->intval == 1) {
        ui_list_append(settings->credentials_locations, ui_strdup(event->obj->ctx, event->eventdata));
        ui_list_update(settings->credentials_locations);
    }
}

static void credentials_location_add(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    ui_dialog(event->obj,
            .title = "Add Location",
            .content = "New Location URL",
            .input = TRUE,
            .result = c_add_location,
            .button1_label = "Add Location",
            .closebutton_label = "Cancel");
}

static void c_edit_location(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(event->intval == 1) {
        CxList *list = settings->credentials_locations->data;
        ssize_t i = cxListFind(list, userdata);
        if(i >= 0) {
            cxListRemove(list, i);
            cxListInsert(list, i, ui_strdup(event->obj->ctx, event->eventdata));
            ui_list_update(settings->credentials_locations);
        }
    }
}

static void credentials_location_edit(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    char *location = ui_list_get(settings->credentials_locations, settings->credentials_location_selected_index);
    if(!location) {
        return;
    }
    ui_dialog(event->obj,
            .title = "Edit Location",
            .content = "Location URL",
            .input_value = location,
            .input = TRUE,
            .result = c_edit_location,
            .resultdata = location,
            .button1_label = "Edit Location",
            .closebutton_label = "Cancel");
}

static void credentials_location_remove(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(settings->credentials_location_selected_index >= 0) {
        CxList *list = settings->credentials_locations->data;
        cxListRemove(list, settings->credentials_location_selected_index);
        ui_list_update(settings->credentials_locations);
    }
}

static void credentials_location_up(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    int index = settings->credentials_location_selected_index;
    if(index >= 1) {
        CxList *list = settings->credentials_locations->data;
        cxListSwap(list, index, index-1);
        ui_list_update(settings->credentials_locations);
        ui_list_setselection(settings->credentials_locations, index-1);
    }
}

static void credentials_location_down(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    int index = settings->credentials_location_selected_index;
    if(index >= 0 && index + 1 < ui_list_count(settings->credentials_locations)) {
        CxList *list = settings->credentials_locations->data;
        cxListSwap(list, index, index+1);
        ui_list_update(settings->credentials_locations);
        ui_list_setselection(settings->credentials_locations, index+1);
    }
}

static void credentials_location_onselect(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    UiListSelection *sel = event->eventdata;
    if(sel->count > 0) {
        settings->credentials_location_selected_index = sel->rows[0];
        ui_set_group(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED);
    } else {
        settings->credentials_location_selected_index = -1;
        ui_unset_group(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED);
    }
}

static void credentials_setmasterpw(UiEvent *event, void *userdata) {
    if(event->intval == 1) {
        SettingsWindow *settings = event->window;
        char *pw = event->eventdata;
        size_t pwlen = strlen(pw);
        if(pwlen > 0) {
            pwdstore_setpassword(settings->pwdstore, event->eventdata);
            memset(pw, 0, pwlen);
            if(!pwdstore_decrypt(settings->pwdstore)) {
                settings_credentials_select(settings, NULL);
            } else {
                ui_dialog(event->obj, .title = "Error", .content = "Cannot decrypt Secret Store", .closebutton_label = "OK");
            }
        }
    }
}

void settings_credentials_decrypt(SettingsWindow *settings) {
    ui_dialog(settings->obj,
            .title = "Secret Store",
            .content = "Master password",
            .password = TRUE,
            .result = credentials_setmasterpw,
            .button1_label = "Decrypt Secret Store",
            .closebutton_label = "Cancel");
}




#define ADDKEY_DIALOG_STATE_NO_IMPORT 10

static void addkey_onclick(UiEvent *event, void *userdata) {
    SettingsNewKeyDialog *wdata = event->window;
    if(event->intval == 4) {
        ui_set(wdata->message, "");
        
        // validate form
        char *name = ui_get(wdata->name);
        if(strlen(name) == 0) {
            ui_set(wdata->message, "A name must be specified!");
            return;
        }
        char *file = ui_get(wdata->file);
        if(strlen(file) == 0) {
            ui_set(wdata->message, "A file path must be specified!");
            return;
        }
        UiListSelection sel = ui_list_getselection(wdata->type);
        if(sel.count == 0) {
            ui_set(wdata->message, "No type selected!");
            return;
        }
        DavCfgKeyType type = sel.rows[0];
        
        // generate key file
        char *path = NULL;
        char *cfg_path = NULL;
        if(file[0] == '/') {
            path = file;
        } else {
            cfg_path = config_file_path(file);
            path = cfg_path;
        }
        FILE *f = fopen(path, "w");
        if(!f) {
            cxmutstr msg = cx_asprintf("Error: cannot write file '%s': %s", path, strerror(errno));
            ui_set(wdata->message, msg.ptr);
            free(msg.ptr);
            free(cfg_path);
            return;
        }
        free(cfg_path);
        
        unsigned char key[32];
        dav_rand_bytes(key, 32);
        
        size_t len;
        switch(type) {
            default:
            case DAV_KEY_TYPE_AES256: len = 32; break;
            case DAV_KEY_TYPE_AES128: len = 16; break;
        }
        size_t w = fwrite(key, 1, len, f);
        fclose(f);
        
        if(w != len) {
            cxmutstr msg = cx_asprintf("Could not write key data: %s", strerror(errno));
            ui_set(wdata->message, msg.ptr);
            free(msg.ptr);
            return;
        }
        
        // add key to config
        DavCfgKey *newkey = dav_key_new(wdata->settings->config);
        const CxAllocator *a = wdata->settings->config->mp->allocator;
        newkey->name.value = cx_strdup_a(a, cx_str(name));
        newkey->file.value = cx_strdup_a(a, cx_str(file));
        newkey->type = type;
        
        dav_config_add_key(wdata->settings->config, newkey);
        settings_reload_repo_keys(wdata->settings);
        settings_reload_keys(wdata->settings);
        
        if(wdata->add_to_repo && (wdata->settings->selected_repo >= 0 || wdata->settings->repo_new)) {
            CxList *repo_keys = wdata->settings->repo_keys->data;
            ssize_t index = cxListFind(repo_keys, name);
            if(index >= 0) {
                ui_list_setselection(wdata->settings->repo_keys, index);
            }
        }
    }
    
    ui_close(event->obj);
}

static void addkey_name_changed(UiEvent *event, void *userdata) {
    SettingsNewKeyDialog *wdata = event->window;
    char *name = ui_get(wdata->name);
    cxmutstr file = cx_asprintf("keys/%s", name);
    ui_set(wdata->file, file.ptr);
    free(file.ptr);
}

static void keys_add(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    
    UiObject *dialog = ui_dialog_window(event->obj,
            .modal = UI_ON,
            .title = "Add Key",
            .show_closebutton = UI_OFF,
            .lbutton1 = "Cancel",
            .rbutton4 = "Add",
            .default_button = 4,
            .onclick = addkey_onclick);
    SettingsNewKeyDialog *wdata = ui_malloc(dialog->ctx, sizeof(SettingsNewKeyDialog));
    wdata->settings = settings;
    wdata->name = ui_string_new(dialog->ctx, NULL);
    wdata->file = ui_string_new(dialog->ctx, NULL);
    wdata->import_path = ui_string_new(dialog->ctx, NULL);
    wdata->message = ui_string_new(dialog->ctx, NULL);
    wdata->secretstore = ui_int_new(dialog->ctx, NULL);
    wdata->type = ui_list_new(dialog->ctx, NULL);
    wdata->add_to_repo = userdata ? TRUE : FALSE;
    dialog->window = wdata;
    
    ui_list_append(wdata->type, "AES256");
    ui_list_append(wdata->type, "AES128");
    
    ui_grid(dialog, .margin = 16, .columnspacing = 40, .rowspacing = 10) {
        /*
        ui_hbox(dialog, .colspan = 2) {
            ui_button(dialog, .label = "Import Key...");
        }
        ui_newline(dialog);
        ui_llabel(dialog, .value = wdata->import_path);
        ui_newline(dialog);
        */
        
        ui_llabel(dialog, .label = "Name");
        ui_textfield(dialog, .value = wdata->name, .onchange = addkey_name_changed, .hexpand = TRUE);
        ui_newline(dialog);
        
        ui_llabel(dialog, .label = "File");
        ui_textfield(dialog, .value = wdata->file);
        ui_newline(dialog);
        
        ui_llabel(dialog, .label = "Type");
        ui_combobox(dialog, .list = wdata->type, .groups = UI_GROUPS(ADDKEY_DIALOG_STATE_NO_IMPORT));
        ui_newline(dialog);
        
        ui_llabel(dialog, .value = wdata->message, .colspan = 2);
    }
    
    ui_set_group(dialog->ctx, ADDKEY_DIALOG_STATE_NO_IMPORT);
    ui_list_setselection(wdata->type, 0);
    
    ui_show(dialog);
}

static void keys_remove(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    DavCfgKey *key = ui_list_get(settings->keys_list, settings->keys_selected_index);
    if(key) {
        dav_key_remove_and_free(settings->config, key);
        ui_list_remove(settings->keys_list, settings->keys_selected_index);
        ui_list_update(settings->keys_list);
        settings_reload_repo_keys(settings);
    }
}

static void keys_onselect(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    UiListSelection *sel = event->eventdata;
    if(sel->count > 0) {
        settings->keys_selected_index = sel->rows[0];
        DavCfgKey *key = ui_list_get(settings->keys_list, sel->rows[0]);
        if(key) {
            settings_edit_key(settings, key);
            ui_set_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED);
        }
    } else {
        settings->keys_selected_index = -1;
        settings_clear_key(settings);
        ui_unset_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED);
    }
}

static void list_str_destructor(void *data, void *ptr) {
    UiContext *ctx = data;
    char *s = ptr;
    ui_free(ctx, ptr);
}

static void secretstore_newmasterpw(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(event->intval == 1) {
        pwdstore_setpassword(settings->pwdstore, event->eventdata);
        set_pwdstore(settings->pwdstore);
        pwdstore_save(settings->pwdstore);
        settings->pwdstore = NULL;
    }
    ui_close(event->obj);
}

void settings_ok(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    // save any changed settings
    settings_store_repository(settings);
    settings_credentials_save(settings);
    
    set_config(settings->config);
    if(store_config()) {
        ui_dialog(event->obj, .title = "Error", .content = "Cannot store settings", .closebutton_label = "OK");
    }
    application_update_repolist(get_application());    settings->config = NULL;
    if(settings->credentials_modified) {
        if(settings->pwdstore->key) {
            set_pwdstore(settings->pwdstore);
            pwdstore_save(settings->pwdstore);
        } else {
            ui_dialog(event->obj,
                    .title = "Secret Store",
                    .content = "Master password",
                    .password = TRUE,
                    .result = secretstore_newmasterpw,
                    .button1_label = "Create Secret Store",
                    .closebutton_label = "Cancel",
                    .result = secretstore_newmasterpw);
            return;
        }
        settings->pwdstore = NULL;
    }
    ui_close(event->obj);
}

void settings_close(UiEvent *event, void *userdata) {
    SettingsWindow *settings = event->window;
    if(settings->config) {
        dav_config_free(settings->config);
    }
    if(settings->pwdstore) {
        pwdstore_free(settings->pwdstore);
    }
}

void settings_cancel(UiEvent *event, void *userdata) {
    ui_close(event->obj);
}

void settings_window_open() {
    DavConfig *config = load_config_file();
    if(!config) {
        return;
    }
    PwdStore *pwdstore = get_pwdstore();
    pwdstore = pwdstore ? pwdstore_clone(pwdstore) : pwdstore_new();
    
    UiObject *obj = ui_simple_window("Settings", NULL);
    ui_context_closefunc(obj->ctx, settings_close, NULL);
    SettingsWindow *wdata = ui_malloc(obj->ctx, sizeof(SettingsWindow));
    memset(wdata, 0, sizeof(SettingsWindow));
    wdata->config = config;
    wdata->pwdstore = pwdstore;
    obj->window = wdata;
    wdata->obj = obj;
    settings_init(obj, wdata);
    
    ui_tabview(obj, .tabview = UI_TABVIEW_NAVIGATION_TOP) {
        ui_tab(obj, "General") {
            ui_grid(obj, .margin = 10) {
                ui_label(obj, .label = "TODO");
            }
        }
        
        ui_tab(obj, "Repositories") {
            
            ui_tabview(obj, .value = wdata->repo_tabview, .tabview = UI_TABVIEW_INVISIBLE) {
                ui_tab(obj, "list") {
                    ui_grid(obj, .margin = 16, .columnspacing = 10, .rowspacing = 10) {
                        ui_hbox(obj, .spacing = 4) {
                            ui_button(obj, .label = "Add", .onclick = repolist_add);
                            ui_button(obj, .label = "Edit", .onclick = repolist_edit, .groups = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED));
                            ui_button(obj, .label = "Remove", .onclick = repolist_remove, .groups = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED));
                        }
                        ui_newline(obj);
                        
                        UiModel* model = ui_model(obj->ctx, UI_STRING, "Name", UI_STRING, "URL", UI_STRING, "User", UI_STRING, "Encrypted", -1);
                        model->getvalue = (ui_getvaluefunc) settings_repolist_getvalue;
                        ui_table(obj,
                                .model = model,
                                .list = wdata->repos,
                                .multiselection = FALSE,
                                .onactivate = repolist_activate,
                                .onselection = repolist_selection,
                                .vexpand = TRUE, .hexpand = TRUE, .colspan = 3);
                    }
                }
                
                ui_tab(obj, "repo") {
                    ui_vbox(obj, .margin = 16, .spacing = 10) {
                        ui_hbox(obj, .fill = UI_OFF, .spacing = 4) {
                            ui_button(obj, .icon = UI_ICON_GO_BACK, .onclick = editrepo_go_back);
                            ui_label(obj, .label = "Repository List");
                        }
                        
                        ui_scrolledwindow(obj, .hexpand = TRUE, .vexpand = TRUE, .subcontainer = UI_CONTAINER_NO_SUB) {
                            ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10) {
                                ui_llabel(obj, .label = "Name");
                                ui_textfield(obj, .value = wdata->repo_name, .width = 15);
                                ui_newline(obj);
                                ui_llabel(obj, .label = "URL");
                                ui_textfield(obj, .value = wdata->repo_url, .hexpand = TRUE);
                                ui_newline(obj);
                                
                                ui_llabel(obj, .label = "Credentials", .style = UI_LABEL_STYLE_TITLE, .colspan = 2);
                                ui_newline(obj);
                                ui_hbox(obj, .spacing = 4, .colspan = 2) {
                                    ui_combobox(obj, .list = wdata->repo_credentials);
                                    ui_button(obj, .label = "New Credentials", .onclick = credentials_new);
                                }
                                ui_newline(obj);
                                ui_expander(obj, .spacing = 10, .colspan = 2, .label = "Unencrypted User/Password", .margin = 10) {
                                    ui_llabel(obj, .label = "Store the credentials unencrypted in the repository and not in the secret store", .style = UI_LABEL_STYLE_DIM);
                                    ui_grid(obj, .rowspacing = 10, .columnspacing = 10, .fill = UI_OFF) {
                                        ui_llabel(obj, .label = "User");
                                        ui_textfield(obj, .value = wdata->repo_user, .width = 15);
                                        ui_newline(obj);
                                        
                                        ui_llabel(obj, .label = "Password");
                                        ui_passwordfield(obj, .value = wdata->repo_password, .width = 15);
                                    }
                                }
                                ui_newline(obj);
                                
                                ui_llabel(obj, .label = "Encryption", .style = UI_LABEL_STYLE_TITLE, .colspan = 2);
                                ui_newline(obj);
                                
                                ui_checkbox(obj, .label = "Enable client-side encryption", .value = wdata->repo_encryption, .colspan = 2, .enable_group = SETTINGS_STATE_REPO_ENCRYPTION);
                                ui_newline(obj);
                                ui_llabel(obj, .label = "Default key");
                                ui_hbox(obj, .spacing = 4) {
                                    ui_combobox(obj, .list = wdata->repo_keys, .groups = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION));
                                    ui_button(obj, .label = "Generate Key", .onclick = keys_add, .onclickdata = "repo", .groups = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION));
                                }
                                ui_newline(obj);
                                
                                ui_llabel(obj, .label = "TLS", .style = UI_LABEL_STYLE_TITLE, .colspan = 2);
                                ui_newline(obj);
                                
                                ui_llabel(obj, .label = "Cert Path");
                                ui_hbox0(obj) {
                                    ui_textfield(obj, .value = wdata->repo_cacert, .width = 15);
                                }
                                ui_newline(obj);
                                
                                ui_llabel(obj, .label = "TLS Version");
                                ui_hbox0(obj) {
                                    ui_combobox(obj, .list = wdata->repo_tls_versions);
                                }
                                ui_newline(obj);
                                ui_checkbox(obj, .label = "Disable TLS verification", .value = wdata->repo_disable_verification, .colspan = 2);
                            }
                        }
                    }
                }
            }
            
            
        }
        
        ui_tab(obj, "Sync Directories") {
            ui_grid(obj, .margin = 10) {
                ui_label(obj, .label = "TODO");
            }
        }
        
        ui_tab(obj, "Credentials") {
            ui_hbox(obj, .margin = 16, .spacing = 30) {
                ui_vbox(obj, .fill = UI_OFF, .spacing = 4) {
                    ui_hbox(obj, .fill = UI_OFF, .spacing = 4) {
                        ui_button(obj, .label = "Add", .onclick = credentials_add);
                        ui_button(obj, .label = "Remove", .onclick = credentials_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
                    }
                    ui_listview(obj, .list = wdata->credentials_users, .fill = UI_ON, .onselection = credentials_onselect);
                }
                
                ui_grid(obj, .columnspacing = 30, .rowspacing = 10) {
                    ui_llabel(obj, .label = "Identifier");
                    ui_textfield(obj, .value = wdata->credentials_id, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
                    ui_newline(obj);
                    
                    ui_llabel(obj, .label = "User");
                    ui_textfield(obj, .value = wdata->credentials_user, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
                    ui_newline(obj);
                    
                    ui_llabel(obj, .label = "Password");
                    ui_passwordfield(obj, .value = wdata->credentials_password, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
                    ui_newline(obj);
                    
                    
                    ui_label(obj, .label = " ");
                    ui_newline(obj);
                    
                    ui_llabel(obj, .style = UI_LABEL_STYLE_TITLE, .label = "Locations", .colspan = 2);
                    ui_newline(obj);
                    ui_llabel(obj, .style = UI_LABEL_STYLE_DIM, .label = "List of URLs for which these credentials should be used (optional)", .colspan = 2);
                    ui_newline(obj);
                    
                    ui_hbox(obj, .colspan = 2, .vexpand = TRUE, .hexpand = TRUE, .spacing = 10) {
#ifndef UI_WINUI
                        ui_callback credentials_activate_callback = credentials_location_edit;
#else
                        ui_callback credentials_activate_callback = NULL;
#endif
                        ui_listview(obj, .list = wdata->credentials_locations, .onactivate = credentials_activate_callback, .onselection = credentials_location_onselect, .colspan = 2, .fill = UI_ON, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
                        ui_vbox(obj, .fill = UI_OFF, .spacing = 4) {
                            ui_button(obj, .label = "Add", .onclick = credentials_location_add, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
                            ui_button(obj, .label = "Edit", .onclick = credentials_location_edit, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
                            ui_button(obj, .label = "Remove", .onclick = credentials_location_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
                            ui_button(obj, .label = "Move Up", .onclick = credentials_location_up, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
                            ui_button(obj, .label = "Move Down", .onclick = credentials_location_down, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
                        }
                    }
                }
            }
        }
        
        ui_tab(obj, "Keys") {
            ui_hbox(obj, .margin = 16, .spacing = 30) {
                ui_vbox(obj, .fill = UI_OFF, .spacing = 4) {
                    ui_hbox(obj, .fill = UI_OFF, .spacing = 4) {
                        ui_button(obj, .label = "Add", .onclick = keys_add);
                        ui_button(obj, .label = "Remove", .onclick = keys_remove, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED));
                    }
                    ui_listview(obj, .list = wdata->keys_list, .fill = UI_ON, .onselection = keys_onselect, .getvalue = keylist_getvalue);
                }
                
                ui_grid(obj, .columnspacing = 30, .rowspacing = 10) {
                    ui_llabel(obj, .label = "Identifier");
                    ui_textfield(obj, .value = wdata->key_name, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED));
                    ui_newline(obj);
                    ui_llabel(obj, .label = "Type");
                    ui_textfield(obj, .value = wdata->key_type, .groups = UI_GROUPS(SETTINGS_STATE_DISABLED));
                    ui_newline(obj);
                    ui_llabel(obj, .label = "File");
                    ui_textfield(obj, .value = wdata->key_file, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED));
                }
            }
        }
        
        /*
        ui_tab(obj, "Properties") {
            ui_grid(obj, .margin = 10) {
                ui_label(obj, .label = "TODO");
            }
        }
        */
    }
        
    ui_hbox(obj, .fill = UI_OFF, .margin = 10) {
        ui_button(obj, .label = "Cancel", .onclick = settings_cancel);
        ui_label(obj, .fill = UI_ON);
        ui_button(obj, .label = "Save", .onclick = settings_ok);
    }
    
    
    ui_show(obj);
}

void settings_init(UiObject *obj, SettingsWindow *settings) {
    settings->repos = ui_list_new(obj->ctx, NULL);
    settings->repo_tabview = ui_int_new(obj->ctx, NULL);
    
    settings->repo_name = ui_string_new(obj->ctx, NULL);
    settings->repo_url = ui_string_new(obj->ctx, NULL);
    settings->repo_user = ui_string_new(obj->ctx, NULL);
    settings->repo_password = ui_string_new(obj->ctx, NULL);
    settings->repo_cacert = ui_string_new(obj->ctx, NULL);
    settings->repo_credentials = ui_list_new(obj->ctx, NULL);
    settings->repo_keys = ui_list_new(obj->ctx, NULL);
    settings->repo_tls_versions = ui_list_new(obj->ctx, NULL);
    settings->repo_encryption = ui_int_new(obj->ctx, NULL);
    settings->repo_disable_verification = ui_int_new(obj->ctx, NULL);
    CxList *repo_keys = settings->repo_keys->data;
    repo_keys->collection.cmpfunc = (cx_compare_func)strcmp;
     
    ui_list_append(settings->repo_tls_versions, "Default");
    ui_list_append(settings->repo_tls_versions, "TLSv1.3");
    ui_list_append(settings->repo_tls_versions, "TLSv1.2");
    ui_list_append(settings->repo_tls_versions, "TLSv1.1");
    ui_list_append(settings->repo_tls_versions, "TLSv1.0");
    
    settings->credentials_selected_index = -1;
    settings->credentials_users = ui_list_new(obj->ctx, NULL);
    settings->credentials_locations = ui_list_new(obj->ctx, NULL);
    settings->credentials_id = ui_string_new(obj->ctx, NULL);
    settings->credentials_user = ui_string_new(obj->ctx, NULL);
    settings->credentials_password = ui_string_new(obj->ctx, NULL);
    CxList *credentials_users = settings->credentials_users->data;
    CxList *credentials_locations = settings->credentials_locations->data;
    credentials_users->collection.advanced_destructor = list_str_destructor;
    credentials_users->collection.destructor_data = settings->obj->ctx;
    credentials_users->collection.cmpfunc = (cx_compare_func)strcmp;
    credentials_locations->collection.advanced_destructor = list_str_destructor;
    credentials_locations->collection.destructor_data = settings->obj->ctx;
    credentials_locations->collection.cmpfunc = (cx_compare_func)strcmp;
    
    settings->keys_list = ui_list_new(obj->ctx, NULL);
    settings->key_name = ui_string_new(obj->ctx, NULL);
    settings->key_type = ui_string_new(obj->ctx, NULL);
    settings->key_file = ui_string_new(obj->ctx, NULL);
    settings->keys_selected_index = -1;
    
    // load some list values, that can be reused
    settings_update_repolist(settings);
    settings_reload_repo_keys(settings);
    settings_reload_credentials(settings);
    settings_reload_repo_credentials(settings);
    settings_reload_keys(settings);
    
    settings->selected_repo = -1;
}

#define SETTINGS_SET_STRING(str, setting) if(setting.value.ptr) ui_set(str, setting.value.ptr);

/* ----------------------------- Repository ----------------------------- */

void settings_edit_repository(SettingsWindow *settings, int repo_index) {
    DavCfgRepository *repo = ui_list_get(settings->repos, repo_index);
    if(!repo) {
        fprintf(stderr, "Error: cannot get repository at index %d\n", repo_index);
        return;
    }
    settings->selected_repo = repo_index;
    
    // load plain string values
    SETTINGS_SET_STRING(settings->repo_name, repo->name);
    SETTINGS_SET_STRING(settings->repo_url, repo->url);
    SETTINGS_SET_STRING(settings->repo_cacert, repo->cert);
    SETTINGS_SET_STRING(settings->repo_user, repo->user);
    // load decrypted password
    if(repo->password.value.ptr) {
        char *decoded_pw = util_base64decode(repo->password.value.ptr);
        ui_set(settings->repo_password, decoded_pw);
        size_t decoded_pw_len = strlen(decoded_pw);
        memset(decoded_pw, 0, decoded_pw_len);
        free(decoded_pw);
    }
    
    // select credentials dropdown value
    CxList *cred = settings->repo_credentials->data;
    cred->collection.cmpfunc = (cx_compare_func)strcmp;
    ssize_t cred_index = repo->stored_user.value.ptr ? cxListFind(cred, repo->stored_user.value.ptr) : 0;
    if(cred_index > 0) {
        ui_list_setselection(settings->repo_credentials, cred_index);
    } else {
        ui_list_setselection(settings->repo_credentials, 0);
    }
    
    // load encryption value and default key value
    ui_set(settings->repo_encryption, repo->full_encryption.value);
    CxList *keys = settings->repo_keys->data;
    keys->collection.cmpfunc = (cx_compare_func)strcmp;
    ssize_t key_index = repo->default_key.value.ptr ? cxListFind(keys, repo->default_key.value.ptr) : 0;
    if(key_index > 0) {
        ui_list_setselection(settings->repo_keys, key_index);
    } else {
        ui_list_setselection(settings->repo_keys, 0);
    }
    
    // select tls version from dropdown menu
    CxList *tlsVersions = settings->repo_tls_versions->data;
    tlsVersions->collection.cmpfunc = (cx_compare_func)strcmp;
    const char *tls_str = dav_tlsversion2str(repo->ssl_version.value);
    if(!tls_str) tls_str = "";
    ssize_t tlsv_index = cxListFind(tlsVersions, tls_str);
    if(tlsv_index > 0) {
        ui_list_setselection(settings->repo_tls_versions, tlsv_index);
    } else {
        ui_list_setselection(settings->repo_tls_versions, 0);
    }
    
    if(!repo->verification.value) {
        ui_set(settings->repo_disable_verification, TRUE);
    }
    
    
    // switch to editing tab
    ui_set(settings->repo_tabview, 1);
}

/*
 * sets a config value
 * if new_value is null, the xml node is removed
 */
static void cfg_string_set_value_or_remove(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename) {
    if(new_value.length == 0) {
        new_value.ptr = NULL;
    }
    dav_cfg_string_set_value(config, str, parent, new_value, nodename);
    if(!new_value.ptr) {
        dav_cfg_string_remove(str);
    }
}

/*
 * return the selected list value, if it is not the default value at index 0
 */
static cxstring default_list_get_value(UiList *list) {
    cxstring ret = { NULL, 0 };
    UiListSelection sel = ui_list_getselection(list);
    if(sel.count > 0) {
        int index = sel.rows[0];
        if(index > 0) {
            ret = cx_str(ui_list_get(list, index));
        }        
        free(sel.rows);
    }
    return ret;
}

void settings_store_repository(SettingsWindow *settings) {
    DavConfig *config = settings->config;
    DavCfgRepository *repo;
    if(settings->repo_new) {
        settings->repo_new = FALSE;
        char *name = ui_get(settings->repo_name);
        if(strlen(name) == 0) {
            return;
        }
        repo = dav_repository_new(config);
        dav_config_add_repository(config, repo);
    } else if(settings->selected_repo >= 0) {
        repo = ui_list_get(settings->repos, settings->selected_repo);
        if(!repo) {
            fprintf(stderr, "Error: cannot get repository at index %d\n", settings->selected_repo);
            return;
        }
    } else {
        return;
    }
    
    // always store name/url nodes
    dav_cfg_string_set_value(config, &repo->name, repo->node, cx_str(ui_get(settings->repo_name)), "name");
    dav_cfg_string_set_value(config, &repo->url, repo->node, cx_str(ui_get(settings->repo_url)), "url");
    // store user of remove node, if no user is configured
    cfg_string_set_value_or_remove(config, &repo->user, repo->node, cx_str(ui_get(settings->repo_user)), "user");
    // store cert or remove node
    cfg_string_set_value_or_remove(config, &repo->cert, repo->node, cx_str(ui_get(settings->repo_cacert)), "cert");
    
    
    // store password or remove node, if no password is configured
    char *pw = ui_get(settings->repo_password);
    size_t pwlen = strlen(pw);
    if(pwlen > 0) {
        char *pwenc = util_base64encode(pw, pwlen);
        memset(pw, 0, pwlen);
        dav_cfg_string_set_value(config, &repo->password, repo->node, cx_str(pwenc), "password");
        free(pwenc);
    } else {
        // set password 0 NULL and remove password config node
        cfg_string_set_value_or_remove(config, &repo->password, repo->node, cx_strn(NULL, 0), "password");
    }
    
    // get the selected credentials and set "stored-user"
    // remove node if not needed
    cxstring stored_user = default_list_get_value(settings->repo_credentials);
    cfg_string_set_value_or_remove(config, &repo->stored_user, repo->node, stored_user, "stored-user");
    
    // adjust full-encryption node if necessary
    int encryption = ui_get(settings->repo_encryption);
    if(encryption || repo->full_encryption.node) {
        dav_cfg_bool_set_value(config, &repo->full_encryption, repo->node, encryption, "full-encryption");
    }
    
    // set default-key
    cxstring key = default_list_get_value(settings->repo_keys);
    cfg_string_set_value_or_remove(config, &repo->default_key, repo->node, key, "default-key");
    
    // if disable_verification is enabled, set repo verification to false
    // otherwise remove the node, because verification is enabled by default
    int disable_verification = ui_get(settings->repo_disable_verification);
    if(disable_verification) {
        dav_cfg_bool_set_value(config, &repo->verification, repo->node, !disable_verification, "verification");
    } else {
        dav_cfg_bool_remove(&repo->verification);
    }
    
    // set tls version if configured
    cxstring tlsversion_str = default_list_get_value(settings->repo_tls_versions);
    int tlsversion = dav_str2ssl_version(tlsversion_str.ptr);
    if(tlsversion >= 0) {
        dav_cfg_int_set_value(config, &repo->ssl_version, repo->node, tlsversion, "ssl-version");
    } else {
        dav_cfg_int_remove(&repo->ssl_version);
    }
    
    settings_update_repolist(settings);
    settings->selected_repo = -1;
}

void settings_clear_repository(SettingsWindow *settings) {
    ui_set(settings->repo_name, "");
    ui_set(settings->repo_url, "");
    ui_set(settings->repo_user, "");
    ui_set(settings->repo_password, "");
    ui_set(settings->repo_cacert, "");
    ui_list_setselection(settings->repo_credentials, 0);
    ui_list_setselection(settings->repo_keys, 0);
    ui_list_setselection(settings->repo_tls_versions, 0);
    ui_set(settings->repo_encryption, 0);
    ui_set(settings->repo_disable_verification, 0);
}

void settings_update_repolist(SettingsWindow *settings) {
    DavConfig *config = settings->config;

    ui_list_clear(settings->repos);

    for (DavCfgRepository *repo = config->repositories; repo; repo = repo->next) {
        ui_list_append(settings->repos, repo);
    }
    
    if(settings->repos->update) {
        ui_list_update(settings->repos);
    }
}

void* settings_repolist_getvalue(DavCfgRepository *repo, int col) {
    switch(col) {
        case 0: {
            return repo->name.value.ptr;
        }
        case 1: {
            return repo->url.value.ptr;
        }
        case 2: {
            return repo->user.value.ptr;
        }
        case 3: {
            return repo->full_encryption.value ? "yes" : "no";
        }
    }
    return NULL;
}

void settings_reload_repo_keys(SettingsWindow *settings) {
    DavConfig *config = settings->config;
    DavCfgKey *key = config->keys; 
    ui_list_clear(settings->repo_keys);
    ui_list_append(settings->repo_keys, "-");
    while(key) {
        if(key->name.value.ptr) {
            ui_list_append(settings->repo_keys, key->name.value.ptr);
        }
        key = key->next;
    }
    ui_list_update(settings->repo_keys);
}

void settings_reload_keys(SettingsWindow *settings) {
    DavConfig *config = settings->config;
    DavCfgKey *key = config->keys; 
    ui_list_clear(settings->keys_list);
    while(key) {
        if(key->name.value.ptr) {
            ui_list_append(settings->keys_list, key);
        }
        key = key->next;
    }
    ui_list_update(settings->keys_list);
}

const char* dav_tlsversion2str(int value) {
    if(value == CURL_SSLVERSION_TLSv1) {
        return "TLSv1";
    } else if(value == CURL_SSLVERSION_SSLv2) {
        return "SSLv2";
    } else if(value == CURL_SSLVERSION_SSLv3) {
        return "SSLv3";
    }
#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034
    else if(value == CURL_SSLVERSION_TLSv1_0) {
        return "TLSv1.0";
    } else if(value == CURL_SSLVERSION_TLSv1_1) {
        return "TLSv1.1";
    } else if(value == CURL_SSLVERSION_TLSv1_2) {
        return "TLSv1.2";
    }
#endif
#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052
    else if(value == CURL_SSLVERSION_TLSv1_3) {
        return "TLSv1.3";
    }
#endif
    return NULL;
}


/* ----------------------------- Credentials ----------------------------- */

void settings_reload_credentials(SettingsWindow *settings) {
    PwdStore *pwd = settings->pwdstore;
    
    ui_list_clear(settings->credentials_users);
    
    CxIterator i = cxListIterator(pwd->noloc);
    cx_foreach(PwdIndexEntry*, entry, i) {
        char *id = ui_strdup(settings->obj->ctx, entry->id);
        ui_list_append(settings->credentials_users, id);
    }
    i = cxListIterator(pwd->locations);
    cx_foreach(PwdIndexEntry*, entry, i) {
        char *id = ui_strdup(settings->obj->ctx, entry->id);
        ui_list_append(settings->credentials_users, id);
    }
    
    ui_list_update(settings->credentials_users);
}

void settings_reload_repo_credentials(SettingsWindow *settings) {
    PwdStore *pwd = settings->pwdstore;
    
    ui_list_clear(settings->repo_credentials);
    
    // repo_credentials needs an entry for selecting no credentials
    ui_list_append(settings->repo_credentials, "-");
    
    CxIterator i = cxListIterator(pwd->noloc);
    cx_foreach(PwdIndexEntry*, entry, i) {
        char *id = ui_strdup(settings->obj->ctx, entry->id);
        ui_list_append(settings->repo_credentials, id);
    }
    i = cxListIterator(pwd->locations);
    cx_foreach(PwdIndexEntry*, entry, i) {
        char *id = ui_strdup(settings->obj->ctx, entry->id);
        ui_list_append(settings->repo_credentials, id);
    }
    
    ui_list_update(settings->repo_credentials);
}


void settings_credentials_select(SettingsWindow *settings, const char *id) {
    if(!id && !settings->credentials_selected_id && !settings->credentials_new) {
        fprintf(stderr, "Error: no credentials id selected\n");
        return;
    }
    
    PwdStore *pwd = settings->pwdstore;
    
    if(id) {
        ui_free(settings->obj->ctx, settings->credentials_selected_id);
        settings->credentials_selected_id = ui_strdup(settings->obj->ctx, id);
    }
    
    if(!pwd->isdecrypted) {
        settings_credentials_decrypt(settings);
        return;
    }
    
    if(settings->credentials_new) {
        ui_free(settings->obj->ctx, settings->credentials_selected_id);
        settings->credentials_selected_id = NULL;
        settings->credentials_selected_index = ui_list_count(settings->credentials_users);
        ui_list_append(settings->credentials_users, ui_strdup(settings->obj->ctx, "new"));
        settings->credentials_ignore_selectionevent = TRUE;
        ui_list_update(settings->credentials_users);
        ui_list_setselection(settings->credentials_users, settings->credentials_selected_index);
        settings->credentials_ignore_selectionevent = FALSE;
        
        ui_set(settings->credentials_id, "new");
        ui_set(settings->credentials_user, "");
        ui_set(settings->credentials_password, "");
        ui_list_clear(settings->credentials_locations);
        ui_list_update(settings->credentials_locations);
    } else {
        PwdEntry *entry = cxMapGet(pwd->ids, settings->credentials_selected_id);
        PwdIndexEntry *index = cxMapGet(pwd->index, settings->credentials_selected_id);
        if(!entry) {
            fprintf(stderr, "Error: cannot get pwd entry %s\n", settings->credentials_selected_id);
            return;
        }
        if(!index) {
            fprintf(stderr, "Error: missing PwdIndexEntry. PwdStore may be broken.\n");
            return;
        }

        ui_set(settings->credentials_id, entry->id);
        ui_set(settings->credentials_user, entry->user);
        ui_set(settings->credentials_password, entry->password);

        ui_list_clear(settings->credentials_locations);
        if(index->locations) {
            CxIterator i = cxListIterator(index->locations);
            cx_foreach(char *, loc, i) {
                ui_list_append(settings->credentials_locations, ui_strdup(settings->obj->ctx, loc));
            }
        }
    }
    
    ui_list_update(settings->credentials_locations);
    
    ui_set_group(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED);
}

void settings_credentials_clear(SettingsWindow *settings) {
    ui_free(settings->obj->ctx, settings->credentials_selected_id);
    settings->credentials_selected_id = NULL;
    settings->credentials_new = FALSE;
    
    ui_set(settings->credentials_id, "");
    ui_set(settings->credentials_user, "");
    ui_set(settings->credentials_password, "");
    ui_list_clear(settings->credentials_locations);
    ui_list_update(settings->credentials_locations);
    
    ui_unset_group(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED);
}

int settings_credentials_save(SettingsWindow *settings) {
    if(settings->credentials_selected_index == -1) {
        return 0;
    }
    
    // check if the credentials list contains an element with the same id
    // if an index is found, it must match the selected index, otherwise
    // we have a duplicate identifier
    char *newid = ui_get(settings->credentials_id);
    if(strlen(newid) == 0) {
        return 0; // TODO: check if other fields are set
    }
    
    ssize_t index = cxListFind(settings->credentials_users->data, newid);
    if(index >=0 && index != settings->credentials_selected_index) {
        cxmutstr s = cx_asprintf("Identifier %s already in use", newid);
        ui_dialog(settings->obj, .title = "Error", .content = s.ptr, .closebutton_label = "OK");
        free(s.ptr);
        return 1;
    }
    
    char *user = ui_get(settings->credentials_user);
    char *password = ui_get(settings->credentials_password);
    CxList *ui_locations = settings->credentials_locations->data;
    size_t numlocations = cxListSize(ui_locations);
    CxList *locations = NULL;
    if(numlocations > 0) {
        locations = cxArrayListCreateSimple(CX_STORE_POINTERS, numlocations);
        CxIterator i = cxListIterator(ui_locations);
        cx_foreach(char*, loc, i) {
            cxListAdd(locations, strdup(loc));
        }
    }
    
    // if the user id changed, mark the list as changed to update it later
    settings->credentials_list_needs_update = settings->credentials_new || strcmp(newid, settings->credentials_selected_id);
    if(settings->credentials_new || strcmp(newid, settings->credentials_selected_id ? settings->credentials_selected_id : "")) {
        settings->credentials_list_needs_update = TRUE;
        cxListRemove(settings->credentials_users->data, settings->credentials_selected_index);
        cxListInsert(settings->credentials_users->data, settings->credentials_selected_index, ui_strdup(settings->obj->ctx, newid));
    }
    
    // if the pwdstore already contains this id, remove it first
    if(settings->credentials_selected_id) {
        pwdstore_remove_entry(settings->pwdstore, settings->credentials_selected_id);
    }
    
    pwdstore_put(settings->pwdstore, newid, user, password);
    pwdstore_put_index(settings->pwdstore, strdup(newid), locations);
    settings->credentials_modified = TRUE;
    settings->credentials_new = FALSE;
    
    return 0;
}

void *keylist_getvalue(void *data, int col) {
    DavCfgKey *key = data;
    return key->name.value.ptr;
}

void settings_edit_key(SettingsWindow *settings, DavCfgKey *key) {
    if(key->name.value.ptr) {
        ui_set(settings->key_name, key->name.value.ptr);
    }
    const char *key_type;
    switch(key->type) {
        default: key_type = ""; break;
        case DAV_KEY_TYPE_AES256: {
            key_type = "AES256";
            break;
        }
        case DAV_KEY_TYPE_AES128: {
            key_type = "AES128";
            break;
        }
        case DAV_KEY_TYPE_UNKNOWN: {
            key_type = "unknown";
            break;
        }
    }
    ui_set(settings->key_type, key_type);
    
    if(key->file.value.ptr) {
        ui_set(settings->key_file, key->file.value.ptr);
    }
}

void settings_clear_key(SettingsWindow *settings) {
    ui_set(settings->key_name, "");
    ui_set(settings->key_type, "");
    ui_set(settings->key_file, "");
}

mercurial