From 1d9fcf710574c7225d793a3e69432ea6de92cbe4 Mon Sep 17 00:00:00 2001 From: simon987 Date: Sun, 12 Jul 2020 15:34:38 -0400 Subject: [PATCH] Manual tagging --- README.md | 2 +- docs/USAGE.md | 37 ++++++++++++++++++++++++++++- docs/manual_tag.png | Bin 0 -> 3984 bytes src/cli.c | 28 +++++++++++++++++++++- src/cli.h | 2 ++ src/ctx.h | 3 +++ src/io/serialize.c | 16 +++++++++---- src/io/store.c | 32 ++++++++++++++++++++++++++ src/io/store.h | 4 ++++ src/main.c | 15 ++++++++++-- src/static/css/dark.css | 12 ++++++++++ src/static/css/light.css | 1 + src/static/js/search.js | 46 +++++++++++++++++++++++++++---------- src/static/search.html | 2 +- src/static/stats.html | 2 +- src/web/serve.c | 34 ++++++++++++++++++--------- src/web/static_generated.c | 10 ++++---- 17 files changed, 206 insertions(+), 40 deletions(-) create mode 100644 docs/manual_tag.png diff --git a/README.md b/README.md index 14d22c6..a5d53f1 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ sist2 (Simple incremental search tool) * Extracts text and metadata from common file types \* * Generates thumbnails \* * Incremental scanning -* Automatic tagging from file attributes via [user scripts](docs/scripting.md) +* Manual tagging from the UI and automatic tagging based on file attributes via [user scripts](docs/scripting.md) * Recursive scan inside archive files \*\* * OCR support with tesseract \*\*\* * Stats page & disk utilisation visualization diff --git a/docs/USAGE.md b/docs/USAGE.md index f0bbadd..18be273 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -15,6 +15,7 @@ * [rewrite_url](#rewrite_url) * [link to specific indices](#link-to-specific-indices) * [exec-script](#exec-script) +* [tagging](#tagging) ``` Usage: sist2 scan [OPTION]... PATH @@ -57,6 +58,7 @@ Web options --es-url= Elasticsearch url. DEFAULT=http://localhost:9200 --bind= Listen on this address. DEFAULT=localhost:4090 --auth= Basic auth in user:password format + --tag-auth= Basic auth in user:password format for tagging Exec-script options --script-file= Path to user script. @@ -145,7 +147,10 @@ documents.idx/ ├── agg_mime.csv ├── agg_date.csv ├── add_size.csv -└── thumbs +├── thumbs +| ├── data.mdb +| └── lock.mdb +└── tags ├── data.mdb └── lock.mdb ``` @@ -270,6 +275,8 @@ sist2 index --print ./my_index/ | jq | less * `--es-url=` Elasticsearch url. * `--bind=` Listen on this address. * `--auth=` Basic auth in user:password format + * `--tag-auth=` Basic auth in user:password format. Works the same way as the + `--auth` argument, but authentication is only applied the `/tag/` endpoint. ### Web examples @@ -301,3 +308,31 @@ not displayed. ## exec-script The `exec-script` command is used to execute a user script for an index that has already been imported to Elasticsearch with the `index` command. Note that the documents will not be reset to their default state before each execution as the `index` command does: if you make undesired changes to the documents by accident, you will need to run `index` again to revert to the original state. + + +# Tagging + +### Manual tagging + +You can modify tags of individual documents directly from the + `web` interface. Note that you can setup authentication for this feature + with the `--tag-auth` option (See [web options](#web-options)) + +![manual_tag](docs/manual_tag.png) + +Tags that are manually added are saved both in the + index folder (in `/tags/`) and in Elasticsearch*. When re-`index`ing, + they are read from the index and automatically applied. + +You can safely copy the `/tags/` database to another index. + +See [Automatic tagging](#automatic-tagging) for information about tag + hierarchies and tag colors. + +\* *It can take a few seconds to take effect in new search queries, and the page needs + to be reloaded for the tag tab to update* + + +### Automatic tagging + +See [scripting](docs/scripting.md) documentation. \ No newline at end of file diff --git a/docs/manual_tag.png b/docs/manual_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..3ddc78cb6d47760cf84cfd639e7a6d3d4a159c90 GIT binary patch literal 3984 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 F{{W5>#A*Nl literal 0 HcmV?d00001 diff --git a/src/cli.c b/src/cli.c index cdafdaf..2576054 100644 --- a/src/cli.c +++ b/src/cli.c @@ -326,7 +326,7 @@ int web_args_validate(web_args_t *args, int argc, const char **argv) { } strncpy(args->auth_user, args->credentials, (ptr - args->credentials)); - strcpy(args->auth_pass, ptr); + strcpy(args->auth_pass, ptr + 1); if (strlen(args->auth_user) == 0) { fprintf(stderr, "--auth username must be at least one character long"); @@ -338,6 +338,31 @@ int web_args_validate(web_args_t *args, int argc, const char **argv) { args->auth_enabled = FALSE; } + if (args->tag_credentials != NULL && args->credentials != NULL) { + fprintf(stderr, "--auth and --tag-auth are mutually exclusive"); + return 1; + } + + if (args->tag_credentials != NULL) { + char *ptr = strstr(args->tag_credentials, ":"); + if (ptr == NULL) { + fprintf(stderr, "Invalid --tag-auth format, see usage\n"); + return 1; + } + + strncpy(args->auth_user, args->tag_credentials, (ptr - args->tag_credentials)); + strcpy(args->auth_pass, ptr + 1); + + if (strlen(args->auth_user) == 0) { + fprintf(stderr, "--tag-auth username must be at least one character long"); + return 1; + } + + args->tag_auth_enabled = TRUE; + } else { + args->tag_auth_enabled = FALSE; + } + args->index_count = argc - 1; args->indices = argv + 1; @@ -352,6 +377,7 @@ int web_args_validate(web_args_t *args, int argc, const char **argv) { LOG_DEBUGF("cli.c", "arg es_url=%s", args->es_url) LOG_DEBUGF("cli.c", "arg listen=%s", args->listen_address) LOG_DEBUGF("cli.c", "arg credentials=%s", args->credentials) + LOG_DEBUGF("cli.c", "arg tag_credentials=%s", args->tag_credentials) LOG_DEBUGF("cli.c", "arg auth_user=%s", args->auth_user) LOG_DEBUGF("cli.c", "arg auth_pass=%s", args->auth_pass) LOG_DEBUGF("cli.c", "arg index_count=%d", args->index_count) diff --git a/src/cli.h b/src/cli.h index c0f785d..ebd853d 100644 --- a/src/cli.h +++ b/src/cli.h @@ -48,9 +48,11 @@ typedef struct web_args { char *es_url; char *listen_address; char *credentials; + char *tag_credentials; char auth_user[256]; char auth_pass[256]; int auth_enabled; + int tag_auth_enabled; int index_count; const char **indices; } web_args_t; diff --git a/src/ctx.h b/src/ctx.h index 4be92a6..04ae8dd 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -60,6 +60,8 @@ typedef struct { char *es_url; int batch_size; tpool_t *pool; + store_t *tag_store; + GHashTable *tags; } IndexCtx_t; typedef struct { @@ -68,6 +70,7 @@ typedef struct { char *auth_user; char *auth_pass; int auth_enabled; + int tag_auth_enabled; struct index_t indices[64]; } WebCtx_t; diff --git a/src/io/serialize.c b/src/io/serialize.c index fde922e..ceecc66 100644 --- a/src/io/serialize.c +++ b/src/io/serialize.c @@ -62,7 +62,7 @@ index_descriptor_t read_index_descriptor(char *path) { int fd = open(path, O_RDONLY); if (fd == -1) { - LOG_FATALF("serialize.c", "Invalid/corrupt index (Could not find descriptor): %s: %s\n", path ,strerror(errno)) + LOG_FATALF("serialize.c", "Invalid/corrupt index (Could not find descriptor): %s: %s\n", path, strerror(errno)) } char *buf = malloc(info.st_size + 1); @@ -172,8 +172,8 @@ void write_document(document_t *doc) { dyn_buffer_t buf = dyn_buffer_create(); // Ignore root directory in the file path - doc->ext = doc->ext - ScanCtx.index.desc.root_len; - doc->base = doc->base - ScanCtx.index.desc.root_len; + doc->ext = (short) (doc->ext - ScanCtx.index.desc.root_len); + doc->base = (short) (doc->base - ScanCtx.index.desc.root_len); doc->filepath += ScanCtx.index.desc.root_len; dyn_buffer_write(&buf, doc, sizeof(line_t)); @@ -230,7 +230,7 @@ void read_index_bin(const char *path, const char *index_id, index_func func) { char uuid_str[UUID_STR_LEN]; uuid_unparse(line.uuid, uuid_str); - const char* mime_text = mime_get_mime_text(line.mime); + const char *mime_text = mime_get_mime_text(line.mime); if (mime_text == NULL) { cJSON_AddNullToObject(document, "mime"); } else { @@ -239,12 +239,18 @@ void read_index_bin(const char *path, const char *index_id, index_func func) { cJSON_AddNumberToObject(document, "size", (double) line.size); cJSON_AddNumberToObject(document, "mtime", line.mtime); - int c; + int c = 0; while ((c = getc(file)) != 0) { dyn_buffer_write_char(&buf, (char) c); } dyn_buffer_write_char(&buf, '\0'); + const char *tags_string = g_hash_table_lookup(IndexCtx.tags, buf.buf); + if (tags_string != NULL) { + cJSON *tags_arr = cJSON_Parse(tags_string); + cJSON_AddItemToObject(document, "tag", tags_arr); + } + cJSON_AddStringToObject(document, "extension", buf.buf + line.ext); if (*(buf.buf + line.ext - 1) == '.') { *(buf.buf + line.ext - 1) = '\0'; diff --git a/src/io/store.c b/src/io/store.c index c0154e9..9e08fac 100644 --- a/src/io/store.c +++ b/src/io/store.c @@ -111,3 +111,35 @@ char *store_read(store_t *store, char *key, size_t key_len, size_t *ret_vallen) return buf; } +GHashTable *store_read_all(store_t *store) { + + int count = 0; + + GHashTable *table = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); + + MDB_txn *txn = NULL; + mdb_txn_begin(store->env, NULL, MDB_RDONLY, &txn); + + MDB_cursor *cur = NULL; + mdb_cursor_open(txn, store->dbi, &cur); + + MDB_val key; + MDB_val value; + + while (mdb_cursor_get(cur, &key, &value, MDB_NEXT) == 0) { + char *key_str = malloc(key.mv_size); + memcpy(key_str, key.mv_data, key.mv_size); + char *val_str = malloc(value.mv_size); + memcpy(val_str, value.mv_data, value.mv_size); + + g_hash_table_insert(table, key_str, val_str); + count += 1; + } + + LOG_DEBUGF("store.c", "Read tags for %d documents", count); + + mdb_cursor_close(cur); + mdb_txn_abort(txn); + return table; +} + diff --git a/src/io/store.h b/src/io/store.h index d4f8b8c..5a8f04e 100644 --- a/src/io/store.h +++ b/src/io/store.h @@ -4,6 +4,8 @@ #include #include +#include + #define STORE_SIZE_TN 1024 * 1024 * 5 #define STORE_SIZE_TAG 1024 * 16 @@ -23,4 +25,6 @@ void store_write(store_t *store, char *key, size_t key_len, char *buf, size_t bu char *store_read(store_t *store, char *key, size_t key_len, size_t *ret_vallen); +GHashTable *store_read_all(store_t *store); + #endif diff --git a/src/main.c b/src/main.c index b06b54b..01fd557 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,7 @@ #define EPILOG "Made by simon987 . Released under GPL-3.0" -static const char *const Version = "2.5.2"; +static const char *const Version = "2.6.0"; static const char *const usage[] = { "sist2 scan [OPTION]... PATH", "sist2 index [OPTION]... INDEX", @@ -271,6 +271,12 @@ void sist2_index(index_args_t *args) { LOG_FATALF("main.c", "Could not open index %s: %s", args->index_path, strerror(errno)) } + char path_tmp[PATH_MAX]; + snprintf(path_tmp, sizeof(path_tmp), "%s/tags", args->index_path); + mkdir(path_tmp, S_IWUSR | S_IRUSR | S_IXUSR); + IndexCtx.tag_store = store_create(path_tmp, STORE_SIZE_TAG); + IndexCtx.tags = store_read_all(IndexCtx.tag_store); + index_func f; if (args->print) { f = print_json; @@ -303,8 +309,11 @@ void sist2_index(index_args_t *args) { if (!args->print) { finish_indexer(args->script, desc.uuid); } - tpool_destroy(IndexCtx.pool); + + store_destroy(IndexCtx.tag_store); + g_hash_table_remove_all(IndexCtx.tags); + g_hash_table_destroy(IndexCtx.tags); } void sist2_exec_script(exec_args_t *args) { @@ -330,6 +339,7 @@ void sist2_web(web_args_t *args) { WebCtx.auth_user = args->auth_user; WebCtx.auth_pass = args->auth_pass; WebCtx.auth_enabled = args->auth_enabled; + WebCtx.tag_auth_enabled = args->tag_auth_enabled; for (int i = 0; i < args->index_count; i++) { char *abs_path = abspath(args->indices[i]); @@ -419,6 +429,7 @@ int main(int argc, const char *argv[]) { OPT_STRING(0, "es-url", &common_es_url, "Elasticsearch url. DEFAULT=http://localhost:9200"), OPT_STRING(0, "bind", &web_args->listen_address, "Listen on this address. DEFAULT=localhost:4090"), OPT_STRING(0, "auth", &web_args->credentials, "Basic auth in user:password format"), + OPT_STRING(0, "tag-auth", &web_args->tag_credentials, "Basic auth in user:password format for tagging"), OPT_GROUP("Exec-script options"), OPT_STRING(0, "script-file", &common_script_path, "Path to user script."), diff --git a/src/static/css/dark.css b/src/static/css/dark.css index fc87456..71d2b2d 100644 --- a/src/static/css/dark.css +++ b/src/static/css/dark.css @@ -197,6 +197,18 @@ a:hover,.btn:hover { margin-right: 3px; } +.badge-delete { + margin-right: -2px; + margin-left: 2px; + margin-top: -1px; + font-family: monospace; + font-size: 90%; + background: rgba(0,0,0,0.2); + padding: 0.1em 0.4em; + color: white; + cursor: pointer; +} + .badge-user { color: #212529; background-color: #e0e0e0; diff --git a/src/static/css/light.css b/src/static/css/light.css index 4493424..18249ff 100644 --- a/src/static/css/light.css +++ b/src/static/css/light.css @@ -113,6 +113,7 @@ body { .badge-delete { margin-right: -2px; margin-left: 2px; + margin-top: -1px; font-family: monospace; font-size: 90%; background: rgba(0,0,0,0.2); diff --git a/src/static/js/search.js b/src/static/js/search.js index d6fc30f..6b54db7 100644 --- a/src/static/js/search.js +++ b/src/static/js/search.js @@ -117,7 +117,7 @@ window.onload = () => { minChars: 1, delay: 200, renderItem: function (item) { - return '
' + item + '
'; + return '
' + item.split("#")[0] + '
'; }, source: async function (term, suggest) { term = term.toLowerCase(); @@ -132,12 +132,18 @@ window.onload = () => { } suggest(matches.sort()); }, - onSelect: function () { + onSelect: function (e, item) { + const name = item.split("#")[0]; + const color = "#" + item.split("#")[1]; + $("#tag-color").val(color); + $("#tag-color").trigger("keyup", color); + tagBar.value = name; + e.preventDefault(); } }); [tagBar, document.getElementById("tag-color")].forEach(elem => { elem.addEventListener("keyup", e => { - if (e.key === "Enter") { + if (e.key === "Enter" && tagBar.value.length > 0) { const tag = tagBar.value + document.getElementById("tag-color").value; saveTag(tag, currentDocToTag).then(() => currentTagCallback(tag)); } @@ -162,7 +168,7 @@ window.onload = () => { }; function saveTag(tag, hit) { - const relPath = hit["_source"]["path"] + "/" + hit["_source"] + ext(hit); + const relPath = hit["_source"]["path"] + "/" + hit["_source"]["name"] + ext(hit); return $.jsonPost("/tag/" + hit["_source"]["index"], { delete: false, @@ -186,7 +192,7 @@ function saveTag(tag, hit) { } function deleteTag(tag, hit) { - const relPath = hit["_source"]["path"] + "/" + hit["_source"] + ext(hit); + const relPath = hit["_source"]["path"] + "/" + hit["_source"]["name"] + ext(hit); return $.jsonPost("/tag/" + hit["_source"]["index"], { delete: true, @@ -344,11 +350,14 @@ $.jsonPost("es", { }); function addTag(map, tag, id, count) { - let tags = tag.split("#")[0].split("."); + // let tags = tag.split("#")[0].split("."); + let tags = tag.split("."); let child = { id: id, - text: tags.length !== 1 ? tags[0] : `${tags[0]} (${count})`, + values: [id], + count: count, + text: tags.length !== 1 ? tags[0] : `${tags[0].split("#")[0]} (${count})`, name: tags[0], children: [], isLeaf: tags.length === 1, @@ -396,10 +405,15 @@ function addTag(map, tag, id, count) { let found = false; map.forEach(node => { - if (node.name === child.name) { + if (node.name.split("#")[0] === child.name.split("#")[0]) { found = true; if (tags.length !== 1) { addTag(node.children, tags.slice(1).join("."), id, count); + } else { + // Same name, different color + node.count += count; + node.text = `${tags[0].split("#")[0]} (${node.count})`; + node.values.push(id); } } }); @@ -451,7 +465,11 @@ function getSelectedNodes(tree) { //Only get children if (selected[i].text.indexOf("(") !== -1) { - selectedNodes.push(selected[i].id); + if (selected[i].values) { + selectedNodes.push(selected[i].values); + } else { + selectedNodes.push(selected[i].id); + } } } @@ -514,7 +532,9 @@ function search(after = null) { let tags = getSelectedNodes(tagTree); if (!tags.includes("any")) { - tags.forEach(term => filters.push({term: {"tag": term}})) + tags.forEach(tagGroup => { + filters.push({terms: {"tag": tagGroup}}) + }) } if (date_min && date_max) { @@ -758,6 +778,7 @@ function getNextDepth(node) { text: `${name}/ (${bucket.doc_count})`, depth: node.depth + 1, index: node.index, + values: [bucket.key], children: true, } }).filter(x => x !== null) @@ -788,6 +809,7 @@ function createPathTree(target) { selectedIndices.forEach(index => { pathTree.addNode({ id: "/" + index, + values: ["/" + index], text: `/[${indexMap[index]}]`, index: index, depth: 0, @@ -838,8 +860,8 @@ function getTagChoices() { resp["suggest"]["tag"][0]["options"].map(opt => opt["_source"]["tag"]).forEach(tags => { tags.forEach(tag => { const t = tag.split("#")[0]; - if (result.indexOf(t) === -1) { - result.push(t); + if (!result.find(x => x.split("#")[0] === t)) { + result.push(tag); } }); }); diff --git a/src/static/search.html b/src/static/search.html index 45d22b6..b6621b2 100644 --- a/src/static/search.html +++ b/src/static/search.html @@ -11,7 +11,7 @@