From daca0fb6cdf0d70411ec53cf2e1464abb62856b2 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Sat, 22 Feb 2025 17:44:59 +0100 Subject: [PATCH] add NotebookModel + async note loader --- application/Makefile | 1 + application/application.h | 52 +++++++++++++++++++++++- application/menu.c | 40 +++++++++++++++++++ application/menu.h | 49 +++++++++++++++++++++++ application/notebook.c | 81 ++++++++++++++++++++++++++++++++++++++ application/notebook.h | 55 ++++++++++++++++++++++++++ application/store.c | 52 +++++++++++++++++++++++- application/store.h | 10 +++++ application/store_sqlite.c | 9 ++--- application/types.c | 25 ++++++------ application/types.h | 10 ++--- application/window.c | 49 ++++++++++++++++++++++- application/window.h | 4 ++ 13 files changed, 410 insertions(+), 27 deletions(-) create mode 100644 application/menu.c create mode 100644 application/menu.h create mode 100644 application/notebook.c create mode 100644 application/notebook.h diff --git a/application/Makefile b/application/Makefile index bd8a595..2df7deb 100644 --- a/application/Makefile +++ b/application/Makefile @@ -38,6 +38,7 @@ SRC += window.c SRC += types.c SRC += store.c SRC += store_sqlite.c +SRC += notebook.c OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT)) diff --git a/application/application.h b/application/application.h index 5c711c1..e2889a8 100644 --- a/application/application.h +++ b/application/application.h @@ -30,6 +30,9 @@ #define APPLICATION_H #include +#include "types.h" + +#include #ifdef __cplusplus extern "C" { @@ -38,20 +41,67 @@ extern "C" { #define APP_STATE_NOTE_SELECTED 100 +typedef struct NotebookModel NotebookModel; + typedef struct MainWindow { + UiObject *obj; + UiList *notebooks; + NotebookModel *current_notebook; + + /* + * key: collection_id + * value: NotebookModel* + */ + CxMap *notebook_cache; UiList *test1; UiList *test2; } MainWindow; + +struct NotebookModel { + /* + * Document context + */ + UiContext *ctx; + + /* + * The window, this notebook model is attached to. + */ + MainWindow *window; + + /* + * Pointer to NoteStore Collection + * + * After a NoteStore refresh, this pointer is invalid and must be renewed. + */ + Collection *collection; + + /* + * same as collection->collection_id, however Collection* may be an invalid + * pointer, therefore the collection_id is required separately. + */ + int64_t collection_id; + + /* + * list of Note* + * + * name: notes + */ + UiList *notes; + + /* + * The mempool used to allocate the current list of notes + */ + CxMempool *current_notes_pool; +}; void application_init(); void application_startup(UiEvent *event, void *data); - void action_note_new(UiEvent *event, void *data); diff --git a/application/menu.c b/application/menu.c new file mode 100644 index 0000000..760cdc8 --- /dev/null +++ b/application/menu.c @@ -0,0 +1,40 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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 "menu.h" + +void menu_init() { + +} + +void toolbar_init() { + ui_toolbar_item("AddNote", .icon = UI_ICON_ADD, .onclick = action_note_new); + + + ui_toolbar_add_default("AddNote", UI_TOOLBAR_LEFT); +} diff --git a/application/menu.h b/application/menu.h new file mode 100644 index 0000000..66b7e57 --- /dev/null +++ b/application/menu.h @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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. + */ + +#ifndef MENU_H +#define MENU_H + +#include + +#include "application.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void menu_init(); +void toolbar_init(); + + +#ifdef __cplusplus +} +#endif + +#endif /* MENU_H */ + diff --git a/application/notebook.c b/application/notebook.c new file mode 100644 index 0000000..8f3fa96 --- /dev/null +++ b/application/notebook.c @@ -0,0 +1,81 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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 "notebook.h" + +#include "store.h" + +NotebookModel* notebookmodel_create() { + NotebookModel *model = ui_document_new(sizeof(NotebookModel)); + model->ctx = ui_document_context(model); + + model->notes = ui_list_new(model->ctx, "notes"); + + + return model; +} + +void notebookmodel_attach(MainWindow *window, NotebookModel *model) { + if(window->current_notebook) { + notebookmodel_detach(window->current_notebook); + } + ui_attach_document(window->obj->ctx, model); + window->current_notebook = model; + model->window = window; +} + +void notebookmodel_detach(NotebookModel *model) { + if(model->window) { + ui_detach_document2(model->window->obj->ctx, model); + model->window->current_notebook = NULL; + } +} + +void notebookmodel_set_collection(NotebookModel *model, Collection *collection) { + model->collection = collection; + model->collection_id = collection->collection_id; +} + + +static void notebook_loaded(UiEvent *event, AsyncListResult *result, void *data) { + NotebookModel *model = data; + if(result->list) { + ui_list_clear(model->notes); + CxIterator i = cxListIterator(result->list); + cx_foreach(Note *, note, i) { + ui_list_append(model->notes, note); + } + ui_list_update(model->notes); + } else { + fprintf(stderr, "Error: get notes failed\n"); + } +} + +void notebookmodel_reload(UiObject *obj, NotebookModel *model) { + note_store_get_notes_async(obj, model->collection_id, notebook_loaded, model); +} diff --git a/application/notebook.h b/application/notebook.h new file mode 100644 index 0000000..4d9a5d4 --- /dev/null +++ b/application/notebook.h @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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. + */ + +#ifndef NOTEBOOK_H +#define NOTEBOOK_H + +#include "application.h" +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +NotebookModel* notebookmodel_create(); + +void notebookmodel_attach(MainWindow *window, NotebookModel *model); + +void notebookmodel_detach(NotebookModel *model); + +void notebookmodel_set_collection(NotebookModel *model, Collection *collection); + +void notebookmodel_reload(UiObject *obj, NotebookModel *model); + + +#ifdef __cplusplus +} +#endif + +#endif /* NOTEBOOK_H */ + diff --git a/application/store.c b/application/store.c index 7405480..cc3f59a 100644 --- a/application/store.c +++ b/application/store.c @@ -55,10 +55,12 @@ "select * from cols\n" \ "order by path;" - +#define SQL_NOTEBOOK_GET_NOTES "select note_id, parent_id, name, title, lastmodified, creationdate, contenttype, created_by from notes where parent_id = ? ;" static DBUConnection *connection; +static UiThreadpool *queue; + static char *settings_host; static char *settings_user; static char *settings_profile_name; @@ -100,6 +102,8 @@ int init_note_store() { } } + queue = ui_threadpool_create(1); + return 0; } @@ -337,3 +341,49 @@ void note_store_reload() { NoteStore* note_store_get() { return current_store; } + + +CxList* note_store_get_notes(const CxAllocator *a, int64_t parent_collection_id) { + DBUQuery *q = connection->createQuery(connection, NULL); + dbuQuerySetSQL(q, SQL_NOTEBOOK_GET_NOTES); + dbuQuerySetParamInt64(q, 1, parent_collection_id); + DBUObjectBuilder *builder = dbuObjectBuilder(notes_class, q, a); + CxList *notes = dbuObjectBuilderGetList(builder); + dbuObjectBuilderDestroy(builder); + return notes; +} + +typedef struct JobGetNotes { + int64_t collection_id; + listresult_func resultcb; + void *userdata; + AsyncListResult result; +} JobGetNotes; + +static void uithr_get_notes_finished(UiEvent *event, JobGetNotes *job) { + if(job->resultcb) { + job->resultcb(event, &job->result, job->userdata); + } else { + cxMempoolFree(job->result.mp); + } + free(job); +} + +static int qthr_get_notes(JobGetNotes *job) { + job->result.list = note_store_get_notes(job->result.mp->allocator, job->collection_id); + if(!job->result.list) { + cxMempoolFree(job->result.mp); + job->result.mp = NULL; + } + return 0; +} + +void note_store_get_notes_async(UiObject* obj, int64_t parent_collection_id, listresult_func resultcb, void *userdata) { + JobGetNotes *job = malloc(sizeof(JobGetNotes)); + job->result.mp = cxMempoolCreate(128, NULL); + job->result.list = NULL; + job->collection_id = parent_collection_id; + job->resultcb = resultcb; + job->userdata = userdata; + ui_threadpool_job(queue, obj, (ui_threadfunc)qthr_get_notes, job, (ui_callback)uithr_get_notes_finished, job); +} diff --git a/application/store.h b/application/store.h index a556989..5974f04 100644 --- a/application/store.h +++ b/application/store.h @@ -45,6 +45,13 @@ typedef struct NoteStore { Collection *root; } NoteStore; +typedef struct AsyncListResult { + CxMempool *mp; + CxList *list; +} AsyncListResult; + +typedef void (*listresult_func)(UiEvent *event, AsyncListResult *result, void *userdata); + int init_note_store(); CxList* note_store_get_user_settings(const CxAllocator *a, const char *host, const char *user, const char *profile); @@ -70,6 +77,9 @@ void note_store_reload(); NoteStore* note_store_get(); +CxList* note_store_get_notes(const CxAllocator *a, int64_t parent_collection_id); +void note_store_get_notes_async(UiObject* obj, int64_t parent_collection_id, listresult_func resultcb, void *userdata); + #ifdef __cplusplus } diff --git a/application/store_sqlite.c b/application/store_sqlite.c index 9676c91..0557bc4 100644 --- a/application/store_sqlite.c +++ b/application/store_sqlite.c @@ -67,17 +67,16 @@ "foreign key (default_collection_id) references collections(collection_id), " \ "unique (host, user, profile_name)" \ ");" -#define SQL_CREATE_TABLE_RESOURCE "create table resources( " \ - "resource_id integer primary key, " \ +#define SQL_CREATE_TABLE_NOTES "create table notes( " \ + "note_id integer primary key, " \ "parent_id integer, " \ "name text, " \ "title text, " \ "lastmodified text, " \ "creationdate text, " \ - "contentlength integer, " \ + "contenttype text, " \ "content text, " \ "bin_content blob, " \ - "note_item integer, " \ "created_by text, " \ "foreign key (parent_id) references collections(collection_id), " \ "unique (parent_id, name) " \ @@ -89,7 +88,7 @@ int store_sqlite_init_db(DBUConnection *connection) { SQL_CREATE_TABLE_REPOSITORIES, SQL_CREATE_TABLE_COLLECTIONS, SQL_CREATE_TABLE_USER_SETTINGS, - SQL_CREATE_TABLE_RESOURCE + SQL_CREATE_TABLE_NOTES }; int nsql = sizeof(sql) / sizeof(char*); diff --git a/application/types.c b/application/types.c index 2fc3a84..3d3cd86 100644 --- a/application/types.c +++ b/application/types.c @@ -33,7 +33,7 @@ static DBUContext *ctx; DBUClass *usersettings_class; DBUClass *repository_class; DBUClass *collection_class; -DBUClass *resource_class; +DBUClass *notes_class; DBUContext* get_dbu_context() { return ctx; @@ -57,16 +57,15 @@ void register_types() { dbuClassAdd(collection_class, Collection, display_name); dbuClassAdd(collection_class, Collection, type); - resource_class = dbuRegisterClass(ctx, "resources", Resource, resource_id); - dbuClassAdd(resource_class, Resource, resource_id); - dbuClassAdd(resource_class, Resource, parent_id); - dbuClassAdd(resource_class, Resource, name); - dbuClassAdd(resource_class, Resource, title); - dbuClassAdd(resource_class, Resource, lastmodified); - dbuClassAdd(resource_class, Resource, creationdate); - dbuClassAdd(resource_class, Resource, contentlength); - dbuClassAdd(resource_class, Resource, content); - dbuClassAdd(resource_class, Resource, bin_content); - dbuClassAdd(resource_class, Resource, note_item); - dbuClassAdd(resource_class, Resource, created_by); + notes_class = dbuRegisterClass(ctx, "notes", Note, note_id); + dbuClassAdd(notes_class, Note, parent_id); + dbuClassAdd(notes_class, Note, name); + dbuClassAdd(notes_class, Note, title); + dbuClassAdd(notes_class, Note, lastmodified); + dbuClassAdd(notes_class, Note, creationdate); + dbuClassAdd(notes_class, Note, contenttype); + dbuClassAdd(notes_class, Note, contentlength); + dbuClassAdd(notes_class, Note, content); + dbuClassAdd(notes_class, Note, bin_content); + dbuClassAdd(notes_class, Note, created_by); } \ No newline at end of file diff --git a/application/types.h b/application/types.h index 41d08e2..48e064a 100644 --- a/application/types.h +++ b/application/types.h @@ -39,7 +39,7 @@ extern "C" { typedef struct UserSettings UserSettings; typedef struct Repository Repository; typedef struct Collection Collection; -typedef struct Resource Resource; +typedef struct Note Note; struct UserSettings { char *host; @@ -72,24 +72,24 @@ struct Collection { CxList *children; }; -struct Resource { - int64_t resource_id; +struct Note { + int64_t note_id; int64_t parent_id; char *name; char *title; char *lastmodified; char *creationdate; + char *contenttype; uint64_t contentlength; cxmutstr content; cxmutstr bin_content; - bool note_item; char *created_by; }; extern DBUClass *usersettings_class; extern DBUClass *repository_class; extern DBUClass *collection_class; -extern DBUClass *resource_class; +extern DBUClass *notes_class; void register_types(); diff --git a/application/window.c b/application/window.c index 9df9370..8b1bdc5 100644 --- a/application/window.c +++ b/application/window.c @@ -29,8 +29,10 @@ #include "window.h" #include "application.h" #include "store.h" +#include "notebook.h" #include +#include void window_create() { @@ -44,7 +46,7 @@ void window_create() { }; */ ui_sidebar(obj) { - ui_sourcelist(obj, .dynamic_sublist = wdata->notebooks, .getvalue = window_sidebar_getvalue, .fill = UI_ON); + ui_sourcelist(obj, .dynamic_sublist = wdata->notebooks, .getvalue = window_sidebar_getvalue, .onactivate = action_notebook_selected, .fill = UI_ON); ui_hbox(obj, .spacing = 2, .fill = UI_OFF) { ui_button(obj, .icon = "folder-new-symbolic", .style_class = "flat"); } @@ -54,7 +56,8 @@ void window_create() { // splitpane left: table UiModel* model = ui_model(obj->ctx, UI_STRING, "Name", UI_STRING_FREE, "Last Modified", -1); model->columnsize[0] = -1; - ui_table(obj, .model = model, .varname = "notes"); + model->getvalue = window_notelist_getvalue; + ui_table(obj, .model = model, .varname = "notes",); // splitpane right: content ui_grid(obj, .columnspacing = 10, .rowspacing = 10, .def_vfill = TRUE) { @@ -72,10 +75,13 @@ void window_create() { MainWindow* window_init_data(UiObject *obj) { MainWindow *wdata = ui_calloc(obj->ctx, 1, sizeof(MainWindow)); obj->window = wdata; + wdata->obj = obj; wdata->notebooks = ui_list_new(obj->ctx, NULL); update_sublists(obj->ctx, wdata->notebooks); + wdata->notebook_cache = cxHashMapCreateSimple(CX_STORE_POINTERS); + return wdata; } @@ -145,3 +151,42 @@ void update_sublists(UiContext *ctx, UiList *sublists) { delete_list->collection.destructor_data = ctx; cxListFree(delete_list); } + +void* window_notelist_getvalue(void *data, int col) { + Note *note = data; + switch(col) { + case 0: { + return note->title ? note->title : note->name; + } + case 1: { + return note->lastmodified ? strdup(note->lastmodified) : NULL; + } + } + + return NULL; +} + +void action_notebook_selected(UiEvent *event, void *userdata) { + UiSubListEventData *data = event->eventdata; + MainWindow *window = event->window; + Collection *collection = data->row_data; + + printf("notebook selected: %s\n", collection->name); + + if(window->current_notebook && window->current_notebook->collection == collection) { + return; // notebook already selected + } + + CxHashKey key = cx_hash_key(&collection->collection_id, sizeof(collection->collection_id)); + NotebookModel *notebook = cxMapGet(window->notebook_cache, key); + if(!notebook) { + printf("notebook not in cache\n"); + notebook = notebookmodel_create(); + notebookmodel_set_collection(notebook, collection); + notebookmodel_reload(event->obj, notebook); + cxMapPut(window->notebook_cache, key, notebook); + } else { + printf("notebook in cache\n"); + } + notebookmodel_attach(window, notebook); +} diff --git a/application/window.h b/application/window.h index 9373298..cd6ae98 100644 --- a/application/window.h +++ b/application/window.h @@ -44,6 +44,10 @@ void window_sidebar_getvalue(void *sublist_userdata, void *rowdata, int index, U void update_sublists(UiContext *ctx, UiList *sublists); +void* window_notelist_getvalue(void *data, int col); + +void action_notebook_selected(UiEvent *event, void *userdata); + #ifdef __cplusplus } -- 2.43.5