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.

<<typedefs>>=
typedef struct wwzet_uuid wwzet_uuid;

This UUID contains a char of 37 bytes: 36 for the UUID (including dashes) plus the null terminator.

<<structs>>=
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.

<<funcdefs>>=
void wwzet_uuid_init(wwzet_uuid *uuid);
<<funcs>>=
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.

<<funcdefs>>=
void wwzet_uuid_rng_init(void);
<<funcs>>=
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.

<<funcdefs>>=
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.

<<funcs>>=
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.

<<funcdefs>>=
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.

<<funcs>>=
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

wwzet_uuid_fromval.

Given a value (presumably, a group), return the UUID. non-zero value is an error.

<<funcdefs>>=
int wwzet_uuid_fromval(weewiki_d *ww,
                       const char *val,
                       int sz,
                       wwzet_uuid *uuid);
<<funcs>>=
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

wwzet_uuid_fromval_prefix

<<funcdefs>>=
int wwzet_uuid_fromval_prefix(weewiki_d *ww,
                              const char *val,
                              int sz,
                              char prefix,
                              wwzet_uuid *uuid);
<<funcs>>=
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.

<<funcdefs>>=
int wwzet_uuid_fromergo(weewiki_d *ww,
                        const char *ergo,
                        int sz,
                        wwzet_uuid *uuid);
<<funcs>>=
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.

<<funcdefs>>=
int wwzet_uuid_resolve(weewiki_d *ww,
                       const char *val,
                       int sz,
                       wwzet_uuid *uuid);
<<funcs>>=
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.

<<funcdefs>>=
int wwzet_entry(weewiki_d *ww,
                const char *msg,
                int sz,
                wwzet_uuid *uuid);
<<funcs>>=
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.

<<funcdefs>>=
int wwzet_entry_withsymbol(weewiki_d *ww,
                           char c,
                           const char *msg,
                           int sz,
                           wwzet_uuid *uuid);
<<funcs>>=
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.

<<funcdefs>>=
void wwzet_insert(weewiki_d *ww,
                  const char *timestamp, int tlen,
                  const char *uuid, int ulen,
                  const char *value, int vlen);
<<funcs>>=
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.

<<funcdefs>>=
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.

<<funcs>>=
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.

<<funcdefs>>=
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 '#'.

<<funcs>>=
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.

<<funcdefs>>=
int wwzet_file(weewiki_d *ww,
               const char *filename,
               int sz,
               wwzet_uuid *uuid);
<<funcs>>=
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 '@'.

<<funcdefs>>=
int wwzet_group(weewiki_d *ww,
               const char *group,
               int sz,
               wwzet_uuid *uuid);
<<funcs>>=
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.

<<funcdefs>>=
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.

<<funcs>>=
<<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';
}
<<hexergo_lookup>>=
static const char *hexergo = "asdfhjklqweruiop";

Convert from ergo to hex string with wwzet_ergo_to_hex.

<<funcdefs>>=
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.

<<funcs>>=
<<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.

<<ergohex_lookup>>=
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.

<<funcdefs>>=
int wwzet_entry_exists(weewiki_d *ww,
                       char sym,
                       const char *val,
                       int sz);
<<funcs>>=
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