# HG changeset patch # User Olaf Wintermann # Date 1731853172 -3600 # Node ID 5360027fb2826ae7f266729117ddf41bb854c568 # Parent 8e7c57c231331d79675db920db3f1756cd2bcb4f download/upload refactoring, add cancel button, resolves #498 fixes #506 diff -r 8e7c57c23133 -r 5360027fb282 application/Makefile --- a/application/Makefile Fri Nov 15 21:50:20 2024 +0100 +++ b/application/Makefile Sun Nov 17 15:19:32 2024 +0100 @@ -35,6 +35,8 @@ SRC += application.c SRC += config.c SRC += davcontroller.c +SRC += download.c +SRC += upload.c SRC += system.c SRC += window.c SRC += settings.c diff -r 8e7c57c23133 -r 5360027fb282 application/davcontroller.c --- a/application/davcontroller.c Fri Nov 15 21:50:20 2024 +0100 +++ b/application/davcontroller.c Sun Nov 17 15:19:32 2024 +0100 @@ -32,6 +32,8 @@ #include #include "config.h" +#include "upload.h" +#include "download.h" #include "system.h" #include "common/context.h" @@ -442,726 +444,28 @@ } -// ------------------------------------- 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 (cxListSize(stack) > 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); + UiObject *dialog = ui_simple_window(wtitle.ptr, 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; + DavFileUpload *upload = dav_upload_create(browser, dialog, files); + transfer_window_init(dialog, action_upload_cancel); + dav_upload_start(upload); } -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 = sys_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; -} - -static int qthr_download_finished(void *data) { - return 0; -} - -static void uithr_download_finished(UiEvent *event, void *data) { - DavFileDownload *download = data; - printf("download finished\n"); - ui_object_unref(download->dialog); -} - - -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 (cxListSize(stack) > 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); - } - } +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); - ui_threadpool_job(download->queue, download->ui, qthr_download_finished, download, uithr_download_finished, download); - - 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; // TODO: is this safe or do we need a copy? - 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); - - ui_object_ref(dialog); - - // start upload and stat threads - ui_job(ui, jobthr_download_scan, download, uithr_download_scan_finished, download); + DavFileDownload *download = dav_download_create(browser, dialog, reslist, local_path); + transfer_window_init(dialog, action_download_cancel); + dav_download_start(download); } diff -r 8e7c57c23133 -r 5360027fb282 application/davcontroller.h --- a/application/davcontroller.h Fri Nov 15 21:50:20 2024 +0100 +++ b/application/davcontroller.h Sun Nov 17 15:19:32 2024 +0100 @@ -41,7 +41,17 @@ #define DAVBROWSER_MAX_NAVLIST 128 +typedef struct TransferProgress { + size_t total_bytes; + size_t total_files; + size_t total_directories; + size_t transferred_bytes; + size_t transferred_files; + size_t transferred_directories; + size_t current_file_size; + size_t current_file_transferred; +} TransferProgress; DavBrowser* davbrowser_create(UiObject *toplevel); diff -r 8e7c57c23133 -r 5360027fb282 application/download.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/download.c Sun Nov 17 15:19:32 2024 +0100 @@ -0,0 +1,308 @@ +/* + * 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 "download.h" + +#include "davcontroller.h" +#include "window.h" + +#include + +#include "config.h" + +#include "system.h" +#include "common/context.h" + +#include +#include + + +static int uithr_download_update_progress(void *data) { + DavFileDownload *download = data; + if(download->cancel) { + return 1; + } + + char *sz_total = util_size_str(FALSE, download->progress.total_bytes); + char *sz_downloaded = util_size_str2(FALSE, download->progress.transferred_bytes, download->progress.total_bytes, 2); + char *sz_downloaded_end = strchr(sz_downloaded, ' '); + if (sz_downloaded_end) { + *sz_downloaded_end = 0; + } + + if (download->progress.total_bytes > 0) { + double progress = (double)download->progress.transferred_bytes / (double)download->progress.total_bytes; + ui_set(download->progressbar, progress*100); + } + + + cxmutstr label1; + if (download->progress.total_files + download->progress.total_directories > 1) { + label1 = cx_asprintf( + "%s/%s %zu/%zu files", + sz_downloaded, + sz_total, + download->progress.transferred_files+download->progress.transferred_directories, + download->progress.total_files+download->progress.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; +} + +static size_t ddfile_write(const void *buf, size_t size, size_t count, void *stream) { + DDFile *file = stream; + if(file->download->cancel) { + return 0; + } + + size_t w = fwrite(buf, size, count, file->fd); + file->download->progress.current_file_transferred += w; + + file->download->progress.transferred_bytes += w; + + if (file->download->progress.current_file_transferred > file->download->progress.current_file_size) { + size_t diff = file->download->progress.current_file_transferred - file->download->progress.current_file_size; + file->download->progress.current_file_size = file->download->progress.current_file_transferred; + file->download->progress.total_bytes += diff; + } + + ui_call_mainthread(uithr_download_update_progress, file->download); + + return w; +} + +static int qthr_download_resource(void *data) { + DDFile *file = data; + if(file->download->cancel) { + return 0; + } + + file->download->progress.current_file_transferred = 0; + file->download->progress.current_file_size = file->size; + + FILE *f = sys_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->progress.transferred_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; +} + +static int qthr_download_finished(void *data) { + return 0; +} + +static void uithr_download_finished(UiEvent *event, void *data) { + DavFileDownload *download = data; + if(download->cancel) { + ui_set(download->label_bottom_left, "Canceled"); + } + if(download->dialog->ref > 1) { + ui_close(download->dialog); + } + ui_object_unref(download->dialog); +} + + +typedef struct DlStackElm { + DavResource *resource; + char *sub_path; +} DlStackElm; + +static int jobthr_download_scan(void *data) { + DavFileDownload *download = data; + + // 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 (cxListSize(stack) > 0 && !download->cancel) { + 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->progress.total_files++; + download->progress.total_bytes += res->contentlength; + + // update ui + ui_call_mainthread(uithr_download_update_progress, download); + + ui_threadpool_job(download->queue, download->dialog, qthr_download_resource, file, NULL, NULL); + } + } + + ui_threadpool_job(download->queue, download->dialog, qthr_download_finished, download, uithr_download_finished, download); + + 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) { + DavFileDownload *download = event->obj->window; + + dav_session_destroy(download->sn); + ui_threadpool_destroy(download->queue); +} + +void action_download_cancel(UiEvent *event, void *data) { + DavFileDownload *download = event->window; + if(!download->cancel) { + ui_set(download->label_bottom_left, "Cancel..."); + download->cancel = TRUE; + } +} + + +DavFileDownload* dav_download_create(DavBrowser *browser, UiObject *dialog, DavResource *reslist, const char *local_path) { + UiContext *ctx = dialog->ctx; + DavFileDownload *download = ui_malloc(ctx, sizeof(DavFileDownload)); + memset(download, 0, sizeof(DavFileDownload)); + download->dialog = dialog; + dialog->window = download; + ui_object_ref(dialog); + + download->browser = browser; + download->sn = reslist->session; + download->download_sn = dav_session_clone(download->sn); + download->reslist = reslist; // TODO: is this safe or do we need a copy? + download->local_path = ui_strdup(dialog->ctx, local_path); + + download->queue = ui_threadpool_create(1); + + CxMempool *mp = ui_cx_mempool(ctx); + cxMempoolRegister(mp, download->download_sn, (cx_destructor_func)dav_session_destroy); + cxMempoolRegister(mp, download->queue, (cx_destructor_func)ui_threadpool_destroy); + + download->progressbar = ui_double_new(ctx, "progressbar"); + download->label_top_left = ui_string_new(ctx, "label_top_left"); + download->label_top_right = ui_string_new(ctx, "label_top_right"); + download->label_bottom_left = ui_string_new(ctx, "label_bottom_left"); + download->label_bottom_right = ui_string_new(ctx, "label_bottom_right"); + + 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->progressbar, 0); + + return download; +} + +void dav_download_start(DavFileDownload *download) { + ui_show(download->dialog); + ui_job(download->dialog, jobthr_download_scan, download, uithr_download_scan_finished, download); +} diff -r 8e7c57c23133 -r 5360027fb282 application/download.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/download.h Sun Nov 17 15:19:32 2024 +0100 @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef DOWNLOAD_H +#define DOWNLOAD_H + +#include "application.h" +#include "davcontroller.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct DavFileDownload { + DavBrowser *browser; + + DavSession *sn; + DavSession *download_sn; + DavResource *reslist; + char *local_path; + DavBool isdirectory; + + UiThreadpool *queue; + + TransferProgress progress; + + UiObject *dialog; + UiDouble *progressbar; + UiString *label_top_left; + UiString *label_top_right; + UiString *label_bottom_left; + UiString *label_bottom_right; + + DavBool cancel; +} DavFileDownload; + +// dav download file +typedef struct DDFile { + DavFileDownload *download; + size_t size; + char *path; + char *to; + FILE *fd; +} DDFile; + +DavFileDownload* dav_download_create(DavBrowser *browser, UiObject *dialog, DavResource *reslist, const char *local_path); + +void dav_download_start(DavFileDownload *download); + +void action_download_cancel(UiEvent *event, void *data); + +#ifdef __cplusplus +} +#endif + +#endif /* DOWNLOAD_H */ + diff -r 8e7c57c23133 -r 5360027fb282 application/upload.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/upload.c Sun Nov 17 15:19:32 2024 +0100 @@ -0,0 +1,441 @@ +/* + * 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 "upload.h" + +#include "davcontroller.h" +#include "window.h" + +#include + +#include "config.h" + +#include "system.h" +#include "common/context.h" + +#include +#include + +// ------------------------------------- File Upload ------------------------------------- + +static double upload_progress(DavFileUpload *upload) { + return ((double)upload->progress.transferred_bytes / (double)upload->progress.total_bytes) * 100; +} + +static void update_upload_labels(DavFileUpload *upload) { + char *sz_total = util_size_str(FALSE, upload->progress.total_bytes); + char *sz_uploaded = util_size_str2(FALSE, upload->progress.transferred_bytes, upload->progress.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->progressbar, progress); + + cxmutstr label1; + if (upload->progress.total_files + upload->progress.total_directories > 1) { + label1 = cx_asprintf( + "%s/%s %zu/%zu files", + sz_uploaded, + sz_total, + upload->progress.transferred_files+upload->progress.transferred_directories, + upload->progress.total_files+upload->progress.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->progress.current_file_size > 0) { + cxmutstr file_label = cx_asprintf("%s (%.0f%%)", upload->current_file_name, ((float)upload->progress.current_file_transferred/(float)upload->progress.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->progress.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->progress.current_file_size; + upload->progress.current_file_size += extra; + upload->progress.total_bytes += extra; + } + + int64_t new_progress = now - upload->progress.current_file_transferred; + upload->progress.transferred_bytes += new_progress; + upload->progress.current_file_transferred = 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) { + if(update->upload->cancel) { + return 0; + } + + // 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 size_t dufile_read(void *buf, size_t size, size_t count, void *stream) { + DUFile *f = (DUFile*)stream; + if(f->upload->cancel) { + return 0; + } + return fread(buf, size, count, f->fd); +} + +static int dufile_seek(const void *stream, long off, int whence) { + DUFile *f = (DUFile*)stream; + return fseek(f->fd, off, whence); +} + +static int qthr_file_upload(void *data) { + DUFile *f = data; + DavFileUpload *upload = f->upload; + DavSession *sn = upload->sn; + + if(upload->cancel) { + return 0; + } + + FILE *in = sys_fopen(f->path, "rb"); + if (!in) { + // TODO: error msg + return 0; + } + f->fd = in; + + upload->upload_file = TRUE; + upload->progress.current_file_size = f->bytes; + upload->progress.current_file_transferred = 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, f, (dav_read_func)dufile_read, (dav_seek_func)dufile_seek); + 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->progress.transferred_files++; + //upload->uploaded_bytes += file->bytes; + + double progress = upload_progress(upload); + ui_set(upload->progressbar, 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->progress.current_file_transferred; + 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->progress.transferred_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; + if(upload->cancel) { + ui_set(upload->label_bottom_left, "Canceled"); + } + if(upload->dialog->ref > 1) { + ui_close(upload->dialog); + } + ui_object_unref(upload->dialog); +} + +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 (cxListSize(stack) > 0 && !upload->cancel) { + 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->progress.total_directories++; + ui_threadpool_job(upload->queue, upload->dialog, 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->progress.total_files++; + upload->progress.total_bytes += s.st_size; + ui_threadpool_job(upload->queue, upload->dialog, qthr_file_upload, f, uithr_file_uploaded, f); + } + } + } // TODO: else error msg + + ui_threadpool_job(upload->queue, upload->dialog, 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) { + DavFileUpload *upload = event->window; + ui_threadpool_destroy(upload->queue); + + dav_session_destroy(upload->sn); +} + +void action_upload_cancel(UiEvent *event, void *data) { + DavFileUpload *upload = event->window; + if(!upload->cancel) { + ui_set(upload->label_bottom_left, "Cancel..."); + upload->cancel = TRUE; + } +} + +DavFileUpload* dav_upload_create(DavBrowser *browser, UiObject *obj, UiFileList files) { + UiContext *ctx = obj->ctx; + DavFileUpload *upload = ui_malloc(ctx, sizeof(DavFileUpload)); + memset(upload, 0, sizeof(DavFileUpload)); + upload->dialog = obj; + obj->window = upload; + ui_object_ref(obj); + + upload->progressbar = ui_double_new(ctx, "progressbar"); + upload->label_top_left = ui_string_new(ctx, "label_top_left"); + upload->label_top_right = ui_string_new(ctx, "label_top_right"); + upload->label_bottom_left = ui_string_new(ctx, "label_bottom_left"); + upload->label_bottom_right = ui_string_new(ctx, "label_bottom_right"); + + // 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); + + dav_session_set_progresscallback(upload_session, NULL, upload_dav_progress, upload); + + upload->browser = browser; + upload->sn = upload_session; + upload->files = files; + upload->base_path = ui_strdup(ctx, browser->current->path); + upload->queue = ui_threadpool_create(1); + + upload->collection = browser->current; + upload->collection_ctn = browser->res_counter; + + CxMempool *mp = ui_cx_mempool(ctx); + cxMempoolRegister(mp, upload_session, (cx_destructor_func)dav_session_destroy); + cxMempoolRegister(mp, upload->queue, (cx_destructor_func)ui_threadpool_destroy); + + 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->progressbar, 0); + + return upload; +} + +void dav_upload_start(DavFileUpload *upload) { + ui_show(upload->dialog); + + // start upload and stat threads + ui_job(upload->dialog, jobthr_upload_scan, upload, uithr_upload_scan_finished, upload); +} diff -r 8e7c57c23133 -r 5360027fb282 application/upload.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/upload.h Sun Nov 17 15:19:32 2024 +0100 @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#ifndef UPLOAD_H +#define UPLOAD_H + +#include "application.h" +#include "davcontroller.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DavFileUpload { + DavBrowser *browser; + DavSession *sn; + UiFileList files; + char *base_path; + UiThreadpool *queue; + + TransferProgress progress; + DavBool upload_file; + + char *current_file_name; + + UiObject *dialog; + UiDouble *progressbar; + 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; + + DavBool cancel; +} DavFileUpload; + +typedef struct DUFile { + FILE *fd; + char *path; + char *upload_path; + size_t bytes; + DavBool isdirectory; + DavFileUpload *upload; + DavError error; +} DUFile; + +DavFileUpload* dav_upload_create(DavBrowser *browser, UiObject *obj, UiFileList files); + +void dav_upload_start(DavFileUpload *upload); + +void action_upload_cancel(UiEvent *event, void *data); + +#ifdef __cplusplus +} +#endif + +#endif /* UPLOAD_H */ + diff -r 8e7c57c23133 -r 5360027fb282 application/window.c --- a/application/window.c Fri Nov 15 21:50:20 2024 +0100 +++ b/application/window.c Sun Nov 17 15:19:32 2024 +0100 @@ -282,6 +282,30 @@ } +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) { diff -r 8e7c57c23133 -r 5360027fb282 application/window.h --- a/application/window.h Fri Nov 15 21:50:20 2024 +0100 +++ b/application/window.h Sun Nov 17 15:19:32 2024 +0100 @@ -59,6 +59,8 @@ void resourceviewer_new(DavBrowser *browser, const char *path, DavResourceViewType type); void* resourceviewer_proplist_getvalue(DavPropertyList *property, int col); +void transfer_window_init(UiObject *dialog, ui_callback btncallback); + void auth_dialog(SessionAuthData *auth); diff -r 8e7c57c23133 -r 5360027fb282 libidav/methods.c --- a/libidav/methods.c Fri Nov 15 21:50:20 2024 +0100 +++ b/libidav/methods.c Sun Nov 17 15:19:32 2024 +0100 @@ -1037,7 +1037,7 @@ curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length); } else if(length == 0) { headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); - curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)1); + curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)-1); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); } else { curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length); diff -r 8e7c57c23133 -r 5360027fb282 make/vs/idav/idav.vcxproj --- a/make/vs/idav/idav.vcxproj Fri Nov 15 21:50:20 2024 +0100 +++ b/make/vs/idav/idav.vcxproj Sun Nov 17 15:19:32 2024 +0100 @@ -169,6 +169,8 @@ + + @@ -178,6 +180,8 @@ + + @@ -201,4 +205,4 @@ - \ No newline at end of file + diff -r 8e7c57c23133 -r 5360027fb282 make/vs/idav/idav.vcxproj.filters --- a/make/vs/idav/idav.vcxproj.filters Fri Nov 15 21:50:20 2024 +0100 +++ b/make/vs/idav/idav.vcxproj.filters Sun Nov 17 15:19:32 2024 +0100 @@ -37,6 +37,12 @@ src + + src + + + src + src @@ -57,8 +63,14 @@ src + + src + + + src + src - \ No newline at end of file + diff -r 8e7c57c23133 -r 5360027fb282 ui/common/context.c --- a/ui/common/context.c Fri Nov 15 21:50:20 2024 +0100 +++ b/ui/common/context.c Sun Nov 17 15:19:32 2024 +0100 @@ -557,7 +557,11 @@ } UIEXPORT void *ui_allocator(UiContext *ctx) { - return ctx ? (void*)ctx->allocator : NULL; + return (void*)ctx->allocator; +} + +void* ui_cx_mempool(UiContext *ctx) { + return ctx->mp; } void* ui_malloc(UiContext *ctx, size_t size) { diff -r 8e7c57c23133 -r 5360027fb282 ui/gtk/toolkit.c --- a/ui/gtk/toolkit.c Fri Nov 15 21:50:20 2024 +0100 +++ b/ui/gtk/toolkit.c Sun Nov 17 15:19:32 2024 +0100 @@ -364,7 +364,10 @@ " background-color: @theme_base_color;\n" " border-radius: 5px;\n" " padding: 0px;\n" -"}" +"}\n" +".pathbar-button-inactive {\n" +" color: alpha(currentColor, 0.5);" +"}\n" ".ui_test {\n" " background-color: red;\n" "}\n" diff -r 8e7c57c23133 -r 5360027fb282 ui/ui/toolkit.h --- a/ui/ui/toolkit.h Fri Nov 15 21:50:20 2024 +0100 +++ b/ui/ui/toolkit.h Sun Nov 17 15:19:32 2024 +0100 @@ -469,6 +469,8 @@ UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups); UIEXPORT void* ui_allocator(UiContext *ctx); +UIEXPORT void* ui_cx_mempool(UiContext *ctx); + UIEXPORT void* ui_malloc(UiContext *ctx, size_t size); UIEXPORT void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize); UIEXPORT void ui_free(UiContext *ctx, void *ptr);