--- /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 <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); +}