3. Low Level Operations
3.1. UUIDs
A universally unique identifer (UUID) is used to label
every item in the zet. The UUIDs are generated courtesy
of the uuid4
library by rxi
, included inside of
the weewiki project.
3.1.1. UUID struct
A full UUID contained inside of a struct called
wwzet_uuid
.
typedef struct wwzet_uuid wwzet_uuid;
This UUID contains a char of 37 bytes: 36 for the UUID (including dashes) plus the null terminator.
struct wwzet_uuid {
char str[37];
};
3.1.2. (re)-initializing a UUID
The UUID is initialized with the function
wwzet_uuid_init
. This will set the UUID to be
00000000-0000-4000-8000-000000000000
, a valid
but zeroed UUID4.
void wwzet_uuid_init(wwzet_uuid *uuid);
void wwzet_uuid_init(wwzet_uuid *uuid)
{
int i;
static const char *zero =
"00000000-0000-4000-8000-000000000000";
for (i = 0; i < 36; i++) uuid->str[i] = zero[i];
uuid->str[36] = 0;
}
3.1.3. Initializing the uuid4 RNG
Before generating a new UUID, the RNG must be
initialized with wwzet_uuid_rng_init
.
This makes a call to uuid4_init
.
void wwzet_uuid_rng_init(void);
void wwzet_uuid_rng_init(void)
{
uuid4_init();
}
3.1.4. UUID generation
Create a new UUID with wwzet_uuid_generate
. This uses
the uuid version 4 protocol, which means it is randomly
generated. This assumes the RNG has been initialized
already.
void wwzet_uuid_generate(wwzet_uuid *uuid);
This function calls uuid4_generate
under the hood, and
then stores the output to the wwzet_uuid
variable
uuid
.
void wwzet_uuid_generate(wwzet_uuid *uuid)
{
uuid4_generate(uuid->str);
}
3.1.5. UUID expansion/validation
wwzet_uuid_expand
will check if a partial UUID exists in
the zettelkasten table, and expand to full UUID value.
The partial value is provided as a null-terminated C string.
The number of matches is returned. Anything not equal to 1 is considered an error.
int wwzet_uuid_expand(weewiki_d *ww,
const char *partial,
int sz,
wwzet_uuid *uuid);
The following SQLite3 statement is used:
SELECT UUID, COUNT(DISTINCT UUID) from wikizet where UUID LIKE(?1);
Where "?1" is the partial match.
This query will return a single row with the first found wikizet, and the number of matches.
Actions will only happen when there is exactly 1 match. This involves copying over the UUID value into the variable.
int wwzet_uuid_expand(weewiki_d *ww,
const char *partial,
int sz,
wwzet_uuid *uuid)
{
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
char *matchstr;
int nmatches;
matchstr = calloc(1, sz + 2);
strcpy(matchstr, partial);
matchstr[sz] = '%';
db = weewiki_db(ww);
sqlite3_prepare_v2(db,
"SELECT UUID, COUNT(DISTINCT UUID) FROM wikizet "
"WHERE UUID LIKE(?1);",
-1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, matchstr, sz + 1, NULL);
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW) {
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
free(matchstr);
sqlite3_finalize(stmt);
return -1;
}
nmatches = sqlite3_column_int(stmt, 1);
if (nmatches == 1) {
int i;
const char *str;
str = (const char *)sqlite3_column_text(stmt, 0);
for (i = 0; i < 36; i++) uuid->str[i] = str[i];
}
free(matchstr);
sqlite3_finalize(stmt);
return nmatches;
}
3.1.6. Get UUID from value
Given a value (presumably, a group), return the UUID. non-zero value is an error.
int wwzet_uuid_fromval(weewiki_d *ww,
const char *val,
int sz,
wwzet_uuid *uuid);
int wwzet_uuid_fromval(weewiki_d *ww,
const char *val,
int sz,
wwzet_uuid *uuid)
{
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
int err;
db = weewiki_db(ww);
err = 0;
sqlite3_prepare_v2(db,
"SELECT UUID FROM wikizet "
"WHERE value LIKE ?1;", -1,
&stmt, NULL);
sqlite3_bind_text(stmt, 1, val, sz, NULL);
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW) {
err = 1;
} else {
int i;
const char *id;
wwzet_uuid_init(uuid);
id = (const char *)sqlite3_column_text(stmt, 0);
for (i = 0; i < 36; i++) {
uuid->str[i] = id[i];
}
}
sqlite3_finalize(stmt);
return err;
}
3.1.7. Get UUID from value and prefix character
int wwzet_uuid_fromval_prefix(weewiki_d *ww,
const char *val,
int sz,
char prefix,
wwzet_uuid *uuid);
int wwzet_uuid_fromval_prefix(weewiki_d *ww,
const char *val,
int sz,
char prefix,
wwzet_uuid *uuid)
{
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
int err;
const char *p;
db = weewiki_db(ww);
err = 0;
sqlite3_prepare_v2(db,
"SELECT UUID FROM wikizet "
"WHERE value LIKE ?2 || ?1;", -1,
&stmt, NULL);
sqlite3_bind_text(stmt, 1, val, sz, NULL);
p = &prefix;
sqlite3_bind_text(stmt, 2, p, 1, NULL);
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW) {
err = 1;
} else {
int i;
const char *id;
wwzet_uuid_init(uuid);
id = (const char *)sqlite3_column_text(stmt, 0);
for (i = 0; i < 36; i++) {
uuid->str[i] = id[i];
}
}
sqlite3_finalize(stmt);
return err;
}
3.1.8. Get UUID from ergo ID
wwzet_uuid_fromergo
will expand a UUID expressed in
ergo format.
int wwzet_uuid_fromergo(weewiki_d *ww,
const char *ergo,
int sz,
wwzet_uuid *uuid);
int wwzet_uuid_fromergo(weewiki_d *ww,
const char *ergo,
int sz,
wwzet_uuid *uuid)
{
char *partial;
int rc;
partial = calloc(1, sz + 1);
wwzet_ergo_to_hex(ergo, sz, partial);
rc = wwzet_uuid_expand(ww, partial, sz, uuid);
free(partial);
return rc;
}
3.1.9. Resolve a UUID
wwzet_uuid_resolve
smartly resolves a UUID from
a string value. Will return a non-zero value on error.
The default behavior of resolve is to expand a partial UUID. However, certain prefixes in the string will cause it to be treated as a value lookup.
Currently, valid prefixes are @
(groups), !
(pages),
and /
(crate
filepaths). Message (>
) and
addresses (#
) are to be ignored because they have less
of a chance of being unique.
It turns out that '!' is rather annoying to type in bash,
as it seems to be a reserved character and must be escaped.
To mitigate this, the '!' page prefix
is also aliased to 'P' using the
function wwzet_uuid_fromval_prefix
.
int wwzet_uuid_resolve(weewiki_d *ww,
const char *val,
int sz,
wwzet_uuid *uuid);
int wwzet_uuid_resolve(weewiki_d *ww,
const char *val,
int sz,
wwzet_uuid *uuid)
{
int err;
int rc;
int special_prefix;
err = 0;
wwzet_uuid_init(uuid);
special_prefix =
val[0] == '@' ||
val[0] == '!' ||
val[0] == '/';
if (special_prefix) {
rc = wwzet_uuid_fromval(ww, val, sz, uuid);
if (rc) err = 1;
} else if (val[0] == 'g') {
rc = wwzet_uuid_fromergo(ww, val + 1, sz - 1, uuid);
if (rc != 1) err = 1;
} else if (val[0] == 'P') {
rc = wwzet_uuid_fromval_prefix(ww, val + 1, sz - 1, '!', uuid);
if (rc) err = 1;
} else {
rc = wwzet_uuid_expand(ww, val, sz, uuid);
if (rc != 1) err = 1;
}
return err;
}
3.2. Create Zet Entry
wwzet_entry
creates a generic zet entry given a message
and timestamps it based on the current system time.
If uuid
is not NULL, the generated UUID is saved here.
It is assumed the uuid RNG is initialized already before
calling this function.
int wwzet_entry(weewiki_d *ww,
const char *msg,
int sz,
wwzet_uuid *uuid);
int wwzet_entry(weewiki_d *ww,
const char *msg,
int sz,
wwzet_uuid *uuid)
{
sqlite3 *db;
sqlite3_stmt *stmt;
wwzet_uuid id;
int rc;
wwzet_uuid_init(&id);
db = weewiki_db(ww);
wwzet_uuid_generate(&id);
sqlite3_prepare_v2(db,
"INSERT INTO "
"wikizet(time, UUID, value)"
"VALUES(datetime(), ?1, ?2);",
-1,
&stmt,
NULL);
sqlite3_bind_text(stmt, 1, id.str, -1, NULL);
sqlite3_bind_text(stmt, 2, msg, sz, NULL);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
return 1;
}
if (uuid != NULL) *uuid = id;
sqlite3_finalize(stmt);
return 0;
}
3.3. Create Zet Entry (With Symbol)
The function wwzet_entry_withsymbol
wraps around
wwzet_entry
to create a new timestamped entry and
prepends it with a single-character symbol.
int wwzet_entry_withsymbol(weewiki_d *ww,
char c,
const char *msg,
int sz,
wwzet_uuid *uuid);
int wwzet_entry_withsymbol(weewiki_d *ww,
char c,
const char *msg,
int sz,
wwzet_uuid *uuid)
{
char *val;
int rc;
val = malloc(sz + 2);
val[0] = c;
strncpy(&val[1], msg, sz);
val[sz + 1] = '\0';
rc = wwzet_entry(ww, val, sz + 1, uuid);
free(val);
return rc;
}
3.4. Insert an Entry
The function wwzet_insert
will perform a low-level
insert command into the wikizet table. The timestamp,
uuid, and value should already be generated or known.
void wwzet_insert(weewiki_d *ww,
const char *timestamp, int tlen,
const char *uuid, int ulen,
const char *value, int vlen);
void wwzet_insert(weewiki_d *ww,
const char *timestamp, int tlen,
const char *uuid, int ilen,
const char *value, int vlen)
{
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
db = weewiki_db(ww);
sqlite3_prepare_v2(db,
"INSERT into wikizet(time,uuid,value) "
"VALUES(?1,?2,?3);",
-1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, timestamp, tlen, NULL);
sqlite3_bind_text(stmt, 2, uuid, ilen, NULL);
sqlite3_bind_text(stmt, 3, value, vlen, NULL);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
}
sqlite3_finalize(stmt);
}
3.5. Create Zet Message
The function wwzet_message
will create a
timestamped entry with a message in the zet table
with a new UUID.
What is required is the main weewiki data, message, as well as the message length. The resulting UUID will get placed in the supplied UUID pointer if it is not NULL.
Be sure to open the database and initialize the UUID4 RNG before calling this.
int wwzet_message(weewiki_d *ww,
const char *msg,
int sz,
wwzet_uuid *uuid);
Under the hood, this will generate a UUID and create an insert SQLite statement using the SQLite API.
int wwzet_message(weewiki_d *ww,
const char *msg,
int sz,
wwzet_uuid *uuid)
{
sqlite3 *db;
sqlite3_stmt *stmt;
wwzet_uuid id;
int rc;
char *val;
val = malloc(sz + 2);
val[0] = '>';
strcpy(&val[1], msg);
wwzet_uuid_init(&id);
db = weewiki_db(ww);
wwzet_uuid_generate(&id);
sqlite3_prepare_v2(db,
"INSERT INTO "
"wikizet(time, UUID, value)"
"VALUES(datetime(), ?1, ?2);",
-1,
&stmt,
NULL);
sqlite3_bind_text(stmt, 1, id.str, -1, NULL);
sqlite3_bind_text(stmt, 2, val, sz + 1, NULL);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
free(val);
return 1;
}
if (uuid != NULL) *uuid = id;
free(val);
sqlite3_finalize(stmt);
return 0;
}
3.6. Create Zet Link
The function wwzet_link
will link UUID A to
UUID B.
void wwzet_link(weewiki_d *ww, wwzet_uuid *a, wwzet_uuid *b);
A link is created by creating a new entry using A's UUID, and having the value be the UUID of B. A UUID is prepended with a '#'.
void wwzet_link(weewiki_d *ww, wwzet_uuid *a, wwzet_uuid *b)
{
char *addr;
addr = calloc(1, 38);
addr[0] = '#';
strcpy(&addr[1], b->str);
wwzet_insert(ww, NULL, 0, a->str, 36, addr, 37);
free(addr);
}
3.7. Create Zet File
A file entry is created with wwzet_file
.
An inserted file is not actually the file, but just
a file path, prepended with a forward slash /
.
Presumably, this would link to an entry in the SQLar table
(which do not have leading slashes). Thus, an entry
/test/foo.txt
would have a corresponding SQLar file
test/foo.txt
.
int wwzet_file(weewiki_d *ww,
const char *filename,
int sz,
wwzet_uuid *uuid);
int wwzet_file(weewiki_d *ww,
const char *filename,
int sz,
wwzet_uuid *uuid)
{
return wwzet_entry_withsymbol(ww, '/', filename, sz, uuid);
}
3.8. Create Zet Group
A group entry is created with wwzet_group
.
Groups are used with the crate interface, and are used to link files to specific sqlar archives.
Groups are prefixed with '@'.
int wwzet_group(weewiki_d *ww,
const char *group,
int sz,
wwzet_uuid *uuid);
int wwzet_group(weewiki_d *ww,
const char *filename,
int sz,
wwzet_uuid *uuid)
{
return wwzet_entry_withsymbol(ww, '@', filename, sz, uuid);
}
3.9. Ergo IDs
Ergonomic IDs, or Ergo IDs are a way of of representing UUIDs in a more typist-friendly QWERTY format. traditional representations of hex values are replaced by easy to access characters in the QWERTY format.
So. What are the easy characters?
The home row is easiest: asdfghjkl;
The top row comes next: qwertyuiop
The keys that do not require any extensions are the most ergonomically efficient. These include:
home: asdfjkl;
top: qweruiop
The semi-colon ';' is a bit of an outlier. To limit things to only the alphabet, one could use 'h', which is familiar enough for vi-inclined individuals used to hjkl.
So that leaves us with:
home: asdfhjkl
top: qweruoip
treating the number system as left-to-right, home-to-top, we get:
asdfhjklqweruiop
0123456789abcdef
Convert from a hex string to ergo ID with
wwzet_hex_to_ergo
.
void wwzet_hex_to_ergo(const char *hex, int sz, char *ergo);
Converting to this ergo-id format is a pretty straightforward process. convert the ascii hex value to a number and send it to a lookup table which is just an array.
<<hexergo_lookup>>
void wwzet_hex_to_ergo(const char *hex, int sz, char *ergo)
{
int i;
for (i = 0; i < sz; i++) {
int pos;
pos = -1;
if (hex[i] >= '0' && hex[i] <= '9') {
pos = hex[i] - '0';
} else if (hex[i] >= 'a' && hex[i] <= 'f') {
pos = (hex[i] - 'a') + 10;
}
if (pos >= 0) {
ergo[i] = hexergo[pos];
} else {
ergo[i] = hex[i];
}
}
ergo[sz] = '\0';
}
static const char *hexergo = "asdfhjklqweruiop";
Convert from ergo to hex string with wwzet_ergo_to_hex
.
void wwzet_ergo_to_hex(const char *ergo, int sz, char *hex);
Converting from the ergo-id to the hex value is a little less straightforward.
<<ergohex_lookup>>
void wwzet_ergo_to_hex(const char *ergo, int sz, char *hex)
{
int i;
for (i = 0; i < sz; i++) {
if (ergo[i] >= 'a' && ergo[i] <= 'w') {
int pos = ergo[i] - 'a';
hex[i] = ergohex[pos];
} else {
hex[i] = ergo[i];
}
}
}
A lookup table will be produced by sorting the values in ascii order:
adefhijklopqrsuw
with the corresponding hex values:
02a34d567ef8b1c9
With '?' as filler:
a??def?hijkl??opqrs?u?w
or
0??2a3?4d567??ef8b1?c?9
or ascii values 97-112. which means a lookup table of size 23 with empty values.
static const char *ergohex = "0??2a3?4d567??ef8b1?c?9";
3.10. Check if entry exists
Usually for things like groups, you want to ensure that it is unique. This function will query a value with a symbol and report of it exists already.
wwzet_entry_exists
will check if a string value
val
of size sz
exists with a prepended symbol sym
.
int wwzet_entry_exists(weewiki_d *ww,
char sym,
const char *val,
int sz);
int wwzet_entry_exists(weewiki_d *ww,
char sym,
const char *val,
int sz)
{
int exists;
sqlite3 *db;
sqlite3_stmt *stmt;
int rc;
exists = 0;
db = weewiki_db(ww);
sqlite3_prepare_v2(db,
"SELECT EXISTS( "
"SELECT value FROM wikizet WHERE "
"value IS ?1 || ?2);",
-1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, &sym, 1, NULL);
sqlite3_bind_text(stmt, 2, val, sz, NULL);
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW) {
fprintf(stderr, "SQLite: '%s'\n", sqlite3_errmsg(db));
exists = -1;
} else {
exists = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
return exists;
}
prev | home | next