Sun, 29 Sep 2024 20:25:41 +0200
update toolkit, fix download/upload progressbar
/* * 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 "system.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); 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); 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); } // ------------------------------ davbrowser_connect2repo ------------------------------ void davbrowser_connect2repo(UiObject *ui, DavBrowser *browser, DavCfgRepository *repo, const char *path) { DavSession *sn = dav_session_new(application_dav_context(), repo->url.value.ptr); if (repo->user.value.ptr && repo->password.value.ptr) { cxmutstr decodedpw = dav_repository_get_decodedpassword(repo); dav_session_set_auth(sn, repo->user.value.ptr, decodedpw.ptr); free(decodedpw.ptr); } 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); davbrowser_query_path(ui, browser, path); } // ------------------------------ 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 { // TODO: error } 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); davbrowser_connect2repo(ui, browser, repo, path); if (path) { free(path); } if (!repo->node) { dav_repository_free(get_config(), repo); } } 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 (browser->navigation_stack->size > DAVBROWSER_MAX_NAVLIST) { char *nav = cxListAt(browser->navigation_stack, browser->navigation_stack->size - 1); free(nav); cxListRemove(browser->navigation_stack, browser->navigation_stack->size - 1); } } } void davbrowser_navigation_back(UiObject *ui, DavBrowser *browser) { if (browser->navstack_pos+1 < browser->navigation_stack->size) { 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; } } 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; } } // ------------------------------------- File Upload ------------------------------------- typedef struct DavFileUpload { UiObject *ui; DavBrowser *browser; DavSession *sn; UiFileList files; char *base_path; UiThreadpool *queue; size_t total_bytes; size_t total_files; size_t total_directories; size_t uploaded_bytes; size_t uploaded_files; size_t uploaded_directories; DavBool upload_file; size_t current_file_size; size_t current_file_upload; char *current_file_name; UiObject *dialog; UiDouble *progress; UiString *label_top_left; UiString *label_top_right; UiString *label_bottom_left; UiString *label_bottom_right; // The collection, the files are uploaded to // It is only safe to access the collection ptr, if // collection == browser->current && collection_ctn == browser->res_counter // and obviously it can only be accessed from the UI thread DavResource *collection; // copy of browser->res_counter, used for integrity check int64_t collection_ctn; // current uploaded resource, created as part of the browser session // only if collection == browser->current && collection_ctn == browser->res_counter DavResource *current_resource; } DavFileUpload; typedef struct DUFile { char *path; char *upload_path; size_t bytes; DavBool isdirectory; DavFileUpload *upload; DavError error; } DUFile; static double upload_progress(DavFileUpload *upload) { return ((double)upload->uploaded_bytes / (double)upload->total_bytes) * 100; } static void update_upload_labels(DavFileUpload *upload) { char *sz_total = util_size_str(FALSE, upload->total_bytes); char *sz_uploaded = util_size_str2(FALSE, upload->uploaded_bytes, upload->total_bytes, 2); char *sz_uploaded_end = strchr(sz_uploaded, ' '); if (sz_uploaded_end) { *sz_uploaded_end = 0; } double progress = upload_progress(upload); ui_set(upload->progress, upload_progress(upload)); cxmutstr label1; if (upload->total_files + upload->total_directories > 1) { label1 = cx_asprintf( "%s/%s %zu/%zu files", sz_uploaded, sz_total, upload->uploaded_files+upload->uploaded_directories, upload->total_files+upload->total_directories); } else { label1 = cx_asprintf( "%s/%s", sz_uploaded, sz_total); } ui_set(upload->label_top_left, label1.ptr); free(sz_total); free(label1.ptr); if (upload->current_file_size > 0) { cxmutstr file_label = cx_asprintf("%s (%.0f%%)", upload->current_file_name, ((float)upload->current_file_upload/(float)upload->current_file_size)*100); ui_set(upload->label_top_right, file_label.ptr); free(file_label.ptr); } } static int uithr_update_upload_labels(void *data) { update_upload_labels(data); return 0; } static void upload_dav_progress(DavResource *res, int64_t total, int64_t now, void *data) { DavFileUpload *upload = data; if (upload->upload_file) { if (now > upload->current_file_size) { // current_file_size is not accurate (either the file was changed after the last stat // or we have some extra bytes because of encryption // adjust current_file_size and the total upload size int64_t extra = now - upload->current_file_size; upload->current_file_size += extra; upload->total_bytes += extra; } int64_t new_progress = now - upload->current_file_upload; upload->uploaded_bytes += new_progress; upload->current_file_upload = now; ui_call_mainthread(uithr_update_upload_labels, upload); } } typedef struct FileNameUpdate { DavFileUpload *upload; char *name; char *path; DavBool iscollection; } FileNameUpdate; static int uithr_update_file_label(FileNameUpdate *update) { // replace upload->current_filename with update->name if (update->upload->current_file_name) { free(update->upload->current_file_name); } update->upload->current_file_name = update->name; ui_set(update->upload->label_top_right, update->name); DavFileUpload *upload = update->upload; DavBrowser *browser = upload->browser; // update the resource list in the browser, if the current collection has not changed if (upload->collection == browser->current && upload->collection_ctn == browser->res_counter) { char *parent = util_parent_path(update->path); cxstring parent_s = cx_str(parent); cxstring colpath_s = cx_str(upload->collection->path); if (parent_s.length > 0 && parent_s.ptr[parent_s.length - 1] == '/') { parent_s.length--; } if (colpath_s.length > 0 && colpath_s.ptr[colpath_s.length - 1] == '/') { colpath_s.length--; } // only update, if the added resource has the current collection as parent if (!cx_strcmp(parent_s, colpath_s)) { DavResource *ui_res = dav_resource_new(upload->collection->session, update->path); ui_res->iscollection = update->iscollection; ui_res->lastmodified = time(NULL); ui_res->creationdate = time(NULL); upload->current_resource = ui_res; ui_list_append(browser->resources, ui_res); browser->resources->update(browser->resources, 0); } else { upload->current_resource = NULL; // maybe not necessary } free(parent); } free(update->path); free(update); return 0; } static int qthr_file_upload(void *data) { DUFile *f = data; DavFileUpload *upload = f->upload; DavSession *sn = upload->sn; FILE *in = sys_fopen(f->path, "rb"); if (!in) { // TODO: error msg return 0; } upload->upload_file = TRUE; upload->current_file_size = f->bytes; upload->current_file_upload = 0; DavResource *res = dav_resource_new(sn, f->upload_path); FileNameUpdate *ui_update = malloc(sizeof(FileNameUpdate)); ui_update->upload = upload; ui_update->name = strdup(res->name); ui_update->path = strdup(res->path); ui_update->iscollection = FALSE; ui_call_mainthread((ui_threadfunc)uithr_update_file_label, ui_update); dav_set_content(res, in, (dav_read_func)fread, (dav_seek_func)fseek); if (dav_store(res)) { f->error = sn->error; } dav_resource_free(res); fclose(in); upload->upload_file = FALSE; return 0; } static void uithr_file_uploaded(UiEvent *event, void *data) { DUFile *file = data; DavFileUpload *upload = file->upload; upload->uploaded_files++; //upload->uploaded_bytes += file->bytes; double progress = upload_progress(upload); ui_set(upload->progress, upload_progress(upload)); update_upload_labels(upload); // update resource content length in the browser DavBrowser *browser = upload->browser; if (upload->collection == browser->current && upload->collection_ctn == browser->res_counter) { if (upload->current_resource) { upload->current_resource->contentlength = upload->current_file_upload; browser->resources->update(browser->resources, 0); } } upload->current_resource = NULL; free(file->path); free(file->upload_path); } static int qthr_dir_upload(void *data) { DUFile *f = data; DavFileUpload *upload = f->upload; DavSession *sn = upload->sn; DavResource *res = dav_resource_new(sn, f->upload_path); res->iscollection = TRUE; FileNameUpdate *ui_update = malloc(sizeof(FileNameUpdate)); ui_update->upload = upload; ui_update->name = strdup(res->name); ui_update->path = strdup(res->path); ui_update->iscollection = TRUE; ui_call_mainthread((ui_threadfunc)uithr_update_file_label, ui_update); if (dav_create(res)) { f->error = sn->error; } dav_resource_free(res); return 0; } static void uithr_dir_uploaded(UiEvent *event, void *data) { DUFile *file = data; DavFileUpload *upload = file->upload; upload->uploaded_directories++; update_upload_labels(upload); upload->current_resource = NULL; free(file->path); free(file->upload_path); } static int qthr_upload_finished(void *data) { return 0; } static void uithr_upload_finished(UiEvent *event, void *data) { DavFileUpload *upload = data; ui_threadpool_destroy(upload->queue); free(upload->base_path); dav_session_destroy(upload->sn); } static int jobthr_upload_scan(void *data) { DavFileUpload *upload = data; CxList *stack = cxLinkedListCreateSimple(CX_STORE_POINTERS); for (int i = 0; i < upload->files.nfiles; i++) { DUFile *f = malloc(sizeof(DUFile)); f->path = strdup(upload->files.files[i]); f->upload_path = util_concat_path(upload->base_path, util_path_file_name(f->path)); f->isdirectory = FALSE; f->bytes = 0; f->upload = upload; f->error = 0; cxListInsert(stack, 0, f); } while (stack->size > 0) { DUFile *f = cxListAt(stack, 0); char *path = util_concat_path(upload->base_path, f->upload_path); cxListRemove(stack, 0); SYS_STAT s; if (!sys_stat(f->path, &s)) { if (S_ISDIR(s.st_mode)) { f->isdirectory = TRUE; upload->total_directories++; ui_threadpool_job(upload->queue, upload->ui, qthr_dir_upload, f, uithr_dir_uploaded, f); SYS_DIR dir = sys_opendir(f->path); if (dir) { SysDirEnt *entry; int nument = 0; while((entry = sys_readdir(dir)) != NULL) { if(!strcmp(entry->name, ".") || !strcmp(entry->name, "..")) { continue; } cxmutstr newpath = util_concat_sys_path(cx_str(f->path), cx_str(entry->name)); char *new_upload_path = util_concat_path(f->upload_path, entry->name); DUFile *child = malloc(sizeof(DUFile)); child->path = newpath.ptr; child->upload_path = new_upload_path; child->isdirectory = FALSE; child->bytes = 0; child->upload = upload; child->error = 0; cxListAdd(stack, child); } sys_closedir(dir); } } else if (S_ISREG(s.st_mode)) { f->isdirectory = FALSE; f->bytes = s.st_size; upload->total_files++; upload->total_bytes += s.st_size; ui_threadpool_job(upload->queue, upload->ui, qthr_file_upload, f, uithr_file_uploaded, f); } } } // TODO: else error msg ui_threadpool_job(upload->queue, upload->ui, qthr_upload_finished, upload, uithr_upload_finished, upload); ui_filelist_free(upload->files); return 0; } static void uithr_upload_scan_finished(UiEvent *event, void *data) { DavFileUpload *upload = data; update_upload_labels(upload); } static void upload_window_closed(UiEvent *event, void *data) { // noop, prevents context destruction } void davbrowser_upload_files(UiObject *ui, DavBrowser *browser, UiFileList files) { if (!browser->sn) { return; // TODO: error msg } // we need a clone of the current session, because the upload // is done in a separate thread DavSession *upload_session = dav_session_clone(browser->sn); // create upload obj, that contains all relevant data for the upload DavFileUpload *upload = malloc(sizeof(DavFileUpload)); memset(upload, 0, sizeof(DavFileUpload)); dav_session_set_progresscallback(upload_session, NULL, upload_dav_progress, upload); upload->ui = ui; upload->browser = browser; upload->sn = upload_session; upload->files = files; upload->base_path = strdup(browser->current->path); upload->queue = ui_threadpool_create(1); upload->collection = browser->current; upload->collection_ctn = browser->res_counter; // create upload progress window cxmutstr wtitle = cx_asprintf("Upload to: %s", ui_get(browser->path)); UiObject *dialog = ui_simple_window(wtitle.ptr, upload); ui_context_closefunc(dialog->ctx, upload_window_closed, NULL); free(wtitle.ptr); upload->dialog = dialog; ui_window_size(dialog, 550, 120); upload->progress = ui_double_new(dialog->ctx, NULL); upload->label_top_left = ui_string_new(dialog->ctx, NULL); upload->label_top_right = ui_string_new(dialog->ctx, NULL); upload->label_bottom_left = ui_string_new(dialog->ctx, NULL); upload->label_bottom_right = ui_string_new(dialog->ctx, NULL); ui_grid(dialog, .margin = 10, .spacing = 10, .fill = TRUE) { ui_llabel(dialog, .value = upload->label_top_left, .hexpand = TRUE); ui_rlabel(dialog, .value = upload->label_top_right); ui_newline(dialog); ui_progressbar(dialog, .value = upload->progress, .colspan = 2, .hexpand = TRUE); ui_newline(dialog); ui_llabel(dialog, .value = upload->label_bottom_left); ui_rlabel(dialog, .value = upload->label_bottom_right); ui_newline(dialog); } ui_set(upload->label_top_left, ""); ui_set(upload->label_top_right, ""); ui_set(upload->label_bottom_left, ""); ui_set(upload->label_bottom_right, ""); ui_set(upload->progress, 0); ui_show(dialog); // start upload and stat threads ui_job(ui, jobthr_upload_scan, upload, uithr_upload_scan_finished, upload); } // ------------------------------------- File Download ------------------------------------- typedef struct DavFileDownload { UiObject *ui; DavBrowser *browser; DavSession *sn; DavSession *download_sn; DavResource *reslist; char *local_path; DavBool isdirectory; UiThreadpool *queue; size_t total_bytes; size_t total_files; size_t total_directories; size_t downloaded_bytes; size_t downloaded_files; size_t downloaded_directories; size_t current_file_size; size_t current_file_downloaded; UiObject *dialog; UiDouble *progress; UiString *label_top_left; UiString *label_top_right; UiString *label_bottom_left; UiString *label_bottom_right; } DavFileDownload; static int uithr_download_update_progress(void *data) { DavFileDownload *download = data; char *sz_total = util_size_str(FALSE, download->total_bytes); char *sz_downloaded = util_size_str2(FALSE, download->downloaded_bytes, download->total_bytes, 2); char *sz_downloaded_end = strchr(sz_downloaded, ' '); if (sz_downloaded_end) { *sz_downloaded_end = 0; } if (download->total_bytes > 0) { double progress = (double)download->downloaded_bytes / (double)download->total_bytes; ui_set(download->progress, progress*100); } cxmutstr label1; if (download->total_files + download->total_directories > 1) { label1 = cx_asprintf( "%s/%s %zu/%zu files", sz_downloaded, sz_total, download->downloaded_files+download->downloaded_directories, download->total_files+download->total_directories); } else { label1 = cx_asprintf( "%s/%s", sz_downloaded, sz_total); } ui_set(download->label_top_left, label1.ptr); free(sz_total); free(label1.ptr); return 1; } // dav download file typedef struct DDFile { DavFileDownload *download; size_t size; char *path; char *to; FILE *fd; } DDFile; static size_t ddfile_write(const void *buf, size_t size, size_t count, void *stream) { DDFile *file = stream; size_t w = fwrite(buf, size, count, file->fd); file->download->current_file_downloaded += w; file->download->downloaded_bytes += w; if (file->download->current_file_downloaded > file->download->current_file_size) { size_t diff = file->download->current_file_downloaded - file->download->current_file_size; file->download->current_file_size = file->download->current_file_downloaded; file->download->total_bytes += diff; } ui_call_mainthread(uithr_download_update_progress, file->download); return w; } static int qthr_download_resource(void *data) { DDFile *file = data; file->download->current_file_downloaded = 0; file->download->current_file_size = file->size; FILE *f = fopen(file->to, "wb"); if (!f) { return 0; } file->fd = f; DavResource *res = dav_resource_new(file->download->download_sn, file->path); dav_get_content(res, file, (dav_write_func)ddfile_write); file->download->downloaded_files++; ui_call_mainthread(uithr_download_update_progress, file->download); dav_resource_free(res); fclose(f); free(file->path); free(file->to); free(file); return 0; } typedef struct DlStackElm { DavResource *resource; char *sub_path; } DlStackElm; static int jobthr_download_scan(void *data) { DavFileDownload *download = data; DavBrowser *browser = download->browser; // check if the specified local location is a directory SYS_STAT s; if (!sys_stat(download->local_path, &s)) { if (S_ISDIR(s.st_mode)) { download->isdirectory = TRUE; } } CxList *stack = cxLinkedListCreateSimple(sizeof(DlStackElm)); // add selected files to the download queue DavResource *res = download->reslist; while (res) { DlStackElm elm; elm.resource = res; elm.sub_path = strdup(res->name); cxListAdd(stack, &elm); res = res->next; } while (stack->size > 0) { DlStackElm *elm = cxListAt(stack, 0); DavResource *res = elm->resource; char *sub_path = elm->sub_path; cxListRemove(stack, 0); if (res->iscollection) { if (dav_load(res)) { // TODO: handle error continue; } // update ui ui_call_mainthread(uithr_download_update_progress, download); char *path = util_concat_path(download->local_path, sub_path); int err = sys_mkdir(path); free(path); if (err) { // TODO: handle error } DavResource *child = res->children; while (child) { char *child_path = util_concat_path(sub_path, child->name); DlStackElm childelm; childelm.resource = child; childelm.sub_path = child_path; cxListAdd(stack, &childelm); child = child->next; } } else { // add the file to the download queue DDFile *file = malloc(sizeof(DDFile)); file->download = download; file->path = strdup(res->path); file->size = res->contentlength; if (download->isdirectory) { file->to = util_concat_path(download->local_path, sub_path); } else { file->to = strdup(download->local_path); } // stats download->total_files++; download->total_bytes += res->contentlength; // update ui ui_call_mainthread(uithr_download_update_progress, download); ui_threadpool_job(download->queue, download->ui, qthr_download_resource, file, NULL, NULL); } } cxListDestroy(stack); return 0; } static void uithr_download_scan_finished(UiEvent *event, void *data) { DavFileDownload *download = data; } static void download_window_closed(UiEvent *event, void *data) { } void davbrowser_download(UiObject *ui, DavBrowser *browser, DavResource *reslist, const char *local_path) { DavFileDownload *download = malloc(sizeof(DavFileDownload)); memset(download, 0, sizeof(DavFileDownload)); download->ui = ui; download->browser = browser; download->sn = reslist->session; download->download_sn = dav_session_clone(download->sn); download->reslist = reslist; download->local_path = strdup(local_path); download->queue = ui_threadpool_create(1); // create download progress window cxmutstr wtitle = cx_asprintf("Download to: %s", local_path); UiObject *dialog = ui_simple_window(wtitle.ptr, download); ui_context_closefunc(dialog->ctx, download_window_closed, NULL); free(wtitle.ptr); download->dialog = dialog; ui_window_size(dialog, 550, 120); download->progress = ui_double_new(dialog->ctx, NULL); download->label_top_left = ui_string_new(dialog->ctx, NULL); download->label_top_right = ui_string_new(dialog->ctx, NULL); download->label_bottom_left = ui_string_new(dialog->ctx, NULL); download->label_bottom_right = ui_string_new(dialog->ctx, NULL); ui_grid(dialog, .margin = 10, .spacing = 10, .fill = TRUE) { ui_llabel(dialog, .value = download->label_top_left, .hexpand = TRUE); ui_rlabel(dialog, .value = download->label_top_right); ui_newline(dialog); ui_progressbar(dialog, .value = download->progress, .min = 0, .max = 100, .colspan = 2, .hexpand = TRUE); ui_newline(dialog); ui_llabel(dialog, .value = download->label_bottom_left); ui_rlabel(dialog, .value = download->label_bottom_right); ui_newline(dialog); } ui_set(download->label_top_left, ""); ui_set(download->label_top_right, ""); ui_set(download->label_bottom_left, ""); ui_set(download->label_bottom_right, ""); ui_set(download->progress, 0); ui_show(dialog); // start upload and stat threads ui_job(ui, jobthr_download_scan, download, uithr_download_scan_finished, download); } // ------------------------------------- Path Operation (DELETE, MKCOL) ------------------------------------- enum DavPathOpType { DAV_PATH_OP_DELETE = 0, DAV_PATH_OP_MKCOL }; 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; // 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; char *path; int res_index; int result; char *errormsg; } DavPathOpResult; 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_mkcol_error(void *data) { DavPathOpResult *result = data; cxmutstr msg = cx_asprintf("Cannot create collection %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_mkcol_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 = TRUE; // 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); 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->path = strdup(res->path); result->result = 0; result->res_index = op->list_indices[i]; result->errormsg = 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_MKCOL) { res->iscollection = TRUE; ui_threadfunc result_callback = uithr_pathop_mkcol_sucess; if (dav_create(res)) { result->errormsg = op->sn->errorstr ? strdup(op->sn->errorstr) : NULL; result_callback = uithr_pathop_mkcol_error; } 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_mkcol(UiObject *ui, DavBrowser *browser, const char *name) { DavPathOp *op = malloc(sizeof(DavPathOp)); op->ui = ui; op->browser = browser; op->op = DAV_PATH_OP_MKCOL; 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->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); }