UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2024 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "application.h" 30 31 #include <string.h> 32 #include <stdbool.h> 33 34 #include <libidav/webdav.h> 35 36 #include "window.h" 37 #include "config.h" 38 #include "davcontroller.h" 39 #include "settings.h" 40 #include "appsettings.h" 41 42 static DavContext* davctx; 43 44 static DavApp *app; 45 46 void application_init(void) { 47 davctx = dav_context_new(); 48 dav_context_set_mtsafe(davctx, true); 49 50 application_create_menu(); 51 } 52 53 DavApp* get_application(void) { 54 return app; 55 } 56 57 void application_startup(UiEvent* event, void* data) { 58 if (load_config(davctx)) { 59 // TODO: error 60 exit(-1); 61 } 62 63 appsettings_init(); 64 window_init(); 65 66 // create document for global settings (repolist, ...) 67 app = application_create_app_document(); 68 UiContext *global = ui_global_context(); 69 ui_attach_document(global, app); 70 71 // create new window 72 UiObject *win = window_create(); 73 // create new browser document and attach it to the main window 74 DavBrowser *doc = davbrowser_create(win); 75 ui_attach_document(win->ctx, doc); 76 77 ui_show(win); 78 } 79 80 static void* davrepo_getname(void *elm, int unused) { 81 DavCfgRepository *repo = elm; 82 return repo->name.value.ptr; 83 } 84 85 void application_create_menu(void) { 86 // menu 87 ui_menu("File") { 88 ui_menuitem(.label = "New Window", .onclick = action_window_new); 89 ui_menuseparator(); 90 ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 91 ui_menuitem(.label = "New File", .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 92 ui_menuitem(.label = "Delete", .onclick = action_delete, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 93 ui_menuseparator(); 94 ui_menuitem(.label = "Upload File", .onclick = action_upload_file, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 95 ui_menuitem(.label = "Upload Folder", .onclick = action_upload_dir, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 96 ui_menuitem(.label = "Download", .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 97 ui_menuitem("Open Properties", .onclick = action_open_properties, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 98 ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 99 ui_menuseparator(); 100 ui_menuitem(.label = "Close", .onclick = action_window_close); 101 } 102 103 ui_menu("Edit") { 104 ui_menuitem(.label = "Preferences", .onclick = action_open_settings); 105 } 106 107 ui_menu("Go") { 108 ui_menuitem(.label = "Parent", .onclick = action_go_parent, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 109 ui_menuitem(.label = "Back", .onclick = action_go_back, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 110 ui_menuitem(.label = "Forward", .onclick = action_go_forward, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 111 ui_menuseparator(); 112 ui_menu("Repositories") { 113 ui_menu_itemlist(.varname = "repolist", .getvalue = davrepo_getname, .onselect = action_repo_selected); 114 } 115 } 116 117 ui_menu("View") { 118 ui_menu_itemlist(.varname = "transferlist", .getvalue = transfers_getlabel, .onselect = action_transfer_selected); 119 } 120 121 // toolbar 122 ui_toolbar_item("Home", .icon = UI_ICON_HOME); 123 ui_toolbar_item("NewWindow", .icon = UI_ICON_NEW_WINDOW, .onclick = action_window_new); 124 ui_toolbar_item("Refresh", .icon = UI_ICON_REFRESH, .onclick = action_refresh, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 125 ui_toolbar_menu("Repo", .label = "Repository") { 126 ui_menu_itemlist(.varname = "repolist", .getvalue = davrepo_getname, .onselect = action_repo_selected); 127 } 128 ui_toolbar_item("NewFolder", .icon = UI_ICON_NEW_FOLDER, .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 129 ui_toolbar_item("NewFile", .icon = UI_ICON_ADD, .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 130 ui_toolbar_item("Upload", .label = "Upload", .icon = UI_ICON_UPLOAD, .onclick = action_upload_file, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 131 ui_toolbar_item("Download", .icon = UI_ICON_SAVE_LOCAL, .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 132 ui_toolbar_item("Remove", .icon = UI_ICON_DELETE, .onclick = action_delete, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 133 ui_toolbar_toggleitem("LocalBrowser", .icon = UI_ICON_DOCK_LEFT, .label = "Local Browser"); 134 ui_toolbar_toggleitem("PreviewPane", .icon = UI_ICON_DOCK_RIGHT); 135 136 ui_toolbar_appmenu() { 137 ui_menuitem("New Window", .onclick = action_window_new); 138 ui_menuseparator(); 139 ui_menuitem("Download", .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 140 ui_menuitem("Upload Files", .onclick = action_upload_file, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 141 ui_menuitem("Upload Directory", .onclick = action_upload_dir, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); 142 ui_menuitem("Open Properties", .onclick = action_open_properties, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 143 ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); 144 ui_menuseparator(); 145 ui_menu("Downloads/Uploads") { 146 ui_menu_itemlist(.varname = "transferlist", .getvalue = transfers_getlabel, .onselect = action_transfer_selected); 147 } 148 ui_menuseparator(); 149 ui_menuitem("Preferences", .onclick = action_open_settings); 150 } 151 152 //ui_toolbar_add_default("Home", UI_TOOLBAR_LEFT); 153 //ui_toolbar_add_default("NewWindow", UI_TOOLBAR_LEFT); 154 //ui_toolbar_add_default("Refresh", UI_TOOLBAR_LEFT); 155 ui_toolbar_add_default("Repo", UI_TOOLBAR_LEFT); 156 157 ui_toolbar_add_default("NewFolder", UI_TOOLBAR_CENTER); 158 ui_toolbar_add_default("NewFile", UI_TOOLBAR_CENTER); 159 ui_toolbar_add_default("Remove", UI_TOOLBAR_CENTER); 160 ui_toolbar_add_default("Download", UI_TOOLBAR_CENTER); 161 ui_toolbar_add_default("Upload", UI_TOOLBAR_CENTER); 162 163 //ui_toolbar_add_default("LocalBrowser", UI_TOOLBAR_RIGHT); 164 //ui_toolbar_add_default("PreviewPane", UI_TOOLBAR_RIGHT); 165 } 166 167 168 169 DavApp* application_create_app_document(void) { 170 DavApp *doc = ui_document_new(sizeof(DavApp)); 171 UiContext *ctx = ui_document_context(doc); 172 doc->repos = ui_list_new(ctx, "repolist"); 173 doc->transfers = ui_list_new(ctx, "transferlist"); 174 CxList *transfers = doc->transfers->data; 175 transfers->collection.cmpfunc = cx_cmp_ptr; 176 177 // create repo list 178 application_update_repolist(doc); 179 180 return doc; 181 } 182 183 void application_update_repolist(DavApp *app) { 184 DavConfig *config = get_config(); 185 186 ui_list_clear(app->repos); 187 188 for (DavCfgRepository *repo = config->repositories; repo; repo = repo->next) { 189 ui_list_append(app->repos, repo); 190 } 191 192 ui_notify(app->repos->observers, NULL); 193 } 194 195 196 DavContext* application_dav_context(void) { 197 return davctx; 198 } 199 200 void* transfers_getlabel(void *data, int col) { 201 DavTransfer *trans = data; 202 return trans->label; 203 } 204 205 void application_register_transfer(DavTransfer *trans) { 206 DavApp *app = get_application(); 207 ui_list_append(app->transfers, trans); 208 application_update_transferlist(); 209 } 210 211 void application_remove_transfer(DavTransfer *trans) { 212 DavApp *app = get_application(); 213 CxList *transfers = app->transfers->data; 214 cxListFindRemove(transfers, trans); 215 application_update_transferlist(); 216 } 217 218 /* 219 static int transfers_cmp(const DavTransfer *left, const DavTransfer *right) { 220 return cx_cmp_double(&right->progress, &left->progress); 221 } 222 */ 223 224 void application_update_transferlist(void) { 225 DavApp *app = get_application(); 226 227 /* 228 CxList *transfers = app->transfers->data; 229 transfers->collection.cmpfunc = (cx_compare_func)transfers_cmp; 230 cxListSort(transfers); 231 transfers->collection.cmpfunc = cx_cmp_ptr; 232 */ 233 234 ui_list_update(app->transfers); 235 ui_list_notify(app->transfers); 236 } 237 238 239 void action_window_new(UiEvent *event, void *data) { 240 UiObject *win = window_create(); 241 // create new browser document and attach it to the main window 242 DavBrowser *doc = davbrowser_create(win); 243 ui_attach_document(win->ctx, doc); 244 245 ui_show(win); 246 } 247 248 void action_window_close(UiEvent *event, void *data) { 249 ui_close(event->obj); 250 } 251 252 void action_refresh(UiEvent *event, void *data) { 253 DavBrowser *browser = event->document; 254 if (browser->current) { 255 davbrowser_query_path(event->obj, browser, browser->current->path); 256 } 257 } 258 259 void action_repo_selected(UiEvent *event, void *data) { 260 DavCfgRepository *repo = event->eventdata; 261 DavBrowser *browser = event->document; 262 davbrowser_connect2repo(event->obj, browser, repo, ""); 263 } 264 265 static void file_selected(UiEvent *event, void *data) { 266 UiFileList *files = event->eventdata; 267 if (files && files->nfiles > 0) { 268 // files will be freed by the filedialog handler, therefore we need a copy 269 UiFileList uploadList = ui_filelist_copy(*files); // uploadList will be freed by davbrowser_upload_files 270 davbrowser_upload_files(event->obj, event->document, uploadList); 271 } 272 273 } 274 275 276 277 static void download_location_selected(UiEvent *event, void *data) { 278 DavBrowser *browser = event->document; 279 DavResource *reslist = data; 280 UiFileList *flist = event->eventdata; 281 282 if (flist && flist->nfiles > 0) { 283 davbrowser_download(event->obj, browser, reslist, flist->files[0]); 284 } else { 285 dav_session_destroy(reslist->session); 286 } 287 } 288 289 void action_download(UiEvent *event, void *data) { 290 DavBrowser *browser = event->document; 291 UiListSelection sel = ui_list_getselection(browser->resources); 292 if (sel.count > 0) {; 293 const char *initialFileName = NULL; 294 if (sel.count == 1) { 295 DavResource *res = ui_list_get(browser->resources, sel.rows[0]); 296 if (res && !res->iscollection) { 297 initialFileName = res->name; 298 } 299 } 300 301 // create a copy of the current session and all selected resources 302 DavSession *sn = dav_session_clone(browser->sn); 303 DavResource *first = NULL; 304 DavResource *last = NULL; 305 for (int i = 0; i < sel.count; i++) { 306 // get selected resource 307 DavResource *res = ui_list_get(browser->resources, sel.rows[i]); 308 // copy resource 309 DavResource *res_copy = dav_resource_new(sn, res->path); 310 res_copy->iscollection = res->iscollection; 311 res_copy->contentlength = res->contentlength; 312 res_copy->lastmodified = res->lastmodified; 313 res_copy->creationdate = res->creationdate; 314 315 // link resources 316 if (!first) { 317 first = res_copy; 318 } 319 if (last) { 320 res_copy->prev = last; 321 last->next = res_copy; 322 } 323 last = res_copy; 324 } 325 326 if (initialFileName) { 327 ui_savefiledialog(event->obj, initialFileName, download_location_selected, first); 328 } else { 329 ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_FOLDER, download_location_selected, first); 330 } 331 332 ui_listselection_free(sel); 333 } 334 } 335 336 void action_upload_file(UiEvent *event, void *data) { 337 DavBrowser *browser = event->document; 338 if(!browser->sn) { 339 return; 340 } 341 ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_MULTI, file_selected, NULL); 342 } 343 344 void action_upload_dir(UiEvent *event, void *data) { 345 DavBrowser *browser = event->document; 346 if(!browser->sn) { 347 return; 348 } 349 ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_FOLDER|UI_FILEDIALOG_SELECT_MULTI, file_selected, NULL); 350 } 351 352 void action_delete(UiEvent *event, void *data) { 353 DavBrowser *browser = event->document; 354 UiListSelection sel = ui_list_getselection(browser->resources); 355 if (sel.count > 0) { 356 davbrowser_delete(event->obj, browser, sel); 357 } 358 ui_listselection_free(sel); 359 } 360 361 void action_selectall(UiEvent *event, void *data) { 362 DavBrowser *browser = event->document; 363 int nres = ui_list_count(browser->resources); 364 for(int i=0;i<nres;i++) { 365 ui_list_setselection(browser->resources, i); 366 } 367 } 368 369 void action_rename(UiEvent *event, void *data) { 370 DavBrowser *browser = event->document; 371 UiListSelection sel = ui_list_getselection(browser->resources); 372 if (sel.count == 1) { 373 davbrowser_rename(event->obj, browser, sel); 374 } else if(sel.count > 1) { 375 fprintf(stderr, "Renaming multiple resources is not implemented yet\n"); 376 } 377 } 378 379 static void newfiledialog_result(UiEvent *event, void *data) { 380 DavBrowser *browser = event->document; 381 char *path = event->eventdata; 382 if (event->intval == 1) { 383 davbrowser_newfile(event->obj, browser, path); 384 } 385 } 386 387 void action_newfile(UiEvent *event, void *data) { 388 DavBrowser *browser = event->document; 389 if(!browser->sn) { 390 return; 391 } 392 393 ui_dialog(event->obj, 394 .content = "New File", 395 .input = TRUE, 396 .button1_label = "Create File", 397 .button2_label = "Cancel", 398 .result = newfiledialog_result); 399 } 400 401 402 static void newfolderdialog_result(UiEvent *event, void *data) { 403 DavBrowser *browser = event->document; 404 char *path = event->eventdata; 405 if (event->intval == 1) { 406 davbrowser_mkcol(event->obj, browser, path); 407 } 408 } 409 410 void action_mkcol(UiEvent *event, void *data) { 411 DavBrowser *browser = event->document; 412 if(!browser->sn) { 413 return; 414 } 415 416 417 ui_dialog(event->obj, 418 .content = "New Folder", 419 .input = TRUE, 420 .button1_label = "Create Folder", 421 .button2_label = "Cancel", 422 .result = newfolderdialog_result); 423 } 424 425 void action_open_settings(UiEvent *event, void *data) { 426 settings_window_open(); 427 } 428 429 void action_open_properties(UiEvent *event, void *data) { 430 char *type = data; 431 DavBrowser *browser = event->document; 432 433 UiListSelection sel = ui_list_getselection(browser->resources); 434 for(int i=0;i<sel.count;i++) { 435 DavResource *res = ui_list_get(browser->resources, sel.rows[i]); 436 if(res) { 437 davbrowser_open_resource(event->obj, browser, res, type); 438 } 439 } 440 ui_listselection_free(sel); 441 } 442 443 void action_transfer_selected(UiEvent *event, void *data) { 444 DavApp *app = get_application(); 445 DavTransfer *trans = ui_list_get(app->transfers, event->intval); 446 if(trans) { 447 ui_show(trans->window); 448 } 449 } 450