implement dnd upload (without progressbar)

Tue, 06 Feb 2024 14:17:22 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 06 Feb 2024 14:17:22 +0100
changeset 18
af411868ab9b
parent 17
7cfd36aa005b
child 19
813c97c5b6d3

implement dnd upload (without progressbar)

application/application.c file | annotate | diff | comparison | revisions
application/application.h file | annotate | diff | comparison | revisions
application/davcontroller.c file | annotate | diff | comparison | revisions
application/davcontroller.h file | annotate | diff | comparison | revisions
application/system.h file | annotate | diff | comparison | revisions
application/window.c file | annotate | diff | comparison | revisions
application/window.h file | annotate | diff | comparison | revisions
libidav/Makefile file | annotate | diff | comparison | revisions
libidav/config.c file | annotate | diff | comparison | revisions
libidav/session.c file | annotate | diff | comparison | revisions
libidav/utils.c file | annotate | diff | comparison | revisions
libidav/utils.h file | annotate | diff | comparison | revisions
libidav/webdav.c file | annotate | diff | comparison | revisions
libidav/webdav.h file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/ui/dnd.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/window.h file | annotate | diff | comparison | revisions
ui/winui/dnd.cpp file | annotate | diff | comparison | revisions
ui/winui/dnd.h file | annotate | diff | comparison | revisions
ui/winui/label.cpp file | annotate | diff | comparison | revisions
ui/winui/pch.h file | annotate | diff | comparison | revisions
ui/winui/table.cpp file | annotate | diff | comparison | revisions
ui/winui/window.cpp file | annotate | diff | comparison | revisions
--- a/application/application.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/application/application.c	Tue Feb 06 14:17:22 2024 +0100
@@ -86,7 +86,7 @@
     }
 	ui_toolbar_item("NewFolder", .icon = "NewFolder");
 	ui_toolbar_item("NewFile", .icon = "Add");
-	ui_toolbar_item("Upload", .label = "Upload", .icon = "Upload");
+	ui_toolbar_item("Upload", .label = "Upload", .icon = "Upload", .onclick = action_upload_file);
 	ui_toolbar_item("Download", .icon = "SaveLocal");
 	ui_toolbar_item("Remove", .icon = "Delete");
 	ui_toolbar_toggleitem("LocalBrowser", .icon = "DockLeft", .label = "Local Browser");
@@ -163,3 +163,13 @@
 	DavBrowser *browser = event->document;
 	davbrowser_connect2repo(event->obj, browser, repo, "");
 }
+
+static void file_selected(UiEvent *event, void *data) {
+	UiFileList *files = event->eventdata;
+
+	char *file = files->files[0];
+}
+
+void action_upload_file(UiEvent *event, void *data) {
+	ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_MULTI, file_selected, NULL);
+}
--- a/application/application.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/application/application.h	Tue Feb 06 14:17:22 2024 +0100
@@ -102,6 +102,8 @@
 
 void action_repo_selected(UiEvent *event, void *data);
 
+void action_upload_file(UiEvent *event, void *data);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/application/davcontroller.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/application/davcontroller.c	Tue Feb 06 14:17:22 2024 +0100
@@ -33,6 +33,8 @@
 
 #include "config.h"
 
+#include "system.h"
+
 #include <libidav/config.h>
 #include <libidav/utils.h>
 
@@ -234,3 +236,137 @@
         browser->navstack_enabled = true;
     }
 }
+
+
+// ------------------------------------- File Upload -------------------------------------
+
+typedef struct DavFileUpload {
+    UiObject *ui;
+    DavBrowser *browser;
+    DavSession *sn;
+    UiFileList files;
+    char *base_path;
+
+    UiObject *dialog;
+    UiDouble *progress;
+    UiString *file_label;
+    UiString *speed_label;
+} DavFileUpload;
+
+typedef struct DUFile {
+    char *path;
+    char *upload_path;
+} DUFile;
+
+static int jobthr_file_upload(void *data) {
+    DavFileUpload *upload = data;
+
+    CxList *stack = cxLinkedListCreateSimple(sizeof(DUFile));
+    for (int i = 0; i < upload->files.nfiles; i++) {
+        DUFile f;
+        f.path = upload->files.files[i];
+        f.upload_path = util_path_file_name(f.path);
+        cxListInsert(stack, 0, &f);
+    }
+
+    while (stack->size > 0) {
+        DUFile *f = cxListAt(stack, 0);
+
+        char *path = util_concat_path(upload->base_path, f->upload_path);
+        DavResource *res = dav_resource_new(upload->sn, path);
+
+        SYS_STAT s;
+        if (!stat(f->path, &s)) {
+            if (S_ISDIR(s.st_mode)) {
+                res->iscollection = 1;
+                dav_create(res);
+
+                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;
+                        child.path = newpath.ptr;
+                        child.upload_path = new_upload_path;
+                        cxListAdd(stack, &child);
+                    }
+
+                    sys_closedir(dir);
+                }
+            } else if (S_ISREG(s.st_mode)) {
+                FILE *in = sys_fopen(f->path, "rb");
+                if (in) {
+                    dav_set_content(res, in, (dav_read_func)fread, (dav_seek_func)fseek);
+                    //dav_set_content_length(res, s.st_size);
+                    int err = dav_store(res);
+                    if (err) {
+                        // TODO: error
+
+                    }
+                    fclose(in);
+                }
+            }
+        } // TODO: else error msg
+
+        dav_resource_free(res);
+        free(path);
+
+        cxListRemove(stack, 0);
+    }
+
+    return 0;
+}
+
+static void uithr_file_upload_finished(UiEvent *event, void *data) {
+    DavFileUpload *upload = data;
+}
+
+
+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));
+    upload->ui = ui;
+    upload->browser = browser;
+    upload->sn = upload_session;
+    upload->files = files;
+    upload->base_path = strdup(browser->current->path);
+
+    // create upload progress window
+    UiObject *dialog = ui_simple_window("Upload", upload);
+    upload->dialog = dialog;
+    ui_window_size(dialog, 450, 120);
+    upload->progress = ui_double_new(dialog->ctx, NULL);
+    upload->file_label = ui_string_new(dialog->ctx, NULL);
+    upload->speed_label = ui_string_new(dialog->ctx, NULL);
+
+    ui_vbox(dialog, .margin = 10, .spacing = 10) {
+        ui_llabel(dialog, .value = upload->file_label);
+        ui_progressbar(dialog, .value = upload->progress);
+        ui_llabel(dialog, .value = upload->speed_label);
+    }
+
+    ui_set(upload->file_label, "");
+    ui_set(upload->speed_label, "");
+    ui_set(upload->progress, 0);
+
+    //ui_show(dialog);
+
+    // start upload and stat threads
+    ui_job(ui, jobthr_file_upload, upload, uithr_file_upload_finished, upload);
+}
--- a/application/davcontroller.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/application/davcontroller.h	Tue Feb 06 14:17:22 2024 +0100
@@ -56,6 +56,8 @@
 void davbrowser_navigation_back(UiObject *ui, DavBrowser *browser);
 void davbrowser_navigation_forward(UiObject *ui, DavBrowser *browser);
 
+void davbrowser_upload_files(UiObject *ui, DavBrowser *browser, UiFileList files);
+
 
 #ifdef	__cplusplus
 }
--- a/application/system.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/application/system.h	Tue Feb 06 14:17:22 2024 +0100
@@ -80,6 +80,19 @@
 
 #endif
 
+
+#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
+#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+
+#ifdef _WIN32
+#ifndef S_ISDIR
+#define S_ISDIR(mode) ((mode) & _S_IFMT) == _S_IFDIR
+#define S_ISREG(mode) ((mode) & _S_IFMT) == _S_IFREG
+#endif
+#endif
+
+
 typedef int(*stat_func)(const char*, SYS_STAT *);
 
 void sys_init(void);
--- a/application/window.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/application/window.c	Tue Feb 06 14:17:22 2024 +0100
@@ -31,6 +31,7 @@
 #include "davcontroller.h"
 
 #include <ui/stock.h>
+#include <ui/dnd.h>
 
 #include <libidav/utils.h>
 
@@ -67,7 +68,7 @@
 	// main content
 	UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Name", UI_STRING, "Type", UI_STRING_FREE, "Last Modified", UI_STRING_FREE, "Size", -1);
 	model->getvalue = (ui_getvaluefunc)window_resource_table_getvalue;
-	ui_table(obj, .fill = UI_ON, .model = model, .onactivate = action_list_activate, .varname = "reslist");
+	ui_table(obj, .fill = UI_ON, .model = model, .onactivate = action_list_activate, .ondrop = action_dnd_drop,.varname = "reslist");
 
 	// status bar
 	ui_hbox(obj, .fill = UI_OFF) {
@@ -215,3 +216,11 @@
 		}
 	}
 }
+
+void action_dnd_drop(UiEvent *event, void *data) {
+	UiListDnd *listdnd = event->eventdata;
+	UiDnD *dnd = listdnd->dnd;
+	UiFileList files = ui_selection_geturis(dnd);
+
+	davbrowser_upload_files(event->obj, event->document, files);
+}
--- a/application/window.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/application/window.h	Tue Feb 06 14:17:22 2024 +0100
@@ -62,6 +62,8 @@
 
 void action_list_activate(UiEvent *event, void *data);
 
+void action_dnd_drop(UiEvent *event, void *data);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/libidav/Makefile	Wed Jan 31 12:55:11 2024 +0100
+++ b/libidav/Makefile	Tue Feb 06 14:17:22 2024 +0100
@@ -26,8 +26,6 @@
 # POSSIBILITY OF SUCH DAMAGE.
 #
 
-BUILD_ROOT = ..
-
 include ../config.mk
 
 # list of source files
--- a/libidav/config.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/libidav/config.c	Tue Feb 06 14:17:22 2024 +0100
@@ -579,6 +579,9 @@
         stype = "https";
     } else if(type == DAV_HTTP_PROXY) {
         stype = "http";
+    } else {
+        fprintf(stderr, "unknown proxy type\n");
+        return 1;
     }
     
     if(!proxy) {
--- a/libidav/session.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/libidav/session.c	Tue Feb 06 14:17:22 2024 +0100
@@ -50,7 +50,7 @@
     }
     DavSession *sn = malloc(sizeof(DavSession));
     memset(sn, 0, sizeof(DavSession));
-    sn->mp = cxMempoolCreate(DAV_SESSION_MEMPOOL_SIZE, NULL);
+    sn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE);
     sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
     sn->key = NULL;
     sn->errorstr = NULL;
@@ -94,7 +94,7 @@
     curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
     
     // add to context
-    cxListAdd(context->sessions, sn);
+    dav_context_add_session(context, sn);
     sn->context = context;
     
     return sn;
@@ -114,6 +114,35 @@
     return sn;
 }
 
+DavSession* dav_session_clone(DavSession *sn) {
+    CURL *newhandle = curl_easy_duphandle(sn->handle);
+
+    DavSession *newsn = malloc(sizeof(DavSession));
+    memset(newsn, 0, sizeof(DavSession));
+    newsn->mp = cxMempoolCreate(DAV_SESSION_MEMPOOL_SIZE, NULL);
+    newsn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+    newsn->key = sn->key;
+    newsn->errorstr = NULL;
+    newsn->error = DAV_OK;
+    newsn->flags = 0;
+
+    newsn->handle = newhandle;
+
+    newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr;
+    newsn->auth_prompt = sn->auth_prompt;
+    newsn->authprompt_userdata = sn->authprompt_userdata;
+    newsn->logfunc = sn->logfunc;
+    newsn->get_progress = sn->get_progress;
+    newsn->put_progress = sn->put_progress;
+    newsn->progress_userdata = sn->progress_userdata;
+
+    // add to context
+    dav_context_add_session(sn->context, newsn);
+    newsn->context = sn->context;
+
+    return newsn;
+}
+
 void dav_session_set_auth(DavSession *sn, const char *user, const char *password) {
     if(user && password) {
         dav_session_set_auth_s(sn, cx_str(user), cx_str(password));
@@ -302,12 +331,8 @@
 
 void dav_session_destroy(DavSession *sn) { 
     // remove session from context
-    CxList *sessions = sn->context->sessions;
-    ssize_t i = cxListFind(sessions, sn);
-    if(i >= 0) {
-        cxListRemove(sessions, i);
-    } else {
-        printf("Error: session not found in ctx->sessions\n");
+    if (dav_context_remove_session(sn->context, sn)) {
+        fprintf(stderr, "Error: session not found in ctx->sessions\n");
         dav_session_destructor(sn);
     }
 }
--- a/libidav/utils.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/libidav/utils.c	Tue Feb 06 14:17:22 2024 +0100
@@ -612,6 +612,28 @@
     }
 }
 
+const char* util_resource_name_c(const char *url, char pathseparator) {
+    cxstring urlstr = cx_str(url);
+    if(urlstr.ptr[urlstr.length-1] == pathseparator) {
+        urlstr.length--;
+    }
+    cxstring resname = cx_strrchr(urlstr, pathseparator);
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+const char* util_path_file_name(const char *url) {
+#ifdef _WIN32
+    return util_resource_name_c(url, '\\');
+#else
+    return util_resource_name_c(url, '/');
+#endif
+}
+
+
 int util_mkdir(char *path, mode_t mode) {
 #ifdef _WIN32
     return mkdir(path);
@@ -658,6 +680,40 @@
     return url;
 }
 
+cxmutstr util_concat_path_ext(cxstring base, cxstring path, char separator) {
+    if(!path.ptr) {
+        path = CX_STR("");
+    }
+
+    int add_separator = 0;
+    if(base.length != 0 && base.ptr[base.length-1] == separator) {
+        if(path.ptr[0] == separator) {
+            base.length--;
+        }
+    } else {
+        if(path.length == 0 || path.ptr[0] != separator) {
+            add_separator = 1;
+        }
+    }
+
+    cxmutstr url;
+    if(add_separator) {
+        url = cx_strcat(3, base, cx_strn(&separator, 1), path);
+    } else {
+        url = cx_strcat(2, base, path);
+    }
+
+    return url;
+}
+
+cxmutstr util_concat_sys_path(cxstring base, cxstring path) {
+#ifdef _WIN32
+    return util_concat_path_ext(base, path, '\\');
+#else
+    return util_concat_path_ext(base, path, '/');
+#endif
+}
+
 char* util_get_url(DavSession *sn, const char *href) {
     cxstring base = cx_str(sn->base_url);
     cxstring href_str = cx_str(href);
@@ -716,6 +772,17 @@
     return parent;
 }
 
+char* util_sys_parent_path(const char *path) {
+    const char *name = util_path_file_name(path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = malloc(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
 char* util_size_str(DavBool iscollection, uint64_t contentlength) {
     char *str = malloc(16);
     uint64_t size = contentlength;
--- a/libidav/utils.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/libidav/utils.h	Tue Feb 06 14:17:22 2024 +0100
@@ -78,8 +78,13 @@
 cxstring util_url_path_s(cxstring url);
 char* util_url_decode(DavSession *sn, const char *url);
 const char* util_resource_name(const char *url);
+const char* util_resource_name_c(const char *url, char pathseparator);
+const char* util_path_file_name(const char *url);
+
 char* util_concat_path(const char *url_base, const char *path);
 cxmutstr util_concat_path_s(cxstring url_base, cxstring path);
+cxmutstr util_concat_path_ext(cxstring url_base, cxstring path, char separator);
+cxmutstr util_concat_sys_path(cxstring base, cxstring path);
 char* util_get_url(DavSession *sn, const char *href);
 void util_set_url(DavSession *sn, const char *href);
 
@@ -97,6 +102,7 @@
 
 char* util_path_to_url(DavSession *sn, const char *path);
 char* util_parent_path(const char *path);
+char* util_sys_parent_path(const char *path);
 
 char* util_size_str(DavBool iscollection, uint64_t contentlength);
 char* util_date_str(time_t tm);
--- a/libidav/webdav.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/libidav/webdav.c	Tue Feb 06 14:17:22 2024 +0100
@@ -146,15 +146,68 @@
     free(ctx);
 }
 
+#ifndef _WIN32
+
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
+    if (enable) {
+        pthread_mutex_init(&ctx->mutex, NULL);
+    } else {
+        pthread_mutex_destroy(&ctx->mutex);
+    }
+    ctx->mtsafe = enable;
+}
+
+void dav_context_lock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        pthread_mutex_lock(&ctx->mutex);
+    }
+}
+
+void dav_context_unlock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        pthread_mutex_unlock(&ctx->mutex);
+    }
+}
+
+#else
+
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
+    if (enable) {
+        ctx->mutex = CreateMutex(NULL, FALSE, NULL);
+    } else {
+        CloseHandle(ctx->mutex);
+    }
+    ctx->mtsafe = enable;
+}
+
+void dav_context_lock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        WaitForSingleObject(ctx->mutex, INFINITE);
+    }
+}
+
+void dav_context_unlock(DavContext *ctx) {
+    if (ctx->mtsafe) {
+        ReleaseMutex(ctx->mutex);
+    }
+}
+
+#endif
+
 void dav_context_add_key(DavContext *context, DavKey *key) {
+    dav_context_lock(context);
     cxMapPut(context->keys, cx_hash_key_str(key->name), key);
+    dav_context_unlock(context);
 }
 
 DavKey* dav_context_get_key(DavContext *context, const char *name) {
+    DavKey *key = NULL;
+    dav_context_lock(context);
     if(name) {
-        return cxMapGet(context->keys, cx_hash_key_str(name));
+        key = cxMapGet(context->keys, cx_hash_key_str(name));
     }
-    return NULL;
+    dav_context_unlock(context);
+    return key;
 }
 
 int dav_add_namespace(DavContext *context, const char *prefix, const char *name) {
@@ -164,8 +217,19 @@
     }
     
     char *p = strdup(prefix);
+    if (!p) {
+        free(namespace);
+        return 1;
+    }
     char *n = strdup(name);
-    
+    if (!n) {
+        free(namespace);
+        free(p);
+        return 1;
+    }
+
+    dav_context_lock(context);
+
     int err = 0;
     if(p && n) {
         namespace->prefix = p;
@@ -178,19 +242,29 @@
         if(p) free(p);
         if(n) free(n);
     }
+
+    dav_context_unlock(context);
     
     return err;
 }
 
 DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) {
-    return cxMapGet(context->namespaces, cx_hash_key_str(prefix));
+    dav_context_lock(context);
+    DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key_str(prefix));
+    dav_context_unlock(context);
+    return ns;
 }
 
 DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) {
-    return cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length));
+    dav_context_lock(context);
+    DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length));
+    dav_context_unlock(context);
+    return ns;
 }
 
 int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) {
+    dav_context_lock(context);
+
     CxHashKey hkey = cx_hash_key_str(ns);
     DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey);
     if(!info) {
@@ -200,15 +274,21 @@
     } else {
         info->encrypt = encrypt;
     }
+
+    dav_context_unlock(context);
     return 0;
 }
 
 int dav_namespace_is_encrypted(DavContext *context, const char *ns) {
+    int ret = 0;
+    dav_context_lock(context);
+    
     DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns));
     if(info) {
-        return info->encrypt;
+        ret = info->encrypt;
     }
-    return 0;
+    dav_context_unlock(context);
+    return ret;
 }
 
 void dav_get_property_namespace_str(
@@ -262,6 +342,28 @@
     }
 }
 
+int dav_context_add_session(DavContext *context, DavSession *sn) {
+    dav_context_lock(context);
+    int ret = cxListAdd(context->sessions, sn);
+    dav_context_unlock(context);
+    return ret;
+}
+
+int dav_context_remove_session(DavContext *context, DavSession *sn) {
+    int ret = 0;
+    dav_context_lock(context);
+    CxList *sessions = context->sessions;
+    ssize_t i = cxListFind(sessions, sn);
+    if(i >= 0) {
+        cxListRemove(sessions, i);
+    } else {
+        ret = 1;
+    }
+    dav_context_unlock(context);
+    return ret;
+}
+
+
 // TODO: add sstr_t version of dav_get_property_ns
 
 void dav_set_effective_href(DavSession *sn, DavResource *resource) {
--- a/libidav/webdav.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/libidav/webdav.h	Tue Feb 06 14:17:22 2024 +0100
@@ -39,6 +39,12 @@
 #include <curl/curl.h>
 #include <libxml/tree.h>
 
+#ifndef _WIN32
+#include <pthread.h>
+#else
+#include <Windows.h>
+#endif
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -67,6 +73,12 @@
 typedef struct DavInputStream  DavInputStream;
 typedef struct DavOutputStream DavOutputStream;
 
+#ifndef _WIN32
+#define DAV_MUTEX pthread_mutex_t 
+#else
+#define DAV_MUTEX HANDLE
+#endif
+
 typedef size_t(*dav_read_func)(void*, size_t, size_t, void*);
 typedef size_t(*dav_write_func)(const void*, size_t, size_t, void*);
 typedef int(*dav_seek_func)(const void *, long, int);
@@ -180,12 +192,14 @@
 };
 
 struct DavContext {
-    CxMap    *namespaces;
-    CxMap    *namespaceinfo;
-    CxMap    *keys;
-    CxList   *sessions;
-    DavProxy *http_proxy;
-    DavProxy *https_proxy;
+    CxMap     *namespaces;
+    CxMap     *namespaceinfo;
+    CxMap     *keys;
+    CxList    *sessions;
+    DavProxy  *http_proxy;
+    DavProxy  *https_proxy;
+    DAV_MUTEX mutex;
+    DavBool   mtsafe;
 };
 
 struct DavProxy {
@@ -251,6 +265,10 @@
 
 DavContext* dav_context_new(void);
 void dav_context_destroy(DavContext *ctx);
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable);
+
+void dav_context_lock(DavContext *ctx);
+void dav_context_unlock(DavContext *ctx);
 
 void dav_context_add_key(DavContext *context, DavKey *key);
 DavKey* dav_context_get_key(DavContext *context, const char *name);
@@ -262,12 +280,16 @@
 int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt);
 int dav_namespace_is_encrypted(DavContext *context, const char *ns);
 
+int dav_context_add_session(DavContext *context, DavSession *sn);
+int dav_context_remove_session(DavContext *context, DavSession *sn);
+
 DavSession* dav_session_new(DavContext *context, char *base_url);
 DavSession* dav_session_new_auth(
         DavContext *context,
         char *base_url,
         char *user,
         char *password);
+DavSession* dav_session_clone(DavSession *sn);
 void dav_session_set_auth(DavSession *sn, const char *user, const char *password);
 void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password);
 void dav_session_set_baseurl(DavSession *sn, char *base_url);
--- a/ui/common/types.c	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/common/types.c	Tue Feb 06 14:17:22 2024 +0100
@@ -466,3 +466,19 @@
 UiStr ui_str_free(char *str, void (*freefunc)(void *v)) {
     return (UiStr) { str, freefunc };
 }
+
+
+UiFileList ui_filelist_copy(UiFileList list) {
+    char **newlist = calloc(sizeof(char*), list.nfiles);
+    for (int i = 0; i < list.nfiles; i++) {
+        newlist[i] = strdup(list.files[i]);
+    }
+    return (UiFileList) { newlist, list.nfiles };
+}
+
+void ui_filelist_free(UiFileList list) {
+    for (int i = 0; i < list.nfiles; i++) {
+        free(list.files[i]);
+    }
+    free(list.files);
+}
--- a/ui/ui/dnd.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/ui/dnd.h	Tue Feb 06 14:17:22 2024 +0100
@@ -41,7 +41,7 @@
 UIEXPORT void ui_selection_seturis(UiDnD *sel, char **uris, int nelm);
 
 UIEXPORT char* ui_selection_gettext(UiDnD *sel);
-UIEXPORT char** ui_selection_geturis(UiDnD *sel, size_t *nelm);
+UIEXPORT UiFileList ui_selection_geturis(UiDnD *sel);
 
 
 #ifdef __cplusplus
--- a/ui/ui/toolkit.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/ui/toolkit.h	Tue Feb 06 14:17:22 2024 +0100
@@ -155,6 +155,8 @@
 
 typedef struct UiStr        UiStr;
 
+typedef struct UiFileList   UiFileList;
+
 /* begin opaque types */
 typedef struct UiContext    UiContext;
 typedef struct UiContainer  UiContainer;
@@ -356,6 +358,11 @@
     UI_OFF
 };
 
+struct UiFileList {
+    char **files;
+    size_t nfiles;
+};
+
 
 UIEXPORT void ui_init(const char *appname, int argc, char **argv);
 UIEXPORT const char* ui_appname();
@@ -453,6 +460,9 @@
 UIEXPORT void  ui_list_addobsv(UiList *list, ui_callback f, void *data);
 UIEXPORT void  ui_list_notify(UiList *list);
 
+UiFileList ui_filelist_copy(UiFileList list);
+void ui_filelist_free(UiFileList list);
+
 UIEXPORT void ui_clipboard_set(char *str);
 UIEXPORT char* ui_clipboard_get();
 
--- a/ui/ui/window.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/ui/window.h	Tue Feb 06 14:17:22 2024 +0100
@@ -35,13 +35,19 @@
 extern "C" {
 #endif
 
+#define UI_FILEDIALOG_SELECT_SINGLE       0
+#define UI_FILEDIALOG_SELECT_MULTI        1
+#define UI_FILEDIALOG_SELECT_FOLDER       2
+
 UIEXPORT UiObject* ui_window(const char *title, void *window_data);
-UIEXPORT UiObject* ui_simplewindow(char *title, void *window_data);
+UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data);
 
 UIEXPORT void ui_window_size(UiObject *obj, int width, int height);
 
-char* ui_openfiledialog(UiObject *obj);
-char* ui_savefiledialog(UiObject *obj);
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata);
+UIEXPORT void ui_savefiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata);
+
+
 
 #ifdef	__cplusplus
 }
--- a/ui/winui/dnd.cpp	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/winui/dnd.cpp	Tue Feb 06 14:17:22 2024 +0100
@@ -31,6 +31,13 @@
 #include "dnd.h"
 #include "util.h"
 
+#include <thread>
+
+using namespace winrt;
+using namespace Windows::ApplicationModel::DataTransfer;
+using namespace Windows::Storage;
+using namespace Windows::Storage::Streams;
+
 UIEXPORT void ui_selection_settext(UiDnD* dnd, char* str, int len) {
 	if (dnd->data) {
 		if (len < 0) {
@@ -54,6 +61,31 @@
 	return nullptr;
 }
 
-UIEXPORT char** ui_selection_geturis(UiDnD* dnd, size_t* nelm) {
-	return nullptr;
+
+UIEXPORT UiFileList ui_selection_geturis(UiDnD *dnd) {
+	UiFileList flist;
+	flist.files = nullptr;
+	flist.nfiles = 0;
+
+    if (dnd->dataview.Contains(StandardDataFormats::StorageItems())) {
+		UiFileList *flist_ptr = &flist;
+
+		// we need to execute this in a different thread
+		// this could block the main gui thread, but shouldn't happen with a simple uri list
+		std::thread getDataThread([dnd, flist_ptr]() {
+				auto items = dnd->dataview.GetStorageItemsAsync().get();
+
+				char **uris = (char**)calloc(items.Size(), sizeof(char*));
+				flist_ptr->files = uris;
+				flist_ptr->nfiles = items.Size();
+
+				int i = 0;
+				for (IStorageItem const& item : items) {
+					winrt::hstring path = item.Path();
+					uris[i++] = wchar2utf8(path.c_str(), path.size());
+				}
+			});
+		getDataThread.join();
+    }
+	return flist;
 }
--- a/ui/winui/dnd.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/winui/dnd.h	Tue Feb 06 14:17:22 2024 +0100
@@ -36,4 +36,5 @@
 	winrt::Microsoft::UI::Xaml::DropCompletedEventArgs dndcompletedargs = { nullptr };
 	winrt::Microsoft::UI::Xaml::DragEventArgs drageventargs = { nullptr };
 	winrt::Windows::ApplicationModel::DataTransfer::DataPackage data = { nullptr };
+	winrt::Windows::ApplicationModel::DataTransfer::DataPackageView dataview = { nullptr };
 };
--- a/ui/winui/label.cpp	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/winui/label.cpp	Tue Feb 06 14:17:22 2024 +0100
@@ -99,7 +99,7 @@
 
 void  ui_label_set(UiString* str, const char* newvalue) {
     UiWidget* widget = (UiWidget*)str->obj;
-    TextBox box = widget->uielement.as<TextBox>();
+    TextBlock box = widget->uielement.as<TextBlock>();
     box.Text(ui_wstring_set(str, newvalue));
 }
 
--- a/ui/winui/pch.h	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/winui/pch.h	Tue Feb 06 14:17:22 2024 +0100
@@ -34,6 +34,12 @@
 #include <winrt/Microsoft.UI.Input.h>
 #include <winrt/Windows.UI.Core.h>
 #include <winrt/Windows.ApplicationModel.h>
+#include <winrt/Windows.Storage.Pickers.h>
+
 #include <winrt\Microsoft.UI.Dispatching.h>
 
 #include <winrt/Windows.Storage.Streams.h>
+
+#include <Microsoft.UI.Xaml.Window.h>
+
+#include <shobjidl_core.h>
--- a/ui/winui/table.cpp	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/winui/table.cpp	Tue Feb 06 14:17:22 2024 +0100
@@ -385,7 +385,7 @@
 						dnd.dndstartargs = { nullptr };
 						dnd.dndcompletedargs = { nullptr };
 						dnd.drageventargs = args;
-						dnd.data = args.Data();
+						dnd.dataview = args.DataView();
 
 						UiListDnd dndevt;
 						dndevt.selection = uiselection();
@@ -404,6 +404,9 @@
 							free(dndevt.selection.rows);
 						}
 					}));
+				cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){
+					args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy);
+					}));
 			}
 
 			// set the cell value
--- a/ui/winui/window.cpp	Wed Jan 31 12:55:11 2024 +0100
+++ b/ui/winui/window.cpp	Tue Feb 06 14:17:22 2024 +0100
@@ -28,6 +28,7 @@
 
 #include "pch.h"
 
+
 #include "window.h"
 
 #include "appmenu.h"
@@ -52,54 +53,12 @@
 using namespace Microsoft::UI::Xaml::Markup;
 using namespace Windows::UI::Xaml::Interop;
 using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Storage::Pickers;
 
 UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {}
 
 UiObject* ui_window(const char* title, void* window_data) {
-	CxMempool* mp = cxBasicMempoolCreate(256);
-	UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));
-
-	obj->ctx = uic_context(obj, mp);
-	obj->window = window_data;
-
-	Window window = Window();
-	//Window window = make<winui::implementation::MainWindow>();
-
-	winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" };
-	Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested);
-
-	window.ExtendsContentIntoTitleBar(true);
-
-	Grid grid = Grid();
-	window.Content(grid);
-
-	StackPanel titleBar = StackPanel();
-	Thickness titleBarPadding = { 10, 5, 5, 10 };
-	titleBar.Padding(titleBarPadding);
-	titleBar.Orientation(Orientation::Horizontal);
-	TextBlock titleLabel = TextBlock();
-	titleBar.Children().Append(titleLabel);
-
-	if (title) {
-		wchar_t* wtitle = str2wstr(title, nullptr);
-		window.Title(wtitle);
-		titleLabel.Text(hstring(wtitle));
-		free(wtitle);
-	}
-
-	window.SetTitleBar(titleBar);
-
-	obj->wobj = new UiWindow(window);
-	ui_context_add_window_destructor(obj->ctx, obj->wobj);
-
-	window.Closed([obj](IInspectable const& sender, WindowEventArgs) {
-		cxMempoolDestroy(obj->ctx->mp);
-	});
-
-	obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);
-
-	titleBar.VerticalAlignment(VerticalAlignment::Top);
-	obj->container->Add(titleBar, false);
+	UiObject* obj = ui_simple_window(title, window_data);
 
 	if (uic_get_menu_list()) {
 		// create/add menubar
@@ -171,6 +130,55 @@
 		obj->container->Add(toolbar_grid, false);
 	}
 
+	return obj;
+}
+
+UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) {
+	CxMempool* mp = cxBasicMempoolCreate(256);
+	UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));
+
+	obj->ctx = uic_context(obj, mp);
+	obj->window = window_data;
+
+	Window window = Window();
+	//Window window = make<winui::implementation::MainWindow>();
+
+	winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" };
+	Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested);
+
+	window.ExtendsContentIntoTitleBar(true);
+
+	Grid grid = Grid();
+	window.Content(grid);
+
+	StackPanel titleBar = StackPanel();
+	Thickness titleBarPadding = { 10, 5, 5, 10 };
+	titleBar.Padding(titleBarPadding);
+	titleBar.Orientation(Orientation::Horizontal);
+	TextBlock titleLabel = TextBlock();
+	titleBar.Children().Append(titleLabel);
+
+	if (title) {
+		wchar_t* wtitle = str2wstr(title, nullptr);
+		window.Title(wtitle);
+		titleLabel.Text(hstring(wtitle));
+		free(wtitle);
+	}
+
+	window.SetTitleBar(titleBar);
+
+	obj->wobj = new UiWindow(window);
+	ui_context_add_window_destructor(obj->ctx, obj->wobj);
+
+	window.Closed([obj](IInspectable const& sender, WindowEventArgs) {
+		cxMempoolDestroy(obj->ctx->mp);
+		});
+
+	obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);
+
+	titleBar.VerticalAlignment(VerticalAlignment::Top);
+	obj->container->Add(titleBar, false);
+
 	obj->window = window_data;
 
 	return obj;
@@ -185,3 +193,99 @@
 		win->window.AppWindow().Resize(wsize);
 	}
 }
+
+static void filedialog_callback(
+	UiObject *obj,
+	ui_callback file_selected_callback,
+	void *cbdata,
+	winrt::Windows::Foundation::Collections::IVectorView<winrt::Windows::Storage::StorageFile> result)
+{
+	UiFileList flist;
+	flist.nfiles = result.Size();
+	flist.files = new char*[flist.nfiles];
+
+	int i = 0;
+	for (auto const& file : result) {
+		winrt::hstring path = file.Path();
+		flist.files[i++] = wchar2utf8(path.c_str(), path.size());
+	}
+
+	UiEvent evt;
+	evt.obj = obj;
+	evt.document = obj->ctx->document;
+	evt.window = obj->window;
+	evt.eventdata = &flist;
+	evt.intval = 0;
+	file_selected_callback(&evt, cbdata);
+
+	for (int i = 0; i < flist.nfiles;i++) {
+		free(flist.files[i]);
+	}
+	delete[] flist.files;
+}
+
+static Windows::Foundation::IAsyncAction open_filedialog_async(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+	FileOpenPicker openFileDialog = FileOpenPicker();
+	auto initializeWithWindow { openFileDialog.as<::IInitializeWithWindow>()
+	};
+
+	HWND hwnd{ nullptr };
+	winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));
+
+	initializeWithWindow->Initialize(hwnd);
+
+	openFileDialog.FileTypeFilter().Append(L"*");
+
+	if ((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {
+		auto files = co_await openFileDialog.PickMultipleFilesAsync();
+		filedialog_callback(obj, file_selected_callback, cbdata, files);
+	} else {
+		auto file = co_await openFileDialog.PickSingleFileAsync();
+		auto files = single_threaded_vector<winrt::Windows::Storage::StorageFile>();
+		files.Append(file);
+		filedialog_callback(obj, file_selected_callback, cbdata, files.GetView());
+	}
+}
+
+static Windows::Foundation::IAsyncAction folderdialog_async(UiObject *obj, ui_callback file_selected_callback, void *cbdata) {
+	FolderPicker folderPicker = FolderPicker();
+	auto initializeWithWindow { folderPicker.as<::IInitializeWithWindow>()
+	};
+
+	HWND hwnd{ nullptr };
+	winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));
+
+	initializeWithWindow->Initialize(hwnd);
+
+	folderPicker.FileTypeFilter().Append(L"*");
+
+	auto folder = co_await folderPicker.PickSingleFolderAsync();
+	if (folder) {
+		winrt::hstring hpath = folder.Path();
+		char *cpath =  wchar2utf8(hpath.c_str(), hpath.size());
+		
+		UiFileList flist;
+		flist.nfiles = 1;
+		flist.files = &cpath;
+
+		UiEvent evt;
+		evt.obj = obj;
+		evt.document = obj->ctx->document;
+		evt.window = obj->window;
+		evt.eventdata = &flist;
+		evt.intval = 0;
+		file_selected_callback(&evt, cbdata);
+	}
+}
+
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+	if ((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+		folderdialog_async(obj, file_selected_callback, cbdata);
+	} else {
+		open_filedialog_async(obj, mode, file_selected_callback, cbdata);
+	}
+}
+
+UIEXPORT void ui_savefiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+
+}

mercurial