--- 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 <cx/printf.h> #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); }