Sun, 17 Nov 2024 15:19:32 +0100
download/upload refactoring, add cancel button, resolves #498 fixes #506
/* * 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 <cx/printf.h> #include "config.h" #include "system.h" #include "common/context.h" #include <libidav/config.h> #include <libidav/utils.h> 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); }