Fri, 15 Nov 2024 21:50:20 +0100
add reference counting to download window
/* * 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 <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); void window_init(void) { folder_icon = ui_foldericon(16); file_icon = ui_fileicon(16); } 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, "Type", UI_STRING_FREE, "Last Modified", UI_STRING_FREE, "Size", -1); model->columnsize[0] = -1; model->columnsize[1] = 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, .ondrop = action_dnd_drop, .varname = "reslist", .multiselection = TRUE); // 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: { // type return res->iscollection ? "Collection" : (res->contenttype ? res->contenttype : "Resource"); } case 3: { // last modified return util_date_str(res->lastmodified); } case 4: { // size return util_size_str(res->iscollection, res->contentlength); } } return NULL; } void window_progress(MainWindow *win, int on) { ui_set(win->progress, on); } 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); // TODO: when properties can be modified, always add the headerbar if(type == DAV_RESOURCE_VIEW_TEXT) { ui_headerbar(win, .showtitle = TRUE) { ui_headerbar_start(win) { ui_button(win, .label = "Save", .style_class = "suggested-action", .onclick = action_resourceviewer_save, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_MODIFIED)); } } } DavResourceViewer *doc = dav_resourceviewer_create(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"); } } } } 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); } 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); } } } } void action_dnd_drop(UiEvent *event, void *data) { UiListDnd *listdnd = event->eventdata; UiDnD *dnd = listdnd->dnd; UiFileList files = ui_selection_geturis(dnd); 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); }