From: Olaf Wintermann Date: Thu, 1 May 2025 14:01:36 +0000 (+0200) Subject: remove collection type/table X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=HEAD;p=note.git remove collection type/table --- diff --git a/application/application.h b/application/application.h index 345d749..a70fab8 100644 --- a/application/application.h +++ b/application/application.h @@ -48,8 +48,8 @@ extern "C" { // typedefs for NotebookModel and NoteModel are in types.h typedef struct NavStack { - int64_t collection_id; - int64_t note_id; + int64_t collection_resource_id; + int64_t resource_id; uint64_t view_flags; } NavStack; @@ -74,7 +74,7 @@ typedef struct MainWindow { NotebookModel *current_notebook; /* - * key: collection_id + * key: resource_id * value: NotebookModel* */ CxMap *notebook_cache; @@ -99,13 +99,13 @@ struct NotebookModel { * * After a NoteStore refresh, this pointer is invalid and must be renewed. */ - Collection *collection; + Resource *collection; /* - * same as collection->collection_id, however Collection* may be an invalid - * pointer, therefore the collection_id is required separately. + * same as collection->resource_id, however Resource* may be an invalid + * pointer, therefore the resource_id is required separately. */ - int64_t collection_id; + int64_t resource_id; /* * list of Note* diff --git a/application/attachment.c b/application/attachment.c index 2fb2725..be271fd 100644 --- a/application/attachment.c +++ b/application/attachment.c @@ -92,7 +92,7 @@ int attachment_set_image_from_data(Attachment *attachment, cxmutstr data) { } void attachment_set_data(Attachment *attachment, cxmutstr data) { - attachment->bin_content = data; + attachment->content = data; } static void attachment_save_result(UiEvent *event, int error, void *userdata) { @@ -116,9 +116,9 @@ void attachment_save(UiObject *obj, Attachment *attachment, bool cleanup_content note_store_save_attachment_async(obj, attachment, attachment_save_result, attachment); attachment->save_in_progress = FALSE; if(cleanup_content) { - cxFree(attachment->ui->note_allocator, attachment->bin_content.ptr); - attachment->bin_content.ptr = NULL; - attachment->bin_content.length = 0; + cxFree(attachment->ui->note_allocator, attachment->content.ptr); + attachment->content.ptr = NULL; + attachment->content.length = 0; } } } diff --git a/application/note.c b/application/note.c index 6ba7d3c..c1d1bf4 100644 --- a/application/note.c +++ b/application/note.c @@ -90,7 +90,7 @@ NoteModel* notemodel_create(const CxAllocator *note_allocator) { void notemodel_set_note(NoteModel *model, Resource *note) { note->model = model; - ui_set(model->title, note->title); + ui_set(model->title, note->displayname); if(note->content_loaded) { // TODO: when multiple note types are implemented, check contenttype @@ -117,7 +117,7 @@ static void note_loading_completed(UiObject *obj, LoadNoteContent *op) { CxIterator i = cxListIterator(note->attachments); cx_foreach(Attachment *, attachment, i) { ui_list_append(note->model->attachments, attachment); - if(attachment_set_image_from_data(attachment, attachment->bin_content)) { + if(attachment_set_image_from_data(attachment, attachment->content)) { fprintf(stderr, "Error: cannot open attachment image data\n"); } } @@ -150,7 +150,7 @@ static void note_attachments_loaded(UiEvent *event, int error, void *userdata) { CxIterator i = cxListIterator(op->note->attachments); cx_foreach(Attachment *, attachment, i) { attachment_create_ui_model(op->note->model->ctx, attachment, op->note); - if(attachment->bin_content.length > 0) { + if(attachment->content.length > 0) { printf("attachment content loaded\n"); attachment->saved = TRUE; } // else: TODO: is this an error? @@ -185,11 +185,11 @@ void note_save(UiObject *obj, NotebookModel *notebook, Resource *note) { char *title = ui_get(m->title); const CxAllocator *a = notebook->current_notes_pool->allocator; - cxFree(a, note->title); - note->title = cx_strdup_a(a, cx_str(title)).ptr; + cxFree(a, note->displayname); + note->displayname = cx_strdup_a(a, cx_str(title)).ptr; - cxFree(a, note->name); - note->name = cx_strdup_a(a, cx_str(title)).ptr; + cxFree(a, note->nodename); + note->nodename = cx_strdup_a(a, cx_str(title)).ptr; cxmutstr content = editor_get_markdown(m->text, a); cxFree(a, note->content.ptr); @@ -271,25 +271,21 @@ void note_update_title(NotebookModel *notebook, Resource *note) { m->modified = TRUE; char *title = ui_get(m->title); - cxFree(m->note_allocator, note->title); - note->title = cx_strdup_a(m->note_allocator, cx_str(title)).ptr; + cxFree(m->note_allocator, note->displayname); + note->displayname = cx_strdup_a(m->note_allocator, cx_str(title)).ptr; notebook->notes->update(notebook->notes, index); } const char* note_get_title(Resource *note) { - return note->title ? note->title : note->name; + return note->displayname ? note->displayname : note->nodename; } void note_destroy(const CxAllocator *a, Resource *note) { - cxFree(a, note->name); - cxFree(a, note->title); - cxFree(a, note->lastmodified); - cxFree(a, note->creationdate); + cxFree(a, note->nodename); + cxFree(a, note->displayname); cxFree(a, note->contenttype); - cxFree(a, note->created_by); cxFree(a, note->content.ptr); - cxFree(a, note->bin_content.ptr); if(note->model) { // TODO: destroy model->context diff --git a/application/notebook.c b/application/notebook.c index a3f47e9..14140da 100644 --- a/application/notebook.c +++ b/application/notebook.c @@ -89,9 +89,9 @@ void notebookmodel_detach(NotebookModel *model) { } } -void notebookmodel_set_collection(NotebookModel *model, Collection *collection) { +void notebookmodel_set_collection(NotebookModel *model, Resource *collection) { model->collection = collection; - model->collection_id = collection->collection_id; + model->resource_id = collection->resource_id; } @@ -111,7 +111,7 @@ static void notebook_loaded(UiEvent *event, AsyncListResult *result, void *data) } void notebookmodel_reload(UiObject *obj, NotebookModel *model) { - note_store_get_notes_async(obj, model->collection_id, notebook_loaded, model); + note_store_get_notes_async(obj, model->resource_id, notebook_loaded, model); } @@ -200,7 +200,7 @@ void notebookmodel_new_note(NotebookModel *model) { Resource *new_note = cxMalloc(model->current_notes_pool->allocator, sizeof(Resource)); memset(new_note, 0, sizeof(Resource)); - new_note->parent_id = model->collection->collection_id; + new_note->parent_id = model->collection->resource_id; notebookmodel_attach_note(model, new_note); new_note->model->modified = TRUE; // initialize note content @@ -251,8 +251,8 @@ void notebookmodel_add2navstack(NotebookModel *model) { // init NavStack element NavStack nav; - nav.collection_id = model->collection_id; - nav.note_id = model->current_note ? model->current_note->resource_id : 0; + nav.collection_resource_id = model->resource_id; + nav.resource_id = model->current_note ? model->current_note->resource_id : 0; nav.view_flags = 0; if(!list_visible) { diff --git a/application/notebook.h b/application/notebook.h index 205bd2f..e031863 100644 --- a/application/notebook.h +++ b/application/notebook.h @@ -42,7 +42,7 @@ void notebookmodel_attach(MainWindow *window, NotebookModel *model); void notebookmodel_detach(NotebookModel *model); -void notebookmodel_set_collection(NotebookModel *model, Collection *collection); +void notebookmodel_set_collection(NotebookModel *model, Resource *collection); void notebookmodel_reload(UiObject *obj, NotebookModel *model); diff --git a/application/store.c b/application/store.c index 0c55f2a..ad88c97 100644 --- a/application/store.c +++ b/application/store.c @@ -47,35 +47,36 @@ #include "cx/mempool.h" #define SQL_NOTEBOOK_STRUCTURE "with recursive cols as (\n" \ - " select 1 as depth, '' as path, * from collections where collection_id = ?\n" \ + " select 1 as depth, '' as path, * from resources where resource_id = ?\n" \ " union all\n" \ - " select p.depth+1 as depth, concat(p.path, '/', c.name), c.* from collections c\n" \ - " inner join cols p on c.parent_id = p.collection_id\n" \ + " select p.depth+1 as depth, concat(p.path, '/', c.nodename), c.* from resources c\n" \ + " inner join cols p on c.parent_id = p.resource_id and c.iscollection = 1 \n" \ " where p.depth < 3\n" \ ")\n" \ "select * from cols\n" \ "order by path;" -#define SQL_NOTEBOOK_GET_NOTES "select resource_id, parent_id, name, title, lastmodified, creationdate, contenttype, created_by from resources where parent_id = ? and bin_content is NULL;" +#define SQL_NOTEBOOK_GET_NOTES "select resource_id, parent_id, nodename, displayname, lastmodified, creationdate, iscollection, contenttype from resources where parent_id = ? ;" -#define SQL_NOTE_GET_CONTENT "select content, bin_content from resources where resource_id = ? ;" +#define SQL_NOTE_GET_CONTENT "select content from resources where resource_id = ? ;" -#define SQL_NOTE_NEW "insert into resources(parent_id, name, title, lastmodified, creationdate, content) values (?, ?, ?, datetime(), datetime(), ?) returning resource_id;" +#define SQL_NOTE_NEW "insert into resources(parent_id, nodename, displayname, lastmodified, creationdate, content) values (?, ?, ?, unixepoch(), unixepoch(), ?) returning resource_id;" -#define SQL_NOTE_SAVE "update resources set name = ? ," \ - "title = ? ," \ - "content = ? " \ +#define SQL_NOTE_SAVE "update resources set nodename = ? ," \ + "displayname = ? ," \ + "content = ?, " \ + "lastmodified = unixepoch() " \ "where resource_id = ? ;" #define SQL_NOTE_MOVE_TO_TRASH "update resources set parent_id = ? where resource_id = ? ;" #define SQL_NOTE_DELETE "delete from resources where resource_id = ? ;" -#define SQL_ATTACHMENT_RESOURCE_NEW "insert into resources(parent_id, name, lastmodified, creationdate, bin_content) values ((select parent_id from resources where resource_id = ?), ?, datetime(), datetime(), ?) returning resource_id;" +#define SQL_ATTACHMENT_RESOURCE_NEW "insert into resources(parent_id, nodename, lastmodified, creationdate, content) values ((select parent_id from resources where resource_id = ?), ?, unixepoch(), unixepoch(), ?) returning resource_id;" #define SQL_ATTACHMENT_NEW "insert into attachments(attachment_resource_id, parent_resource_id, type) values (?, ?, ?) returning attachment_id;" #define SQL_ATTACHMENTS_GET "select attachment_id, attachment_resource_id, parent_resource_id, a.type, "\ - "r.name, r.bin_content from attachments a "\ + "r.name, r.content from attachments a "\ "inner join resources r on a.attachment_resource_id = r.resource_id " \ "where parent_resource_id = ? order by attachment_id;" @@ -138,19 +139,19 @@ CxList* note_store_get_user_settings(const CxAllocator *a, const char *host, con DBUQuery *query = connection->createQuery(connection, NULL); dbuQuerySetSQL(query, "select * from user_settings;"); if(host) { - dbuQuerySetParamString(query, 0, cx_str(host)); + dbuQuerySetParamString(query, 1, cx_str(host)); } else { - dbuQuerySetParamNull(query, 0); + dbuQuerySetParamNull(query, 1); } if(user) { - dbuQuerySetParamString(query, 1, cx_str(user)); + dbuQuerySetParamString(query, 2, cx_str(user)); } else { - dbuQuerySetParamNull(query, 1); + dbuQuerySetParamNull(query, 2); } if(profile) { - dbuQuerySetParamString(query, 2, cx_str(profile)); + dbuQuerySetParamString(query, 3, cx_str(profile)); } else { - dbuQuerySetParamNull(query, 2); + dbuQuerySetParamNull(query, 3); } DBUObjectBuilder *builder = dbuObjectBuilder(usersettings_class, query, a); @@ -220,7 +221,7 @@ int note_store_create_default(const char *host, const char *user) { const char *profile_name = "default"; const char *sql1 = "insert into repositories(name, local_path) values ('default', '$documents/notes') returning repository_id;"; - const char *sql2 = "insert into collections(repository_id, name) values (?, ?) returning collection_id;"; + const char *sql2 = "insert into resources(nodename, iscollection) values (?, 1) returning resource_id;"; const char *sql3 = "insert into user_settings(host, user, profile_name, default_repository_id, default_collection_id) values (?, ?, ?, ?, ?)"; int err = 1; @@ -280,18 +281,16 @@ int note_store_create_default(const char *host, const char *user) { err = 1; DBUQuery *q = connection->createQuery(connection, NULL); - dbuQuerySetSQL(q, "insert into collections(parent_id, repository_id, name) values (?, ?, 'Notebooks') returning collection_id;"); + dbuQuerySetSQL(q, "insert into resources(parent_id, nodename, iscollection) values (?, 'Notebooks', 1) returning resource_id;"); dbuQuerySetParamInt64(q, 1, collection_id); - dbuQuerySetParamInt64(q, 2, repo_id); if(!q->exec(q)) { err = dbuResultAsValue(q->getResult(q), ¬ebooks_id); } dbuQueryFree(q); q = connection->createQuery(connection, NULL); - dbuQuerySetSQL(q, "insert into collections(parent_id, repository_id, name) values (?, ?, 'My Notes') returning collection_id;"); + dbuQuerySetSQL(q, "insert into resources(parent_id, nodename, iscollection) values (?, 'My Notes', 1) returning resource_id;"); dbuQuerySetParamInt64(q, 1, notebooks_id); - dbuQuerySetParamInt64(q, 2, repo_id); if(!q->exec(q)) { err = dbuResultAsValue(q->getResult(q), &mynotes_id); } @@ -312,7 +311,7 @@ void note_store_reload() { DBUQuery *q = connection->createQuery(connection, NULL); dbuQuerySetSQL(q, SQL_NOTEBOOK_STRUCTURE); dbuQuerySetParamInt64(q, 1, settings_default_node_id); - DBUObjectBuilder *builder = dbuObjectBuilder(collection_class, q, mp->allocator); + DBUObjectBuilder *builder = dbuObjectBuilder(resource_class, q, mp->allocator); CxList *collections = dbuObjectBuilderGetList(builder); dbuObjectBuilderDestroy(builder); @@ -333,8 +332,8 @@ void note_store_reload() { // convert result list to tree CxIterator i = cxListIterator(collections); - cx_foreach(Collection *, col, i) { - CxHashKey key = cx_hash_key(&col->collection_id, sizeof(int64_t)); + cx_foreach(Resource *, col, i) { + CxHashKey key = cx_hash_key(&col->resource_id, sizeof(int64_t)); cxMapPut(collection_map, key, col); if(i.index == 0) { store->root = col; @@ -342,7 +341,7 @@ void note_store_reload() { } if(col->parent_id != 0) { CxHashKey parent_key = cx_hash_key(&col->parent_id, sizeof(int64_t)); - Collection *parent = cxMapGet(collection_map, parent_key); + Resource *parent = cxMapGet(collection_map, parent_key); if(parent) { if(!parent->children) { parent->children = cxArrayListCreate(a, NULL, CX_STORE_POINTERS, 8); @@ -370,14 +369,14 @@ 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); + DBUObjectBuilder *builder = dbuObjectBuilder(resource_class, q, a); CxList *notes = dbuObjectBuilderGetList(builder); dbuObjectBuilderDestroy(builder); return notes; } typedef struct JobGetNotes { - int64_t collection_id; + int64_t parent_resource_id; listresult_func resultcb; void *userdata; AsyncListResult result; @@ -393,7 +392,7 @@ static void uithr_get_notes_finished(UiEvent *event, JobGetNotes *job) { } static int qthr_get_notes(JobGetNotes *job) { - job->result.list = note_store_get_notes(job->result.mp->allocator, job->collection_id); + job->result.list = note_store_get_notes(job->result.mp->allocator, job->parent_resource_id); if(!job->result.list) { cxMempoolFree(job->result.mp); job->result.mp = NULL; @@ -401,11 +400,11 @@ static int qthr_get_notes(JobGetNotes *job) { return 0; } -void note_store_get_notes_async(UiObject* obj, int64_t parent_collection_id, listresult_func resultcb, void *userdata) { +void note_store_get_notes_async(UiObject* obj, int64_t parent_resource_id, listresult_func resultcb, void *userdata) { JobGetNotes *job = malloc(sizeof(JobGetNotes)); job->result.mp = cxMempoolCreateSimple(128); job->result.list = NULL; - job->collection_id = parent_collection_id; + job->parent_resource_id = parent_resource_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); @@ -504,8 +503,8 @@ static int qthr_new_note(SaveNoteJob *job) { DBUQuery *q = connection->createQuery(connection, NULL); dbuQuerySetSQL(q, SQL_NOTE_NEW); dbuQuerySetParamInt64(q, 1, n->parent_id); - dbuQuerySetParamString(q, 2, cx_str(n->title)); - dbuQuerySetParamString(q, 3, cx_str(n->title)); + dbuQuerySetParamString(q, 2, cx_str(n->displayname)); + dbuQuerySetParamString(q, 3, cx_str(n->displayname)); dbuQuerySetParamString(q, 4, cx_strcast(n->content)); if(dbuQueryExec(q)) { job->error = 1; @@ -542,8 +541,8 @@ static int qthr_save_note(SaveNoteJob *job) { Resource *n = job->note; DBUQuery *q = connection->createQuery(connection, NULL); dbuQuerySetSQL(q, SQL_NOTE_SAVE); - dbuQuerySetParamString(q, 1, cx_str(n->title)); - dbuQuerySetParamString(q, 2, cx_str(n->title)); + dbuQuerySetParamString(q, 1, cx_str(n->displayname)); + dbuQuerySetParamString(q, 2, cx_str(n->displayname)); dbuQuerySetParamString(q, 3, cx_strcast(n->content)); dbuQuerySetParamInt64(q, 4, n->resource_id); if(dbuQueryExec(q)) { @@ -582,7 +581,7 @@ static int qthr_delete_note(DeleteNoteJob *job) { DBUQuery *q = connection->createQuery(connection, NULL); if(job->move_to_trash && current_store->trash) { dbuQuerySetSQL(q, SQL_NOTE_MOVE_TO_TRASH); - dbuQuerySetParamInt64(q, 1, current_store->trash->collection_id); + dbuQuerySetParamInt64(q, 1, current_store->trash->resource_id); dbuQuerySetParamInt64(q, 2, job->note_id); } else { dbuQuerySetSQL(q, SQL_NOTE_DELETE); @@ -684,7 +683,7 @@ void note_store_save_attachment_async(UiObject *obj, Attachment *attachment, exe job->attachment_id = attachment->attachment_id; job->type = attachment->type; job->name = strdup(attachment->name); - job->content = attachment->bin_content.ptr ? cx_strdup(cx_strcast(attachment->bin_content)) : (cxmutstr){NULL, 0}; + job->content = attachment->content.ptr ? cx_strdup(cx_strcast(attachment->content)) : (cxmutstr){NULL, 0}; job->resultcb = resultcb; job->error = 0; job->userdata = userdata; diff --git a/application/store.h b/application/store.h index 131bcc3..84405b3 100644 --- a/application/store.h +++ b/application/store.h @@ -42,8 +42,8 @@ extern "C" { typedef struct NoteStore { CxMempool *mp; - Collection *root; - Collection *trash; + Resource *root; + Resource *trash; } NoteStore; typedef struct AsyncListResult { @@ -82,7 +82,7 @@ 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); +void note_store_get_notes_async(UiObject *obj, int64_t parent_resource_id, listresult_func resultcb, void *userdata); cxmutstr note_store_get_note_content(const CxAllocator *a, int64_t note_id); void note_store_get_note_content_async(UiObject *obj, const CxAllocator *a, int64_t note_id, stringresult_func resultcb, void *userdata); diff --git a/application/store_sqlite.c b/application/store_sqlite.c index aca8c82..396fa39 100644 --- a/application/store_sqlite.c +++ b/application/store_sqlite.c @@ -43,18 +43,6 @@ "default_key text, " \ "authmethod integer, " \ "local_path text);" -#define SQL_CREATE_TABLE_COLLECTIONS "create table collections( " \ - "collection_id integer primary key, " \ - "parent_id integer, " \ - "repository_id integer, " \ - "name text not null, " \ - "display_name text, " \ - "type text, " \ - "created_by text, " \ - "created_at text, " \ - "foreign key (parent_id) references collections(collection_id), " \ - "foreign key (repository_id) references repositories(repository_id)" \ - ");" #define SQL_CREATE_TABLE_USER_SETTINGS "create table user_settings( " \ "host text, " \ "user text, " \ @@ -64,22 +52,23 @@ "created_by text, " \ "created_at text, " \ "foreign key (default_repository_id) references repositories(repository_id), " \ - "foreign key (default_collection_id) references collections(collection_id), " \ + "foreign key (default_collection_id) references resources(resource_id), " \ "unique (host, user, profile_name)" \ ");" -#define SQL_CREATE_TABLE_NOTES "create table resources( " \ +#define SQL_CREATE_TABLE_RESOURCES "create table resources( " \ "resource_id integer primary key, " \ "parent_id integer, " \ - "name text, " \ - "title text, " \ - "lastmodified text, " \ - "creationdate text, " \ + "nodename text, " \ + "displayname text, " \ + "iscollection integer, " \ "contenttype text, " \ - "content text, " \ - "bin_content blob, " \ - "created_by text, " \ - "foreign key (parent_id) references collections(collection_id), " \ - "unique (parent_id, name) " \ + "contentlength integer, " \ + "lastmodified integer, " \ + "creationdate integer, " \ + "etag text, " \ + "content blob, " \ + "foreign key (parent_id) references resources(resource_id), " \ + "unique (parent_id, nodename) " \ ");" #define SQL_CREATE_TABLE_ATTACHMENTS "create table attachments( " \ "attachment_id integer primary key, " \ @@ -94,9 +83,8 @@ int store_sqlite_init_db(DBUConnection *connection) { char *sql[] = { SQL_CREATE_TABLE_NOTEDB, SQL_CREATE_TABLE_REPOSITORIES, - SQL_CREATE_TABLE_COLLECTIONS, SQL_CREATE_TABLE_USER_SETTINGS, - SQL_CREATE_TABLE_NOTES, + SQL_CREATE_TABLE_RESOURCES, SQL_CREATE_TABLE_ATTACHMENTS }; int nsql = sizeof(sql) / sizeof(char*); diff --git a/application/types.c b/application/types.c index d87c856..178f2d3 100644 --- a/application/types.c +++ b/application/types.c @@ -32,8 +32,7 @@ static DBUContext *ctx; DBUClass *usersettings_class; DBUClass *repository_class; -DBUClass *collection_class; -DBUClass *notes_class; +DBUClass *resource_class; DBUClass *attachments_class; DBUContext* get_dbu_context() { @@ -51,25 +50,17 @@ void register_types() { dbuClassAdd(usersettings_class, UserSettings, default_collection_id); dbuClassAdd(usersettings_class, UserSettings, created_by); - collection_class = dbuRegisterClass(ctx, "collections", Collection, collection_id); - dbuClassAdd(collection_class, Collection, parent_id); - dbuClassAdd(collection_class, Collection, repository_id); - dbuClassAdd(collection_class, Collection, name); - dbuClassAdd(collection_class, Collection, display_name); - dbuClassAdd(collection_class, Collection, type); - - notes_class = dbuRegisterClass(ctx, "resources", Resource, resource_id); - dbuClassAdd(notes_class, Resource, parent_id); - dbuClassAdd(notes_class, Resource, name); - dbuClassAdd(notes_class, Resource, title); - dbuClassAdd(notes_class, Resource, lastmodified); - dbuClassAdd(notes_class, Resource, creationdate); - dbuClassAdd(notes_class, Resource, contenttype); - dbuClassAdd(notes_class, Resource, contentlength); - dbuClassAdd(notes_class, Resource, content); - dbuClassAdd(notes_class, Resource, bin_content); - dbuClassAdd(notes_class, Resource, created_by); - dbuClassAdd(notes_class, Resource, content_loaded); + resource_class = dbuRegisterClass(ctx, "resources", Resource, resource_id); + dbuClassAdd(resource_class, Resource, parent_id); + dbuClassAdd(resource_class, Resource, nodename); + dbuClassAdd(resource_class, Resource, displayname); + dbuClassAdd(resource_class, Resource, lastmodified); + dbuClassAdd(resource_class, Resource, creationdate); + dbuClassAdd(resource_class, Resource, contenttype); + dbuClassAdd(resource_class, Resource, contentlength); + dbuClassAdd(resource_class, Resource, content); + //dbuClassAdd(notes_class, Resource, created_by); + dbuClassAdd(resource_class, Resource, content_loaded); repository_class = dbuRegisterClass(ctx, "repositories", Repository, repository_id); dbuClassAdd(repository_class, Repository, name); @@ -84,5 +75,5 @@ void register_types() { dbuClassAdd(attachments_class, Attachment, parent_resource_id); dbuClassAdd(attachments_class, Attachment, type); dbuClassAdd(attachments_class, Attachment, name); - dbuClassAdd(attachments_class, Attachment, bin_content); + dbuClassAdd(attachments_class, Attachment, content); } diff --git a/application/types.h b/application/types.h index d107db8..2dd8bc3 100644 --- a/application/types.h +++ b/application/types.h @@ -44,8 +44,9 @@ typedef struct AttachmentModel AttachmentModel; typedef struct UserSettings UserSettings; typedef struct Repository Repository; -typedef struct Collection Collection; typedef struct Resource Resource; +typedef struct Notebook Notebook; +typedef struct Note Note; typedef struct Attachment Attachment; struct UserSettings { @@ -68,29 +69,24 @@ struct Repository { bool encryption; }; -struct Collection { - int64_t collection_id; - int64_t parent_id; - int64_t repository_id; - char *name; - char *display_name; - char *type; - - CxList *children; -}; - struct Resource { int64_t resource_id; int64_t parent_id; - char *name; - char *title; - char *lastmodified; - char *creationdate; + char *nodename; + char *displayname; char *contenttype; uint64_t contentlength; - cxmutstr content; - cxmutstr bin_content; - char *created_by; + cxmutstr etag; + cxmutstr content; + bool iscollection; + time_t lastmodified; + time_t creationdate; + + + /* + * type: Resource* + */ + CxList *children; /* * type: Attachment* @@ -110,6 +106,13 @@ struct Resource { NoteModel *model; }; +struct Notebook { + int64_t notebook_id; + int64_t resource_id; + int64_t repository_id; + int type; +}; + typedef enum AttachmentType { NOTE_ATTACHMENT_FILE = 0, NOTE_ATTACHMENT_IMAGE @@ -124,7 +127,7 @@ struct Attachment { // temp (from table resources) char *name; - cxmutstr bin_content; + cxmutstr content; bool content_loaded; // is the attachment content already stored persistently @@ -143,7 +146,7 @@ struct Attachment { extern DBUClass *usersettings_class; extern DBUClass *repository_class; extern DBUClass *collection_class; -extern DBUClass *notes_class; +extern DBUClass *resource_class; extern DBUClass *attachments_class; void register_types(); diff --git a/application/window.c b/application/window.c index 8cfe55d..80fd2ad 100644 --- a/application/window.c +++ b/application/window.c @@ -134,8 +134,8 @@ void window_notelist_setvisible(MainWindow *window, UiBool visible) { } void window_sidebar_getvalue(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item) { - Collection *notebook = rowdata; - item->label = strdup(notebook->display_name ? notebook->display_name : notebook->name); + Resource *notebook = rowdata; + item->label = strdup(notebook->displayname ? notebook->displayname : notebook->nodename); } @@ -175,16 +175,16 @@ void update_sublists(UiContext *ctx, UiList *sublists) { } i = cxListIterator(notestore->root->children); - cx_foreach(Collection *, col, i) { + cx_foreach(Resource *, col, i) { UiSubList *sublist = calloc(1, sizeof(UiSubList)); - sublist->header = strdup(col->display_name ? col->display_name : col->name); + sublist->header = strdup(col->displayname ? col->displayname : col->nodename); sublist->value = ui_list_new(ctx, NULL); sublist->userdata = col; ui_list_append(sublists, sublist); if(col->children) { CxIterator j = cxListIterator(col->children); - cx_foreach(Collection *, nb, j) { + cx_foreach(Resource *, nb, j) { ui_list_append(sublist->value, nb); } } @@ -204,10 +204,11 @@ void* window_notelist_getvalue(void *data, int col) { Resource *note = data; switch(col) { case 0: { - return note->title ? note->title : note->name; + return note->displayname ? note->displayname : note->nodename; } case 1: { - return note->lastmodified ? strdup(note->lastmodified) : NULL; + //return note->lastmodified ? strdup(note->lastmodified) : NULL; + return NULL; // TODO } } @@ -225,18 +226,18 @@ NotebookModel* window_get_cached_notebook(MainWindow *window, int64_t collection * Loads the notebook/note with UI options specified by the NavStack element */ void window_navigate(MainWindow *window, NavStack *nav) { - if(nav->collection_id == 0) { + if(nav->collection_resource_id == 0) { return; } // when a collection is on the navstack, it should also be in the cache - NotebookModel *notebook = window_get_cached_notebook(window, nav->collection_id); + NotebookModel *notebook = window_get_cached_notebook(window, nav->collection_resource_id); if(!notebook) { printf("window_navigate: notebook not in cache\n"); return; } size_t note_index; - Resource *note = nav->note_id != 0 ? notebookmodel_get_note_by_id(notebook, nav->note_id, ¬e_index) : NULL; + Resource *note = nav->resource_id != 0 ? notebookmodel_get_note_by_id(notebook, nav->resource_id, ¬e_index) : NULL; window_notelist_setvisible(window, !(nav->view_flags & VIEW_FLAGS_NO_BROWSER)); @@ -256,9 +257,9 @@ void window_navigate(MainWindow *window, NavStack *nav) { void action_notebook_selected(UiEvent *event, void *userdata) { UiSubListEventData *data = event->eventdata; MainWindow *window = event->window; - Collection *collection = data->row_data; + Resource *collection = data->row_data; - printf("notebook selected: %s\n", collection->name); + printf("notebook selected: %s\n", collection->nodename); ui_set_group(event->obj->ctx, APP_STATE_NOTEBOOK_SELECTED); if(window->current_notebook && window->current_notebook->collection == collection) { @@ -268,7 +269,7 @@ void action_notebook_selected(UiEvent *event, void *userdata) { // reset splitpane visibility window_notelist_setvisible(window, TRUE); - CxHashKey key = cx_hash_key(&collection->collection_id, sizeof(collection->collection_id)); + CxHashKey key = cx_hash_key(&collection->resource_id, sizeof(collection->resource_id)); NotebookModel *notebook = cxMapGet(window->notebook_cache, key); if(!notebook) { printf("notebook not in cache\n"); diff --git a/ucx/buffer.c b/ucx/buffer.c index 981b161..7e91f73 100644 --- a/ucx/buffer.c +++ b/ucx/buffer.c @@ -32,6 +32,24 @@ #include #include +#ifdef _WIN32 +#include +#include +static unsigned long system_page_size() { + static unsigned long ps = 0; + if (ps == 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + ps = sysinfo.dwPageSize; + } + return ps; +} +#define SYSTEM_PAGE_SIZE system_page_size() +#else +#include +#define SYSTEM_PAGE_SIZE sysconf(_SC_PAGESIZE) +#endif + static int buffer_copy_on_write(CxBuffer* buffer) { if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; void *newspace = cxMalloc(buffer->allocator, buffer->capacity); @@ -147,11 +165,16 @@ int cxBufferSeek( npos += offset; if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { - errno = EOVERFLOW; + // to be compliant with fseek() specification + // we return EINVAL on underflow + errno = EINVAL; return -1; } if (npos > buffer->size) { + // not compliant with fseek() specification + // but this is the better behavior for CxBuffer + errno = EINVAL; return -1; } else { buffer->pos = npos; @@ -185,6 +208,28 @@ int cxBufferMinimumCapacity( return 0; } + unsigned long pagesize = SYSTEM_PAGE_SIZE; + // if page size is larger than 64 KB - for some reason - truncate to 64 KB + if (pagesize > 65536) pagesize = 65536; + if (newcap < pagesize) { + // when smaller as one page, map to the next power of two + newcap--; + newcap |= newcap >> 1; + newcap |= newcap >> 2; + newcap |= newcap >> 4; + // last operation only needed for pages larger 4096 bytes + // but if/else would be more expensive than just doing this + newcap |= newcap >> 8; + newcap++; + } else { + // otherwise, map to a multiple of the page size + newcap -= newcap % pagesize; + newcap += pagesize; + // note: if newcap is already page aligned, + // this gives a full additional page (which is good) + } + + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; if (buffer->flags & force_copy_flags) { void *newspace = cxMalloc(buffer->allocator, newcap); @@ -204,6 +249,28 @@ int cxBufferMinimumCapacity( } } +void cxBufferShrink( + CxBuffer *buffer, + size_t reserve +) { + // Ensure buffer is in a reallocatable state + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; + if (buffer->flags & force_copy_flags) { + // do nothing when we are not allowed to reallocate + return; + } + + // calculate new capacity + size_t newCapacity = buffer->size + reserve; + + // If new capacity is smaller than current capacity, resize the buffer + if (newCapacity < buffer->capacity) { + if (0 == cxReallocate(buffer->allocator, &buffer->bytes, newCapacity)) { + buffer->capacity = newCapacity; + } + } +} + static size_t cx_buffer_flush_helper( const CxBuffer *buffer, const unsigned char *src, diff --git a/ucx/cx/buffer.h b/ucx/cx/buffer.h index 4330580..e546cae 100644 --- a/ucx/cx/buffer.h +++ b/ucx/cx/buffer.h @@ -474,10 +474,14 @@ bool cxBufferEof(const CxBuffer *buffer); * * If the current capacity is not sufficient, the buffer will be extended. * + * The new capacity will be a power of two until the system's page size is reached. + * Then, the new capacity will be a multiple of the page size. + * * @param buffer the buffer * @param capacity the minimum required capacity for this buffer * @retval zero the capacity was already sufficient or successfully increased * @retval non-zero on allocation failure + * @see cxBufferShrink() */ cx_attr_nonnull cx_attr_export @@ -486,6 +490,29 @@ int cxBufferMinimumCapacity( size_t capacity ); +/** + * Shrinks the capacity of the buffer to fit its current size. + * + * If @p reserve is larger than zero, the buffer is shrunk to its size plus + * the number of reserved bytes. + * + * If the current capacity is not larger than the size plus the reserved bytes, + * nothing happens. + * + * If the #CX_BUFFER_COPY_ON_WRITE or #CX_BUFFER_COPY_ON_EXTEND flag is set, + * this function does nothing. + * + * @param buffer the buffer + * @param reserve the number of bytes that shall remain reserved + * @see cxBufferMinimumCapacity() + */ +cx_attr_nonnull +cx_attr_export +void cxBufferShrink( + CxBuffer *buffer, + size_t reserve +); + /** * Writes data to a CxBuffer. * diff --git a/ucx/cx/common.h b/ucx/cx/common.h index b54153d..0fc9e47 100644 --- a/ucx/cx/common.h +++ b/ucx/cx/common.h @@ -240,20 +240,6 @@ */ #define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__) -#if __STDC_VERSION__ >= 202300L - -/** - * Do not warn about unused variable. - */ -#define cx_attr_unused [[maybe_unused]] - -/** - * Warn about discarded return value. - */ -#define cx_attr_nodiscard [[nodiscard]] - -#else // no C23 - /** * Do not warn about unused variable. */ @@ -264,8 +250,6 @@ */ #define cx_attr_nodiscard __attribute__((__warn_unused_result__)) -#endif // __STDC_VERSION__ - // --------------------------------------------------------------------------- // MSVC specifics diff --git a/ucx/cx/list.h b/ucx/cx/list.h index a358d02..c45f9d5 100644 --- a/ucx/cx/list.h +++ b/ucx/cx/list.h @@ -202,6 +202,22 @@ struct cx_list_class_s { ); }; +/** + * Common type for all list implementations. + */ +typedef struct cx_list_s CxList; + +/** + * A shared instance of an empty list. + * + * Writing to that list is not allowed. + * + * You can use this is a placeholder for initializing CxList pointers + * for which you do not want to reserve memory right from the beginning. + */ +cx_attr_export +extern CxList *const cxEmptyList; + /** * Default implementation of an array insert. * @@ -335,11 +351,6 @@ void cx_list_init( size_t elem_size ); -/** - * Common type for all list implementations. - */ -typedef struct cx_list_s CxList; - /** * Returns the number of elements currently stored in the list. * @@ -689,6 +700,24 @@ static inline void *cxListAt( return list->cl->at(list, index); } + +/** + * Sets the element at the specified index in the list + * + * @param list the list to set the element in + * @param index the index to set the element at + * @param elem element to set + * @retval zero on success + * @retval non-zero when index is out of bounds + */ +cx_attr_nonnull +cx_attr_export +int cxListSet( + CxList *list, + size_t index, + const void *elem +); + /** * Returns an iterator pointing to the item at the specified index. * @@ -773,14 +802,14 @@ CxIterator cxListMutBackwardsIteratorAt( * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, 0, false); } @@ -789,14 +818,14 @@ static inline CxIterator cxListIterator(const CxList *list) { * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListMutIterator(CxList *list) { + if (list == NULL) list = cxEmptyList; return cxListMutIteratorAt(list, 0); } @@ -806,14 +835,14 @@ static inline CxIterator cxListMutIterator(CxList *list) { * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListBackwardsIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, list->collection.size - 1, true); } @@ -822,14 +851,14 @@ static inline CxIterator cxListBackwardsIterator(const CxList *list) { * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListMutBackwardsIterator(CxList *list) { + if (list == NULL) list = cxEmptyList; return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); } @@ -842,6 +871,7 @@ static inline CxIterator cxListMutBackwardsIterator(CxList *list) { * @param elem the element to find * @return the index of the element or the size of the list when the element is not found * @see cxListIndexValid() + * @see cxListContains() */ cx_attr_nonnull cx_attr_nodiscard @@ -852,6 +882,26 @@ static inline size_t cxListFind( return list->cl->find_remove((CxList*)list, elem, false); } +/** + * Checks, if the list contains the specified element. + * + * The elements are compared with the list's comparator function. + * + * @param list the list + * @param elem the element to find + * @retval true if the element is contained + * @retval false if the element is not contained + * @see cxListFind() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline bool cxListContains( + const CxList* list, + const void* elem +) { + return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size; +} + /** * Checks if the specified index is within bounds. * @@ -943,17 +993,6 @@ int cxListCompare( cx_attr_export void cxListFree(CxList *list); -/** - * A shared instance of an empty list. - * - * Writing to that list is not allowed. - * - * You can use this is a placeholder for initializing CxList pointers - * for which you do not want to reserve memory right from the beginning. - */ -cx_attr_export -extern CxList *const cxEmptyList; - #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/string.h b/ucx/cx/string.h index fee35d2..5c8ea88 100644 --- a/ucx/cx/string.h +++ b/ucx/cx/string.h @@ -39,6 +39,12 @@ #include "common.h" #include "allocator.h" +/** Expands a UCX string as printf arguments. */ +#define CX_SFMT(s) (int) (s).length, (s).ptr + +/** Format specifier for a UCX string */ +#define CX_PRIstr ".*s" + /** * The maximum length of the "needle" in cx_strstr() that can use SBO. */ @@ -333,6 +339,47 @@ void cx_strfree_a( cxmutstr *str ); +/** + * Copies a string. + * + * The memory in the @p dest structure is either allocated or re-allocated to fit the entire + * source string, including a zero-terminator. + * + * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is. + * + * @param alloc the allocator + * @param dest a pointer to the structure where to copy the contents to + * @param src the source string + * + * @retval zero success + * @retval non-zero if re-allocation failed + */ +cx_attr_nonnull_arg(1) +cx_attr_export +int cx_strcpy_a( + const CxAllocator *alloc, + cxmutstr *dest, + cxstring src +); + + +/** + * Copies a string. + * + * The memory in the @p dest structure is either allocated or re-allocated to fit the entire + * source string, including a zero-terminator. + * + * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is. + * + * @param alloc (@c CxAllocator*) the allocator + * @param dest (@c cxmutstr*) a pointer to the structure where to copy the contents to + * @param src (@c cxstring) the source string + * + * @retval zero success + * @retval non-zero if re-allocation failed + */ +#define cx_strcpy(dest, src) cx_strcpy_a(cxDefaultAllocator, dest, src) + /** * Returns the accumulated length of all specified strings. * diff --git a/ucx/cx/tree.h b/ucx/cx/tree.h index b7f49b5..9686623 100644 --- a/ucx/cx/tree.h +++ b/ucx/cx/tree.h @@ -120,6 +120,7 @@ typedef struct cx_tree_iterator_s { size_t stack_size; /** * The current depth in the tree. + * The node with which the iteration starts has depth 1. */ size_t depth; }; @@ -135,6 +136,7 @@ struct cx_tree_visitor_queue_s { void *node; /** * The depth of the node. + * The first visited node has depth 1. */ size_t depth; /** @@ -1187,6 +1189,18 @@ cx_attr_nodiscard cx_attr_export size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); +/** + * Determines the size of the entire tree. + * + * @param tree the tree + * @return the tree size, counting the root as one + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline size_t cxTreeSize(CxTree *tree) { + return tree->size; +} + /** * Determines the depth of the entire tree. * diff --git a/ucx/list.c b/ucx/list.c index daf6b9e..497c66e 100644 --- a/ucx/list.c +++ b/ucx/list.c @@ -494,3 +494,24 @@ void cxListFree(CxList *list) { if (list == NULL) return; list->cl->deallocate(list); } + +int cxListSet( + CxList *list, + size_t index, + const void *elem +) { + if (index >= list->collection.size) { + return 1; + } + + if (list->collection.store_pointer) { + // For pointer collections, always use climpl + void **target = list->climpl->at(list, index); + *target = (void *)elem; + } else { + void *target = list->cl->at(list, index); + memcpy(target, elem, list->collection.elem_size); + } + + return 0; +} diff --git a/ucx/string.c b/ucx/string.c index 14b732e..ab3d466 100644 --- a/ucx/string.c +++ b/ucx/string.c @@ -80,6 +80,22 @@ void cx_strfree_a( str->length = 0; } +int cx_strcpy_a( + const CxAllocator *alloc, + cxmutstr *dest, + cxstring src +) { + if (cxReallocate(alloc, &dest->ptr, src.length + 1)) { + return 1; + } + + memcpy(dest->ptr, src.ptr, src.length); + dest->length = src.length; + dest->ptr[dest->length] = '\0'; + + return 0; +} + size_t cx_strlen( size_t count, ... @@ -106,27 +122,16 @@ cxmutstr cx_strcat_ma( ... ) { if (count == 0) return str; - - cxstring strings_stack[8]; - cxstring *strings; - if (count > 8) { - strings = calloc(count, sizeof(cxstring)); - if (strings == NULL) { - return (cxmutstr) {NULL, 0}; - } - } else { - strings = strings_stack; - } - va_list ap; va_start(ap, count); + va_list ap2; + va_copy(ap2, ap); - // get all args and overall length + // compute overall length bool overflow = false; size_t slen = str.length; for (size_t i = 0; i < count; i++) { - cxstring s = va_arg (ap, cxstring); - strings[i] = s; + cxstring s = va_arg(ap, cxstring); if (slen > SIZE_MAX - str.length) overflow = true; slen += s.length; } @@ -134,10 +139,8 @@ cxmutstr cx_strcat_ma( // abort in case of overflow if (overflow) { + va_end(ap2); errno = EOVERFLOW; - if (strings != strings_stack) { - free(strings); - } return (cxmutstr) { NULL, 0 }; } @@ -149,9 +152,7 @@ cxmutstr cx_strcat_ma( newstr = cxRealloc(alloc, str.ptr, slen + 1); } if (newstr == NULL) { - if (strings != strings_stack) { - free(strings); - } + va_end(ap2); return (cxmutstr) {NULL, 0}; } str.ptr = newstr; @@ -160,19 +161,15 @@ cxmutstr cx_strcat_ma( size_t pos = str.length; str.length = slen; for (size_t i = 0; i < count; i++) { - cxstring s = strings[i]; + cxstring s = va_arg(ap2, cxstring); memcpy(str.ptr + pos, s.ptr, s.length); pos += s.length; } + va_end(ap2); // terminate string str.ptr[str.length] = '\0'; - // free temporary array - if (strings != strings_stack) { - free(strings); - } - return str; } @@ -588,27 +585,6 @@ bool cx_strcasesuffix( #endif } -#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE -#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64 -#endif - -struct cx_strreplace_ibuf { - size_t *buf; - struct cx_strreplace_ibuf *next; - unsigned int len; -}; - -static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) { - // remember, the first data is on the stack! - buf = buf->next; - while (buf) { - struct cx_strreplace_ibuf *next = buf->next; - free(buf->buf); - free(buf); - buf = next; - } -} - cxmutstr cx_strreplacen_a( const CxAllocator *allocator, cxstring str, @@ -616,108 +592,60 @@ cxmutstr cx_strreplacen_a( cxstring replacement, size_t replmax ) { - - if (search.length == 0 || search.length > str.length || replmax == 0) + // special cases + if (search.length == 0 || search.length > str.length || replmax == 0) { return cx_strdup_a(allocator, str); - - // Compute expected buffer length - size_t ibufmax = str.length / search.length; - size_t ibuflen = replmax < ibufmax ? replmax : ibufmax; - if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) { - ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE; } - // First index buffer can be on the stack - struct cx_strreplace_ibuf ibuf, *curbuf = &ibuf; - size_t ibuf_sbo[CX_STRREPLACE_INDEX_BUFFER_SIZE]; - ibuf.buf = ibuf_sbo; - ibuf.next = NULL; - ibuf.len = 0; + size_t in_len = str.length; + size_t search_len = search.length; + size_t repl_len = replacement.length; - // Search occurrences - cxstring searchstr = str; - size_t found = 0; - do { - cxstring match = cx_strstr(searchstr, search); - if (match.length > 0) { - // Allocate next buffer in chain, if required - if (curbuf->len == ibuflen) { - struct cx_strreplace_ibuf *nextbuf = - calloc(1, sizeof(struct cx_strreplace_ibuf)); - if (!nextbuf) { - cx_strrepl_free_ibuf(&ibuf); - return cx_mutstrn(NULL, 0); - } - nextbuf->buf = calloc(ibuflen, sizeof(size_t)); - if (!nextbuf->buf) { - free(nextbuf); - cx_strrepl_free_ibuf(&ibuf); - return cx_mutstrn(NULL, 0); - } - curbuf->next = nextbuf; - curbuf = nextbuf; - } - - // Record match index - found++; - size_t idx = match.ptr - str.ptr; - curbuf->buf[curbuf->len++] = idx; - searchstr.ptr = match.ptr + search.length; - searchstr.length = str.length - idx - search.length; - } else { - break; - } - } while (searchstr.length > 0 && found < replmax); - - // Allocate result string - cxmutstr result; - { - long long adjlen = (long long) replacement.length - (long long) search.length; - size_t rcount = 0; - curbuf = &ibuf; - do { - rcount += curbuf->len; - curbuf = curbuf->next; - } while (curbuf); - result.length = str.length + rcount * adjlen; - result.ptr = cxMalloc(allocator, result.length + 1); - if (!result.ptr) { - cx_strrepl_free_ibuf(&ibuf); - return cx_mutstrn(NULL, 0); - } + // first run, count the occurrences + // and remember where the first is + size_t occurrences = 1; + cxstring first = cx_strstr(str, search); + if (first.length == 0) { + // special case, no replacements + return cx_strdup_a(allocator, str); + } + cxstring tmp = cx_strsubs(first, search_len); + while (occurrences < replmax && + (tmp = cx_strstr(tmp, search)).length > 0) { + occurrences++; + tmp = cx_strsubs(tmp, search_len); } - // Build result string - curbuf = &ibuf; - size_t srcidx = 0; - char *destptr = result.ptr; - do { - for (size_t i = 0; i < curbuf->len; i++) { - // Copy source part up to next match - size_t idx = curbuf->buf[i]; - size_t srclen = idx - srcidx; - if (srclen > 0) { - memcpy(destptr, str.ptr + srcidx, srclen); - destptr += srclen; - srcidx += srclen; - } - - // Copy the replacement and skip the source pattern - srcidx += search.length; - memcpy(destptr, replacement.ptr, replacement.length); - destptr += replacement.length; - } - curbuf = curbuf->next; - } while (curbuf); - memcpy(destptr, str.ptr + srcidx, str.length - srcidx); - - // Result is guaranteed to be zero-terminated - result.ptr[result.length] = '\0'; + // calculate necessary memory + signed long long diff_len = (signed long long) repl_len - search_len; + size_t out_len = in_len + diff_len * occurrences; + cxmutstr out = { + cxMalloc(allocator, out_len + 1), + out_len + }; + if (out.ptr == NULL) return out; + + // second run: perform the replacements + // but start where we found the first occurrence + const char *inp = str.ptr; + tmp = first; + char *outp = out.ptr; + while (occurrences-- > 0 && (tmp = cx_strstr(tmp, search)).length > 0) { + size_t copylen = tmp.ptr - inp; + memcpy(outp, inp, copylen); + outp += copylen; + memcpy(outp, replacement.ptr, repl_len); + outp += repl_len; + inp += copylen + search_len; + tmp = cx_strsubs(tmp, search_len); + } - // Free index buffer - cx_strrepl_free_ibuf(&ibuf); + // add the remaining string + size_t copylen = in_len - (inp - str.ptr); + memcpy(outp, inp, copylen); + out.ptr[out_len] = '\0'; - return result; + return out; } CxStrtokCtx cx_strtok_( diff --git a/ucx/tree.c b/ucx/tree.c index 397f908..73f3b35 100644 --- a/ucx/tree.c +++ b/ucx/tree.c @@ -226,14 +226,14 @@ int cx_tree_search( int ret_elem = sfunc(elem, node); if (ret_elem == 0) { // if found, exit the search - *result = (void *) elem; + *result = elem; ret = 0; break; } else if (ret_elem > 0 && ret_elem < ret) { // new distance is better *result = elem; ret = ret_elem; - } else { + } else if (ret_elem < 0 || ret_elem > ret) { // not contained or distance is worse, skip entire subtree cxTreeIteratorContinue(iter); } @@ -307,10 +307,10 @@ static void cx_tree_iter_next(void *it) { // search for the next node void *next; cx_tree_iter_search_next: - // check if there is a sibling + // check if there is a sibling, but only if we are not a (subtree-)root if (iter->exiting) { next = iter->node_next; - } else { + } else if (iter->depth > 1) { next = tree_next(iter->node); iter->node_next = next; }