UNIXworkcode

/* * 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 "window.h" #include "davcontroller.h" #include "appsettings.h" #include "xml.h" #include <ui/stock.h> #include <ui/dnd.h> #include <libidav/utils.h> #include <cx/printf.h> static UiIcon* folder_icon; static UiIcon* file_icon; static UiPathElm* dav_get_pathelm(const char *full_path, size_t len, size_t *ret_nelm, void* data); static UiMenuBuilder *contextmenu; void window_init(void) { folder_icon = ui_foldericon(16); file_icon = ui_fileicon(16); // initialize the browser context menu ui_contextmenu(&contextmenu) { ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuitem(.label = "New File", .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuseparator(); //ui_menuitem(.label = "Cut", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); //ui_menuitem(.label = "Copy", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); //ui_menuitem(.label = "Paste", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_menuitem(.label = "Download", .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_menuitem(.label = "Delete", .onclick = action_delete, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuitem(.label = "Select All", .onclick = action_selectall, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuseparator(); ui_menuitem(.label = "Rename", .onclick = action_rename, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_menuseparator(); ui_menuitem("Open Properties", .onclick = action_open_properties, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); } } UiObject* window_create(void) { UiObject* obj = ui_window("iDAV", NULL); ui_window_size(obj, 900, 700); MainWindow* wdata = ui_malloc(obj->ctx, sizeof (MainWindow)); memset(wdata, 0, sizeof (MainWindow)); obj->window = wdata; wdata->progress = ui_int_new(obj->ctx, "progress"); // navigation bar ui_hbox(obj, .fill = UI_OFF, .margin = 8, .spacing = 8) { ui_hbox(obj, .fill = UI_OFF, .style_class="linked") { ui_button(obj, .icon = UI_ICON_GO_BACK, .onclick = action_go_back); ui_button(obj, .icon = UI_ICON_GO_FORWARD, .onclick = action_go_forward); } ui_path_textfield(obj, .fill = UI_ON, .getpathelm = dav_get_pathelm, .onactivate = action_path_selected, .varname = "path"); ui_progressspinner(obj, .value = wdata->progress); } // main content UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Name", UI_STRING_FREE, "Flags", UI_STRING, "Type", UI_STRING_FREE, "Last Modified", UI_STRING_FREE, "Size", -1); model->columnsize[0] = -1; model->columnsize[2] = 150; model->getvalue = (ui_getvaluefunc) window_resource_table_getvalue; ui_table(obj, .fill = UI_ON, .model = model, .onselection = action_list_selection, .onactivate = action_list_activate, .ondragstart = action_dnd_start, .ondragcomplete = action_dnd_end, .ondrop = action_dnd_drop, .varname = "reslist", .multiselection = TRUE, .contextmenu = contextmenu); // status bar ui_hbox(obj, .fill = UI_OFF) { ui_label(obj, .label = ""); } return obj; } void* window_resource_table_getvalue(DavResource *res, int col) { switch (col) { case 0: { // icon return res->iscollection ? folder_icon : file_icon; } case 1: { // resource name return res->name; } case 2: { // flags char *keyprop = dav_get_string_property_ns( res, DAV_NS, "crypto-key"); DavXmlNode *lockdiscovery = dav_get_property(res, "D:lockdiscovery"); char *executable = dav_get_string_property_ns( res, "http://apache.org/dav/props/", "executable"); cxmutstr flags = cx_asprintf("%s%s%s", appsettings_get_cryptoflag(keyprop ? 1 : 0), appsettings_get_lockflag(lockdiscovery ? 1 : 0), appsettings_get_execflag(executable ? 1 : 0)); return flags.ptr; } case 3: { // type return res->iscollection ? "Collection" : (res->contenttype ? res->contenttype : "Resource"); } case 4: { // last modified return util_date_str(res->lastmodified); } case 5: { // size return util_size_str(res->iscollection, res->contentlength); } } return NULL; } void window_progress(MainWindow *win, int on) { ui_set(win->progress, on); } void action_dnd_start(UiEvent *event, void *data) { //ui_selection_settext(event->eventdata, "hello world", -1); char *uri = "file:///export/home/olaf/test.txt"; ui_selection_seturis(event->eventdata, &uri, 1); } void action_dnd_end(UiEvent *event, void *data) { } static void resourceviewer_close(UiEvent *event, void *data) { DavResourceViewer *doc = data; doc->window_closed = TRUE; if(doc->loaded) { dav_resourceviewer_destroy(doc); } if (doc->tmp_file) { unlink(doc->tmp_file); free(doc->tmp_file); } } void resourceviewer_new(DavBrowser *browser, const char *path, DavResourceViewType type) { const char *name = util_resource_name(path); UiObject *win = ui_simple_window(name, NULL); ui_window_size(win, 600, 600); ui_headerbar(win, .showtitle = TRUE) { ui_headerbar_start(win) { ui_button(win, .label = "Save", .onclick = action_resourceviewer_save, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_MODIFIED)); } } DavResourceViewer *doc = dav_resourceviewer_create(win, browser->sn, path, type); ui_attach_document(win->ctx, doc); ui_context_closefunc(win->ctx, resourceviewer_close, doc); ui_tabview(win, .tabview = UI_TABVIEW_INVISIBLE, .varname = "tabview") { /* loading / message tab */ ui_tab(win, NULL) { ui_hbox(win, .margin = 16, .spacing = 10, .fill = UI_OFF) { ui_progressspinner(win, .varname = "loading"); ui_label(win, .varname = "message"); } } /* preview tab */ ui_tab(win, NULL) { ui_tabview0(win) { if(type == DAV_RESOURCE_VIEW_TEXT) { ui_tab(win, "Content") { ui_textarea(win, .varname = "text", .onchange = action_resourceviewer_text_modified); } } else if(type == DAV_RESOURCE_VIEW_IMAGE) { ui_tab(win, "Preview") { ui_imageviewer(win, .varname = "image"); } } ui_tab(win, "Info") { ui_grid(win, .margin = 16, .columnspacing = 30, .rowspacing = 6) { ui_llabel(win, .label = "URL"); ui_llabel(win, .varname = "info_url"); ui_newline(win); ui_llabel(win, .label = "Name"); ui_llabel(win, .varname = "info_name"); ui_newline(win); ui_llabel(win, .label = "Type"); ui_llabel(win, .varname = "info_type"); ui_newline(win); ui_llabel(win, .label = "Encrypted"); ui_llabel(win, .varname = "info_encrypted"); ui_newline(win); ui_llabel(win, .label = "ETag"); ui_llabel(win, .varname = "info_etag"); ui_newline(win); ui_llabel(win, .label = "Size"); ui_llabel(win, .varname = "info_size"); ui_newline(win); } } ui_tab(win, "Properties") { UiModel* model = ui_model(win->ctx, UI_STRING, "Namespace", UI_STRING, "Name", UI_STRING, "Value", -1); model->getvalue = (ui_getvaluefunc) resourceviewer_proplist_getvalue; ui_table(win, .fill = UI_ON, .model = model, .varname = "properties", .onselection = action_resourceviewer_property_select, .onactivate = action_resourceviewer_property_activate); ui_hbox(win, .fill = UI_OFF, .margin = 4, .spacing = 4) { ui_button(win, .label = "Add", .onclick = action_resourceviewer_property_add); ui_button(win, .label = "Edit", .onclick = action_resourceviewer_property_edit, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED)); ui_button(win, .label = "Remove", .onclick = action_resourceviewer_property_remove, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED)); } } } } } dav_resourceviewer_load(win, doc); ui_show(win); } void* resourceviewer_proplist_getvalue(DavPropertyList *property, int col) { switch(col) { case 0: { return property->ns; } case 1: { return property->name; } case 2: { return property->value_simplified ? property->value_simplified : property->value_full; } } return NULL; } typedef struct AuthDialogWindow { UiString *user; UiString *password; } AuthDialogWindow; static void auth_dialog_action(UiEvent *event, void *data) { SessionAuthData *auth = data; AuthDialogWindow *wdata = event->window; int result = 0; if(event->intval == 4) { char *user = ui_get(wdata->user); char *password = ui_get(wdata->password); davbrowser_auth_set_user_pwd(auth, user, password); result = 1; } ui_condvar_signal(auth->cond, NULL, result); ui_close(event->obj); } void auth_dialog(SessionAuthData *auth) { UiObject *obj = ui_dialog_window(auth->obj, .title = "Authentication", .lbutton1 = "Cancel", .rbutton4 = "Connect", .default_button = 4, .show_closebutton = UI_OFF, .onclick = auth_dialog_action, .onclickdata = auth); AuthDialogWindow *wdata = ui_malloc(obj->ctx, sizeof(AuthDialogWindow)); wdata->user = ui_string_new(obj->ctx, NULL); wdata->password = ui_string_new(obj->ctx, NULL); obj->window = wdata; ui_grid(obj, .margin = 20, .columnspacing = 12, .rowspacing = 12) { cxmutstr heading = cx_asprintf("Authentication required for: %s", auth->sn->base_url); ui_llabel(obj, .label = heading.ptr, .colspan = 2); free(heading.ptr); ui_newline(obj); ui_llabel(obj, .label = "User"); ui_textfield(obj, .value = wdata->user, .hexpand = TRUE); ui_newline(obj); ui_llabel(obj, .label = "Password"); ui_passwordfield(obj, .value = wdata->password, .hexpand = TRUE); } if(auth->user) { ui_set(wdata->user, auth->user); } ui_show(obj); } void transfer_window_init(UiObject *dialog, ui_callback btncallback) { ui_window_size(dialog, 550, 120); ui_grid(dialog, .margin = 10, .spacing = 10, .fill = TRUE) { ui_llabel(dialog, .varname = "label_top_left", .hexpand = TRUE); ui_rlabel(dialog, .varname = "label_top_right"); ui_newline(dialog); ui_progressbar(dialog, .varname = "progressbar", .min = 0, .max = 100, .colspan = 2, .hexpand = TRUE); ui_newline(dialog); ui_llabel(dialog, .varname = "label_bottom_left", .hexpand = TRUE); ui_rlabel(dialog, .varname = "label_bottom_right"); ui_newline(dialog); ui_label(dialog, .vexpand = TRUE); ui_newline(dialog); ui_hbox(dialog, .colspan = 2, .hexpand = TRUE) { ui_label(dialog, .hexpand = TRUE); ui_button(dialog, .label = "Cancel", .onclick = btncallback); } } } static UiPathElm* dav_get_pathelm(const char *full_path, size_t len, size_t *ret_nelm, void* data) { if (len == 0) { *ret_nelm = 0; return NULL; } cxstring fpath = cx_strn(full_path, len); int protocol = 0; if (cx_strcaseprefix(fpath, CX_STR("http://"))) { protocol = 7; } else if (cx_strcaseprefix(fpath, CX_STR("https://"))) { protocol = 8; } size_t start = 0; size_t end = 0; for (size_t i = protocol; i < len; i++) { if (full_path[i] == '/') { end = i; break; } } int skip = 0; if (end == 0) { // no '/' found or first char is '/' end = len > 0 && full_path[0] == '/' ? 1 : len; } else if (end + 1 <= len) { skip++; // skip first '/' } cxmutstr base = cx_strdup(cx_strn(full_path, end)); cxmutstr base_path = cx_strcat(2, cx_strcast(base), CX_STR("/")); cxstring path = cx_strsubs(fpath, end + skip); cxstring trail = cx_str(len > 0 && full_path[len-1] == '/' ? "/" : ""); cxstring *pathelms; size_t nelm = 0; if (path.length > 0) { nelm = cx_strsplit_a(cxDefaultAllocator, path, CX_STR("/"), 4096, &pathelms); if (nelm == 0) { *ret_nelm = 0; return NULL; } } UiPathElm* elms = (UiPathElm*) calloc(nelm + 1, sizeof (UiPathElm)); size_t n = nelm + 1; elms[0].name = base.ptr; elms[0].name_len = base.length; elms[0].path = base_path.ptr; elms[0].path_len = base_path.length; int j = 1; for (int i = 0; i < nelm; i++) { cxstring c = pathelms[i]; if (c.length == 0) { if (i == 0) { c.length = 1; } else { n--; continue; } } cxmutstr m = cx_strdup(c); elms[j].name = m.ptr; elms[j].name_len = m.length; size_t elm_path_len = c.ptr + c.length - full_path; cxmutstr elm_path = cx_strcat(2, cx_strn(full_path, elm_path_len), i+1 < nelm ? CX_STR("/") : trail); elms[j].path = elm_path.ptr; elms[j].path_len = elm_path.length; j++; } *ret_nelm = n; return elms; } void action_go_parent(UiEvent *event, void *data) { DavBrowser *browser = event->document; davbrowser_navigation_parent(event->obj, browser); } void action_go_back(UiEvent *event, void *data) { DavBrowser *browser = event->document; davbrowser_navigation_back(event->obj, browser); } void action_go_forward(UiEvent *event, void *data) { DavBrowser *browser = event->document; davbrowser_navigation_forward(event->obj, browser); } void action_path_selected(UiEvent *event, void *data) { DavBrowser *browser = event->document; char *path = event->eventdata; if (path && strlen(path) > 0) { davbrowser_query_url(event->obj, browser, path); } } void action_list_selection(UiEvent *event, void *data) { UiListSelection *selection = event->eventdata; if (selection->count > 0) { ui_set_group(event->obj->ctx, APP_STATE_BROWSER_SELECTION); } else { ui_unset_group(event->obj->ctx, APP_STATE_BROWSER_SELECTION); } } void action_list_activate(UiEvent *event, void *data) { UiListSelection *selection = event->eventdata; DavBrowser *browser = event->document; if (selection->count == 1) { DavResource *res = ui_list_get(browser->resources, selection->rows[0]); if (res) { if (res->iscollection) { davbrowser_query_path(event->obj, browser, res->path); } else { davbrowser_open_resource(event->obj, browser, res, NULL); } } } } static int filelist_uri2path(UiFileList *files) { for(int i=0;i<files->nfiles;i++) { char *uri = files->files[i]; if(uri[0] == '/') { continue; } cxstring uri_s = cx_str(uri); if(!cx_strprefix(uri_s, CX_STR("file://"))) { return 1; } files->files[i] = cx_strdup(cx_strsubs(uri_s, 7)).ptr; free(uri); } return 0; } void action_dnd_drop(UiEvent *event, void *data) { UiDnD *dnd = event->eventdata; UiFileList files = ui_selection_geturis(dnd); if(files.nfiles == 0) { return; } if(filelist_uri2path(&files)) { ui_dnd_accept(dnd, FALSE); ui_filelist_free(files); return; } davbrowser_upload_files(event->obj, event->document, files); } /* ------------------------ resource viewer actions ------------------------ */ void action_resourceviewer_text_modified(UiEvent *event, void *data) { DavResourceViewer *doc = event->document; if(doc->loaded) { ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); } } void action_resourceviewer_save(UiEvent *event, void *data) { DavResourceViewer *doc = event->document; dav_resourceviewer_save(event->obj, doc); } void action_resourceviewer_property_select(UiEvent *event, void *data) { DavResourceViewer *doc = event->document; UiListSelection *selection = event->eventdata; if(selection->count == 1) { ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED); doc->selected_property = ui_list_get(doc->properties, selection->rows[0]); } else { ui_unset_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED); doc->selected_property = NULL; } } void action_resourceviewer_property_activate(UiEvent *event, void *data) { action_resourceviewer_property_select(event, data); action_resourceviewer_property_edit(event, data); } typedef struct PropertyDialog { UiInteger *type; UiString *ns; UiString *name; UiText *value; } PropertyDialog; static void propertydialog_action(UiEvent *event, void *data) { DavResourceViewer *res = data; if(event->intval == 4) { char *ns = ui_get(res->property_ns); char *name = ui_get(res->property_name); int type = ui_get(res->property_type); char *nsdef = ui_get(res->property_nsdef); char *value = ui_get(res->property_value); if(strlen(ns) == 0) { ui_set(res->property_errormsg, "Namespace must not be empty!"); return; } if(strlen(name) == 0) { ui_set(res->property_errormsg, "Name must not be empty!"); return; } char *textvalue = NULL; DavXmlNode *xmlvalue = NULL; if(type == 0) { // text value textvalue = value; } else { // xml value } DavBool add = FALSE; if(res->edit_property) { if(strcmp(res->edit_property->ns, ns) || strcmp(res->edit_property->name, name)) { // name or namespace changed, remove existing and create new property dav_resourceviewer_property_remove(res, res->edit_property); add = TRUE; } } else { add = TRUE; } if(add) { if(textvalue) { dav_resourceviewer_property_add_text(res, ns, name, textvalue); } else { dav_resourceviewer_property_add_xml(res, ns, name, nsdef, xmlvalue); } } else { if(textvalue) { dav_resourceviewer_property_update_text(res, res->edit_property, textvalue); } else { dav_resourceviewer_property_update_xml(res, res->edit_property, xmlvalue); } } } ui_close(event->obj); } static void prop_type_changed(UiEvent *event, void *data) { DavResourceViewer *res = data; switch(ui_get(res->property_type)) { case 0: { ui_unset_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); break; } case 1: { ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); char *ns = ui_get(res->property_ns); char *nsdef = ui_get(res->property_nsdef); if(strlen(nsdef) == 0) { cxmutstr def = cx_asprintf("xmlns:x0=\"%s\"", ns); ui_set(res->property_nsdef, def.ptr); free(def.ptr); } break; } } } static void edit_property_dialog(DavResourceViewer *res, const char *title, DavPropertyList *prop) { res->edit_property = prop; UiObject *obj = ui_dialog_window(res->obj, .title = title, .show_closebutton = UI_OFF, .lbutton1 = "Cancel", .rbutton4 = "Save", .default_button = 4, .onclick = propertydialog_action, .onclickdata = res, .width = 600, .height = 500); ui_grid(obj, .margin = 16, .columnspacing = 8, .rowspacing = 12) { ui_llabel(obj, .label = "Namespace"); ui_textfield(obj, .hexpand = TRUE, .value = res->property_ns); ui_newline(obj); ui_llabel(obj, .label = "Property Name"); ui_textfield(obj, .hexpand = TRUE, .value = res->property_name); ui_newline(obj); ui_llabel(obj, .label = "Type"); ui_hbox(obj, .spacing = 8, .colspan = 2) { ui_radiobutton(obj, .label = "Text", .value = res->property_type, .onchange = prop_type_changed, .onchangedata = res); ui_radiobutton(obj, .label = "XML", .value = res->property_type, .onchange = prop_type_changed, .onchangedata = res); } ui_newline(obj); ui_llabel(obj, .label = "Namespace Declarations"); ui_textfield(obj, .hexpand = TRUE, .value = res->property_nsdef, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_XML)); ui_newline(obj); ui_textarea(obj, .value = res->property_value, .hexpand = TRUE, .vexpand = TRUE, .colspan = 2); ui_newline(obj); ui_llabel(obj, .colspan = 2, .value = res->property_errormsg); } if(prop && prop->ns && prop->name) { ui_set(res->property_ns, prop->ns); ui_set(res->property_name, prop->name); if(prop->value_full) { ui_set(res->property_type, 0); ui_set(res->property_nsdef, ""); ui_set(res->property_value, prop->value_full); ui_unset_group(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); } else if(prop->xml) { ui_set(res->property_type, 1); cxmutstr xml; cxmutstr nsdef; property_xml2str(prop->xml, prop->ns, &xml, &nsdef); ui_set(res->property_nsdef, nsdef.ptr); ui_set(res->property_value, xml.ptr); free(xml.ptr); free(nsdef.ptr); ui_set_group(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); } } else { ui_set(res->property_ns, ""); ui_set(res->property_name, ""); ui_set(res->property_nsdef, ""); ui_set(res->property_type, 0); ui_set(res->property_value, ""); } ui_set(res->property_errormsg, ""); ui_show(obj); } void action_resourceviewer_property_add(UiEvent *event, void *data) { DavResourceViewer *doc = event->document; edit_property_dialog(doc, "Add Property", NULL); } void action_resourceviewer_property_edit(UiEvent *event, void *data) { DavResourceViewer *doc = event->document; edit_property_dialog(doc, "Edit Property", doc->selected_property); } void action_resourceviewer_property_remove(UiEvent *event, void *data) { DavResourceViewer *doc = event->document; if(!doc->selected_property) { return; // shouldn't happen } dav_resourceviewer_property_remove(doc, doc->selected_property); }