// 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;
NotebookModel *current_notebook;
/*
- * key: collection_id
+ * key: resource_id
* value: NotebookModel*
*/
CxMap *notebook_cache;
*
* 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*
}
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) {
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;
}
}
}
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
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");
}
}
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?
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);
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
}
}
-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;
}
}
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);
}
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
// 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) {
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);
#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;"
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);
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;
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);
}
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);
// 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;
}
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);
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;
}
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;
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);
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;
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)) {
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);
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;
typedef struct NoteStore {
CxMempool *mp;
- Collection *root;
- Collection *trash;
+ Resource *root;
+ Resource *trash;
} NoteStore;
typedef struct AsyncListResult {
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);
"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, " \
"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, " \
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*);
DBUClass *usersettings_class;
DBUClass *repository_class;
-DBUClass *collection_class;
-DBUClass *notes_class;
+DBUClass *resource_class;
DBUClass *attachments_class;
DBUContext* get_dbu_context() {
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);
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);
}
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 {
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*
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
// temp (from table resources)
char *name;
- cxmutstr bin_content;
+ cxmutstr content;
bool content_loaded;
// is the attachment content already stored persistently
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();
}
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);
}
}
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);
}
}
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
}
}
* 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));
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) {
// 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");
#include <string.h>
#include <errno.h>
+#ifdef _WIN32
+#include <Windows.h>
+#include <sysinfoapi.h>
+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 <unistd.h>
+#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);
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;
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);
}
}
+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,
*
* 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
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.
*
*/
#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.
*/
*/
#define cx_attr_nodiscard __attribute__((__warn_unused_result__))
-#endif // __STDC_VERSION__
-
// ---------------------------------------------------------------------------
// MSVC specifics
);
};
+/**
+ * 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.
*
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.
*
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.
*
*
* 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);
}
*
* 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);
}
*
* 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);
}
*
* 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);
}
* @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
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.
*
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"
#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.
*/
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.
*
size_t stack_size;
/**
* The current depth in the tree.
+ * The node with which the iteration starts has depth 1.
*/
size_t depth;
};
void *node;
/**
* The depth of the node.
+ * The first visited node has depth 1.
*/
size_t depth;
/**
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.
*
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;
+}
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,
...
...
) {
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;
}
// abort in case of overflow
if (overflow) {
+ va_end(ap2);
errno = EOVERFLOW;
- if (strings != strings_stack) {
- free(strings);
- }
return (cxmutstr) { NULL, 0 };
}
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;
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;
}
#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,
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_(
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);
}
// 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;
}