Thu, 28 Nov 2024 18:03:12 +0100
implement UI for editing properties, relates to #497
/* * 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 "davcontroller.h" #include "window.h" #include <cx/printf.h> #include "config.h" #include "upload.h" #include "download.h" #include "system.h" #include "common/context.h" #include <libidav/config.h> #include <libidav/utils.h> DavBrowser* davbrowser_create(UiObject *toplevel) { DavBrowser *doc = ui_document_new(sizeof(DavBrowser)); UiContext *ctx = ui_document_context(doc); CxMempool *mp = ui_cx_mempool(ctx); doc->window = toplevel; doc->ctx = ctx; doc->navigation_stack = cxLinkedListCreateSimple(CX_STORE_POINTERS); doc->navstack_enabled = true; doc->navstack_pos = 0; doc->dav_queue = ui_threadpool_create(1); cxMempoolRegister(mp, doc->dav_queue, (cx_destructor_func)ui_threadpool_destroy); doc->res_open_inprogress = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 4); doc->path = ui_string_new(ctx, "path"); doc->resources = ui_list_new(ctx, "reslist"); return doc; } void davbrowser_set_collection(UiObject *ui, DavBrowser *browser, DavResource *collection) { if (browser->current) { dav_resource_free_all(browser->current); } ui_list_clear(browser->resources); browser->current = collection; browser->res_counter++; for (DavResource *res = collection->children; res; res = res->next) { ui_list_append(browser->resources, res); } browser->resources->update(browser->resources, 0); ui_set_group(ui->ctx, APP_STATE_BROWSER_SESSION); } // ------------------------------ davbrowser_connect2repo ------------------------------ typedef struct DavConnect2Repo { UiObject *ui; DavBrowser *browser; DavCfgRepository *repo; char *path; UiString *password; } DavConnect2Repo; static void dialog_secretstore_decrypt(UiEvent *event, void *data) { DavConnect2Repo *c2r = event->window; if(event->intval == 4) { char *pw = ui_get(c2r->password); PwdStore *secrets = get_pwdstore(); pwdstore_setpassword(secrets, pw); if(pwdstore_decrypt(secrets)) { ui_dialog(c2r->ui, .title = "Error", .content = "Cannot decrypt Secret Store", .closebutton_label = "OK"); } else { davbrowser_connect2repo(c2r->ui, c2r->browser, c2r->repo, c2r->path); } } free(c2r->path); if(!c2r->repo->node) { dav_repository_free(get_config(), c2r->repo); } ui_close(event->obj); } int davbrowser_connect2repo(UiObject *ui, DavBrowser *browser, DavCfgRepository *repo, const char *path) { char *user = NULL; char *password = NULL; char *password_free = NULL; if (repo->user.value.ptr && repo->password.value.ptr) { user = repo->user.value.ptr; cxmutstr decodedpw = dav_repository_get_decodedpassword(repo); password = decodedpw.ptr; password_free = decodedpw.ptr; } else { PwdStore *secrets = get_pwdstore(); const char *credentials = NULL; if(repo->stored_user.value.ptr) { if(pwdstore_has_id(secrets, repo->stored_user.value.ptr)) { credentials = repo->stored_user.value.ptr; } } else { credentials = get_location_credentials(repo, path); } if(credentials) { if(!secrets->isdecrypted) { UiObject *obj = ui_dialog_window(ui, .title = "Authentication", .lbutton1 = "Cancel", .rbutton4 = "Connect", .default_button = 4, .show_closebutton = UI_OFF, .onclick = dialog_secretstore_decrypt); DavConnect2Repo *c2r = ui_malloc(obj->ctx, sizeof(DavConnect2Repo)); c2r->ui = ui; c2r->browser = browser; c2r->repo = repo; c2r->path = path ? strdup(path) : NULL; c2r->password = ui_string_new(obj->ctx, NULL); obj->window = c2r; ui_grid(obj, .margin = 20, .columnspacing = 12, .rowspacing = 16) { ui_llabel(obj, .label = "Decrypt Secret Store", .colspan = 2); ui_newline(obj); ui_llabel(obj, .label = "Password"); ui_passwordfield(obj, .value = c2r->password, .hexpand = TRUE); } ui_show(obj); return 1; } if(!get_stored_credentials(credentials, &user, &password)) { fprintf(stderr, "Error: failed to get user/password for credentials %s\n", credentials); } } } DavSession *sn = dav_session_new(application_dav_context(), repo->url.value.ptr); if (user && password) { dav_session_set_auth(sn, user, password); } free(password_free); sn->flags = dav_repository_get_flags(repo); sn->key = dav_context_get_key(application_dav_context(), repo->default_key.value.ptr); curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version); if(repo->cert.value.ptr) { curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr); } if(!repo->verification.value) { curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0); } browser->sn = sn; if (repo->name.value.length > 0) { browser->repo_base = cx_strdup(cx_strn(repo->name.value.ptr, repo->name.value.length)).ptr; } else { browser->repo_base = cx_strdup(cx_strn(repo->url.value.ptr, repo->url.value.length)).ptr; } char *dav_path = util_concat_path(browser->repo_base, path); ui_set(browser->path, dav_path); free(dav_path); SessionAuthData *auth = cxMalloc(sn->mp->allocator, sizeof(SessionAuthData)); auth->obj = ui; auth->cond = ui_condvar_create(); auth->sn = sn; auth->user = repo->user.value.ptr ? cx_strdup_a(sn->mp->allocator, cx_strcast(repo->user.value)).ptr : NULL; auth->password = NULL; dav_session_set_authcallback(sn, jobthr_davbrowser_auth, auth); davbrowser_query_path(ui, browser, path); return 0; } // ------------------------------ davbrowser_auth ------------------------------ static int davbrowser_auth_dialog(void *data) { SessionAuthData *auth = data; auth_dialog(auth); return 0; } void davbrowser_auth_set_user_pwd(SessionAuthData *auth, const char *user, const char *password) { dav_session_free(auth->sn, auth->user); dav_session_free(auth->sn, auth->password); auth->user = user ? dav_session_strdup(auth->sn, user) : NULL; auth->password = password ? dav_session_strdup(auth->sn, password) : NULL; } int jobthr_davbrowser_auth(DavSession *sn, void *data) { SessionAuthData *auth = data; ui_call_mainthread(davbrowser_auth_dialog, auth); ui_condvar_wait(auth->cond); if(auth->cond->intdata) { dav_session_set_auth(sn, auth->user, auth->password); } dav_session_free(auth->sn, auth->password); auth->password = NULL; return 0; } // ------------------------------ davbrowser_query_path ------------------------------ typedef struct DavBrowserQueryPath { UiThreadpool *pool; DavBrowser *browser; char *path; DavResource *result; } DavBrowserQueryPath; static int browser_query_path(void *data) { DavBrowserQueryPath *query = data; DavSession *sn = query->browser->sn; DavResource *res = dav_query(sn, "select `idav:crypto-name`,`idav:crypto-key`,D:lockdiscovery from %s with depth = 1 order by iscollection desc, name", query->path); query->result = res; return 0; } static void browser_query_finished(UiEvent *event, void *data) { DavBrowserQueryPath *query = data; DavBrowser *browser = event->document; if (query->pool == browser->dav_queue) { if (query->result) { davbrowser_set_collection(event->obj, browser, query->result); } else { cxmutstr error = cx_asprintf("Error %d", query->browser->sn->error); ui_dialog(event->obj, .title = "Error", .content = error.ptr, .closebutton_label = "OK"); } window_progress(event->window, 0); } else { // operation aborted if (query->result) { dav_resource_free_all(query->result); } } free(query->path); free(query); } void davbrowser_query_path(UiObject *ui, DavBrowser *browser, const char *path) { if (!browser->sn) { // TODO: error return; } // for comparison, we need the current base_url/repo_name + path size_t len = path ? strlen(path) : 0; if (len == 1 && *path == '/') { path = ""; } // check if the new path is a prefix of the current path // if not, we have to set the pathbar string to the new path char *full_path = util_concat_path(browser->repo_base, path); char *full_path_col = util_concat_path(full_path, "/"); char *current_path = ui_get(browser->path); cxstring cpath = cx_str(current_path); cxstring newc = cx_str(full_path_col); if (!cx_strprefix(cpath, newc)) { ui_set(browser->path, full_path); } free(full_path); free(full_path_col); DavBrowserQueryPath *query = malloc(sizeof(DavBrowserQueryPath)); query->pool = browser->dav_queue; query->browser = browser; query->path = strdup(path); query->result = NULL; ui_threadpool_job(browser->dav_queue, ui, browser_query_path, query, browser_query_finished, query); window_progress(ui->window, 1); davbrowser_add2navstack(browser, browser->repo_base, path); } void davbrowser_query_url(UiObject *ui, DavBrowser *browser, const char *url) { if (browser->repo_base) { cxstring base = cx_str(browser->repo_base); cxstring newurl = cx_str(url); if (cx_strprefix(newurl, base)) { const char *path = url + base.length; davbrowser_query_path(ui, browser, path); return; } } char *path = NULL; DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); int ret = davbrowser_connect2repo(ui, browser, repo, path); free(path); if(ret) { return; } if (!repo->node) { dav_repository_free(get_config(), repo); } } void davbrowser_open_resource(UiObject *ui, DavBrowser *browser, DavResource *res, const char *contenttype) { DavResourceViewType type = DAV_RESOURCE_VIEW_PROPERTIES; if(!contenttype) { contenttype = res->contenttype; } if(res->iscollection) { // default type } else if(contenttype) { cxstring ctype = cx_str(contenttype); if(cx_strprefix(ctype, CX_STR("text/"))) { type = DAV_RESOURCE_VIEW_TEXT; } else if(cx_strprefix(ctype, CX_STR("image/"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strprefix(ctype, CX_STR("application/"))) { if(cx_strsuffix(ctype, CX_STR("json"))) { type = DAV_RESOURCE_VIEW_TEXT; } else if(cx_strsuffix(ctype, CX_STR("/xml"))) { type = DAV_RESOURCE_VIEW_TEXT; } else if(cx_strsuffix(ctype, CX_STR("+xml"))) { type = DAV_RESOURCE_VIEW_TEXT; } else if(cx_strsuffix(ctype, CX_STR("/xml"))) { type = DAV_RESOURCE_VIEW_TEXT; } } } else { cxstring path = cx_str(res->path); if(cx_strsuffix(path, CX_STR(".png"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".jpg"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".jpeg"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".tif"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".tiff"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".webp"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".bmp"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".gif"))) { type = DAV_RESOURCE_VIEW_IMAGE; } else if(cx_strsuffix(path, CX_STR(".txt"))) { type = DAV_RESOURCE_VIEW_TEXT; } else if(cx_strsuffix(path, CX_STR(".md"))) { type = DAV_RESOURCE_VIEW_TEXT; } else if(cx_strsuffix(path, CX_STR(".xml"))) { type = DAV_RESOURCE_VIEW_TEXT; } } resourceviewer_new(browser, res->path, type); } void davbrowser_add2navstack(DavBrowser *browser, const char *base, const char *path) { if (browser->navstack_enabled) { for (int i = 0; i < browser->navstack_pos; i++) { char *nav_url = cxListAt(browser->navigation_stack, 0); cxListRemove(browser->navigation_stack, 0); free(nav_url); } browser->navstack_pos = 0; char *nav_url = util_concat_path(base, path); cxListInsert(browser->navigation_stack, 0, nav_url); if (cxListSize(browser->navigation_stack) > DAVBROWSER_MAX_NAVLIST) { char *nav = cxListAt(browser->navigation_stack, cxListSize(browser->navigation_stack) - 1); free(nav); cxListRemove(browser->navigation_stack, cxListSize(browser->navigation_stack) - 1); } } } void davbrowser_navigation_parent(UiObject *ui, DavBrowser *browser) { if(browser->current) { char *parent = util_parent_path(browser->current->path); if(strlen(parent) > 0) { davbrowser_query_path(ui, browser, parent); } free(parent); } } void davbrowser_navigation_back(UiObject *ui, DavBrowser *browser) { if (browser->navstack_pos+1 < cxListSize(browser->navigation_stack)) { browser->navstack_pos++; char *nav_url = cxListAt(browser->navigation_stack, browser->navstack_pos); browser->navstack_enabled = false; davbrowser_query_url(ui, browser, nav_url); browser->navstack_enabled = true; ui_set(browser->path, nav_url); } } void davbrowser_navigation_forward(UiObject *ui, DavBrowser *browser) { if (browser->navstack_pos > 0) { browser->navstack_pos--; char *nav_url = cxListAt(browser->navigation_stack, browser->navstack_pos); browser->navstack_enabled = false; davbrowser_query_url(ui, browser, nav_url); browser->navstack_enabled = true; ui_set(browser->path, nav_url); } } void davbrowser_upload_files(UiObject *ui, DavBrowser *browser, UiFileList files) { if (!browser->sn) { return; // TODO: error msg } cxmutstr wtitle = cx_asprintf("Upload to: %s", ui_get(browser->path)); UiObject *dialog = ui_simple_window(wtitle.ptr, NULL); free(wtitle.ptr); DavFileUpload *upload = dav_upload_create(browser, dialog, files); transfer_window_init(dialog, action_upload_cancel); dav_upload_start(upload); application_register_transfer(&upload->trans); } void davbrowser_download(UiObject *ui, DavBrowser *browser, DavResource *reslist, const char *local_path) { cxmutstr wtitle = cx_asprintf("Download to: %s", local_path); UiObject *dialog = ui_simple_window(wtitle.ptr, NULL); free(wtitle.ptr); DavFileDownload *download = dav_download_create(browser, dialog, reslist, local_path); transfer_window_init(dialog, action_download_cancel); dav_download_start(download); application_register_transfer(&download->trans); } // ------------------------------------- Path Operation (DELETE, MKCOL) ------------------------------------- enum DavPathOpType { DAV_PATH_OP_DELETE = 0, DAV_PATH_OP_CREATE }; typedef struct DavPathOp { UiObject *ui; DavBrowser *browser; // operation type (delete, mkcol, ...) enum DavPathOpType op; // clone of the browser's DavSession DavSession *sn; // path array char **path; // browser->resources indices size_t *list_indices; // number of path/list_indices elements size_t nelm; // path is collection DavBool iscollection; // browser->current ptr when the PathOp started // used in combination with collection_ctn to check if the browser list changed DavResource *collection; int64_t collection_ctn; } DavPathOp; typedef struct DavPathOpResult { UiObject *ui; DavBrowser *browser; DavResource *collection; int64_t collection_ctn; enum DavPathOpType op; DavBool iscollection; char *path; int res_index; int result; char *errormsg; time_t result_lastmodified; uint64_t result_contentlength; char *result_contenttype; } DavPathOpResult; typedef struct DavRenameOp { UiObject *ui; DavBrowser *browser; // clone of the browser's DavSession DavSession *sn; char *path; char *newname; int result; char *errormsg; // browser->resources index size_t index; // browser->current ptr when the PathOp started // used in combination with collection_ctn to check if the browser list changed DavResource *collection; int64_t collection_ctn; } DavRenameOp; static int uithr_pathop_delete_error(void *data) { DavPathOpResult *result = data; cxmutstr msg = cx_asprintf("Cannot delete resource %s", result->path); ui_dialog(result->ui, .title = "Error", .content = msg.ptr, .button1_label = "OK"); free(msg.ptr); if (result->errormsg) { free(result->errormsg); } free(result->path); free(result); return 0; } static int uithr_pathop_delete_sucess(void *data) { DavPathOpResult *result = data; if (result->browser->current == result->collection && result->browser->res_counter == result->collection_ctn) { ui_list_remove(result->browser->resources, result->res_index); result->browser->resources->update(result->browser->resources, 0); } free(result->path); free(result); return 0; } static int uithr_pathop_create_resource_error(void *data) { DavPathOpResult *result = data; cxmutstr msg = cx_asprintf("Cannot create %s %s", result->iscollection ? "collection" : "resource", result->path); ui_dialog(result->ui, .title = "Error", .content = msg.ptr, .button1_label = "OK"); free(msg.ptr); if (result->errormsg) { free(result->errormsg); } free(result->path); free(result); return 0; } static int uithr_pathop_create_resource_sucess(void *data) { DavPathOpResult *result = data; if (result->browser->current == result->collection && result->browser->res_counter == result->collection_ctn) { DavResource *res = dav_resource_new(result->browser->sn, result->path); res->iscollection = result->iscollection; res->lastmodified = result->result_lastmodified; res->contentlength = result->result_contentlength; res->contenttype = result->result_contenttype ? dav_session_strdup(res->session, result->result_contenttype) : NULL; // TODO: add the resource at the correct position or sort the list after append ui_list_append(result->browser->resources, res); result->browser->resources->update(result->browser->resources, 0); } free(result->path); free(result->result_contenttype); free(result); return 0; } static int jobthr_path_op(void *data) { DavPathOp *op = data; for (int i = op->nelm-1; i >= 0; i--) { if (op->path[i]) { DavResource *res = dav_resource_new(op->sn, op->path[i]); DavPathOpResult *result = malloc(sizeof(DavPathOpResult)); result->ui = op->ui; result->browser = op->browser; result->collection = op->collection; result->collection_ctn = op->collection_ctn; result->op = op->op; result->path = strdup(res->path); result->result = 0; result->res_index = op->list_indices[i]; result->errormsg = NULL; result->iscollection = op->iscollection; result->result_lastmodified = 0; result->result_contentlength = 0; result->result_contenttype = NULL; if (op->op == DAV_PATH_OP_DELETE) { ui_threadfunc result_callback = uithr_pathop_delete_sucess; if (dav_delete(res)) { result->errormsg = op->sn->errorstr ? strdup(op->sn->errorstr) : NULL; result_callback = uithr_pathop_delete_error; } ui_call_mainthread(result_callback, result); } else if (op->op == DAV_PATH_OP_CREATE) { res->iscollection = op->iscollection; ui_threadfunc result_callback = uithr_pathop_create_resource_sucess; if (dav_create(res)) { result->errormsg = op->sn->errorstr ? strdup(op->sn->errorstr) : NULL; result_callback = uithr_pathop_create_resource_error; } else { // try to load some basic resource properties // we don't care about the result, if it fails, // we just don't have the new properties dav_load_prop(res, NULL, 0); result->result_lastmodified = res->lastmodified; result->result_contentlength = res->contentlength; result->result_contenttype = res->contenttype ? strdup(res->contenttype) : NULL; } ui_call_mainthread(result_callback, result); } dav_resource_free(res); free(op->path[i]); } } dav_session_destroy(op->sn); free(op->path); free(op->list_indices); free(op); return 0; } void davbrowser_delete(UiObject *ui, DavBrowser *browser, UiListSelection selection) { DavPathOp *op = malloc(sizeof(DavPathOp)); op->ui = ui; op->browser = browser; op->op = DAV_PATH_OP_DELETE; op->sn = dav_session_clone(browser->sn); op->path = calloc(selection.count, sizeof(char*)); op->list_indices = calloc(selection.count, sizeof(size_t)); op->nelm = selection.count; op->collection = browser->current; op->collection_ctn = browser->res_counter; for (int i = 0; i < selection.count; i++) { DavResource *res = ui_list_get(browser->resources, selection.rows[i]); if (res) { op->path[i] = strdup(res->path); op->list_indices[i] = selection.rows[i]; } } ui_job(ui, jobthr_path_op, op, NULL, NULL); } void davbrowser_create_resource(UiObject *ui, DavBrowser *browser, const char *name, DavBool iscollection) { DavPathOp *op = malloc(sizeof(DavPathOp)); op->ui = ui; op->browser = browser; op->op = DAV_PATH_OP_CREATE; op->sn = dav_session_clone(browser->sn); op->path = calloc(1, sizeof(char*)); op->list_indices = calloc(1, sizeof(size_t)); op->nelm = 1; op->iscollection = iscollection; op->path[0] = util_concat_path(browser->current->path, name); op->collection = browser->current; op->collection_ctn = browser->res_counter; ui_job(ui, jobthr_path_op, op, NULL, NULL); } void davbrowser_mkcol(UiObject *ui, DavBrowser *browser, const char *name) { davbrowser_create_resource(ui, browser, name, TRUE); } void davbrowser_newfile(UiObject *ui, DavBrowser *browser, const char *name) { davbrowser_create_resource(ui, browser, name, FALSE); } static int jobthr_rename(void *data) { DavRenameOp *op = data; DavResource *res = dav_get(op->sn, op->path, NULL); if(!res) { fprintf(stderr, "Error: Cannot get resource %s\n", op->path); op->result = 1; return 0; } char *cryptoname = dav_get_string_property_ns(res, DAV_NS, "crypto-name"); char *cryptokey = dav_get_string_property_ns(res, DAV_NS, "crypto-key"); if(cryptoname && cryptokey) { // encrypted resource, the name is stored in the crypto-name property // these properties are only loaded if encryption is enabled for // this session DavKey *key = dav_context_get_key(op->sn->context, cryptokey); if(!key) { cxmutstr error = cx_asprintf_a(op->sn->mp->allocator, "Cannot rename resource: crypto key %s not found.", cryptokey); op->errormsg = error.ptr; op->result = 1; return 0; } // check if a resource with this name already exists char *parent = util_parent_path(res->path); char *newpath = util_concat_path(parent, op->newname); DavResource *testres = dav_resource_new(op->sn, newpath); if(dav_exists(testres)) { cxmutstr error = cx_asprintf_a(op->sn->mp->allocator, "A resource with the name %s already exists.", op->newname); op->errormsg = error.ptr; op->result = 1; } else { char *crname = aes_encrypt(op->newname, strlen(op->newname), key); dav_set_string_property_ns(res, DAV_NS, "crypto-name", crname); free(crname); if(dav_store(res)) { op->result = 1; } } free(parent); free(newpath); } else { // rename the resource by changing the url mapping with MOVE char *parent = util_parent_path(res->href); char *new_href = util_concat_path(parent, op->newname); char *dest = util_get_url(op->sn, new_href); free(parent); free(new_href); if(dav_moveto(res, dest, false)) { op->result = 1; } free(dest); } if(op->result && !op->errormsg) { cxmutstr error = cx_asprintf_a(op->sn->mp->allocator, "Error: %d", op->sn->error); op->errormsg = error.ptr; } return 0; } static void uithr_rename_finished(UiEvent *event, void *data) { DavRenameOp *op = data; if(!op->result) { // update name in the browser list if (op->browser->current == op->collection && op->browser->res_counter == op->collection_ctn) { DavResource *res = ui_list_get(op->browser->resources, op->index); char *parent = util_parent_path(res->path); char *newpath = util_concat_path(parent, op->newname); dav_session_free(res->session, res->path); dav_session_free(res->session, res->name); res->path = dav_session_strdup(res->session, newpath); res->name = dav_session_strdup(res->session, op->newname); op->browser->resources->update(op->browser->resources, 0); free(parent); free(newpath); } } else { // error ui_dialog(op->ui, .title = "Error", .content = op->errormsg, .closebutton_label = "OK"); } dav_session_destroy(op->sn); } static void action_resource_rename(UiEvent *event, void *data) { DavRenameOp *op = data; if(event->intval == 1) { char *newname = event->eventdata; if(!newname || strlen(newname) == 0) { ui_dialog(op->ui, .title = "Error", .content = "No name specified", .closebutton_label = "OK"); dav_session_destroy(op->sn); return; } char *s = strchr(newname, '/'); if(s) { ui_dialog(op->ui, .title = "Error", .content = "Character '/' is not allowed", .closebutton_label = "OK"); dav_session_destroy(op->sn); return; } op->newname = dav_session_strdup(op->sn, newname); ui_job(op->ui, jobthr_rename, op, uithr_rename_finished, op); return; } dav_session_destroy(op->sn); } void davbrowser_rename(UiObject *ui, DavBrowser *browser, UiListSelection selection) { DavSession *sn = dav_session_clone(browser->sn); DavResource *res = ui_list_get(browser->resources, selection.rows[0]); DavRenameOp *rename = dav_session_malloc(sn, sizeof(DavRenameOp)); memset(rename, 0, sizeof(DavRenameOp)); rename->browser = browser; rename->ui = ui; rename->sn = sn; rename->path = dav_session_strdup(sn, res->path); rename->index = selection.rows[0]; rename->collection = browser->current; rename->collection_ctn = browser->res_counter; ui_dialog(ui, .title = "Rename", .content = res->name, .input = TRUE, .input_value = res->name, .result = action_resource_rename, .resultdata = rename, .button1_label = "Rename", .closebutton_label = "Cancel"); } DavResourceViewer* dav_resourceviewer_create(UiObject *toplevel, DavSession *sn, const char *path, DavResourceViewType type) { DavResourceViewer *doc = ui_document_new(sizeof(DavResourceViewer)); UiContext *ctx = ui_document_context(doc); CxMempool *mp = ui_cx_mempool(ctx); doc->obj = toplevel; doc->ctx = ctx; doc->sn = dav_session_clone(sn); doc->dav_queue = ui_threadpool_create(1); cxMempoolRegister(mp, doc->dav_queue, (cx_destructor_func)ui_threadpool_destroy); doc->path = strdup(path); doc->type = type; doc->tabview = ui_int_new(ctx, "tabview"); doc->loading = ui_int_new(ctx, "loading"); doc->message = ui_string_new(ctx, "message"); doc->text = ui_text_new(ctx, "text"); doc->image = ui_generic_new(ctx, "image"); doc->properties = ui_list_new(ctx, "properties"); doc->info_url = ui_string_new(ctx, "info_url"); doc->info_name = ui_string_new(ctx, "info_name"); doc->info_type = ui_string_new(ctx, "info_type"); doc->info_encrypted = ui_string_new(ctx, "info_encrypted"); doc->info_etag = ui_string_new(ctx, "info_etag"); doc->info_size = ui_string_new(ctx, "info_size"); doc->property_type = ui_int_new(ctx, NULL); doc->property_ns = ui_string_new(ctx, NULL); doc->property_name = ui_string_new(ctx, NULL); doc->property_nsdef = ui_string_new(ctx, NULL); doc->property_value = ui_text_new(ctx, NULL); doc->property_errormsg = ui_string_new(ctx, NULL); return doc; } static char* gen_tmp_download_filename(const char *name) { char *dir = ui_getappdir(); unsigned char rd[8]; memset(rd, 0, 8); dav_rand_bytes(rd, 8); char *hex = util_hexstr(rd, 8); cxmutstr tmp = cx_asprintf("%sdownload-%s-%s", dir, hex, name); return tmp.ptr; } static int jobthr_resourceviewer_load(void *data) { DavResourceViewer *doc = data; DavResource *res = dav_resource_new(doc->sn, doc->path); doc->error = dav_load(res); if(!doc->error) { doc->current = res; if(res->contentlength < DAV_RESOURCEVIEWER_PREVIEW_MAX_SIZE) { if(doc->type == DAV_RESOURCE_VIEW_TEXT) { doc->text_content = cxBufferCreate(NULL, res->contentlength, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); int err = dav_get_content(res, doc->text_content, (dav_write_func)cxBufferWrite); cxBufferPut(doc->text_content, 0); if(err) { doc->error = err; doc->message_str = "Cannot load content"; // TODO: better message } } else if(doc->type == DAV_RESOURCE_VIEW_IMAGE) { char *tmp = gen_tmp_download_filename(res->name); FILE *f = sys_fopen(tmp, "wb"); if(f) { int err = dav_get_content(res, f, (dav_write_func)fwrite); if(!err) { doc->tmp_file = tmp; } else { free(tmp); } fclose(f); } else { free(tmp); } } } else { // TODO: add file too large message } } else { doc->message_str = "Cannot load properties"; // TODO: better message dav_resource_free(res); } return 0; } static void resourceviewer_set_info(DavResourceViewer *doc) { DavResource *res = doc->current; if(!res) { return; } char *url = util_concat_path(res->session->base_url, res->href); ui_set(doc->info_url, url); free(url); ui_set(doc->info_name, res->name); if(res->iscollection) { ui_set(doc->info_type, "Collection"); } else { if(res->contenttype) { cxmutstr type = cx_asprintf("Resource (%s)", res->contenttype); ui_set(doc->info_type, type.ptr); free(type.ptr); } else { ui_set(doc->info_type, "Resource"); } } char *keyprop = dav_get_string_property_ns( res, DAV_NS, "crypto-key"); if(keyprop) { cxmutstr info_encrypted = cx_asprintf("Yes Key: %s", keyprop); ui_set(doc->info_encrypted, info_encrypted.ptr); free(info_encrypted.ptr); } else { ui_set(doc->info_encrypted, "No"); } char *etag = dav_get_string_property_ns( res, "DAV:", "getetag"); ui_set(doc->info_etag, etag); if(res->contentlength > 0) { char *sz = util_size_str(FALSE, res->contentlength); cxmutstr size_str = cx_asprintf("%s (%" PRIu64 " bytes)", sz, res->contentlength); ui_set(doc->info_size, size_str.ptr); free(size_str.ptr); } else { ui_set(doc->info_size, "0"); } } static void resourceviewer_update_proplist(DavResourceViewer *doc) { DavResource *res = doc->current; if(!res) { return; } size_t count = 0; DavPropName *properties = dav_get_property_names(res, &count); for(int i=0;i<count;i++) { DavPropertyList *prop = ui_malloc(doc->ctx, sizeof(DavPropertyList)); prop->ns = properties[i].ns ? ui_strdup(doc->ctx, properties[i].ns) : NULL; prop->name = ui_strdup(doc->ctx, properties[i].name); prop->value_simplified = NULL; prop->value_full = NULL; prop->update = FALSE; prop->isnew = FALSE; DavXmlNode *xval = dav_get_property_ns(res, prop->ns, prop->name); prop->xml = xval; if(xval) { if(dav_xml_isstring(xval)) { char *value = dav_xml_getstring(xval); if(value) { prop->value_simplified = NULL; prop->value_full = ui_strdup(doc->ctx, value); } } else { DavXmlNode *x = xval->type == DAV_XML_ELEMENT ? xval : dav_xml_nextelm(xval); cxmutstr value = cx_asprintf_a(ui_allocator(doc->ctx), "<%s>...</%s>", x->name, x->name); prop->value_simplified = value.ptr; } } ui_list_append(doc->properties, prop); } doc->properties->update(doc->properties, 0); } static void resourceviewer_load_finished(UiEvent *event, void *data) { DavResourceViewer *doc = data; if(doc->window_closed) { dav_resourceviewer_destroy(doc); return; } resourceviewer_set_info(doc); resourceviewer_update_proplist(doc); if(doc->type == DAV_RESOURCE_VIEW_TEXT) { ui_set(doc->text, doc->text_content->space); } else if(doc->type == DAV_RESOURCE_VIEW_IMAGE) { ui_image_load_file(doc->image, doc->tmp_file); } ui_set(doc->tabview, 1); doc->loaded = TRUE; } void dav_resourceviewer_load(UiObject *ui, DavResourceViewer *res) { ui_set(res->loading, 1); ui_set(res->message, "Loading..."); ui_set(res->tabview, 0); ui_job(ui, jobthr_resourceviewer_load, res, resourceviewer_load_finished, res); } typedef struct ResourceViewerUploadFile { UiObject *ui; DavSession *sn; char *path; cxmutstr text; int error; } ResourceViewerUploadFile; static int jobthr_upload_text(void *data) { ResourceViewerUploadFile *upload = data; DavResource *res = dav_resource_new(upload->sn, upload->path); dav_set_content_data(res, upload->text.ptr, upload->text.length); upload->error = dav_store(res); dav_resource_free(res); return 0; } static void uithr_upload_text_finished(UiEvent *event, void *data) { ResourceViewerUploadFile *upload = data; ui_object_unref(event->obj); if(upload->error) { cxmutstr errormsg = cx_asprintf("Upload failed: %d", upload->sn->error); // TODO: add full error message ui_dialog(event->obj, .title = "Error", .content = errormsg.ptr, .closebutton_label = "OK"); free(errormsg.ptr); ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); } free(upload->text.ptr); free(upload); } static int jobthr_store_properties(void *data) { DavResourceViewer *res = data; res->error = dav_store(res->current); return 0; } static void uithr_store_properties_finished(UiEvent *event, void *data) { DavResourceViewer *res = data; ui_object_unref(event->obj); if(res->error) { cxmutstr errormsg = cx_asprintf("Proppatch failed: %d", res->sn->error); // TODO: add full error message ui_dialog(event->obj, .title = "Error", .content = errormsg.ptr, .closebutton_label = "OK"); free(errormsg.ptr); ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; } else { CxList *properties = res->properties->data; CxIterator i = cxListIterator(properties); cx_foreach(DavPropertyList *, prop, i) { prop->update = FALSE; prop->isnew = FALSE; } } } void dav_resourceviewer_save(UiObject *ui, DavResourceViewer *res) { if(res->type == DAV_RESOURCE_VIEW_TEXT) { ResourceViewerUploadFile *upload = malloc(sizeof(ResourceViewerUploadFile)); upload->ui = ui; upload->sn = res->sn; upload->path = res->current->path; char *text = ui_get(res->text); upload->text = cx_strdup(cx_str(text)); ui_object_ref(res->obj); ui_threadpool_job(res->dav_queue, ui, jobthr_upload_text, upload, uithr_upload_text_finished, upload); } if(res->properties_modified) { CxList *properties = res->properties->data; CxIterator i = cxListIterator(properties); cx_foreach(DavPropertyList *, prop, i) { if(prop->update) { if(prop->value_full) { // text dav_set_string_property_ns(res->current, prop->ns, prop->name, prop->value_full); } else { // xml dav_set_property_ns(res->current, prop->ns, prop->name, prop->xml); } } } ui_object_ref(res->obj); ui_threadpool_job(res->dav_queue, ui, jobthr_store_properties, res, uithr_store_properties_finished, res); } ui_unset_group(ui->ctx, RESOURCEVIEWER_STATE_MODIFIED); } void dav_resourceviewer_destroy(DavResourceViewer *res) { } void dav_resourceviewer_property_remove(DavResourceViewer *res, DavPropertyList *prop) { if(!prop->isnew) { dav_remove_property_ns(res->current, prop->ns, prop->name); ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; } CxList *properties = res->properties->data; cxListFindRemove(properties, prop); ui_free(res->ctx, prop->ns); ui_free(res->ctx, prop->name); ui_free(res->ctx, prop->value_simplified); ui_free(res->ctx, prop->value_full); // prop->xml freed by DavSession ui_free(res->ctx, prop); ui_list_update(res->properties); } void dav_resourceviewer_property_update_text(DavResourceViewer *res, DavPropertyList *prop, const char *text) { ui_free(res->ctx, prop->value_simplified); ui_free(res->ctx, prop->value_full); prop->xml = NULL; prop->value_full = ui_strdup(res->ctx, text); prop->value_simplified = NULL; prop->update = TRUE; ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; ui_list_update(res->properties); } void dav_resourceviewer_property_update_xml(DavResourceViewer *res, DavPropertyList *prop, DavXmlNode *xml) { } void dav_resourceviewer_property_add_text(DavResourceViewer *res, const char *ns, const char *name, const char *text) { DavPropertyList *prop = ui_malloc(res->ctx, sizeof(DavPropertyList)); prop->ns = ui_strdup(res->ctx, ns); prop->name = ui_strdup(res->ctx, name); prop->value_simplified = NULL; prop->value_full = ui_strdup(res->ctx, text); prop->xml = NULL; prop->isnew = TRUE; prop->update = TRUE; ui_list_append(res->properties, prop); ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; ui_list_update(res->properties); } void dav_resourceviewer_property_add_xml(DavResourceViewer *res, const char *ns, const char *name, const char *nsdef, DavXmlNode *xml) { } uint64_t dav_transfer_speed(TransferProgress *progress, time_t current) { size_t bytes = progress->transferred_bytes - progress->speedtest_bytes; time_t t = current - progress->speedtest_start; progress->speedtest_start = current; progress->speedtest_bytes = progress->transferred_bytes; return bytes/t; }