Compare commits

..

23 Commits

Author SHA1 Message Date
f423863acb Add option to search in path for sqlite #402 2023-10-16 21:14:46 -04:00
49a21a5a25 Version bump 2023-10-13 19:02:26 -04:00
560aa82ce7 add discord invite 2023-10-09 18:10:51 -04:00
b8c905bd64 expose indexRoot value to documents 2023-10-08 21:00:03 -04:00
8299237ea0 version bump 2023-10-07 15:04:19 -04:00
31646a2747 Fix CURL error 2023-10-07 13:16:22 -04:00
d9d77de47f Update docs 2023-10-07 11:07:13 -04:00
5f0957d029 Update readme 2023-10-07 10:15:41 -04:00
1cc48f7f33 Version bump 2023-10-07 10:15:03 -04:00
e1e22fd79a Add missing image 2023-09-28 17:52:57 -04:00
786bbc3859 Make sure dateMin and dateMax are not equal with sqlite frontend 2023-09-27 19:49:43 -04:00
9698ea0c37 Fix #419 2023-09-26 19:51:17 -04:00
f345fc1a9a version bump, 2023-09-26 17:58:43 -04:00
660fbf75d8 Potential tpool_wait fix for #416, #397, #399 2023-09-26 17:58:07 -04:00
33ae585879 Fix #426 2023-09-25 21:36:50 -04:00
5729cbd6b4 update gitignore 2023-09-16 09:44:29 -04:00
a19ec3305a Fix #415, fix sqlite-index error 2023-09-16 09:43:55 -04:00
8fdb832c85 refactor index schema, remove sidecar parsing, remove TS 2023-09-05 18:59:18 -04:00
b81ccebdb1 Fix #406 2023-08-20 19:53:50 -04:00
b2d214a19a Merge pull request #412 from simon987/dependabot/npm_and_yarn/sist2-vue/protobufjs-6.11.4
Bump protobufjs from 6.11.3 to 6.11.4 in /sist2-vue
2023-08-20 19:45:09 -04:00
69438464bf Fix python path for user scripts 2023-08-19 17:04:47 -04:00
dependabot[bot]
aa60b526f4 Bump protobufjs from 6.11.3 to 6.11.4 in /sist2-vue
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.11.3 to 6.11.4.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/commits)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-19 20:09:14 +00:00
2ea8b51b34 Merge pull request #411 from simon987/embeddings
rework user scripts, add support for embedding search
2023-08-19 16:08:50 -04:00
100 changed files with 1579 additions and 2581 deletions

View File

@@ -1,9 +0,0 @@
FROM simon987/sist2-build
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash
RUN apt update -y; apt install -y nodejs && rm -rf /var/lib/apt/lists/*
ENV DEBIAN_FRONTEND=noninteractive
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

View File

@@ -1,16 +0,0 @@
{
"name": "sist2-dev",
"dockerComposeFile": [
"docker-compose.yml"
],
"service": "sist2-dev",
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cpptools-extension-pack"
]
}
},
"remoteUser": "root",
"workspaceFolder": "/app/"
}

View File

@@ -1,8 +0,0 @@
version: "3"
services:
sist2-dev:
build: .
command: sleep infinity
volumes:
- ../:/app

View File

@@ -38,4 +38,6 @@ build/
__pycache__/ __pycache__/
sist2-vue/dist sist2-vue/dist
sist2-admin/frontend/dist sist2-admin/frontend/dist
*.fts *.fts
.git
third-party/libscan/third-party/ext_*/*

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ thumbs
*.cbp *.cbp
CMakeCache.txt CMakeCache.txt
CMakeFiles CMakeFiles
cmake-build-default-event-trace
cmake-build-debug cmake-build-debug
cmake_install.cmake cmake_install.cmake
Makefile Makefile

View File

@@ -53,7 +53,6 @@ add_executable(
src/types.h src/types.h
src/log.c src/log.h src/log.c src/log.h
src/cli.c src/cli.h src/cli.c src/cli.h
src/parsing/sidecar.c src/parsing/sidecar.h
src/database/database.c src/database/database.h src/database/database.c src/database/database.h
src/parsing/fs_util.h src/parsing/fs_util.h

View File

@@ -48,5 +48,6 @@ COPY --from=build /build/build/sist2 /root/sist2
# sist2-admin # sist2-admin
WORKDIR /root/sist2-admin WORKDIR /root/sist2-admin
COPY sist2-admin/requirements.txt /root/sist2-admin/ COPY sist2-admin/requirements.txt /root/sist2-admin/
RUN python3 -m pip install --no-cache -r /root/sist2-admin/requirements.txt RUN ln /usr/bin/python3 /usr/bin/python
RUN python -m pip install --no-cache -r /root/sist2-admin/requirements.txt
COPY --from=build /build/sist2-admin/ /root/sist2-admin/ COPY --from=build /build/sist2-admin/ /root/sist2-admin/

View File

@@ -4,6 +4,8 @@
**Demo**: [sist2.simon987.net](https://sist2.simon987.net/) **Demo**: [sist2.simon987.net](https://sist2.simon987.net/)
**Community URL:** [Discord](https://discord.gg/2PEjDy3Rfs)
# sist2 # sist2
sist2 (Simple incremental search tool) sist2 (Simple incremental search tool)
@@ -46,7 +48,7 @@ services:
- "discovery.type=single-node" - "discovery.type=single-node"
- "ES_JAVA_OPTS=-Xms2g -Xmx2g" - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
sist2-admin: sist2-admin:
image: simon987/sist2:3.1.4-x64-linux image: simon987/sist2:3.3.4-x64-linux
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- ./sist2-admin-data/:/sist2-admin/ - ./sist2-admin-data/:/sist2-admin/
@@ -153,10 +155,10 @@ indices, but it uses much less memory and is easier to set up.
| Query syntax | [fts5](https://www.sqlite.org/fts5.html) | [query_string](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) | | Query syntax | [fts5](https://www.sqlite.org/fts5.html) | [query_string](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) |
| Fuzzy search | | ✓ | | Fuzzy search | | ✓ |
| Media Types tree real-time updating | | ✓ | | Media Types tree real-time updating | | ✓ |
| Search in file `path` | [WIP](https://github.com/simon987/sist2/issues/402) | ✓ |
| Manual tagging | ✓ | ✓ | | Manual tagging | ✓ | ✓ |
| User scripts | ✓ | ✓ | | User scripts | ✓ | ✓ |
| Media Type breakdown for search results | | ✓ | | Media Type breakdown for search results | | ✓ |
| Embeddings search | ✓ *O(n)* | ✓ *O(logn)* |
### NER ### NER

View File

@@ -16,9 +16,9 @@ Lightning-fast file system indexer and search tool.
Scan options Scan options
-t, --threads=<int> Number of threads. DEFAULT: 1 -t, --threads=<int> Number of threads. DEFAULT: 1
-q, --thumbnail-quality=<int> Thumbnail quality, on a scale of 0 to 100, 100 being the best. DEFAULT: 50 -q, --thumbnail_count-quality=<int> Thumbnail quality, on a scale of 0 to 100, 100 being the best. DEFAULT: 50
--thumbnail-size=<int> Thumbnail size, in pixels. DEFAULT: 552 --thumbnail_count-size=<int> Thumbnail size, in pixels. DEFAULT: 552
--thumbnail-count=<int> Number of thumbnails to generate. Set a value > 1 to create video previews, set to 0 to disable thumbnails. DEFAULT: 1 --thumbnail_count-count=<int> Number of thumbnails to generate. Set a value > 1 to create video previews, set to 0 to disable thumbnails. DEFAULT: 1
--content-size=<int> Number of bytes to be extracted from text documents. Set to 0 to disable. DEFAULT: 32768 --content-size=<int> Number of bytes to be extracted from text documents. Set to 0 to disable. DEFAULT: 32768
-o, --output=<str> Output index file path. DEFAULT: index.sist2 -o, --output=<str> Output index file path. DEFAULT: index.sist2
--incremental If the output file path exists, only scan new or modified files. --incremental If the output file path exists, only scan new or modified files.
@@ -78,9 +78,9 @@ Made by simon987 <me@simon987.net>. Released under GPL-3.0
#### Thumbnail database size estimation #### Thumbnail database size estimation
See chart below for rough estimate of thumbnail size vs. thumbnail size & quality arguments: See chart below for rough estimate of thumbnail_count size vs. thumbnail_count size & quality arguments:
For example, `--thumbnail-size=500`, `--thumbnail-quality=50` for a directory with 8 million images will create a thumbnail database For example, `--thumbnail_count-size=500`, `--thumbnail_count-quality=50` for a directory with 8 million images will create a thumbnail_count database
that is about `8000000 * 11.8kB = 94.4GB`. that is about `8000000 * 11.8kB = 94.4GB`.
![thumbnail_size](thumbnail_size.png) ![thumbnail_size](thumbnail_size.png)
@@ -92,7 +92,7 @@ Simple scan
sist2 scan ~/Documents sist2 scan ~/Documents
sist2 scan \ sist2 scan \
--threads 4 --content-size 16000000 --thumbnail-quality 2 --archive shallow \ --threads 4 --content-size 16000000 --thumbnail_count-quality 2 --archive shallow \
--name "My Documents" --rewrite-url "http://nas.domain.local/My Documents/" \ --name "My Documents" --rewrite-url "http://nas.domain.local/My Documents/" \
~/Documents -o ./documents.sist2 ~/Documents -o ./documents.sist2
``` ```
@@ -175,6 +175,32 @@ Using a version >=7.14.0 is recommended to enable the following features:
When using a legacy version of ES, a notice will be displayed next to the sist2 version in the web UI. When using a legacy version of ES, a notice will be displayed next to the sist2 version in the web UI.
If you don't care about the features above, you can ignore it or disable it in the configuration page. If you don't care about the features above, you can ignore it or disable it in the configuration page.
# Embeddings search
Since v3.2.0, User scripts can be used to generate _embeddings_ (vector of float32 numbers) which are stored in the .sist2 index file
(see [scripting](scripting.md)). Embeddings can be used for:
* Nearest-neighbor queries (e.g. "return the documents most similar to this one")
* Semantic searches (e.g. "return the documents that are most closely related to the given topic")
In theory, embeddings can be created for any type of documents (image, text, audio etc.).
For example, the [clip](https://github.com/simon987/sist2-script-clip) User Script, generates 512-d embeddings of images
(videos are also supported using the thumbnails generated by sist2). When the user enters a query in the "Embeddings Search"
textbox, the query's embedding is generated in their browser, leveraging the ONNX web runtime.
<details>
<summary>Screenshots</summary>
![embeddings-1](embeddings-1.png)
![embeddings-2](embeddings-2.png)
1. Embeddings search bar. You can select the model using the dropdown on the left.
2. This icon appears for indices with embeddings search enabled.
3. Documents with this icon have embeddings. Click on the icon to perform KNN search.
</details>
# Tagging # Tagging
### Manual tagging ### Manual tagging
@@ -199,43 +225,4 @@ See [Automatic tagging](#automatic-tagging) for information about tag
### Automatic tagging ### Automatic tagging
See [scripting](scripting.md) documentation. See [scripting](scripting.md) documentation.
# Sidecar files
When scanning, sist2 will read metadata from `.s2meta` JSON files and overwrite the
original document's indexed metadata (does not modify the actual file). Sidecar metadata files will also work inside archives.
Sidecar files themselves are not saved in the index.
This feature is useful to leverage third-party applications such as speech-to-text or
OCR to add additional metadata to a file.
**Example**
```
~/Documents/
├── Video.mp4
└── Video.mp4.s2meta
```
The sidecar file must have exactly the same file path and the `.s2meta` suffix.
`Video.mp4.s2meta`:
```json
{
"content": "This sidecar file will overwrite some metadata fields of Video.mp4",
"author": "Some author",
"duration": 12345,
"bitrate": 67890,
"some_arbitrary_field": [1,2,3]
}
```
```
sist2 scan ~/Documents -o ./docs.sist2
sist2 index ./docs.sist2
```
*NOTE*: It is technically possible to overwrite the `tag` value using sidecar files, however,
it is not currently possible to restore both manual tags and sidecar tags without user scripts
while reindexing.

BIN
docs/embeddings-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
docs/embeddings-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -1,131 +0,0 @@
import sqlite3
import orjson as json
import os
import string
from hashlib import md5
import random
from tqdm import tqdm
schema = """
CREATE TABLE thumbnail (
id TEXT NOT NULL CHECK (
length(id) = 32
),
num INTEGER NOT NULL,
data BLOB NOT NULL,
PRIMARY KEY(id, num)
) WITHOUT ROWID;
CREATE TABLE version (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);
CREATE TABLE document (
id TEXT PRIMARY KEY NOT NULL CHECK (
length(id) = 32
),
marked INTEGER NOT NULL DEFAULT (1),
version INTEGER NOT NULL REFERENCES version(id),
mtime INTEGER NOT NULL,
size INTEGER NOT NULL,
json_data TEXT NOT NULL CHECK (
json_valid(json_data)
)
);
CREATE TABLE delete_list (
id TEXT PRIMARY KEY CHECK (
length(id) = 32
)
) WITHOUT ROWID;
CREATE TABLE tag (
id TEXT NOT NULL,
tag TEXT NOT NULL,
PRIMARY KEY (id, tag)
);
CREATE TABLE document_sidecar (
id TEXT PRIMARY KEY NOT NULL, json_data TEXT NOT NULL
) WITHOUT ROWID;
CREATE TABLE descriptor (
id TEXT NOT NULL, version_major INTEGER NOT NULL,
version_minor INTEGER NOT NULL, version_patch INTEGER NOT NULL,
root TEXT NOT NULL, name TEXT NOT NULL,
rewrite_url TEXT, timestamp INTEGER NOT NULL
);
CREATE TABLE stats_treemap (
path TEXT NOT NULL, size INTEGER NOT NULL
);
CREATE TABLE stats_size_agg (
bucket INTEGER NOT NULL, count INTEGER NOT NULL
);
CREATE TABLE stats_date_agg (
bucket INTEGER NOT NULL, count INTEGER NOT NULL
);
CREATE TABLE stats_mime_agg (
mime TEXT NOT NULL, size INTEGER NOT NULL,
count INTEGER NOT NULL
);
CREATE TABLE embedding (
id TEXT REFERENCES document(id),
model_id INTEGER NOT NULL references model(id),
start INTEGER NOT NULL,
end INTEGER,
embedding BLOB NOT NULL,
PRIMARY KEY (id, model_id, start)
);
CREATE TABLE model (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE CHECK (
length(name) < 16
),
url TEXT,
path TEXT NOT NULL UNIQUE,
size INTEGER NOT NULL,
type TEXT NOT NULL CHECK (
type IN ('flat', 'nested')
)
);
"""
content = "".join(random.choices(string.ascii_letters, k=500))
def gen_document():
return [
md5(random.randbytes(8)).hexdigest(),
json.dumps({
"content": content,
"mime": "image/jpeg",
"extension": "jpeg",
"name": "test",
"path": "",
})
]
if __name__ == "__main__":
DB_NAME = "big_index.sist2"
SIZE = 30_000_000
os.remove(DB_NAME)
db = sqlite3.connect(DB_NAME)
db.executescript(schema)
db.executescript("""
PRAGMA journal_mode = OFF;
PRAGMA synchronous = 0;
""")
for _ in tqdm(range(SIZE), total=SIZE):
db.execute(
"INSERT INTO document (id, version, mtime, size, json_data) VALUES (?, 1, 1000000, 10000, ?)",
gen_document()
)
# 1. Enable rowid from document
# 2. CREATE TABLE marked (
# id INTEGER PRIMARY KEY,
# marked int
# );
# 3. Set FK for document_sidecar, embedding, tag, thumbnail
# 4. Toggle FK if debug
db.commit()

View File

@@ -449,5 +449,4 @@ image/x-sigma-x3f, xf3
image/x-sony-arw, arw image/x-sony-arw, arw
image/x-sony-sr2, sr2 image/x-sony-sr2, sr2
image/x-sony-srf, srf image/x-sony-srf, srf
image/x-epson-erf, erf image/x-epson-erf, erf
sist2/sidecar, s2meta
1 application/x-matlab-data mat
449 image/x-sony-arw arw
450 image/x-sony-sr2 sr2
451 image/x-sony-srf srf
452 image/x-epson-erf erf
sist2/sidecar s2meta

View File

@@ -3,6 +3,7 @@ import zlib
mimes = {} mimes = {}
noparse = set() noparse = set()
ext_in_hash = set() ext_in_hash = set()
mime_ids = {}
major_mime = { major_mime = {
"sist2": 0, "sist2": 0,
@@ -102,6 +103,9 @@ cnt = 1
def mime_id(mime): def mime_id(mime):
if mime in mime_ids:
return mime_ids[mime]
global cnt global cnt
major = mime.split("/")[0] major = mime.split("/")[0]
mime_id = str((major_mime[major] << 16) + cnt) mime_id = str((major_mime[major] << 16) + cnt)
@@ -127,9 +131,7 @@ def mime_id(mime):
elif mime == "application/x-empty": elif mime == "application/x-empty":
cnt -= 1 cnt -= 1
return "1" return "1"
elif mime == "sist2/sidecar": mime_ids[mime] = mime_id
cnt -= 1
return "2"
return mime_id return mime_id
@@ -197,4 +199,12 @@ with open("scripts/mime.csv") as f:
print(f"case {crc(mime)}: return {clean(mime)};") print(f"case {crc(mime)}: return {clean(mime)};")
print("default: return 0;}}") print("default: return 0;}}")
# mime list
mime_list = ",".join(mime_id(x) for x in mimes.keys()) + ",0"
print(f"unsigned int mime_ids[] = {{{mime_list}}};")
print("unsigned int* get_mime_ids() { return mime_ids; }")
print("#endif") print("#endif")

View File

@@ -81,7 +81,7 @@ function humanDuration(sec_num) {
return `${seconds}s`; return `${seconds}s`;
} }
return "<0s"; return "<1s";
} }
export default { export default {
@@ -134,7 +134,7 @@ export default {
duration: this.taskDuration(row), duration: this.taskDuration(row),
time: moment.utc(row.started).local().format("dd, MMM Do YYYY, HH:mm:ss"), time: moment.utc(row.started).local().format("dd, MMM Do YYYY, HH:mm:ss"),
logs: null, logs: null,
status: [0,1].includes(row.return_code) ? "ok" : "failed", status: row.return_code === 0 ? "ok" : "failed",
_row: row _row: row
})); }));
}); });

View File

@@ -222,7 +222,10 @@ async def delete_job(name: str):
@app.delete("/api/frontend/{name:str}") @app.delete("/api/frontend/{name:str}")
async def delete_frontend(name: str): async def delete_frontend(name: str):
if name in RUNNING_FRONTENDS: if name in RUNNING_FRONTENDS:
os.kill(RUNNING_FRONTENDS[name], signal.SIGTERM) try:
os.kill(RUNNING_FRONTENDS[name], signal.SIGTERM)
except ProcessLookupError:
pass
del RUNNING_FRONTENDS[name] del RUNNING_FRONTENDS[name]
frontend = db["frontends"][name] frontend = db["frontends"][name]

View File

@@ -120,6 +120,10 @@ class Sist2Task:
logger.info(f"Started task {self.display_name}") logger.info(f"Started task {self.display_name}")
def set_pid(self, pid):
self.pid = pid
class Sist2ScanTask(Sist2Task): class Sist2ScanTask(Sist2Task):
@@ -133,13 +137,10 @@ class Sist2ScanTask(Sist2Task):
else: else:
self.job.scan_options.output = None self.job.scan_options.output = None
def set_pid(pid): return_code = sist2.scan(self.job.scan_options, logs_cb=self.log_callback, set_pid_cb=self.set_pid)
self.pid = pid
return_code = sist2.scan(self.job.scan_options, logs_cb=self.log_callback, set_pid_cb=set_pid)
self.ended = datetime.utcnow() self.ended = datetime.utcnow()
is_ok = return_code in (0, 1) is_ok = (return_code in (0, 1)) if "debug" in sist2.bin_path else (return_code == 0)
if not is_ok: if not is_ok:
self._logger.error(json.dumps({"sist2-admin": f"Process returned non-zero exit code ({return_code})"})) self._logger.error(json.dumps({"sist2-admin": f"Process returned non-zero exit code ({return_code})"}))
@@ -165,6 +166,9 @@ class Sist2ScanTask(Sist2Task):
self.job.previous_index_path = self.job.index_path self.job.previous_index_path = self.job.index_path
db["jobs"][self.job.name] = self.job db["jobs"][self.job.name] = self.job
if is_ok:
return 0
return return_code return return_code
@@ -185,7 +189,7 @@ class Sist2IndexTask(Sist2Task):
logger.debug(f"Fetched search backend options for {self.job.index_options.search_backend}") logger.debug(f"Fetched search backend options for {self.job.index_options.search_backend}")
return_code = sist2.index(self.job.index_options, search_backend, logs_cb=self.log_callback) return_code = sist2.index(self.job.index_options, search_backend, logs_cb=self.log_callback, set_pid_cb=self.set_pid)
self.ended = datetime.utcnow() self.ended = datetime.utcnow()
duration = self.ended - self.started duration = self.ended - self.started
@@ -249,7 +253,7 @@ class Sist2UserScriptTask(Sist2Task):
super().run(sist2, db) super().run(sist2, db)
try: try:
self.user_script.setup(self.log_callback) self.user_script.setup(self.log_callback, self.set_pid)
except Exception as e: except Exception as e:
logger.error(f"Setup for {self.user_script.name} failed: ") logger.error(f"Setup for {self.user_script.name} failed: ")
logger.exception(e) logger.exception(e)
@@ -269,7 +273,7 @@ class Sist2UserScriptTask(Sist2Task):
self.log_callback({"sist2-admin": f"Starting user script with {executable=}, {index_path=}, {extra_args=}"}) self.log_callback({"sist2-admin": f"Starting user script with {executable=}, {index_path=}, {extra_args=}"})
proc = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.user_script.script_dir()) proc = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.user_script.script_dir())
self.pid = proc.pid self.set_pid(proc.pid)
t_stderr = Thread(target=self._consume_logs, args=(self.log_callback, proc, "stderr", False)) t_stderr = Thread(target=self._consume_logs, args=(self.log_callback, proc, "stderr", False))
t_stderr.start() t_stderr.start()
@@ -316,7 +320,7 @@ class TaskQueue:
def _tasks_failed(self): def _tasks_failed(self):
done = set() done = set()
for row in self._db["task_done"].sql("WHERE return_code NOT IN (0,1)"): for row in self._db["task_done"].sql("WHERE return_code != 0"):
done.add(uuid.UUID(row["id"])) done.add(uuid.UUID(row["id"]))
return done return done

View File

@@ -20,7 +20,7 @@ def set_executable(file):
os.chmod(file, os.stat(file).st_mode | stat.S_IEXEC) os.chmod(file, os.stat(file).st_mode | stat.S_IEXEC)
def _initialize_git_repository(url, path, log_cb, force_clone): def _initialize_git_repository(url, path, log_cb, force_clone, set_pid_cb):
log_cb({"sist2-admin": f"Cloning {url}"}) log_cb({"sist2-admin": f"Cloning {url}"})
if force_clone or not os.path.exists(os.path.join(path, ".git")): if force_clone or not os.path.exists(os.path.join(path, ".git")):
@@ -36,14 +36,18 @@ def _initialize_git_repository(url, path, log_cb, force_clone):
log_cb({"sist2-admin": f"Executing setup script {setup_script}"}) log_cb({"sist2-admin": f"Executing setup script {setup_script}"})
set_executable(setup_script) set_executable(setup_script)
result = subprocess.run([setup_script], cwd=path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) proc = subprocess.Popen([setup_script], cwd=path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in result.stdout.split(b"\n"): set_pid_cb(proc.pid)
proc.wait()
stdout = proc.stdout.read()
for line in stdout.split(b"\n"):
if line: if line:
log_cb({"stdout": line.decode()}) log_cb({"stdout": line.decode()})
log_cb({"stdout": f"Executed setup script {setup_script}, return code = {result.returncode}"}) log_cb({"stdout": f"Executed setup script {setup_script}, return code = {proc.returncode}"})
if result.returncode != 0: if proc.returncode != 0:
raise Exception("Error when running setup script!") raise Exception("Error when running setup script!")
log_cb({"sist2-admin": f"Initialized git repository in {path}"}) log_cb({"sist2-admin": f"Initialized git repository in {path}"})
@@ -60,11 +64,11 @@ class UserScript(BaseModel):
def script_dir(self): def script_dir(self):
return os.path.join(SCRIPT_FOLDER, self.name) return os.path.join(SCRIPT_FOLDER, self.name)
def setup(self, log_cb): def setup(self, log_cb, set_pid_cb):
os.makedirs(self.script_dir(), exist_ok=True) os.makedirs(self.script_dir(), exist_ok=True)
if self.type == ScriptType.GIT: if self.type == ScriptType.GIT:
_initialize_git_repository(self.git_repository, self.script_dir(), log_cb, self.force_clone) _initialize_git_repository(self.git_repository, self.script_dir(), log_cb, self.force_clone, set_pid_cb)
self.force_clone = False self.force_clone = False
elif self.type == ScriptType.SIMPLE: elif self.type == ScriptType.SIMPLE:
self._setup_simple() self._setup_simple()

View File

@@ -243,7 +243,7 @@ class Sist2:
self.bin_path = bin_path self.bin_path = bin_path
self._data_dir = data_directory self._data_dir = data_directory
def index(self, options: IndexOptions, search_backend: Sist2SearchBackend, logs_cb): def index(self, options: IndexOptions, search_backend: Sist2SearchBackend, logs_cb, set_pid_cb):
args = [ args = [
self.bin_path, self.bin_path,
@@ -255,6 +255,8 @@ class Sist2:
logs_cb({"sist2-admin": f"Starting sist2 command with args {args}"}) logs_cb({"sist2-admin": f"Starting sist2 command with args {args}"})
proc = Popen(args, stdout=PIPE, stderr=PIPE) proc = Popen(args, stdout=PIPE, stderr=PIPE)
set_pid_cb(proc.pid)
t_stderr = Thread(target=self._consume_logs_stderr, args=(logs_cb, proc)) t_stderr = Thread(target=self._consume_logs_stderr, args=(logs_cb, proc))
t_stderr.start() t_stderr.start()

View File

@@ -33,7 +33,6 @@
"@types/underscore": "^1.11.6", "@types/underscore": "^1.11.6",
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8", "@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-plugin-vuex": "~5.0.8", "@vue/cli-plugin-vuex": "~5.0.8",
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"@vue/test-utils": "^1.0.3", "@vue/test-utils": "^1.0.3",
@@ -45,7 +44,6 @@
"portal-vue": "^2.1.7", "portal-vue": "^2.1.7",
"sass": "^1.26.11", "sass": "^1.26.11",
"sass-loader": "^10.0.2", "sass-loader": "^10.0.2",
"typescript": "^4.9.5",
"vue-cli-plugin-bootstrap-vue": "~0.8.2", "vue-cli-plugin-bootstrap-vue": "~0.8.2",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.11"
} }
@@ -2332,12 +2330,6 @@
"integrity": "sha512-G2oC64I/sR817KDL2b2Mc7+diXyxcibyUeLMyexU4K/sG8hyt/YMlbBK0TVhx/YQ1ehfzgXhLuq2YQHIL4bXUQ==", "integrity": "sha512-G2oC64I/sR817KDL2b2Mc7+diXyxcibyUeLMyexU4K/sG8hyt/YMlbBK0TVhx/YQ1ehfzgXhLuq2YQHIL4bXUQ==",
"dev": true "dev": true
}, },
"node_modules/@types/webpack-env": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.0.tgz",
"integrity": "sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw==",
"dev": true
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.4", "version": "8.5.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
@@ -2638,218 +2630,6 @@
"@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0"
} }
}, },
"node_modules/@vue/cli-plugin-typescript": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-typescript/-/cli-plugin-typescript-5.0.8.tgz",
"integrity": "sha512-JKJOwzJshBqsmp4yLBexwVMebOZ4VGJgbnYvmHVxasJOStF2RxwyW28ZF+zIvASGdat4sAUuo/3mAQyVhm7JHg==",
"dev": true,
"dependencies": {
"@babel/core": "^7.12.16",
"@types/webpack-env": "^1.15.2",
"@vue/cli-shared-utils": "^5.0.8",
"babel-loader": "^8.2.2",
"fork-ts-checker-webpack-plugin": "^6.4.0",
"globby": "^11.0.2",
"thread-loader": "^3.0.0",
"ts-loader": "^9.2.5",
"webpack": "^5.54.0"
},
"peerDependencies": {
"@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0",
"cache-loader": "^4.1.0",
"typescript": ">=2",
"vue": "^2 || ^3.2.13",
"vue-template-compiler": "^2.0.0"
},
"peerDependenciesMeta": {
"cache-loader": {
"optional": true
},
"vue-template-compiler": {
"optional": true
}
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/@vue/cli-plugin-typescript/node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"dependencies": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz",
"integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==",
"dev": true,
"dependencies": {
"chalk": "^4.1.0",
"enhanced-resolve": "^5.0.0",
"micromatch": "^4.0.0",
"semver": "^7.3.4"
},
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"typescript": "*",
"webpack": "^5.0.0"
}
},
"node_modules/@vue/cli-plugin-typescript/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@vue/cli-plugin-vuex": { "node_modules/@vue/cli-plugin-vuex": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz",
@@ -4279,14 +4059,6 @@
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true "dev": true
}, },
"node_modules/buffer-json": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-json/-/buffer-json-2.0.0.tgz",
"integrity": "sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/bytes": { "node_modules/bytes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -4296,25 +4068,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/cache-loader": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-4.1.0.tgz",
"integrity": "sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"buffer-json": "^2.0.0",
"find-cache-dir": "^3.0.0",
"loader-utils": "^1.2.3",
"mkdirp": "^0.5.1",
"neo-async": "^2.6.1",
"schema-utils": "^2.0.0"
},
"engines": {
"node": ">= 8.9.0"
}
},
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -4938,22 +4691,6 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"node_modules/cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dev": true,
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "6.0.5", "version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -5442,15 +5179,6 @@
"node": ">=6.0" "node": ">=6.0"
} }
}, },
"node_modules/deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/default-gateway": { "node_modules/default-gateway": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz",
@@ -6392,166 +6120,6 @@
} }
} }
}, },
"node_modules/fork-ts-checker-webpack-plugin": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz",
"integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"chokidar": "^3.4.2",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"glob": "^7.1.6",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"engines": {
"node": ">=10",
"yarn": ">=1.0.0"
},
"peerDependencies": {
"eslint": ">= 6",
"typescript": ">= 2.7",
"vue-template-compiler": "*",
"webpack": ">= 4"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
},
"vue-template-compiler": {
"optional": true
}
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
},
"engines": {
"node": ">= 8.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/forwarded": { "node_modules/forwarded": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -9431,9 +8999,9 @@
"dev": true "dev": true
}, },
"node_modules/protobufjs": { "node_modules/protobufjs": {
"version": "6.11.3", "version": "6.11.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
"integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@protobufjs/aspromise": "^1.1.2", "@protobufjs/aspromise": "^1.1.2",
@@ -10503,15 +10071,6 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/terser": { "node_modules/terser": {
"version": "5.16.1", "version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
@@ -10732,19 +10291,6 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/underscore": { "node_modules/underscore": {
"version": "1.13.1", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",
@@ -13492,12 +13038,6 @@
"integrity": "sha512-G2oC64I/sR817KDL2b2Mc7+diXyxcibyUeLMyexU4K/sG8hyt/YMlbBK0TVhx/YQ1ehfzgXhLuq2YQHIL4bXUQ==", "integrity": "sha512-G2oC64I/sR817KDL2b2Mc7+diXyxcibyUeLMyexU4K/sG8hyt/YMlbBK0TVhx/YQ1ehfzgXhLuq2YQHIL4bXUQ==",
"dev": true "dev": true
}, },
"@types/webpack-env": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.0.tgz",
"integrity": "sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw==",
"dev": true
},
"@types/ws": { "@types/ws": {
"version": "8.5.4", "version": "8.5.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
@@ -13736,153 +13276,6 @@
"@vue/cli-shared-utils": "^5.0.8" "@vue/cli-shared-utils": "^5.0.8"
} }
}, },
"@vue/cli-plugin-typescript": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-typescript/-/cli-plugin-typescript-5.0.8.tgz",
"integrity": "sha512-JKJOwzJshBqsmp4yLBexwVMebOZ4VGJgbnYvmHVxasJOStF2RxwyW28ZF+zIvASGdat4sAUuo/3mAQyVhm7JHg==",
"dev": true,
"requires": {
"@babel/core": "^7.12.16",
"@types/webpack-env": "^1.15.2",
"@vue/cli-shared-utils": "^5.0.8",
"babel-loader": "^8.2.2",
"fork-ts-checker-webpack-plugin": "^6.4.0",
"globby": "^11.0.2",
"thread-loader": "^3.0.0",
"ts-loader": "^9.2.5",
"webpack": "^5.54.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"requires": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"ts-loader": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz",
"integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==",
"dev": true,
"requires": {
"chalk": "^4.1.0",
"enhanced-resolve": "^5.0.0",
"micromatch": "^4.0.0",
"semver": "^7.3.4"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"@vue/cli-plugin-vuex": { "@vue/cli-plugin-vuex": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz",
@@ -14956,36 +14349,12 @@
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true "dev": true
}, },
"buffer-json": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-json/-/buffer-json-2.0.0.tgz",
"integrity": "sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw==",
"dev": true,
"optional": true,
"peer": true
},
"bytes": { "bytes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"dev": true "dev": true
}, },
"cache-loader": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-4.1.0.tgz",
"integrity": "sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw==",
"dev": true,
"optional": true,
"peer": true,
"requires": {
"buffer-json": "^2.0.0",
"find-cache-dir": "^3.0.0",
"loader-utils": "^1.2.3",
"mkdirp": "^0.5.1",
"neo-async": "^2.6.1",
"schema-utils": "^2.0.0"
}
},
"call-bind": { "call-bind": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -15467,19 +14836,6 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dev": true,
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
}
},
"cross-spawn": { "cross-spawn": {
"version": "6.0.5", "version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -15899,12 +15255,6 @@
"ms": "2.1.2" "ms": "2.1.2"
} }
}, },
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
},
"default-gateway": { "default-gateway": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz",
@@ -16636,113 +15986,6 @@
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
}, },
"fork-ts-checker-webpack-plugin": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz",
"integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"chokidar": "^3.4.2",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"glob": "^7.1.6",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"forwarded": { "forwarded": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -18886,9 +18129,9 @@
"dev": true "dev": true
}, },
"protobufjs": { "protobufjs": {
"version": "6.11.3", "version": "6.11.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
"integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
"requires": { "requires": {
"@protobufjs/aspromise": "^1.1.2", "@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2", "@protobufjs/base64": "^1.1.2",
@@ -19757,12 +19000,6 @@
} }
} }
}, },
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
"dev": true
},
"terser": { "terser": {
"version": "5.16.1", "version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
@@ -19912,12 +19149,6 @@
"mime-types": "~2.1.24" "mime-types": "~2.1.24"
} }
}, },
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true
},
"underscore": { "underscore": {
"version": "1.13.1", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",

View File

@@ -32,7 +32,6 @@
"@types/underscore": "^1.11.6", "@types/underscore": "^1.11.6",
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8", "@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-plugin-vuex": "~5.0.8", "@vue/cli-plugin-vuex": "~5.0.8",
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"@vue/test-utils": "^1.0.3", "@vue/test-utils": "^1.0.3",
@@ -44,7 +43,6 @@
"portal-vue": "^2.1.7", "portal-vue": "^2.1.7",
"sass": "^1.26.11", "sass": "^1.26.11",
"sass-loader": "^10.0.2", "sass-loader": "^10.0.2",
"typescript": "^4.9.5",
"vue-cli-plugin-bootstrap-vue": "~0.8.2", "vue-cli-plugin-bootstrap-vue": "~0.8.2",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.11"
}, },

View File

@@ -1,116 +1,20 @@
import axios from "axios"; import axios from "axios";
import {ext, strUnescape, lum} from "./util"; import {strUnescape, lum, sid} from "./util";
import Sist2Query from "@/Sist2ElasticsearchQuery"; import Sist2Query from "@/Sist2ElasticsearchQuery";
import store from "@/store"; import store from "@/store";
export interface EsTag {
id: string
count: number
color: string | undefined
isLeaf: boolean
}
export interface Tag {
style: string
text: string
rawText: string
fg: string
bg: string
userTag: boolean
}
export interface Index {
name: string
version: string
id: string
idPrefix: string
timestamp: number
models: []
}
export interface EsHit {
_index: string
_id: string
_score: number
_type: string
_tags: Tag[]
_seq: number
_source: {
path: string
size: number
mime: string
name: string
extension: string
index: string
_depth: number
mtime: number
videoc: string
audioc: string
parent: string
width: number
height: number
duration: number
tag: string[]
checksum: string
thumbnail: string
}
_props: {
isSubDocument: boolean
isImage: boolean
isGif: boolean
isVideo: boolean
isPlayableVideo: boolean
isPlayableImage: boolean
isAudio: boolean
hasThumbnail: boolean
hasVidPreview: boolean
imageAspectRatio: number
/** Number of thumbnails available */
tnNum: number
}
highlight: {
name: string[] | undefined,
content: string[] | undefined,
}
}
function getIdPrefix(indices: Index[], id: string): string {
for (let i = 4; i < 32; i++) {
const prefix = id.slice(0, i);
if (indices.filter(idx => idx.id.slice(0, i) == prefix).length == 1) {
return prefix;
}
}
return id;
}
export interface EsResult {
took: number
hits: {
// TODO: ES 6.X ?
total: {
value: number
}
hits: EsHit[]
}
aggregations: any
}
class Sist2Api { class Sist2Api {
private readonly baseUrl: string baseUrl;
private sist2Info: any sist2Info;
private queryfunc: () => EsResult; queryfunc;
constructor(baseUrl: string) { constructor(baseUrl) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
init(queryFunc: () => EsResult) { init(queryFunc) {
this.queryfunc = queryFunc; this.queryfunc = queryFunc;
} }
@@ -127,29 +31,16 @@ class Sist2Api {
.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i) .filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i)
} }
getSist2Info(): Promise<any> { getSist2Info() {
return axios.get(`${this.baseUrl}i`).then(resp => { return axios.get(`${this.baseUrl}i`).then(resp => {
const indices = resp.data.indices as Index[];
resp.data.indices = indices.map(idx => {
return {
id: idx.id,
name: idx.name,
timestamp: idx.timestamp,
version: idx.version,
models: idx.models,
idPrefix: getIdPrefix(indices, idx.id),
} as Index;
});
this.sist2Info = resp.data; this.sist2Info = resp.data;
return resp.data; return resp.data;
}) })
} }
setHitProps(hit: EsHit): void { setHitProps(hit) {
hit["_props"] = {} as any; hit["_props"] = {};
const mimeCategory = hit._source.mime == null ? null : hit._source.mime.split("/")[0]; const mimeCategory = hit._source.mime == null ? null : hit._source.mime.split("/")[0];
@@ -157,7 +48,7 @@ class Sist2Api {
hit._props.isSubDocument = true; hit._props.isSubDocument = true;
} }
if ("thumbnail" in hit._source) { if ("thumbnail" in hit._source && hit._source.thumbnail > 0) {
hit._props.hasThumbnail = true; hit._props.hasThumbnail = true;
if (Number.isNaN(Number(hit._source.thumbnail))) { if (Number.isNaN(Number(hit._source.thumbnail))) {
@@ -213,8 +104,8 @@ class Sist2Api {
} }
} }
setHitTags(hit: EsHit): void { setHitTags(hit) {
const tags = [] as Tag[]; const tags = [];
// User tags // User tags
if ("tag" in hit._source) { if ("tag" in hit._source) {
@@ -226,10 +117,10 @@ class Sist2Api {
hit._tags = tags; hit._tags = tags;
} }
createUserTag(tag: string): Tag { createUserTag(tag) {
const tokens = tag.split("."); const tokens = tag.split(".");
const colorToken = tokens.pop() as string; const colorToken = tokens.pop();
const bg = colorToken; const bg = colorToken;
const fg = lum(colorToken) > 50 ? "#000" : "#fff"; const fg = lum(colorToken) > 50 ? "#000" : "#fff";
@@ -241,25 +132,32 @@ class Sist2Api {
text: tokens.join("."), text: tokens.join("."),
rawText: tag, rawText: tag,
userTag: true, userTag: true,
} as Tag; };
} }
search(): Promise<EsResult> { search() {
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
return this.ftsQuery(this.queryfunc()) return this.ftsQuery(this.queryfunc())
} else { } else {
return this.esQuery(this.queryfunc()); return this.esQuery(this.queryfunc());
} }
} }
esQuery(query: any): Promise<EsResult> { _getIndexRoot(indexId) {
console.log(indexId)
console.log(this.sist2Info.indices.find(idx => idx.id === indexId))
return this.sist2Info.indices.find(idx => idx.id === indexId).root;
}
esQuery(query) {
return axios.post(`${this.baseUrl}es`, query).then(resp => { return axios.post(`${this.baseUrl}es`, query).then(resp => {
const res = resp.data as EsResult; const res = resp.data;
if (res.hits?.hits) { if (res.hits?.hits) {
res.hits.hits.forEach((hit: EsHit) => { res.hits.hits.forEach((hit) => {
hit["_source"]["name"] = strUnescape(hit["_source"]["name"]); hit["_source"]["name"] = strUnescape(hit["_source"]["name"]);
hit["_source"]["path"] = strUnescape(hit["_source"]["path"]); hit["_source"]["path"] = strUnescape(hit["_source"]["path"]);
hit["_source"]["indexRoot"] = this._getIndexRoot(hit["_source"]["index"]);
this.setHitProps(hit); this.setHitProps(hit);
this.setHitTags(hit); this.setHitTags(hit);
@@ -270,9 +168,9 @@ class Sist2Api {
}); });
} }
ftsQuery(query: any): Promise<EsResult> { ftsQuery(query) {
return axios.post(`${this.baseUrl}fts/search`, query).then(resp => { return axios.post(`${this.baseUrl}fts/search`, query).then(resp => {
const res = resp.data as any; const res = resp.data;
if (res.hits.hits) { if (res.hits.hits) {
res.hits.hits.forEach(hit => { res.hits.hits.forEach(hit => {
@@ -293,7 +191,7 @@ class Sist2Api {
}); });
} }
private getMimeTypesEs(query) { getMimeTypesEs(query) {
const AGGS = { const AGGS = {
mimeTypes: { mimeTypes: {
terms: { terms: {
@@ -322,7 +220,7 @@ class Sist2Api {
}); });
} }
private getMimeTypesSqlite(): Promise<[{ mime: string, count: number }]> { getMimeTypesSqlite() {
return axios.get(`${this.baseUrl}fts/mimetypes`) return axios.get(`${this.baseUrl}fts/mimetypes`)
.then(resp => { .then(resp => {
return resp.data; return resp.data;
@@ -332,15 +230,15 @@ class Sist2Api {
async getMimeTypes(query = undefined) { async getMimeTypes(query = undefined) {
let buckets; let buckets;
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
buckets = await this.getMimeTypesSqlite(); buckets = await this.getMimeTypesSqlite();
} else { } else {
buckets = await this.getMimeTypesEs(query); buckets = await this.getMimeTypesEs(query);
} }
const mimeMap: any[] = []; const mimeMap = [];
buckets.sort((a: any, b: any) => a.mime > b.mime).forEach((bucket: any) => { buckets.sort((a, b) => a.mime > b.mime).forEach((bucket) => {
const tmp = bucket.mime.split("/"); const tmp = bucket.mime.split("/");
const category = tmp[0]; const category = tmp[0];
const mime = tmp[1]; const mime = tmp[1];
@@ -374,7 +272,7 @@ class Sist2Api {
return {buckets, mimeMap}; return {buckets, mimeMap};
} }
_createEsTag(tag: string, count: number): EsTag { _createEsTag(tag, count) {
const tokens = tag.split("."); const tokens = tag.split(".");
if (/.*\.#[0-9a-fA-F]{6}/.test(tag)) { if (/.*\.#[0-9a-fA-F]{6}/.test(tag)) {
@@ -394,7 +292,7 @@ class Sist2Api {
}; };
} }
private getTagsEs() { getTagsEs() {
return this.esQuery({ return this.esQuery({
aggs: { aggs: {
tags: { tags: {
@@ -407,21 +305,21 @@ class Sist2Api {
size: 0, size: 0,
}).then(resp => { }).then(resp => {
return resp["aggregations"]["tags"]["buckets"] return resp["aggregations"]["tags"]["buckets"]
.sort((a: any, b: any) => a["key"].localeCompare(b["key"])) .sort((a, b) => a["key"].localeCompare(b["key"]))
.map((bucket: any) => this._createEsTag(bucket["key"], bucket["doc_count"])); .map((bucket) => this._createEsTag(bucket["key"], bucket["doc_count"]));
}); });
} }
private getTagsSqlite() { getTagsSqlite() {
return axios.get(`${this.baseUrl}/fts/tags`) return axios.get(`${this.baseUrl}/fts/tags`)
.then(resp => { .then(resp => {
return resp.data.map(tag => this._createEsTag(tag.tag, tag.count)) return resp.data.map(tag => this._createEsTag(tag.tag, tag.count))
}); });
} }
async getTags(): Promise<EsTag[]> { async getTags() {
let tags; let tags;
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
tags = await this.getTagsSqlite(); tags = await this.getTagsSqlite();
} else { } else {
tags = await this.getTagsEs(); tags = await this.getTagsEs();
@@ -430,7 +328,7 @@ class Sist2Api {
// Remove duplicates (same tag with different color) // Remove duplicates (same tag with different color)
const seen = new Set(); const seen = new Set();
return tags.filter((t: EsTag) => { return tags.filter((t) => {
if (seen.has(t.id)) { if (seen.has(t.id)) {
return false; return false;
} }
@@ -439,31 +337,29 @@ class Sist2Api {
}); });
} }
saveTag(tag: string, hit: EsHit) { saveTag(tag, hit) {
return axios.post(`${this.baseUrl}tag/` + hit["_source"]["index"], { return axios.post(`${this.baseUrl}tag/${sid(hit)}`, {
delete: false, delete: false,
name: tag, name: tag,
doc_id: hit["_id"]
}); });
} }
deleteTag(tag: string, hit: EsHit) { deleteTag(tag, hit) {
return axios.post(`${this.baseUrl}tag/` + hit["_source"]["index"], { return axios.post(`${this.baseUrl}tag/${sid(hit)}`, {
delete: true, delete: true,
name: tag, name: tag,
doc_id: hit["_id"]
}); });
} }
searchPaths(indexId, minDepth, maxDepth, prefix = null) { searchPaths(indexId, minDepth, maxDepth, prefix = null) {
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
return this.searchPathsSqlite(indexId, minDepth, minDepth, prefix); return this.searchPathsSqlite(indexId, minDepth, minDepth, prefix);
} else { } else {
return this.searchPathsEs(indexId, minDepth, maxDepth, prefix); return this.searchPathsEs(indexId, minDepth, maxDepth, prefix);
} }
} }
private searchPathsSqlite(indexId, minDepth, maxDepth, prefix) { searchPathsSqlite(indexId, minDepth, maxDepth, prefix) {
return axios.post(`${this.baseUrl}fts/paths`, { return axios.post(`${this.baseUrl}fts/paths`, {
indexId, minDepth, maxDepth, prefix indexId, minDepth, maxDepth, prefix
}).then(resp => { }).then(resp => {
@@ -471,7 +367,7 @@ class Sist2Api {
}); });
} }
private searchPathsEs(indexId, minDepth, maxDepth, prefix): Promise<[{ path: string, count: number }]> { searchPathsEs(indexId, minDepth, maxDepth, prefix) {
const query = { const query = {
query: { query: {
@@ -516,23 +412,25 @@ class Sist2Api {
}); });
} }
private getDateRangeSqlite() { getDateRangeSqlite() {
return axios.get(`${this.baseUrl}fts/dateRange`) return axios.get(`${this.baseUrl}fts/dateRange`)
.then(resp => ({ .then(resp => ({
min: resp.data.dateMin, min: resp.data.dateMin,
max: resp.data.dateMax, max: (resp.data.dateMax === resp.data.dateMin)
? resp.data.dateMax + 1
: resp.data.dateMax,
})); }));
} }
getDateRange(): Promise<{ min: number, max: number }> { getDateRange() {
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
return this.getDateRangeSqlite(); return this.getDateRangeSqlite();
} else { } else {
return this.getDateRangeEs(); return this.getDateRangeEs();
} }
} }
private getDateRangeEs() { getDateRangeEs() {
return this.esQuery({ return this.esQuery({
// TODO: filter current selected indices // TODO: filter current selected indices
aggs: { aggs: {
@@ -549,7 +447,7 @@ class Sist2Api {
if (range.min == null) { if (range.min == null) {
range.min = 0; range.min = 0;
range.max = 1; range.max = 1;
} else if (range.min == range.max) { } else if (range.min === range.max) {
range.max += 1; range.max += 1;
} }
@@ -557,7 +455,7 @@ class Sist2Api {
}); });
} }
private getPathSuggestionsSqlite(text: string) { getPathSuggestionsSqlite(text) {
return axios.post(`${this.baseUrl}fts/paths`, { return axios.post(`${this.baseUrl}fts/paths`, {
prefix: text, prefix: text,
minDepth: 1, minDepth: 1,
@@ -567,7 +465,7 @@ class Sist2Api {
}) })
} }
private getPathSuggestionsEs(text) { getPathSuggestionsEs(text) {
return this.esQuery({ return this.esQuery({
suggest: { suggest: {
path: { path: {
@@ -585,31 +483,31 @@ class Sist2Api {
}); });
} }
getPathSuggestions(text: string): Promise<string[]> { getPathSuggestions(text) {
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
return this.getPathSuggestionsSqlite(text); return this.getPathSuggestionsSqlite(text);
} else { } else {
return this.getPathSuggestionsEs(text) return this.getPathSuggestionsEs(text)
} }
} }
getTreemapStat(indexId: string) { getTreemapStat(indexId) {
return `${this.baseUrl}s/${indexId}/TMAP`; return `${this.baseUrl}s/${indexId}/TMAP`;
} }
getMimeStat(indexId: string) { getMimeStat(indexId) {
return `${this.baseUrl}s/${indexId}/MAGG`; return `${this.baseUrl}s/${indexId}/MAGG`;
} }
getSizeStat(indexId: string) { getSizeStat(indexId) {
return `${this.baseUrl}s/${indexId}/SAGG`; return `${this.baseUrl}s/${indexId}/SAGG`;
} }
getDateStat(indexId: string) { getDateStat(indexId) {
return `${this.baseUrl}s/${indexId}/DAGG`; return `${this.baseUrl}s/${indexId}/DAGG`;
} }
private getDocumentEs(docId: string, highlight: boolean, fuzzy: boolean) { getDocumentEs(sid, highlight, fuzzy) {
const query = Sist2Query.searchQuery(); const query = Sist2Query.searchQuery();
if (highlight) { if (highlight) {
@@ -648,7 +546,7 @@ class Sist2Api {
query.query.bool.must = [query.query.bool.must]; query.query.bool.must = [query.query.bool.must];
} }
query.query.bool.must.push({match: {_id: docId}}); query.query.bool.must.push({match: {_id: sid}});
delete query["sort"]; delete query["sort"];
delete query["aggs"]; delete query["aggs"];
@@ -669,35 +567,35 @@ class Sist2Api {
}); });
} }
private getDocumentSqlite(docId: string): Promise<EsHit> { getDocumentSqlite(sid) {
return axios.get(`${this.baseUrl}/fts/d/${docId}`) return axios.get(`${this.baseUrl}/fts/d/${sid}`)
.then(resp => ({ .then(resp => ({
_source: resp.data _source: resp.data
} as EsHit)); }));
} }
getDocument(docId: string, highlight: boolean, fuzzy: boolean): Promise<EsHit | null> { getDocument(sid, highlight, fuzzy) {
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
return this.getDocumentSqlite(docId); return this.getDocumentSqlite(sid);
} else { } else {
return this.getDocumentEs(docId, highlight, fuzzy); return this.getDocumentEs(sid, highlight, fuzzy);
} }
} }
getTagSuggestions(prefix: string): Promise<string[]> { getTagSuggestions(prefix) {
if (this.backend() == "sqlite") { if (this.backend() === "sqlite") {
return this.getTagSuggestionsSqlite(prefix); return this.getTagSuggestionsSqlite(prefix);
} else { } else {
return this.getTagSuggestionsEs(prefix); return this.getTagSuggestionsEs(prefix);
} }
} }
private getTagSuggestionsSqlite(prefix): Promise<string[]> { getTagSuggestionsSqlite(prefix) {
return axios.post(`${this.baseUrl}/fts/suggestTags`, prefix) return axios.post(`${this.baseUrl}/fts/suggestTags`, prefix)
.then(resp => (resp.data)); .then(resp => (resp.data));
} }
private getTagSuggestionsEs(prefix): Promise<string[]> { getTagSuggestionsEs(prefix) {
return this.esQuery({ return this.esQuery({
suggest: { suggest: {
tag: { tag: {
@@ -723,8 +621,8 @@ class Sist2Api {
}); });
} }
getEmbeddings(indexId, docId, modelId) { getEmbeddings(sid, modelId) {
return axios.post(`${this.baseUrl}/e/${indexId}/${docId}/${modelId.toString().padStart(3, '0')}`) return axios.post(`${this.baseUrl}/e/${sid}/${modelId.toString().padStart(3, '0')}`)
.then(resp => (resp.data)); .then(resp => (resp.data));
} }
} }

View File

@@ -1,5 +1,5 @@
import store from "./store"; import store from "@/store";
import sist2Api, {EsHit, Index} from "@/Sist2Api"; import Sist2Api from "@/Sist2Api";
const SORT_MODES = { const SORT_MODES = {
score: { score: {
@@ -7,62 +7,62 @@ const SORT_MODES = {
{_score: {order: "desc"}}, {_score: {order: "desc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._score key: (hit) => hit._score
}, },
random: { random: {
mode: [ mode: [
{_score: {order: "desc"}}, {_score: {order: "desc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._score key: (hit) => hit._score
}, },
dateAsc: { dateAsc: {
mode: [ mode: [
{mtime: {order: "asc"}}, {mtime: {order: "asc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._source.mtime key: (hit) => hit._source.mtime
}, },
dateDesc: { dateDesc: {
mode: [ mode: [
{mtime: {order: "desc"}}, {mtime: {order: "desc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._source.mtime key: (hit) => hit._source.mtime
}, },
sizeAsc: { sizeAsc: {
mode: [ mode: [
{size: {order: "asc"}}, {size: {order: "asc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._source.size key: (hit) => hit._source.size
}, },
sizeDesc: { sizeDesc: {
mode: [ mode: [
{size: {order: "desc"}}, {size: {order: "desc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._source.size key: (hit) => hit._source.size
}, },
nameAsc: { nameAsc: {
mode: [ mode: [
{name: {order: "asc"}}, {name: {order: "asc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._source.name key: (hit) => hit._source.name
}, },
nameDesc: { nameDesc: {
mode: [ mode: [
{name: {order: "desc"}}, {name: {order: "desc"}},
{_tie: {order: "asc"}} {_tie: {order: "asc"}}
], ],
key: (hit: EsHit) => hit._source.name key: (hit) => hit._source.name
} }
} as any; };
class Sist2ElasticsearchQuery { class Sist2ElasticsearchQuery {
searchQuery(blankSearch: boolean = false): any { searchQuery(blankSearch = false) {
const getters = store.getters; const getters = store.getters;
@@ -76,7 +76,7 @@ class Sist2ElasticsearchQuery {
const fuzzy = getters.fuzzy; const fuzzy = getters.fuzzy;
const size = getters.size; const size = getters.size;
const after = getters.lastDoc; const after = getters.lastDoc;
const selectedIndexIds = getters.selectedIndices.map((idx: Index) => idx.id) const selectedIndexIds = getters.selectedIndices.map((idx) => idx.id)
const selectedMimeTypes = getters.selectedMimeTypes; const selectedMimeTypes = getters.selectedMimeTypes;
const selectedTags = getters.selectedTags; const selectedTags = getters.selectedTags;
const sortMode = getters.embedding ? "score" : getters.sortMode; const sortMode = getters.embedding ? "score" : getters.sortMode;
@@ -86,7 +86,7 @@ class Sist2ElasticsearchQuery {
const filters = [ const filters = [
{terms: {index: selectedIndexIds}} {terms: {index: selectedIndexIds}}
] as any[]; ];
const fields = [ const fields = [
"name^8", "name^8",
@@ -138,7 +138,7 @@ class Sist2ElasticsearchQuery {
if (getters.optTagOrOperator) { if (getters.optTagOrOperator) {
filters.push({terms: {"tag": selectedTags}}); filters.push({terms: {"tag": selectedTags}});
} else { } else {
selectedTags.forEach((tag: string) => filters.push({term: {"tag": tag}})); selectedTags.forEach((tag) => filters.push({term: {"tag": tag}}));
} }
} }
} }
@@ -173,7 +173,7 @@ class Sist2ElasticsearchQuery {
}, },
sort: SORT_MODES[sortMode].mode, sort: SORT_MODES[sortMode].mode,
size: size, size: size,
} as any; };
if (!after) { if (!after) {
q.aggs = { q.aggs = {
@@ -193,7 +193,7 @@ class Sist2ElasticsearchQuery {
if (getters.embedding) { if (getters.embedding) {
delete q.query; delete q.query;
const field = "emb." + sist2Api.models().find(m => m.id == getters.embeddingsModel).path; const field = "emb." + Sist2Api.models().find(m => m.id === getters.embeddingsModel).path;
if (hasKnn) { if (hasKnn) {
// Use knn (8.8+) // Use knn (8.8+)

View File

@@ -1,5 +1,4 @@
import store from "./store"; import store from "@/store";
import {EsHit, Index} from "@/Sist2Api";
const SORT_MODES = { const SORT_MODES = {
score: { score: {
@@ -29,18 +28,12 @@ const SORT_MODES = {
"sort": "name", "sort": "name",
"sortAsc": false "sortAsc": false
} }
} as any; };
interface SortMode {
text: string
mode: any[]
key: (hit: EsHit) => any
}
class Sist2ElasticsearchQuery { class Sist2ElasticsearchQuery {
searchQuery(): any { searchQuery() {
const getters = store.getters; const getters = store.getters;
@@ -52,7 +45,7 @@ class Sist2ElasticsearchQuery {
const dateMax = getters.dateMax; const dateMax = getters.dateMax;
const size = getters.size; const size = getters.size;
const after = getters.lastDoc; const after = getters.lastDoc;
const selectedIndexIds = getters.selectedIndices.map((idx: Index) => idx.id) const selectedIndexIds = getters.selectedIndices.map((idx) => idx.id)
const selectedMimeTypes = getters.selectedMimeTypes; const selectedMimeTypes = getters.selectedMimeTypes;
const selectedTags = getters.selectedTags; const selectedTags = getters.selectedTags;
@@ -95,7 +88,7 @@ class Sist2ElasticsearchQuery {
if (selectedTags.length > 0) { if (selectedTags.length > 0) {
q["tags"] = selectedTags q["tags"] = selectedTags
} }
if (getters.sortMode == "random") { if (getters.sortMode === "random") {
q["seed"] = getters.seed; q["seed"] = getters.seed;
} }
if (getters.optHighlight) { if (getters.optHighlight) {
@@ -108,11 +101,13 @@ class Sist2ElasticsearchQuery {
q["embedding"] = getters.embedding; q["embedding"] = getters.embedding;
q["sort"] = "embedding"; q["sort"] = "embedding";
q["sortAsc"] = false; q["sortAsc"] = false;
} else if (getters.sortMode == "embedding") { } else if (getters.sortMode === "embedding") {
q["sort"] = "sort" q["sort"] = "sort"
q["sortAsc"] = true; q["sortAsc"] = true;
} }
q["searchInPath"] = getters.optSearchInPath;
return q; return q;
} }
} }

View File

@@ -9,8 +9,7 @@
<script> <script>
import * as d3 from "d3"; import * as d3 from "d3";
import {burrow} from "@/util-js" import {humanFileSize, burrow} from "@/util";
import {humanFileSize} from "@/util";
import Sist2Api from "@/Sist2Api"; import Sist2Api from "@/Sist2Api";
import domtoimage from "dom-to-image"; import domtoimage from "dom-to-image";

View File

@@ -31,8 +31,7 @@
<script> <script>
import noUiSlider from 'nouislider'; import noUiSlider from 'nouislider';
import 'nouislider/dist/nouislider.css'; import 'nouislider/dist/nouislider.css';
import {humanDate} from "@/util"; import {humanDate, mergeTooltips} from "@/util";
import {mergeTooltips} from "@/util-js";
export default { export default {
name: "DateSlider", name: "DateSlider",

View File

@@ -16,7 +16,7 @@
<!-- Audio player--> <!-- Audio player-->
<audio v-if="doc._props.isAudio" ref="audio" preload="none" class="audio-fit fit" controls <audio v-if="doc._props.isAudio" ref="audio" preload="none" class="audio-fit fit" controls
:type="doc._source.mime" :type="doc._source.mime"
:src="`f/${doc._source.index}/${doc._id}`" :src="`f/${sid(doc)}`"
@play="onAudioPlay()"></audio> @play="onAudioPlay()"></audio>
<b-card-body class="padding-03"> <b-card-body class="padding-03">
@@ -43,7 +43,7 @@
</template> </template>
<script> <script>
import {ext, humanFileSize, humanTime} from "@/util"; import {ext, humanFileSize, humanTime, sid} from "@/util";
import TagContainer from "@/components/TagContainer.vue"; import TagContainer from "@/components/TagContainer.vue";
import DocFileTitle from "@/components/DocFileTitle.vue"; import DocFileTitle from "@/components/DocFileTitle.vue";
import DocInfoModal from "@/components/DocInfoModal.vue"; import DocInfoModal from "@/components/DocInfoModal.vue";
@@ -69,13 +69,14 @@ export default {
} }
}, },
methods: { methods: {
sid: sid,
humanFileSize: humanFileSize, humanFileSize: humanFileSize,
humanTime: humanTime, humanTime: humanTime,
onInfoClick() { onInfoClick() {
this.showInfo = true; this.showInfo = true;
}, },
onEmbeddingClick() { onEmbeddingClick() {
Sist2Api.getEmbeddings(this.doc._source.index, this.doc._id, this.$store.state.embeddingsModel).then(embeddings => { Sist2Api.getEmbeddings(sid(this.doc), this.$store.state.embeddingsModel).then(embeddings => {
this.$store.commit("setEmbeddingText", ""); this.$store.commit("setEmbeddingText", "");
this.$store.commit("setEmbedding", embeddings); this.$store.commit("setEmbedding", embeddings);
this.$store.commit("setEmbeddingDoc", this.doc); this.$store.commit("setEmbeddingDoc", this.doc);

View File

@@ -1,15 +1,16 @@
<template> <template>
<GridLayout <GridLayout
ref="grid-layout" ref="grid-layout"
:options="gridOptions" :options="gridOptions"
@append="append" @append="append"
@layout-complete="$emit('layout-complete')" @layout-complete="$emit('layout-complete')"
> >
<DocCard v-for="doc in docs" :key="doc._id" :doc="doc" :width="width"></DocCard> <DocCard v-for="doc in docs" :key="sid(doc)" :doc="doc" :width="width"></DocCard>
</GridLayout> </GridLayout>
</template> </template>
<script> <script>
import {sid} from "@/util";
import Vue from "vue"; import Vue from "vue";
import DocCard from "@/components/DocCard"; import DocCard from "@/components/DocCard";
@@ -18,50 +19,53 @@ import VueInfiniteGrid from "@egjs/vue-infinitegrid";
Vue.use(VueInfiniteGrid); Vue.use(VueInfiniteGrid);
export default Vue.extend({ export default Vue.extend({
components: { components: {
DocCard, DocCard,
},
props: ["docs", "append"],
data() {
return {
width: 0,
gridOptions: {
align: "center",
margin: 0,
transitionDuration: 0,
isOverflowScroll: false,
isConstantSize: false,
useFit: false,
// Indicates whether keep the number of DOMs is maintained. If the useRecycle value is 'true', keep the number
// of DOMs is maintained. If the useRecycle value is 'false', the number of DOMs will increase as card elements
// are added.
useRecycle: false
}
}
},
computed: {
colCount() {
const columns = this.$store.getters["optColumns"];
if (columns === "auto") {
return Math.round(this.$refs["grid-layout"].$el.scrollWidth / 300)
}
return columns;
}, },
}, props: ["docs", "append"],
mounted() { data() {
this.width = this.$refs["grid-layout"].$el.scrollWidth / this.colCount; return {
width: 0,
gridOptions: {
align: "center",
margin: 0,
transitionDuration: 0,
isOverflowScroll: false,
isConstantSize: false,
useFit: false,
// Indicates whether keep the number of DOMs is maintained. If the useRecycle value is 'true', keep the number
// of DOMs is maintained. If the useRecycle value is 'false', the number of DOMs will increase as card elements
// are added.
useRecycle: false
}
}
},
methods: {
sid,
},
computed: {
colCount() {
const columns = this.$store.getters["optColumns"];
if (this.colCount === 1) { if (columns === "auto") {
this.$refs["grid-layout"].$el.classList.add("grid-single-column"); return Math.round(this.$refs["grid-layout"].$el.scrollWidth / 300)
} }
return columns;
},
},
mounted() {
this.width = this.$refs["grid-layout"].$el.scrollWidth / this.colCount;
this.$store.subscribe((mutation) => { if (this.colCount === 1) {
if (mutation.type === "busUpdateWallItems" && this.$refs["grid-layout"]) { this.$refs["grid-layout"].$el.classList.add("grid-single-column");
this.$refs["grid-layout"].updateItems(); }
}
}); this.$store.subscribe((mutation) => {
}, if (mutation.type === "busUpdateWallItems" && this.$refs["grid-layout"]) {
this.$refs["grid-layout"].updateItems();
}
});
},
}); });
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<a :href="`f/${doc._source.index}/${doc._id}`" <a :href="`f/${sid(doc)}`"
:class="doc._source.embedding ? 'file-title-anchor-with-embedding' : 'file-title-anchor'" target="_blank"> :class="doc._source.embedding ? 'file-title-anchor-with-embedding' : 'file-title-anchor'" target="_blank">
<div class="file-title" :title="doc._source.path + '/' + doc._source.name + ext(doc)" <div class="file-title" :title="doc._source.path + '/' + doc._source.name + ext(doc)"
v-html="fileName() + ext(doc)"></div> v-html="fileName() + ext(doc)"></div>
@@ -7,12 +7,13 @@
</template> </template>
<script> <script>
import {ext} from "@/util"; import {ext, sid} from "@/util";
export default { export default {
name: "DocFileTitle", name: "DocFileTitle",
props: ["doc"], props: ["doc"],
methods: { methods: {
sid: sid,
ext: ext, ext: ext,
fileName() { fileName() {
if (!this.doc.highlight) { if (!this.doc.highlight) {

View File

@@ -1,33 +1,34 @@
<template> <template>
<b-modal :visible="show" size="lg" :hide-footer="true" static lazy @close="$emit('close')" @hide="$emit('close')" <b-modal :visible="show" size="lg" :hide-footer="true" static lazy @close="$emit('close')" @hide="$emit('close')"
> >
<template #modal-title> <template #modal-title>
<h5 class="modal-title" :title="doc._source.name + ext(doc)"> <h5 class="modal-title" :title="doc._source.name + ext(doc)">
{{ doc._source.name + ext(doc) }} {{ doc._source.name + ext(doc) }}
<router-link :to="`/file?byId=${doc._id}`">#</router-link> <router-link :to="`/file?byId=${doc._id}`">#</router-link>
</h5> </h5>
</template> </template>
<img v-if="doc._props.hasThumbnail" :src="`t/${doc._source.index}/${doc._id}`" alt="" class="fit card-img-top"> <img v-if="doc._props.hasThumbnail" :src="`t/${sid(doc)}`" alt="" class="fit card-img-top">
<InfoTable :doc="doc"></InfoTable> <InfoTable :doc="doc"></InfoTable>
<LazyContentDiv :doc-id="doc._id"></LazyContentDiv> <LazyContentDiv :sid="sid(doc)"></LazyContentDiv>
</b-modal> </b-modal>
</template> </template>
<script> <script>
import {ext} from "@/util"; import {ext, sid} from "@/util";
import InfoTable from "@/components/InfoTable"; import InfoTable from "@/components/InfoTable";
import LazyContentDiv from "@/components/LazyContentDiv"; import LazyContentDiv from "@/components/LazyContentDiv";
export default { export default {
name: "DocInfoModal", name: "DocInfoModal",
components: {LazyContentDiv, InfoTable}, components: {LazyContentDiv, InfoTable},
props: ["doc", "show"], props: ["doc", "show"],
methods: { methods: {
ext: ext, ext: ext,
} sid: sid
}
} }
</script> </script>

View File

@@ -1,45 +1,49 @@
<template> <template>
<b-list-group class="mt-3"> <b-list-group class="mt-3">
<DocListItem v-for="doc in docs" :key="doc._id" :doc="doc"></DocListItem> <DocListItem v-for="doc in docs" :key="sid(doc)" :doc="doc"></DocListItem>
</b-list-group> </b-list-group>
</template> </template>
<script lang="ts"> <script>
import {sid} from "@/util";
import DocListItem from "@/components/DocListItem.vue"; import DocListItem from "@/components/DocListItem.vue";
import Vue from "vue"; import Vue from "vue";
export default Vue.extend({ export default Vue.extend({
name: "DocList", name: "DocList",
components: {DocListItem}, components: {DocListItem},
props: ["docs", "append"], props: ["docs", "append"],
mounted() { mounted() {
window.addEventListener("scroll", () => { window.addEventListener("scroll", () => {
const threshold = 400; const threshold = 400;
const app = document.getElementById("app"); const app = document.getElementById("app");
if ((window.innerHeight + window.scrollY) >= app.offsetHeight - threshold) { if ((window.innerHeight + window.scrollY) >= app.offsetHeight - threshold) {
this.append(); this.append();
} }
}); });
} },
methods: {
sid: sid
}
}); });
</script> </script>
<style> <style>
.theme-black .list-group-item { .theme-black .list-group-item {
background: #212121; background: #212121;
color: #e0e0e0; color: #e0e0e0;
border-bottom: none; border-bottom: none;
border-left: none; border-left: none;
border-right: none; border-right: none;
border-radius: 0; border-radius: 0;
padding: .25rem 0.5rem; padding: .25rem 0.5rem;
} }
.theme-black .list-group-item:first-child { .theme-black .list-group-item:first-child {
border-top: none; border-top: none;
} }
</style> </style>

View File

@@ -17,10 +17,10 @@
</div> </div>
<img v-if="doc._props.isPlayableImage || doc._props.isPlayableVideo" <img v-if="doc._props.isPlayableImage || doc._props.isPlayableVideo"
:src="(doc._props.isGif && hover) ? `f/${doc._source.index}/${doc._id}` : `t/${doc._source.index}/${doc._id}`" :src="(doc._props.isGif && hover) ? `f/${sid(doc)}` : `t/${sid(doc)}`"
alt="" alt=""
class="pointer fit-sm" @click="onThumbnailClick()"> class="pointer fit-sm" @click="onThumbnailClick()">
<img v-else :src="`t/${doc._source.index}/${doc._id}`" alt="" <img v-else :src="`t/${sid(doc)}`" alt=""
class="fit-sm"> class="fit-sm">
</div> </div>
</div> </div>
@@ -70,6 +70,7 @@ import FileIcon from "@/components/icons/FileIcon";
import FeaturedFieldsLine from "@/components/FeaturedFieldsLine"; import FeaturedFieldsLine from "@/components/FeaturedFieldsLine";
import MLIcon from "@/components/icons/MlIcon.vue"; import MLIcon from "@/components/icons/MlIcon.vue";
import Sist2Api from "@/Sist2Api"; import Sist2Api from "@/Sist2Api";
import {sid} from "@/util";
export default { export default {
name: "DocListItem", name: "DocListItem",
@@ -82,12 +83,13 @@ export default {
} }
}, },
methods: { methods: {
sid: sid,
async onThumbnailClick() { async onThumbnailClick() {
this.$store.commit("setUiLightboxSlide", this.doc._seq); this.$store.commit("setUiLightboxSlide", this.doc._seq);
await this.$store.dispatch("showLightbox"); await this.$store.dispatch("showLightbox");
}, },
onEmbeddingClick() { onEmbeddingClick() {
Sist2Api.getEmbeddings(this.doc._source.index, this.doc._id, this.$store.state.embeddingsModel).then(embeddings => { Sist2Api.getEmbeddings(sid(this.doc), this.$store.state.embeddingsModel).then(embeddings => {
this.$store.commit("setEmbeddingText", ""); this.$store.commit("setEmbeddingText", "");
this.$store.commit("setEmbedding", embeddings); this.$store.commit("setEmbedding", embeddings);
this.$store.commit("setEmbeddingDoc", this.doc); this.$store.commit("setEmbeddingDoc", this.doc);

View File

@@ -1,188 +1,189 @@
<template> <template>
<div v-if="doc._props.hasThumbnail" class="img-wrapper" @mouseenter="onTnEnter()" @mouseleave="onTnLeave()" <div v-if="doc._props.hasThumbnail" class="img-wrapper" @mouseenter="onTnEnter()" @mouseleave="onTnLeave()"
@touchstart="onTouchStart()"> @touchstart="onTouchStart()">
<div v-if="doc._props.isAudio" class="card-img-overlay" :class="{'small-badge': smallBadge}"> <div v-if="doc._props.isAudio" class="card-img-overlay" :class="{'small-badge': smallBadge}">
<span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span> <span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span>
</div>
<div
v-if="doc._props.isImage && doc._props.imageAspectRatio < 5"
class="card-img-overlay"
:class="{'small-badge': smallBadge}">
<span class="badge badge-resolution">{{ `${doc._source.width}x${doc._source.height}` }}</span>
</div>
<div v-if="(doc._props.isVideo || doc._props.isGif) && doc._source.duration > 0"
class="card-img-overlay"
:class="{'small-badge': smallBadge}">
<span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span>
</div>
<div v-if="doc._props.isPlayableVideo" class="play">
<svg viewBox="0 0 494.942 494.942" xmlns="http://www.w3.org/2000/svg">
<path d="m35.353 0 424.236 247.471-424.236 247.471z"/>
</svg>
</div>
<img ref="tn"
v-if="doc._props.isPlayableImage || doc._props.isPlayableVideo"
:src="tnSrc"
alt=""
:style="{height: (doc._props.isGif && hover) ? `${tnHeight()}px` : undefined}"
class="pointer fit card-img-top" @click="onThumbnailClick()">
<img v-else :src="tnSrc" alt=""
class="fit card-img-top">
<ThumbnailProgressBar v-if="hover && doc._props.hasVidPreview"
:progress="(currentThumbnailNum + 1) / (doc._props.tnNum)"
></ThumbnailProgressBar>
</div> </div>
<div
v-if="doc._props.isImage && doc._props.imageAspectRatio < 5"
class="card-img-overlay"
:class="{'small-badge': smallBadge}">
<span class="badge badge-resolution">{{ `${doc._source.width}x${doc._source.height}` }}</span>
</div>
<div v-if="(doc._props.isVideo || doc._props.isGif) && doc._source.duration > 0"
class="card-img-overlay"
:class="{'small-badge': smallBadge}">
<span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span>
</div>
<div v-if="doc._props.isPlayableVideo" class="play">
<svg viewBox="0 0 494.942 494.942" xmlns="http://www.w3.org/2000/svg">
<path d="m35.353 0 424.236 247.471-424.236 247.471z"/>
</svg>
</div>
<img ref="tn"
v-if="doc._props.isPlayableImage || doc._props.isPlayableVideo"
:src="tnSrc"
alt=""
:style="{height: (doc._props.isGif && hover) ? `${tnHeight()}px` : undefined}"
class="pointer fit card-img-top" @click="onThumbnailClick()">
<img v-else :src="tnSrc" alt=""
class="fit card-img-top">
<ThumbnailProgressBar v-if="hover && doc._props.hasVidPreview"
:progress="(currentThumbnailNum + 1) / (doc._props.tnNum)"
></ThumbnailProgressBar>
</div>
</template> </template>
<script> <script>
import {humanTime} from "@/util"; import {humanTime, sid} from "@/util";
import ThumbnailProgressBar from "@/components/ThumbnailProgressBar"; import ThumbnailProgressBar from "@/components/ThumbnailProgressBar";
export default { export default {
name: "FullThumbnail", name: "FullThumbnail",
props: ["doc", "smallBadge"], props: ["doc", "smallBadge"],
components: {ThumbnailProgressBar}, components: {ThumbnailProgressBar},
data() { data() {
return { return {
hover: false, hover: false,
currentThumbnailNum: 0, currentThumbnailNum: 0,
timeoutId: null timeoutId: null
}
},
created() {
this.$store.subscribe((mutation) => {
if (mutation.type === "busTnTouchStart" && mutation.payload !== this.doc._id) {
this.onTnLeave();
}
});
},
computed: {
tnSrc() {
return this.getThumbnailSrc(this.currentThumbnailNum);
},
},
methods: {
sid: sid,
getThumbnailSrc(thumbnailNum) {
const doc = this.doc;
const props = doc._props;
if (props.isGif && this.hover) {
return `f/${sid(doc)}`;
}
return (this.currentThumbnailNum === 0)
? `t/${sid(doc)}`
: `t/${sid(doc)}/${String(thumbnailNum).padStart(4, "0")}`;
},
humanTime: humanTime,
onThumbnailClick() {
this.$emit("onThumbnailClick");
},
tnHeight() {
return this.$refs.tn.height;
},
tnWidth() {
return this.$refs.tn.width;
},
onTnEnter() {
this.hover = true;
const start = Date.now()
if (this.doc._props.hasVidPreview) {
let img = new Image();
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
img.onload = () => {
this.currentThumbnailNum += 1;
this.scheduleNextTnNum(Date.now() - start);
}
}
},
onTnLeave() {
this.currentThumbnailNum = 0;
this.hover = false;
if (this.timeoutId !== null) {
window.clearTimeout(this.timeoutId);
this.timeoutId = null;
}
},
scheduleNextTnNum(offset = 0) {
const INTERVAL = (this.$store.state.optVidPreviewInterval ?? 700) - offset;
this.timeoutId = window.setTimeout(() => {
const start = Date.now();
if (!this.hover) {
return;
}
if (this.currentThumbnailNum === this.doc._props.tnNum - 1) {
this.currentThumbnailNum = 0;
this.scheduleNextTnNum();
} else {
let img = new Image();
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
img.onload = () => {
this.currentThumbnailNum += 1;
this.scheduleNextTnNum(Date.now() - start);
}
}
}, INTERVAL);
},
onTouchStart() {
this.$store.commit("busTnTouchStart", this.doc._id);
if (!this.hover) {
this.onTnEnter()
}
},
} }
},
created() {
this.$store.subscribe((mutation) => {
if (mutation.type === "busTnTouchStart" && mutation.payload !== this.doc._id) {
this.onTnLeave();
}
});
},
computed: {
tnSrc() {
return this.getThumbnailSrc(this.currentThumbnailNum);
},
},
methods: {
getThumbnailSrc(thumbnailNum) {
const doc = this.doc;
const props = doc._props;
if (props.isGif && this.hover) {
return `f/${doc._source.index}/${doc._id}`;
}
return (this.currentThumbnailNum === 0)
? `t/${doc._source.index}/${doc._id}`
: `t/${doc._source.index}/${doc._id}/${String(thumbnailNum).padStart(4, "0")}`;
},
humanTime: humanTime,
onThumbnailClick() {
this.$emit("onThumbnailClick");
},
tnHeight() {
return this.$refs.tn.height;
},
tnWidth() {
return this.$refs.tn.width;
},
onTnEnter() {
this.hover = true;
const start = Date.now()
if (this.doc._props.hasVidPreview) {
let img = new Image();
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
img.onload = () => {
this.currentThumbnailNum += 1;
this.scheduleNextTnNum(Date.now() - start);
}
}
},
onTnLeave() {
this.currentThumbnailNum = 0;
this.hover = false;
if (this.timeoutId !== null) {
window.clearTimeout(this.timeoutId);
this.timeoutId = null;
}
},
scheduleNextTnNum(offset = 0) {
const INTERVAL = (this.$store.state.optVidPreviewInterval ?? 700) - offset;
this.timeoutId = window.setTimeout(() => {
const start = Date.now();
if (!this.hover) {
return;
}
if (this.currentThumbnailNum === this.doc._props.tnNum - 1) {
this.currentThumbnailNum = 0;
this.scheduleNextTnNum();
} else {
let img = new Image();
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
img.onload = () => {
this.currentThumbnailNum += 1;
this.scheduleNextTnNum(Date.now() - start);
}
}
}, INTERVAL);
},
onTouchStart() {
this.$store.commit("busTnTouchStart", this.doc._id);
if (!this.hover) {
this.onTnEnter()
}
},
}
} }
</script> </script>
<style scoped> <style scoped>
.img-wrapper { .img-wrapper {
position: relative; position: relative;
} }
.img-wrapper:hover svg { .img-wrapper:hover svg {
fill: rgba(0, 0, 0, 1); fill: rgba(0, 0, 0, 1);
} }
.card-img-top { .card-img-top {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
} }
.play { .play {
position: absolute; position: absolute;
width: 25px; width: 25px;
height: 25px; height: 25px;
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
pointer-events: none; pointer-events: none;
} }
.play svg { .play svg {
fill: rgba(0, 0, 0, 0.7); fill: rgba(0, 0, 0, 0.7);
} }
.badge-resolution { .badge-resolution {
color: #c6c6c6; color: #c6c6c6;
background-color: #272727CC; background-color: #272727CC;
padding: 2px 3px; padding: 2px 3px;
} }
.card-img-overlay { .card-img-overlay {
pointer-events: none; pointer-events: none;
padding: 2px 6px; padding: 2px 6px;
bottom: 4px; bottom: 4px;
top: unset; top: unset;
left: unset; left: unset;
right: 0; right: 0;
} }
.small-badge { .small-badge {
padding: 1px 3px; padding: 1px 3px;
font-size: 70%; font-size: 70%;
} }
</style> </style>

View File

@@ -27,11 +27,12 @@
@click.shift="shiftClick(idx, $event)" @click.shift="shiftClick(idx, $event)"
class="d-flex justify-content-between align-items-center list-group-item-action pointer" class="d-flex justify-content-between align-items-center list-group-item-action pointer"
:class="{active: lastClickIndex === idx}" :class="{active: lastClickIndex === idx}"
:key="idx.id"
> >
<div class="d-flex"> <div class="d-flex">
<b-checkbox style="pointer-events: none" :checked="isSelected(idx)"></b-checkbox> <b-checkbox style="pointer-events: none" :checked="isSelected(idx)"></b-checkbox>
{{ idx.name }} {{ idx.name }}
<div style="vertical-align: center; margin-left: 5px"> <div v-if="hasEmbeddings(idx)" style="vertical-align: center; margin-left: 5px">
<MLIcon small style="top: -1px; position: relative"></MLIcon> <MLIcon small style="top: -1px; position: relative"></MLIcon>
</div> </div>
<span class="text-muted timestamp-text ml-2" <span class="text-muted timestamp-text ml-2"
@@ -43,7 +44,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script>
import SmallBadge from "./SmallBadge.vue" import SmallBadge from "./SmallBadge.vue"
import {mapActions, mapGetters} from "vuex"; import {mapActions, mapGetters} from "vuex";
import Vue from "vue"; import Vue from "vue";
@@ -111,7 +112,7 @@ export default Vue.extend({
onSelect(value) { onSelect(value) {
this.setSelectedIndices(this.indices.filter(idx => value.includes(idx.id))); this.setSelectedIndices(this.indices.filter(idx => value.includes(idx.id)));
}, },
formatIdxDate(timestamp: number): string { formatIdxDate(timestamp) {
return format(new Date(timestamp * 1000), "yyyy-MM-dd"); return format(new Date(timestamp * 1000), "yyyy-MM-dd");
}, },
toggleIndex(index, e) { toggleIndex(index, e) {
@@ -121,14 +122,17 @@ export default Vue.extend({
this.lastClickIndex = index; this.lastClickIndex = index;
if (this.isSelected(index)) { if (this.isSelected(index)) {
this.setSelectedIndices(this.selectedIndices.filter(idx => idx.id != index.id)); this.setSelectedIndices(this.selectedIndices.filter(idx => idx.id !== index.id));
} else { } else {
this.setSelectedIndices([index, ...this.selectedIndices]); this.setSelectedIndices([index, ...this.selectedIndices]);
} }
}, },
isSelected(index) { isSelected(index) {
return this.selectedIndices.find(idx => idx.id == index.id) != null; return this.selectedIndices.find(idx => idx.id === index.id) != null;
} },
hasEmbeddings(index) {
return index.models.length > 0;
},
}, },
}) })
</script> </script>

View File

@@ -10,7 +10,7 @@
>{{ $t("ml.analyzeText") }} >{{ $t("ml.analyzeText") }}
</b-button> </b-button>
<b-select :disabled="mlPredictionsLoading || mlLoading" class="ml-2" v-model="nerModel"> <b-select :disabled="mlPredictionsLoading || mlLoading" class="ml-2" v-model="nerModel">
<b-select-option :value="opt.value" v-for="opt of ModelsRepo.getOptions()">{{ opt.text }} <b-select-option :value="opt.value" v-for="opt of ModelsRepo.getOptions()" :key="opt.value">{{ opt.text }}
</b-select-option> </b-select-option>
</b-select> </b-select>
</b-form> </b-form>
@@ -46,7 +46,7 @@ import {mapGetters, mapMutations} from "vuex";
export default { export default {
name: "LazyContentDiv", name: "LazyContentDiv",
components: {AnalyzedContentSpansContainer, Preloader}, components: {AnalyzedContentSpansContainer, Preloader},
props: ["docId"], props: ["sid"],
data() { data() {
return { return {
ModelsRepo, ModelsRepo,
@@ -70,7 +70,7 @@ export default {
} }
Sist2Api Sist2Api
.getDocument(this.docId, this.$store.state.optHighlight, this.$store.state.fuzzy) .getDocument(this.sid, this.$store.state.optHighlight, this.$store.state.fuzzy)
.then(doc => { .then(doc => {
this.loading = false; this.loading = false;

View File

@@ -43,8 +43,7 @@
</b-card> </b-card>
</template> </template>
<script lang="ts"> <script>
import Sist2Api, {EsResult} from "@/Sist2Api";
import Vue from "vue"; import Vue from "vue";
import {humanFileSize} from "@/util"; import {humanFileSize} from "@/util";
import DisplayModeToggle from "@/components/DisplayModeToggle.vue"; import DisplayModeToggle from "@/components/DisplayModeToggle.vue";
@@ -52,6 +51,7 @@ import SortSelect from "@/components/SortSelect.vue";
import Preloader from "@/components/Preloader.vue"; import Preloader from "@/components/Preloader.vue";
import Sist2Query from "@/Sist2ElasticsearchQuery"; import Sist2Query from "@/Sist2ElasticsearchQuery";
import ClipboardIcon from "@/components/icons/ClipboardIcon.vue"; import ClipboardIcon from "@/components/icons/ClipboardIcon.vue";
import Sist2Api from "@/Sist2Api";
export default Vue.extend({ export default Vue.extend({
name: "ResultsCard", name: "ResultsCard",
@@ -64,7 +64,7 @@ export default Vue.extend({
return this.$store.state.lastQueryResults != null; return this.$store.state.lastQueryResults != null;
}, },
hitCount() { hitCount() {
return (this.$store.state.firstQueryResults as EsResult).aggregations.total_count.value; return (this.$store.state.firstQueryResults).aggregations.total_count.value;
}, },
tableItems() { tableItems() {
const items = []; const items = [];
@@ -79,10 +79,10 @@ export default Vue.extend({
}, },
methods: { methods: {
took() { took() {
return (this.$store.state.lastQueryResults as EsResult).took + "ms"; return (this.$store.state.lastQueryResults).took + "ms";
}, },
totalSize() { totalSize() {
return humanFileSize((this.$store.state.firstQueryResults as EsResult).aggregations.total_size.value); return humanFileSize((this.$store.state.firstQueryResults).aggregations.total_size.value);
}, },
onToggle() { onToggle() {
const show = !document.getElementById("collapse-1").classList.contains("show"); const show = !document.getElementById("collapse-1").classList.contains("show");

View File

@@ -5,8 +5,7 @@
<script> <script>
import noUiSlider from 'nouislider'; import noUiSlider from 'nouislider';
import 'nouislider/dist/nouislider.css'; import 'nouislider/dist/nouislider.css';
import {humanFileSize} from "@/util"; import {humanFileSize, mergeTooltips} from "@/util";
import {mergeTooltips} from "@/util-js";
export default { export default {
name: "SizeSlider", name: "SizeSlider",

View File

@@ -2,7 +2,7 @@
<b-badge variant="secondary" :pill="pill">{{ text }}</b-badge> <b-badge variant="secondary" :pill="pill">{{ text }}</b-badge>
</template> </template>
<script lang="ts"> <script>
import Vue from "vue"; import Vue from "vue";
export default Vue.extend({ export default Vue.extend({

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="thumbnail-progress-bar" :style="{width: `${percentProgress}%`}"></div> <div class="thumbnail_count-progress-bar" :style="{width: `${percentProgress}%`}"></div>
</template> </template>
<script> <script>
@@ -16,7 +16,7 @@ export default {
<style scoped> <style scoped>
.thumbnail-progress-bar { .thumbnail_count-progress-bar {
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
@@ -27,11 +27,11 @@ export default {
z-index: 9; z-index: 9;
} }
.theme-black .thumbnail-progress-bar { .theme-black .thumbnail_count-progress-bar {
background: rgba(0, 188, 212, 0.95); background: rgba(0, 188, 212, 0.95);
} }
.sub-document .thumbnail-progress-bar { .sub-document .thumbnail_count-progress-bar {
max-width: calc(100% - 8px); max-width: calc(100% - 8px);
left: 4px; left: 4px;
} }

View File

@@ -21,7 +21,7 @@ const authGuard = (to, from, next) => {
next(); next();
} }
const routes: Array<RouteConfig> = [ const routes = [
{ {
path: "/", path: "/",
name: "SearchPage", name: "SearchPage",

View File

@@ -1,20 +1,18 @@
import Vue from "vue" import Vue from "vue"
import Vuex from "vuex" import Vuex from "vuex"
import VueRouter, {Route} from "vue-router";
import {EsHit, EsResult, EsTag, Index} from "@/Sist2Api";
import {deserializeMimes, randomSeed, serializeMimes} from "@/util"; import {deserializeMimes, randomSeed, serializeMimes} from "@/util";
import {getInstance} from "@/plugins/auth0.js"; import {getInstance} from "@/plugins/auth0.js";
const CONF_VERSION = 3; const CONF_VERSION = 3;
Vue.use(Vuex) Vue.use(Vuex);
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
seed: 0, seed: 0,
indices: [] as Index[], indices: [],
tags: [] as EsTag[], tags: [],
sist2Info: null as any, sist2Info: null,
sizeMin: undefined, sizeMin: undefined,
sizeMax: undefined, sizeMax: undefined,
@@ -64,12 +62,12 @@ export default new Vuex.Store({
optAutoAnalyze: false, optAutoAnalyze: false,
optMlDefaultModel: null, optMlDefaultModel: null,
_onLoadSelectedIndices: [] as string[], _onLoadSelectedIndices: [],
_onLoadSelectedMimeTypes: [] as string[], _onLoadSelectedMimeTypes: [],
_onLoadSelectedTags: [] as string[], _onLoadSelectedTags: [],
selectedIndices: [] as Index[], selectedIndices: [],
selectedMimeTypes: [] as string[], selectedMimeTypes: [],
selectedTags: [] as string[], selectedTags: [],
lastQueryResults: null, lastQueryResults: null,
firstQueryResults: null, firstQueryResults: null,
@@ -80,10 +78,10 @@ export default new Vuex.Store({
uiSqliteMode: false, uiSqliteMode: false,
uiLightboxIsOpen: false, uiLightboxIsOpen: false,
uiShowLightbox: false, uiShowLightbox: false,
uiLightboxSources: [] as string[], uiLightboxSources: [],
uiLightboxThumbs: [] as string[], uiLightboxThumbs: [],
uiLightboxCaptions: [] as any[], uiLightboxCaptions: [],
uiLightboxTypes: [] as string[], uiLightboxTypes: [],
uiLightboxKey: 0, uiLightboxKey: 0,
uiLightboxSlide: 0, uiLightboxSlide: 0,
uiReachedScrollEnd: false, uiReachedScrollEnd: false,
@@ -91,7 +89,7 @@ export default new Vuex.Store({
uiDetailsMimeAgg: null, uiDetailsMimeAgg: null,
uiShowDetails: false, uiShowDetails: false,
uiMimeMap: [] as any[], uiMimeMap: [],
auth0Token: null, auth0Token: null,
nerModel: { nerModel: {
@@ -122,7 +120,7 @@ export default new Vuex.Store({
if (state._onLoadSelectedIndices.length > 0) { if (state._onLoadSelectedIndices.length > 0) {
state.selectedIndices = val.filter( state.selectedIndices = val.filter(
(idx: Index) => state._onLoadSelectedIndices.some(prefix => idx.id.startsWith(prefix)) (idx) => state._onLoadSelectedIndices.some(id => id === idx.id.toString(16))
); );
} else { } else {
state.selectedIndices = val; state.selectedIndices = val;
@@ -145,18 +143,18 @@ export default new Vuex.Store({
setSelectedIndices: (state, val) => state.selectedIndices = val, setSelectedIndices: (state, val) => state.selectedIndices = val,
setSelectedMimeTypes: (state, val) => state.selectedMimeTypes = val, setSelectedMimeTypes: (state, val) => state.selectedMimeTypes = val,
setSelectedTags: (state, val) => state.selectedTags = val, setSelectedTags: (state, val) => state.selectedTags = val,
setUiLightboxIsOpen: (state, val: boolean) => state.uiLightboxIsOpen = val, setUiLightboxIsOpen: (state, val) => state.uiLightboxIsOpen = val,
_setUiShowLightbox: (state, val: boolean) => state.uiShowLightbox = val, _setUiShowLightbox: (state, val) => state.uiShowLightbox = val,
setUiLightboxKey: (state, val: number) => state.uiLightboxKey = val, setUiLightboxKey: (state, val) => state.uiLightboxKey = val,
_setKeySequence: (state, val: number) => state.keySequence = val, _setKeySequence: (state, val) => state.keySequence = val,
_setQuerySequence: (state, val: number) => state.querySequence = val, _setQuerySequence: (state, val) => state.querySequence = val,
addLightboxSource: (state, {source, thumbnail, caption, type}) => { addLightboxSource: (state, {source, thumbnail, caption, type}) => {
state.uiLightboxSources.push(source); state.uiLightboxSources.push(source);
state.uiLightboxThumbs.push(thumbnail); state.uiLightboxThumbs.push(thumbnail);
state.uiLightboxCaptions.push(caption); state.uiLightboxCaptions.push(caption);
state.uiLightboxTypes.push(type); state.uiLightboxTypes.push(type);
}, },
setUiLightboxSlide: (state, val: number) => state.uiLightboxSlide = val, setUiLightboxSlide: (state, val) => state.uiLightboxSlide = val,
setUiLightboxSources: (state, val) => state.uiLightboxSources = val, setUiLightboxSources: (state, val) => state.uiLightboxSources = val,
setUiLightboxThumbs: (state, val) => state.uiLightboxThumbs = val, setUiLightboxThumbs: (state, val) => state.uiLightboxThumbs = val,
@@ -230,7 +228,7 @@ export default new Vuex.Store({
store.commit("setOptLang", val.lang); store.commit("setOptLang", val.lang);
} }
}, },
loadFromArgs({commit}, route: Route) { loadFromArgs({commit}, route) {
if (route.query.q) { if (route.query.q) {
commit("setSearchText", route.query.q); commit("setSearchText", route.query.q);
@@ -265,11 +263,11 @@ export default new Vuex.Store({
} }
if (route.query.m) { if (route.query.m) {
commit("_setOnLoadSelectedMimeTypes", deserializeMimes(route.query.m as string)); commit("_setOnLoadSelectedMimeTypes", deserializeMimes(route.query.m));
} }
if (route.query.t) { if (route.query.t) {
commit("_setOnLoadSelectedTags", (route.query.t as string).split(",")); commit("_setOnLoadSelectedTags", (route.query.t).split(","));
} }
if (route.query.sort) { if (route.query.sort) {
@@ -280,7 +278,7 @@ export default new Vuex.Store({
commit("setSeed", Number(route.query.seed)); commit("setSeed", Number(route.query.seed));
} }
}, },
async updateArgs({state}, router: VueRouter) { async updateArgs({state}, router) {
if (router.currentRoute.path !== "/") { if (router.currentRoute.path !== "/") {
return; return;
@@ -290,14 +288,14 @@ export default new Vuex.Store({
query: { query: {
q: state.searchText.trim() ? state.searchText.trim().replace(/\s+/g, " ") : undefined, q: state.searchText.trim() ? state.searchText.trim().replace(/\s+/g, " ") : undefined,
fuzzy: state.fuzzy ? null : undefined, fuzzy: state.fuzzy ? null : undefined,
i: state.selectedIndices ? state.selectedIndices.map((idx: Index) => idx.idPrefix) : undefined, i: state.selectedIndices ? state.selectedIndices.map((idx) => idx.id.toString(16)) : undefined,
dMin: state.dateMin, dMin: state.dateMin,
dMax: state.dateMax, dMax: state.dateMax,
sMin: state.sizeMin, sMin: state.sizeMin,
sMax: state.sizeMax, sMax: state.sizeMax,
path: state.pathText ? state.pathText : undefined, path: state.pathText ? state.pathText : undefined,
m: serializeMimes(state.selectedMimeTypes), m: serializeMimes(state.selectedMimeTypes),
t: state.selectedTags.length == 0 ? undefined : state.selectedTags.join(","), t: state.selectedTags.length === 0 ? undefined : state.selectedTags.join(","),
sort: state.sortMode === "score" ? undefined : state.sortMode, sort: state.sortMode === "score" ? undefined : state.sortMode,
seed: state.sortMode === "random" ? state.seed.toString() : undefined seed: state.sortMode === "random" ? state.seed.toString() : undefined
} }
@@ -306,11 +304,11 @@ export default new Vuex.Store({
}); });
}, },
updateConfiguration({state}) { updateConfiguration({state}) {
const conf = {} as any; const conf = {};
Object.keys(state).forEach((key) => { Object.keys(state).forEach((key) => {
if (key.startsWith("opt")) { if (key.startsWith("opt")) {
conf[key] = (state as any)[key]; conf[key] = (state)[key];
} }
}); });
@@ -323,14 +321,14 @@ export default new Vuex.Store({
if (confString) { if (confString) {
const conf = JSON.parse(confString); const conf = JSON.parse(confString);
if (!("version" in conf) || conf["version"] != CONF_VERSION) { if (!("version" in conf) || conf["version"] !== CONF_VERSION) {
localStorage.removeItem("sist2_configuration"); localStorage.removeItem("sist2_configuration");
window.location.reload(); window.location.reload();
} }
Object.keys(state).forEach((key) => { Object.keys(state).forEach((key) => {
if (key.startsWith("opt")) { if (key.startsWith("opt")) {
(state as any)[key] = conf[key]; (state)[key] = conf[key];
} }
}); });
} }
@@ -386,7 +384,7 @@ export default new Vuex.Store({
indices: state => state.indices, indices: state => state.indices,
sist2Info: state => state.sist2Info, sist2Info: state => state.sist2Info,
indexMap: state => { indexMap: state => {
const map = {} as any; const map = {};
state.indices.forEach(idx => map[idx.id] = idx); state.indices.forEach(idx => map[idx.id] = idx);
return map; return map;
}, },
@@ -405,12 +403,12 @@ export default new Vuex.Store({
size: state => state.optSize, size: state => state.optSize,
sortMode: state => state.sortMode, sortMode: state => state.sortMode,
lastQueryResult: state => state.lastQueryResults, lastQueryResult: state => state.lastQueryResults,
lastDoc: function (state): EsHit | null { lastDoc: function (state) {
if (state.lastQueryResults == null) { if (state.lastQueryResults == null) {
return null; return null;
} }
return (state.lastQueryResults as unknown as EsResult).hits.hits.slice(-1)[0]; return (state.lastQueryResults).hits.hits.slice(-1)[0];
}, },
uiShowLightbox: state => state.uiShowLightbox, uiShowLightbox: state => state.uiShowLightbox,
uiLightboxSources: state => state.uiLightboxSources, uiLightboxSources: state => state.uiLightboxSources,
@@ -451,7 +449,7 @@ export default new Vuex.Store({
optMlRepositories: state => state.optMlRepositories, optMlRepositories: state => state.optMlRepositories,
mlRepositoryList: state => { mlRepositoryList: state => {
const repos = state.optMlRepositories.split("\n") const repos = state.optMlRepositories.split("\n")
return repos[0] == "" ? [] : repos; return repos[0] === "" ? [] : repos;
}, },
optMlDefaultModel: state => state.optMlDefaultModel, optMlDefaultModel: state => state.optMlDefaultModel,
optAutoAnalyze: state => state.optAutoAnalyze, optAutoAnalyze: state => state.optAutoAnalyze,

View File

@@ -1,139 +0,0 @@
export function mergeTooltips(slider, threshold, separator, fixTooltips) {
const isMobile = window.innerWidth <= 650;
if (isMobile) {
threshold = 25;
}
const textIsRtl = getComputedStyle(slider).direction === 'rtl';
const isRtl = slider.noUiSlider.options.direction === 'rtl';
const isVertical = slider.noUiSlider.options.orientation === 'vertical';
const tooltips = slider.noUiSlider.getTooltips();
const origins = slider.noUiSlider.getOrigins();
// Move tooltips into the origin element. The default stylesheet handles this.
tooltips.forEach(function (tooltip, index) {
if (tooltip) {
origins[index].appendChild(tooltip);
}
});
slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) {
const pools = [[]];
const poolPositions = [[]];
const poolValues = [[]];
let atPool = 0;
// Assign the first tooltip to the first pool, if the tooltip is configured
if (tooltips[0]) {
pools[0][0] = 0;
poolPositions[0][0] = positions[0];
poolValues[0][0] = values[0];
}
for (let i = 1; i < positions.length; i++) {
if (!tooltips[i] || (positions[i] - positions[i - 1]) > threshold) {
atPool++;
pools[atPool] = [];
poolValues[atPool] = [];
poolPositions[atPool] = [];
}
if (tooltips[i]) {
pools[atPool].push(i);
poolValues[atPool].push(values[i]);
poolPositions[atPool].push(positions[i]);
}
}
pools.forEach(function (pool, poolIndex) {
const handlesInPool = pool.length;
for (let j = 0; j < handlesInPool; j++) {
const handleNumber = pool[j];
if (j === handlesInPool - 1) {
let offset = 0;
poolPositions[poolIndex].forEach(function (value) {
offset += 1000 - 10 * value;
});
const direction = isVertical ? 'bottom' : 'right';
const last = isRtl ? 0 : handlesInPool - 1;
const lastOffset = 1000 - 10 * poolPositions[poolIndex][last];
offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset;
// Center this tooltip over the affected handles
tooltips[handleNumber].innerHTML = poolValues[poolIndex].join(separator);
tooltips[handleNumber].style.display = 'block';
tooltips[handleNumber].style[direction] = offset + '%';
} else {
// Hide this tooltip
tooltips[handleNumber].style.display = 'none';
}
}
});
if (fixTooltips) {
const isMobile = window.innerWidth <= 650;
const len = isMobile ? 20 : 5;
if (positions[0] < len) {
tooltips[0].style.right = `${(1 - ((positions[0]) / len)) * -35}px`
} else {
tooltips[0].style.right = "0"
}
if (positions[1] > (100 - len)) {
tooltips[1].style.right = `${((positions[1] - (100 - len)) / len) * 35}px`
} else {
tooltips[1].style.right = "0"
}
}
});
}
export function burrow(table, addSelfDir, rootName) {
const root = {};
table.forEach(row => {
let layer = root;
row.taxonomy.forEach(key => {
layer[key] = key in layer ? layer[key] : {};
layer = layer[key];
});
if (Object.keys(layer).length === 0) {
layer["$size$"] = row.size;
} else if (addSelfDir) {
layer["."] = {
"$size$": row.size,
};
}
});
const descend = function (obj, depth) {
return Object.keys(obj).filter(k => k !== "$size$").map(k => {
const child = {
name: k,
depth: depth,
value: 0,
children: descend(obj[k], depth + 1)
};
if ("$size$" in obj[k]) {
child.value = obj[k]["$size$"];
}
return child;
});
};
return {
name: rootName,
children: descend(root, 1),
value: 0,
depth: 0,
}
}

329
sist2-vue/src/util.js Normal file
View File

@@ -0,0 +1,329 @@
export function mergeTooltips(slider, threshold, separator, fixTooltips) {
const isMobile = window.innerWidth <= 650;
if (isMobile) {
threshold = 25;
}
const textIsRtl = getComputedStyle(slider).direction === 'rtl';
const isRtl = slider.noUiSlider.options.direction === 'rtl';
const isVertical = slider.noUiSlider.options.orientation === 'vertical';
const tooltips = slider.noUiSlider.getTooltips();
const origins = slider.noUiSlider.getOrigins();
// Move tooltips into the origin element. The default stylesheet handles this.
tooltips.forEach(function (tooltip, index) {
if (tooltip) {
origins[index].appendChild(tooltip);
}
});
slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) {
const pools = [[]];
const poolPositions = [[]];
const poolValues = [[]];
let atPool = 0;
// Assign the first tooltip to the first pool, if the tooltip is configured
if (tooltips[0]) {
pools[0][0] = 0;
poolPositions[0][0] = positions[0];
poolValues[0][0] = values[0];
}
for (let i = 1; i < positions.length; i++) {
if (!tooltips[i] || (positions[i] - positions[i - 1]) > threshold) {
atPool++;
pools[atPool] = [];
poolValues[atPool] = [];
poolPositions[atPool] = [];
}
if (tooltips[i]) {
pools[atPool].push(i);
poolValues[atPool].push(values[i]);
poolPositions[atPool].push(positions[i]);
}
}
pools.forEach(function (pool, poolIndex) {
const handlesInPool = pool.length;
for (let j = 0; j < handlesInPool; j++) {
const handleNumber = pool[j];
if (j === handlesInPool - 1) {
let offset = 0;
poolPositions[poolIndex].forEach(function (value) {
offset += 1000 - 10 * value;
});
const direction = isVertical ? 'bottom' : 'right';
const last = isRtl ? 0 : handlesInPool - 1;
const lastOffset = 1000 - 10 * poolPositions[poolIndex][last];
offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset;
// Center this tooltip over the affected handles
tooltips[handleNumber].innerHTML = poolValues[poolIndex].join(separator);
tooltips[handleNumber].style.display = 'block';
tooltips[handleNumber].style[direction] = offset + '%';
} else {
// Hide this tooltip
tooltips[handleNumber].style.display = 'none';
}
}
});
if (fixTooltips) {
const isMobile = window.innerWidth <= 650;
const len = isMobile ? 20 : 5;
if (positions[0] < len) {
tooltips[0].style.right = `${(1 - ((positions[0]) / len)) * -35}px`
} else {
tooltips[0].style.right = "0"
}
if (positions[1] > (100 - len)) {
tooltips[1].style.right = `${((positions[1] - (100 - len)) / len) * 35}px`
} else {
tooltips[1].style.right = "0"
}
}
});
}
export function burrow(table, addSelfDir, rootName) {
const root = {};
table.forEach(row => {
let layer = root;
row.taxonomy.forEach(key => {
layer[key] = key in layer ? layer[key] : {};
layer = layer[key];
});
if (Object.keys(layer).length === 0) {
layer["$size$"] = row.size;
} else if (addSelfDir) {
layer["."] = {
"$size$": row.size,
};
}
});
const descend = function (obj, depth) {
return Object.keys(obj).filter(k => k !== "$size$").map(k => {
const child = {
name: k,
depth: depth,
value: 0,
children: descend(obj[k], depth + 1)
};
if ("$size$" in obj[k]) {
child.value = obj[k]["$size$"];
}
return child;
});
};
return {
name: rootName,
children: descend(root, 1),
value: 0,
depth: 0,
}
}
export function ext(hit) {
return srcExt(hit._source)
}
export function srcExt(src) {
return Object.prototype.hasOwnProperty.call(src, "extension")
&& src["extension"] !== "" ? "." + src["extension"] : "";
}
export function strUnescape(str) {
let result = "";
for (let i = 0; i < str.length; i++) {
const c = str[i];
const next = str[i + 1];
if (c === "]") {
if (next === "]") {
result += c;
i += 1;
} else {
result += String.fromCharCode(parseInt(str.slice(i, i + 2), 16));
i += 2;
}
} else {
result += c;
}
}
return result;
}
const thresh = 1000;
const units = ["k", "M", "G", "T", "P", "E", "Z", "Y"];
export function humanFileSize(bytes) {
if (bytes === 0) {
return "0 B"
}
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
let u = -1;
do {
bytes /= thresh;
++u;
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1) + units[u];
}
export function humanTime(sec_num) {
sec_num = Math.floor(sec_num);
const hours = Math.floor(sec_num / 3600);
const minutes = Math.floor((sec_num - (hours * 3600)) / 60);
const seconds = sec_num - (hours * 3600) - (minutes * 60);
if (sec_num < 60) {
return `${sec_num}s`
}
if (sec_num < 3600) {
return `${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
}
return `${hours < 10 ? "0" : ""}${hours}:${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
}
export function humanDate(numMilis) {
const date = (new Date(numMilis * 1000));
return date.getUTCFullYear() + "-" + ("0" + (date.getUTCMonth() + 1)).slice(-2) + "-" + ("0" + date.getUTCDate()).slice(-2)
}
export function lum(c) {
c = c.substring(1);
const rgb = parseInt(c, 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
export function getSelectedTreeNodes(tree) {
const selectedNodes = new Set();
const selected = tree.selected();
for (let i = 0; i < selected.length; i++) {
if (selected[i].id === "any") {
return ["any"]
}
//Only get children
if (selected[i].text.indexOf("(") !== -1) {
if (selected[i].values) {
selectedNodes.add(selected[i].values.slice(-1)[0]);
} else {
selectedNodes.add(selected[i].id);
}
}
}
return Array.from(selectedNodes);
}
export function getTreeNodeAttributes(tree) {
const nodes = tree.selectable();
const attributes = {};
for (let i = 0; i < nodes.length; i++) {
let id = null;
if (nodes[i].text.indexOf("(") !== -1 && nodes[i].values) {
id = nodes[i].values.slice(-1)[0];
} else {
id = nodes[i].id
}
attributes[id] = {
checked: nodes[i].itree.state.checked,
collapsed: nodes[i].itree.state.collapsed,
}
}
return attributes;
}
export function serializeMimes(mimes) {
if (mimes.length === 0) {
return undefined;
}
return mimes.map(mime => compressMime(mime)).join("");
}
export function deserializeMimes(mimeString) {
return mimeString
.replaceAll(/([IVATUF])/g, "$$$&")
.split("$")
.map(mime => decompressMime(mime))
.slice(1) // Ignore the first (empty) token
}
export function compressMime(mime) {
return mime
.replace("image/", "I")
.replace("video/", "V")
.replace("application/", "A")
.replace("text/", "T")
.replace("audio/", "U")
.replace("font/", "F")
.replace("+", ",")
.replace("x-", "X")
}
export function decompressMime(mime) {
return mime
.replace("I", "image/")
.replace("V", "video/")
.replace("A", "application/")
.replace("T", "text/")
.replace("U", "audio/")
.replace("F", "font/")
.replace(",", "+")
.replace("X", "x-")
}
export function randomSeed() {
return Math.round(Math.random() * 100000);
}
export function sid(doc) {
if (doc._id.includes(".")) {
return doc._id
}
const num = BigInt(doc._id);
const indexId = (num >> BigInt(32));
const docId = num & BigInt(0xFFFFFFFF);
return indexId.toString(16).padStart(8, "0") + "." + docId.toString(16).padStart(8, "0");
}

View File

@@ -1,177 +0,0 @@
import {EsHit} from "@/Sist2Api";
export function ext(hit: EsHit) {
return srcExt(hit._source)
}
export function srcExt(src) {
return Object.prototype.hasOwnProperty.call(src, "extension")
&& src["extension"] !== "" ? "." + src["extension"] : "";
}
export function strUnescape(str: string): string {
let result = "";
for (let i = 0; i < str.length; i++) {
const c = str[i];
const next = str[i + 1];
if (c === "]") {
if (next === "]") {
result += c;
i += 1;
} else {
result += String.fromCharCode(parseInt(str.slice(i, i + 2), 16));
i += 2;
}
} else {
result += c;
}
}
return result;
}
const thresh = 1000;
const units = ["k", "M", "G", "T", "P", "E", "Z", "Y"];
export function humanFileSize(bytes: number): string {
if (bytes === 0) {
return "0 B"
}
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
let u = -1;
do {
bytes /= thresh;
++u;
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1) + units[u];
}
export function humanTime(sec_num: number): string {
sec_num = Math.floor(sec_num);
const hours = Math.floor(sec_num / 3600);
const minutes = Math.floor((sec_num - (hours * 3600)) / 60);
const seconds = sec_num - (hours * 3600) - (minutes * 60);
if (sec_num < 60) {
return `${sec_num}s`
}
if (sec_num < 3600) {
return `${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
}
return `${hours < 10 ? "0" : ""}${hours}:${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
}
export function humanDate(numMilis: number): string {
const date = (new Date(numMilis * 1000));
return date.getUTCFullYear() + "-" + ("0" + (date.getUTCMonth() + 1)).slice(-2) + "-" + ("0" + date.getUTCDate()).slice(-2)
}
export function lum(c: string) {
c = c.substring(1);
const rgb = parseInt(c, 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
export function getSelectedTreeNodes(tree: any) {
const selectedNodes = new Set();
const selected = tree.selected();
for (let i = 0; i < selected.length; i++) {
if (selected[i].id === "any") {
return ["any"]
}
//Only get children
if (selected[i].text.indexOf("(") !== -1) {
if (selected[i].values) {
selectedNodes.add(selected[i].values.slice(-1)[0]);
} else {
selectedNodes.add(selected[i].id);
}
}
}
return Array.from(selectedNodes);
}
export function getTreeNodeAttributes(tree: any) {
const nodes = tree.selectable();
const attributes = {};
for (let i = 0; i < nodes.length; i++) {
let id = null;
if (nodes[i].text.indexOf("(") !== -1 && nodes[i].values) {
id = nodes[i].values.slice(-1)[0];
} else {
id = nodes[i].id
}
attributes[id] = {
checked: nodes[i].itree.state.checked,
collapsed: nodes[i].itree.state.collapsed,
}
}
return attributes;
}
export function serializeMimes(mimes: string[]): string | undefined {
if (mimes.length == 0) {
return undefined;
}
return mimes.map(mime => compressMime(mime)).join("");
}
export function deserializeMimes(mimeString: string): string[] {
return mimeString
.replaceAll(/([IVATUF])/g, "$$$&")
.split("$")
.map(mime => decompressMime(mime))
.slice(1) // Ignore the first (empty) token
}
export function compressMime(mime: string): string {
return mime
.replace("image/", "I")
.replace("video/", "V")
.replace("application/", "A")
.replace("text/", "T")
.replace("audio/", "U")
.replace("font/", "F")
.replace("+", ",")
.replace("x-", "X")
}
export function decompressMime(mime: string): string {
return mime
.replace("I", "image/")
.replace("V", "video/")
.replace("A", "application/")
.replace("T", "text/")
.replace("U", "audio/")
.replace("F", "font/")
.replace(",", "+")
.replace("X", "x-")
}
export function randomSeed(): number {
return Math.round(Math.random() * 100000);
}

View File

@@ -136,7 +136,7 @@
{{ $t("opt.fuzzy") }} {{ $t("opt.fuzzy") }}
</b-form-checkbox> </b-form-checkbox>
<b-form-checkbox :disabled="uiSqliteMode" :checked="optSearchInPath" @input="setOptSearchInPath">{{ <b-form-checkbox :checked="optSearchInPath" @input="setOptSearchInPath">{{
$t("opt.searchInPath") $t("opt.searchInPath")
}} }}
</b-form-checkbox> </b-form-checkbox>

View File

@@ -1,142 +1,142 @@
<template> <template>
<div style="margin-left: auto; margin-right: auto;" class="container"> <div style="margin-left: auto; margin-right: auto;" class="container">
<Preloader v-if="loading"></Preloader> <Preloader v-if="loading"></Preloader>
<b-card v-else-if="!loading && found"> <b-card v-else-if="!loading && found">
<b-card-title :title="doc._source.name + ext(doc)"> <b-card-title :title="doc._source.name + ext(doc)">
{{ doc._source.name + ext(doc) }} {{ doc._source.name + ext(doc) }}
</b-card-title> </b-card-title>
<!-- Thumbnail--> <!-- Thumbnail-->
<div style="position: relative; margin-left: auto; margin-right: auto; text-align: center"> <div style="position: relative; margin-left: auto; margin-right: auto; text-align: center">
<FullThumbnail :doc="doc" :small-badge="false" @onThumbnailClick="onThumbnailClick()"></FullThumbnail> <FullThumbnail :doc="doc" :small-badge="false" @onThumbnailClick="onThumbnailClick()"></FullThumbnail>
</div> </div>
<!-- Audio player--> <!-- Audio player-->
<audio v-if="doc._props.isAudio" ref="audio" preload="none" class="audio-fit fit" controls <audio v-if="doc._props.isAudio" ref="audio" preload="none" class="audio-fit fit" controls
:type="doc._source.mime" :type="doc._source.mime"
:src="`f/${doc._source.index}/${doc._id}`"></audio> :src="`f/${sid(doc)}`"></audio>
<InfoTable :doc="doc" v-if="doc"></InfoTable> <InfoTable :doc="doc" v-if="doc"></InfoTable>
<div v-if="doc._source.content" class="content-div">{{ doc._source.content }}</div> <div v-if="doc._source.content" class="content-div">{{ doc._source.content }}</div>
</b-card> </b-card>
<div v-else> <div v-else>
<b-card> <b-card>
<b-card-title>{{ $t("filePage.notFound") }}</b-card-title> <b-card-title>{{ $t("filePage.notFound") }}</b-card-title>
</b-card> </b-card>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
import Preloader from "@/components/Preloader.vue"; import Preloader from "@/components/Preloader.vue";
import InfoTable from "@/components/InfoTable.vue"; import InfoTable from "@/components/InfoTable.vue";
import Sist2Api from "@/Sist2Api"; import Sist2Api from "@/Sist2Api";
import {ext} from "@/util"; import {ext, sid} from "@/util";
import Vue from "vue"; import Vue from "vue";
import sist2 from "@/Sist2Api";
import FullThumbnail from "@/components/FullThumbnail"; import FullThumbnail from "@/components/FullThumbnail";
export default Vue.extend({ export default Vue.extend({
name: "FilePage", name: "FilePage",
components: { components: {
FullThumbnail, FullThumbnail,
Preloader, Preloader,
InfoTable InfoTable
},
data() {
return {
loading: true,
found: false,
doc: null
}
},
methods: {
ext: ext,
onThumbnailClick() {
window.open(`/f/${this.doc.index}/${this.doc._id}`, "_blank");
}, },
findByCustomField(field, id) { data() {
return { return {
query: { loading: true,
bool: { found: false,
must: [ doc: null
{ }
match: {
[field]: id
}
}
]
}
},
size: 1
}
}, },
findById(id) { methods: {
return { ext: ext,
query: { sid: sid,
bool: { onThumbnailClick() {
must: [ window.open(`/f/${sid(this.doc)}`, "_blank");
{
match: {
"_id": id
}
}
]
}
}, },
size: 1 findByCustomField(field, id) {
} return {
}, query: {
findByName(name) { bool: {
return { must: [
query: { {
bool: { match: {
must: [ [field]: id
{ }
match: { }
"name": name ]
} }
} },
] size: 1
} }
}, },
size: 1 findById(id) {
} return {
} query: {
bool: {
}, must: [
mounted() { {
let query = null; match: {
if (this.$route.query.byId) { "_id": id
query = this.findById(this.$route.query.byId); }
} else if (this.$route.query.byName) { }
query = this.findByName(this.$route.query.byName); ]
} else if (this.$route.query.by && this.$route.query.q) { }
query = this.findByCustomField(this.$route.query.by, this.$route.query.q) },
} size: 1
}
if (query) { },
Sist2Api.esQuery(query).then(result => { findByName(name) {
if (result.hits.hits.length === 0) { return {
this.found = false; query: {
} else { bool: {
this.doc = result.hits.hits[0]; must: [
this.found = true; {
match: {
"name": name
}
}
]
}
},
size: 1
}
} }
this.loading = false; },
}); mounted() {
} else { let query = null;
this.loading = false; if (this.$route.query.byId) {
this.found = false; query = this.findById(this.$route.query.byId);
} else if (this.$route.query.byName) {
query = this.findByName(this.$route.query.byName);
} else if (this.$route.query.by && this.$route.query.q) {
query = this.findByCustomField(this.$route.query.by, this.$route.query.q)
}
if (query) {
Sist2Api.esQuery(query).then(result => {
if (result.hits.hits.length === 0) {
this.found = false;
} else {
this.doc = result.hits.hits[0];
this.found = true;
}
this.loading = false;
});
} else {
this.loading = false;
this.found = false;
}
} }
}
}); });
</script> </script>
<style scoped> <style scoped>
.img-wrapper { .img-wrapper {
display: inline-block; display: inline-block;
} }
</style> </style>

View File

@@ -60,6 +60,7 @@
</template> </template>
<script> <script>
import {sid} from "@/util";
import Preloader from "@/components/Preloader.vue"; import Preloader from "@/components/Preloader.vue";
import {mapActions, mapGetters, mapMutations} from "vuex"; import {mapActions, mapGetters, mapMutations} from "vuex";
import SearchBar from "@/components/SearchBar.vue"; import SearchBar from "@/components/SearchBar.vue";
@@ -92,7 +93,6 @@ export default Vue.extend({
SizeSlider, PathTree, ResultsCard, MimePicker, Lightbox, DocCardWall, IndexPicker, SearchBar, Preloader SizeSlider, PathTree, ResultsCard, MimePicker, Lightbox, DocCardWall, IndexPicker, SearchBar, Preloader
}, },
data: () => ({ data: () => ({
loading: false,
uiLoading: true, uiLoading: true,
search: undefined, search: undefined,
docs: [], docs: [],
@@ -105,6 +105,9 @@ export default Vue.extend({
}), }),
computed: { computed: {
...mapGetters(["indices", "optDisplay"]), ...mapGetters(["indices", "optDisplay"]),
hasEmbeddings() {
return Sist2Api.models().length > 0;
},
}, },
mounted() { mounted() {
// Handle touch events // Handle touch events
@@ -172,12 +175,6 @@ export default Vue.extend({
setDateBoundsMax: "setDateBoundsMax", setDateBoundsMax: "setDateBoundsMax",
setTags: "setTags", setTags: "setTags",
}), }),
hasEmbeddings() {
if (!this.loading) {
return false;
}
return Sist2Api.models().some();
},
showErrorToast() { showErrorToast() {
this.$bvToast.toast( this.$bvToast.toast(
this.$t("toast.esConnErr"), this.$t("toast.esConnErr"),
@@ -248,9 +245,9 @@ export default Vue.extend({
if (hit._props.isPlayableImage || hit._props.isPlayableVideo) { if (hit._props.isPlayableImage || hit._props.isPlayableVideo) {
hit._seq = await this.$store.dispatch("getKeySequence"); hit._seq = await this.$store.dispatch("getKeySequence");
this.$store.commit("addLightboxSource", { this.$store.commit("addLightboxSource", {
source: `f/${hit._source.index}/${hit._id}`, source: `f/${sid(hit)}`,
thumbnail: hit._props.hasThumbnail thumbnail_count: hit._props.hasThumbnail
? `t/${hit._source.index}/${hit._id}` ? `t/${sid(hit)}`
: null, : null,
caption: { caption: {
component: LightboxCaption, component: LightboxCaption,

View File

@@ -1,40 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": false,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": false,
"baseUrl": ".",
"types": [
"webpack-env",
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@@ -74,6 +74,21 @@ void sqlite_index_args_destroy(sqlite_index_args_t *args) {
free(args); free(args);
} }
char *add_trailing_slash(char *abs_path) {
if (strcmp(abs_path, "/") == 0) {
// Special case: don't add trailing slash for "/"
return abs_path;
}
char *new_abs_path = realloc(abs_path, strlen(abs_path) + 2);
if (new_abs_path == NULL) {
LOG_FATALF("cli.c", "FIXME: realloc() failed for abs_path=%s", abs_path);
}
strcat(new_abs_path, "/");
return new_abs_path;
}
int scan_args_validate(scan_args_t *args, int argc, const char **argv) { int scan_args_validate(scan_args_t *args, int argc, const char **argv) {
if (argc < 2) { if (argc < 2) {
fprintf(stderr, "Required positional argument: PATH.\n"); fprintf(stderr, "Required positional argument: PATH.\n");
@@ -83,15 +98,10 @@ int scan_args_validate(scan_args_t *args, int argc, const char **argv) {
char *abs_path = abspath(argv[1]); char *abs_path = abspath(argv[1]);
if (abs_path == NULL) { if (abs_path == NULL) {
LOG_FATALF("cli.c", "Invalid PATH argument. File not found: %s", argv[1]); LOG_FATALF("cli.c", "Invalid PATH argument. File not found: %s", argv[1]);
} else {
char *new_abs_path = realloc(abs_path, strlen(abs_path) + 2);
if (new_abs_path == NULL) {
LOG_FATALF("cli.c", "FIXME: realloc() failed for argv[1]=%s, abs_path=%s", argv[1], abs_path);
}
strcat(new_abs_path, "/");
args->path = new_abs_path;
} }
args->path = add_trailing_slash(abs_path);
if (args->tn_quality == OPTION_VALUE_UNSPECIFIED) { if (args->tn_quality == OPTION_VALUE_UNSPECIFIED) {
args->tn_quality = DEFAULT_QUALITY; args->tn_quality = DEFAULT_QUALITY;
} else if (args->tn_quality < 0 || args->tn_quality > 100) { } else if (args->tn_quality < 0 || args->tn_quality > 100) {

View File

@@ -1,8 +1,6 @@
#include "ctx.h" #include "ctx.h"
ScanCtx_t ScanCtx = { ScanCtx_t ScanCtx = {
.stat_index_size = 0,
.stat_tn_size = 0,
.pool = NULL, .pool = NULL,
.index.path = {0,}, .index.path = {0,},
}; };

View File

@@ -31,9 +31,6 @@ typedef struct {
int depth; int depth;
int calculate_checksums; int calculate_checksums;
size_t stat_tn_size;
size_t stat_index_size;
pcre *exclude; pcre *exclude;
pcre_extra *exclude_extra; pcre_extra *exclude_extra;
int fast; int fast;

View File

@@ -4,6 +4,7 @@
#include <string.h> #include <string.h>
#include <pthread.h> #include <pthread.h>
#include "src/util.h" #include "src/util.h"
#include "src/parsing/mime.h"
#include <time.h> #include <time.h>
@@ -64,9 +65,11 @@ static int sep_rfind(const char *str) {
} }
void path_parent_func(sqlite3_context *ctx, int argc, sqlite3_value **argv) { void path_parent_func(sqlite3_context *ctx, int argc, sqlite3_value **argv) {
#ifdef SIST_DEBUG
if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_TEXT) { if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
sqlite3_result_error(ctx, "Invalid parameters", -1); sqlite3_result_error(ctx, "Invalid parameters", -1);
} }
#endif
const char *value = (const char *) sqlite3_value_text(argv[0]); const char *value = (const char *) sqlite3_value_text(argv[0]);
@@ -82,28 +85,27 @@ void path_parent_func(sqlite3_context *ctx, int argc, sqlite3_value **argv) {
} }
void random_func(sqlite3_context *ctx, int argc, UNUSED(sqlite3_value **argv)) { void random_func(sqlite3_context *ctx, int argc, UNUSED(sqlite3_value **argv)) {
#ifdef SIST_DEBUG
if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_INTEGER) { if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_INTEGER) {
sqlite3_result_error(ctx, "Invalid parameters", -1); sqlite3_result_error(ctx, "Invalid parameters", -1);
} }
#endif
char state_buf[128] = {0,}; char state_buf[8] = {0,};
struct random_data buf;
int result;
long seed = sqlite3_value_int64(argv[0]); long seed = sqlite3_value_int64(argv[0]);
initstate_r((int) seed, state_buf, sizeof(state_buf), &buf); initstate((int) seed, state_buf, sizeof(state_buf));
random_r(&buf, &result); sqlite3_result_int(ctx, (int) random());
sqlite3_result_int(ctx, result);
} }
void save_current_job_info(sqlite3_context *ctx, int argc, sqlite3_value **argv) { void save_current_job_info(sqlite3_context *ctx, int argc, sqlite3_value **argv) {
#ifdef SIST_DEBUG
if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_TEXT) { if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
sqlite3_result_error(ctx, "Invalid parameters", -1); sqlite3_result_error(ctx, "Invalid parameters", -1);
} }
#endif
database_ipc_ctx_t *ipc_ctx = sqlite3_user_data(ctx); database_ipc_ctx_t *ipc_ctx = sqlite3_user_data(ctx);
@@ -146,6 +148,12 @@ void database_open(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "PRAGMA temp_store = memory;", NULL, NULL, NULL)); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "PRAGMA temp_store = memory;", NULL, NULL, NULL));
} }
#ifdef SIST_DEBUG
// CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "PRAGMA foreign_keys = ON;", NULL, NULL, NULL));
#else
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "PRAGMA ignore_check_constraints = ON;", NULL, NULL, NULL));
#endif
if (db->type == INDEX_DATABASE) { if (db->type == INDEX_DATABASE) {
// Prepare statements; // Prepare statements;
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
@@ -154,16 +162,15 @@ void database_open(database_t *db) {
&db->select_thumbnail_stmt, NULL)); &db->select_thumbnail_stmt, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db, db->db,
"UPDATE document SET marked=1 WHERE id=? AND mtime=? RETURNING id", "UPDATE marked SET marked=1 WHERE id=(SELECT ROWID FROM document WHERE path=?) AND mtime=? RETURNING id",
-1, -1,
&db->mark_document_stmt, NULL)); &db->mark_document_stmt, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db, db->db,
"REPLACE INTO document_sidecar (id, json_data) VALUES (?,?)", -1, "INSERT INTO document (path, parent, mime, mtime, size, thumbnail_count, json_data, version) "
&db->write_document_sidecar_stmt, NULL)); "VALUES (?, (SELECT id FROM document WHERE path=?), ?, ?, ?, ?, ?, (SELECT max(id) FROM version)) "
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( "ON CONFLICT (path) DO UPDATE SET json_data=excluded.json_data "
db->db, "RETURNING id;",
"REPLACE INTO document (id, mtime, size, json_data, version) VALUES (?, ?, ?, ?, (SELECT max(id) FROM version));",
-1, -1,
&db->write_document_stmt, NULL)); &db->write_document_stmt, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
@@ -173,7 +180,12 @@ void database_open(database_t *db) {
&db->write_thumbnail_stmt, NULL)); &db->write_thumbnail_stmt, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db, "SELECT json_data FROM document WHERE id=?", -1, db->db, "SELECT json_set(json_data, "
"'$._id', CAST (doc.id AS TEXT),"
"'$.thumbnail', doc.thumbnail_count,"
"'$.mime', m.name,"
"'$.size', doc.size"
") FROM document doc LEFT JOIN mime m ON m.id=doc.mime WHERE doc.id=?", -1,
&db->get_document, NULL)); &db->get_document, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
@@ -184,6 +196,12 @@ void database_open(database_t *db) {
db->db, "SELECT embedding FROM embedding WHERE id=? AND model_id=? AND start=0", -1, db->db, "SELECT embedding FROM embedding WHERE id=? AND model_id=? AND start=0", -1,
&db->get_embedding, NULL)); &db->get_embedding, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db,
"INSERT INTO tag (id, tag) VALUES (?,?) ON CONFLICT DO NOTHING;",
-1,
&db->write_tag_stmt, NULL));
// Create functions // Create functions
sqlite3_create_function( sqlite3_create_function(
db->db, db->db,
@@ -228,7 +246,7 @@ void database_open(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db, db->db,
"DELETE FROM index_job WHERE id = (SELECT MIN(id) FROM index_job)" "DELETE FROM index_job WHERE id = (SELECT MIN(id) FROM index_job)"
" RETURNING doc_id,type,line;", " RETURNING sid,type,line;",
-1, &db->pop_index_job_stmt, NULL -1, &db->pop_index_job_stmt, NULL
)); ));
@@ -243,7 +261,7 @@ void database_open(database_t *db) {
db->db, "INSERT INTO parse_job (filepath,mtime,st_size) VALUES (?,?,?);", -1, db->db, "INSERT INTO parse_job (filepath,mtime,st_size) VALUES (?,?,?);", -1,
&db->insert_parse_job_stmt, NULL)); &db->insert_parse_job_stmt, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db, "INSERT INTO index_job (doc_id,type,line) VALUES (?,?,?);", -1, db->db, "INSERT INTO index_job (sid,type,line) VALUES (?,?,?);", -1,
&db->insert_index_job_stmt, NULL)); &db->insert_index_job_stmt, NULL));
} else if (db->type == FTS_DATABASE) { } else if (db->type == FTS_DATABASE) {
@@ -294,6 +312,12 @@ void database_open(database_t *db) {
db->db, "SELECT mime, sum(count) FROM mime_index WHERE mime is not NULL GROUP BY mime", -1, db->db, "SELECT mime, sum(count) FROM mime_index WHERE mime is not NULL GROUP BY mime", -1,
&db->fts_get_mimetypes, NULL)); &db->fts_get_mimetypes, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db,
"INSERT INTO tag (id, index_id, tag) VALUES (?,?,?) ON CONFLICT DO NOTHING;",
-1,
&db->fts_write_tag_stmt, NULL));
sqlite3_create_function( sqlite3_create_function(
db->db, db->db,
"random_seeded", "random_seeded",
@@ -340,13 +364,6 @@ void database_open(database_t *db) {
} }
if (db->type == FTS_DATABASE || db->type == INDEX_DATABASE) { if (db->type == FTS_DATABASE || db->type == INDEX_DATABASE) {
// Tag table is the same schema for FTS database & index database
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db,
"INSERT INTO tag (id, tag) VALUES (?,?) ON CONFLICT DO NOTHING;",
-1,
&db->write_tag_stmt, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2( CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare_v2(
db->db, db->db,
"DELETE FROM tag WHERE id=? AND tag=?;", "DELETE FROM tag WHERE id=? AND tag=?;",
@@ -356,7 +373,7 @@ void database_open(database_t *db) {
} }
void database_close(database_t *db, int optimize) { void database_close(database_t *db, int optimize) {
LOG_DEBUGF("database.c", "Closing database %s", db->filename); LOG_DEBUGF("database.c", "Closing database %s (%p)", db->filename, db->db);
if (optimize) { if (optimize) {
LOG_DEBUG("database.c", "Optimizing database"); LOG_DEBUG("database.c", "Optimizing database");
@@ -376,8 +393,8 @@ void database_close(database_t *db, int optimize) {
db = NULL; db = NULL;
} }
void *database_read_thumbnail(database_t *db, const char *id, int num, size_t *return_value_len) { void *database_read_thumbnail(database_t *db, int doc_id, int num, size_t *return_value_len) {
sqlite3_bind_text(db->select_thumbnail_stmt, 1, id, -1, SQLITE_STATIC); sqlite3_bind_int(db->select_thumbnail_stmt, 1, doc_id);
sqlite3_bind_int(db->select_thumbnail_stmt, 2, num); sqlite3_bind_int(db->select_thumbnail_stmt, 2, num);
int ret = sqlite3_step(db->select_thumbnail_stmt); int ret = sqlite3_step(db->select_thumbnail_stmt);
@@ -410,7 +427,7 @@ void database_write_index_descriptor(database_t *db, index_descriptor_t *desc) {
sqlite3_prepare_v2(db->db, "INSERT INTO descriptor (id, version_major, version_minor, version_patch," sqlite3_prepare_v2(db->db, "INSERT INTO descriptor (id, version_major, version_minor, version_patch,"
" root, name, rewrite_url, timestamp) VALUES (?,?,?,?,?,?,?,?);", -1, &stmt, NULL); " root, name, rewrite_url, timestamp) VALUES (?,?,?,?,?,?,?,?);", -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, desc->id, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 1, desc->id);
sqlite3_bind_int(stmt, 2, desc->version_major); sqlite3_bind_int(stmt, 2, desc->version_major);
sqlite3_bind_int(stmt, 3, desc->version_minor); sqlite3_bind_int(stmt, 3, desc->version_minor);
sqlite3_bind_int(stmt, 4, desc->version_patch); sqlite3_bind_int(stmt, 4, desc->version_patch);
@@ -433,7 +450,7 @@ index_descriptor_t *database_read_index_descriptor(database_t *db) {
CRASH_IF_STMT_FAIL(sqlite3_step(stmt)); CRASH_IF_STMT_FAIL(sqlite3_step(stmt));
const char *id = (char *) sqlite3_column_text(stmt, 0); int id = sqlite3_column_int(stmt, 0);
int v_major = sqlite3_column_int(stmt, 1); int v_major = sqlite3_column_int(stmt, 1);
int v_minor = sqlite3_column_int(stmt, 2); int v_minor = sqlite3_column_int(stmt, 2);
int v_patch = sqlite3_column_int(stmt, 3); int v_patch = sqlite3_column_int(stmt, 3);
@@ -443,7 +460,7 @@ index_descriptor_t *database_read_index_descriptor(database_t *db) {
int timestamp = sqlite3_column_int(stmt, 7); int timestamp = sqlite3_column_int(stmt, 7);
index_descriptor_t *desc = malloc(sizeof(index_descriptor_t)); index_descriptor_t *desc = malloc(sizeof(index_descriptor_t));
strcpy(desc->id, id); desc->id = id;
snprintf(desc->version, sizeof(desc->version), "%d.%d.%d", v_major, v_minor, v_patch); snprintf(desc->version, sizeof(desc->version), "%d.%d.%d", v_major, v_minor, v_patch);
desc->version_major = v_major; desc->version_major = v_major;
desc->version_minor = v_minor; desc->version_minor = v_minor;
@@ -461,7 +478,8 @@ index_descriptor_t *database_read_index_descriptor(database_t *db) {
database_iterator_t *database_create_delete_list_iterator(database_t *db) { database_iterator_t *database_create_delete_list_iterator(database_t *db) {
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
sqlite3_prepare_v2(db->db, "SELECT id FROM delete_list;", -1, &stmt, NULL); sqlite3_prepare_v2(db->db, "SELECT doc.id FROM delete_list "
"INNER JOIN document doc ON doc.ROWID = delete_list.id;", -1, &stmt, NULL);
database_iterator_t *iter = malloc(sizeof(database_iterator_t)); database_iterator_t *iter = malloc(sizeof(database_iterator_t));
@@ -471,14 +489,11 @@ database_iterator_t *database_create_delete_list_iterator(database_t *db) {
return iter; return iter;
} }
char *database_delete_list_iter(database_iterator_t *iter) { int database_delete_list_iter(database_iterator_t *iter) {
int ret = sqlite3_step(iter->stmt); int ret = sqlite3_step(iter->stmt);
if (ret == SQLITE_ROW) { if (ret == SQLITE_ROW) {
const char *id = (const char *) sqlite3_column_text(iter->stmt, 0); return sqlite3_column_int(iter->stmt, 0);
char *id_heap = malloc(strlen(id) + 1);
strcpy(id_heap, id);
return id_heap;
} }
if (ret != SQLITE_DONE) { if (ret != SQLITE_DONE) {
@@ -491,7 +506,7 @@ char *database_delete_list_iter(database_iterator_t *iter) {
iter->stmt = NULL; iter->stmt = NULL;
return NULL; return 0;
} }
database_iterator_t *database_create_document_iterator(database_t *db) { database_iterator_t *database_create_document_iterator(database_t *db) {
@@ -501,27 +516,31 @@ database_iterator_t *database_create_document_iterator(database_t *db) {
CRASH_IF_NOT_SQLITE_OK( CRASH_IF_NOT_SQLITE_OK(
sqlite3_prepare_v2( sqlite3_prepare_v2(
db->db, db->db,
"WITH doc (j) AS (SELECT CASE" "WITH doc (id, j) AS ("
" WHEN emb.embedding IS NULL THEN" "SELECT"
" json_set(document.json_data, " " document.id,"
" '$._id', document.id, " " json_set(document.json_data,"
" '$.size', document.size, " " '$._id', document.id,"
" '$.mtime', document.mtime, " " '$.index', (SELECT id FROM descriptor),"
" '$.tag', json_group_array((SELECT tag FROM tag WHERE document.id = tag.id)))" " '$.size', document.size,"
" ELSE" " '$.mtime', document.mtime,"
" json_set(document.json_data," " '$.mime', mim.name,"
" '$._id', document.id," " '$.thumbnail', document.thumbnail_count,"
" '$.size', document.size," " '$.tag', json_group_array(t.tag))"
" '$.mtime', document.mtime,"
" '$.tag', json_group_array((SELECT tag FROM tag WHERE document.id = tag.id)),"
" '$.emb', json_group_object(m.path, json(emb_to_json(emb.embedding))),"
" '$.embedding', 1)"
" END"
" FROM document" " FROM document"
" LEFT JOIN embedding emb ON document.id = emb.id" " LEFT JOIN mime mim ON mim.id = document.mime"
" LEFT JOIN model m ON emb.model_id = m.id" " LEFT JOIN tag t ON t.id = document.id"
" GROUP BY document.id)" " GROUP BY document.id)"
" SELECT json_set(j, '$.index', (SELECT id FROM descriptor)) FROM doc", "SELECT CASE"
" WHEN emb.embedding IS NULL THEN j"
" ELSE json_set(j,"
" '$.emb', json_group_object(m.path, json(emb_to_json(emb.embedding))),"
" '$.embedding', 1"
" ) END"
" FROM doc"
" LEFT JOIN embedding emb ON doc.id = emb.id"
" LEFT JOIN model m ON emb.model_id = m.id"
" GROUP BY doc.id",
-1, &stmt, NULL)); -1, &stmt, NULL));
database_iterator_t *iter = malloc(sizeof(database_iterator_t)); database_iterator_t *iter = malloc(sizeof(database_iterator_t));
@@ -573,43 +592,49 @@ cJSON *database_document_iter(database_iterator_t *iter) {
cJSON *database_incremental_scan_begin(database_t *db) { cJSON *database_incremental_scan_begin(database_t *db) {
LOG_DEBUG("database.c", "Preparing database for incremental scan"); LOG_DEBUG("database.c", "Preparing database for incremental scan");
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "UPDATE document SET marked=0;", NULL, NULL, NULL)); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "DELETE FROM marked;", NULL, NULL, NULL));
LOG_DEBUG("database.c", "Preparing database for incremental scan (create marked table)");
CRASH_IF_NOT_SQLITE_OK(
sqlite3_exec(db->db, "INSERT INTO marked SELECT id, 0, mtime FROM document;", NULL, NULL, NULL));
} }
cJSON *database_incremental_scan_end(database_t *db) { cJSON *database_incremental_scan_end(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"DELETE FROM delete_list WHERE id IN (SELECT id FROM document WHERE marked=1);", "DELETE FROM delete_list WHERE id IN (SELECT id FROM marked WHERE marked = 1);",
NULL, NULL, NULL NULL, NULL, NULL
)); ));
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"DELETE FROM thumbnail WHERE id IN (SELECT id FROM document WHERE marked=0);", "DELETE FROM thumbnail WHERE EXISTS ("
" SELECT document.id FROM document INNER JOIN marked m ON m.id = document.ROWID"
" WHERE marked=0 and document.id = thumbnail.id)",
NULL, NULL, NULL NULL, NULL, NULL
)); ));
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"INSERT INTO delete_list (id) SELECT id FROM document WHERE marked=0;", "INSERT INTO delete_list (id) "
"SELECT id FROM marked WHERE marked=0 ON CONFLICT DO NOTHING;",
NULL, NULL, NULL NULL, NULL, NULL
)); ));
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"DELETE FROM document_sidecar WHERE id IN (SELECT id FROM document WHERE marked=0);", "DELETE FROM document WHERE ROWID IN (SELECT id FROM marked WHERE marked=0);",
NULL, NULL, NULL NULL, NULL, NULL
)); ));
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"DELETE FROM document WHERE marked=0;", "DELETE FROM marked;",
NULL, NULL, NULL NULL, NULL, NULL
)); ));
} }
int database_mark_document(database_t *db, const char *id, int mtime) { int database_mark_document(database_t *db, const char *path, int mtime) {
sqlite3_bind_text(db->mark_document_stmt, 1, id, -1, SQLITE_STATIC); sqlite3_bind_text(db->mark_document_stmt, 1, path, -1, SQLITE_STATIC);
sqlite3_bind_int(db->mark_document_stmt, 2, mtime); sqlite3_bind_int(db->mark_document_stmt, 2, mtime);
pthread_mutex_lock(&db->ipc_ctx->index_db_mutex); pthread_mutex_lock(&db->ipc_ctx->index_db_mutex);
@@ -631,31 +656,38 @@ int database_mark_document(database_t *db, const char *id, int mtime) {
CRASH_IF_STMT_FAIL(ret); CRASH_IF_STMT_FAIL(ret);
} }
void database_write_document(database_t *db, document_t *doc, const char *json_data) { int database_write_document(database_t *db, document_t *doc, const char *json_data) {
sqlite3_bind_text(db->write_document_stmt, 1, doc->doc_id, -1, SQLITE_STATIC);
sqlite3_bind_int(db->write_document_stmt, 2, doc->mtime); const char *rel_path = doc->filepath + ScanCtx.index.desc.root_len;
sqlite3_bind_int64(db->write_document_stmt, 3, (long) doc->size); const char *parent_rel_path = doc->parent[0] != '\0'
sqlite3_bind_text(db->write_document_stmt, 4, json_data, -1, SQLITE_STATIC); ? doc->parent + ScanCtx.index.desc.root_len
: NULL;
// path, parent, mtime, size, json_data
sqlite3_bind_text(db->write_document_stmt, 1, rel_path, -1, SQLITE_STATIC);
sqlite3_bind_text(db->write_document_stmt, 2, parent_rel_path, -1, SQLITE_STATIC);
sqlite3_bind_int64(db->write_document_stmt, 3, doc->mime);
sqlite3_bind_int(db->write_document_stmt, 4, doc->mtime);
sqlite3_bind_int64(db->write_document_stmt, 5, (long) doc->size);
sqlite3_bind_int(db->write_document_stmt, 6, doc->thumbnail_count);
if (json_data) {
sqlite3_bind_text(db->write_document_stmt, 7, json_data, -1, SQLITE_STATIC);
} else {
sqlite3_bind_null(db->write_document_stmt, 7);
}
pthread_mutex_lock(&db->ipc_ctx->index_db_mutex); pthread_mutex_lock(&db->ipc_ctx->index_db_mutex);
CRASH_IF_STMT_FAIL(sqlite3_step(db->write_document_stmt)); CRASH_IF_STMT_FAIL(sqlite3_step(db->write_document_stmt));
int id = sqlite3_column_int(db->write_document_stmt, 0);
CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->write_document_stmt)); CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->write_document_stmt));
pthread_mutex_unlock(&db->ipc_ctx->index_db_mutex); pthread_mutex_unlock(&db->ipc_ctx->index_db_mutex);
return id;
} }
void database_write_document_sidecar(database_t *db, const char *id, const char *json_data) { void database_write_thumbnail(database_t *db, int doc_id, int num, void *data, size_t data_size) {
sqlite3_bind_text(db->write_document_sidecar_stmt, 1, id, -1, SQLITE_STATIC); sqlite3_bind_int(db->write_thumbnail_stmt, 1, doc_id);
sqlite3_bind_text(db->write_document_sidecar_stmt, 2, json_data, -1, SQLITE_STATIC);
pthread_mutex_lock(&db->ipc_ctx->index_db_mutex);
CRASH_IF_STMT_FAIL(sqlite3_step(db->write_document_sidecar_stmt));
CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->write_document_sidecar_stmt));
pthread_mutex_unlock(&db->ipc_ctx->index_db_mutex);
}
void database_write_thumbnail(database_t *db, const char *id, int num, void *data, size_t data_size) {
sqlite3_bind_text(db->write_thumbnail_stmt, 1, id, -1, SQLITE_STATIC);
sqlite3_bind_int(db->write_thumbnail_stmt, 2, num); sqlite3_bind_int(db->write_thumbnail_stmt, 2, num);
sqlite3_bind_blob(db->write_thumbnail_stmt, 3, data, (int) data_size, SQLITE_STATIC); sqlite3_bind_blob(db->write_thumbnail_stmt, 3, data, (int) data_size, SQLITE_STATIC);
@@ -716,7 +748,7 @@ job_t *database_get_work(database_t *db, job_type_t job_type) {
} else { } else {
job->bulk_line = malloc(sizeof(es_bulk_line_t)); job->bulk_line = malloc(sizeof(es_bulk_line_t));
} }
strcpy(job->bulk_line->doc_id, (const char *) sqlite3_column_text(db->pop_index_job_stmt, 0)); strcpy(job->bulk_line->sid, (const char *) sqlite3_column_text(db->pop_index_job_stmt, 0));
job->bulk_line->type = sqlite3_column_int(db->pop_index_job_stmt, 1); job->bulk_line->type = sqlite3_column_int(db->pop_index_job_stmt, 1);
job->bulk_line->next = NULL; job->bulk_line->next = NULL;
@@ -767,7 +799,7 @@ void database_add_work(database_t *db, job_t *job) {
} while (ret != SQLITE_DONE && ret != SQLITE_OK); } while (ret != SQLITE_DONE && ret != SQLITE_OK);
} else if (job->type == JOB_BULK_LINE) { } else if (job->type == JOB_BULK_LINE) {
do { do {
sqlite3_bind_text(db->insert_index_job_stmt, 1, job->bulk_line->doc_id, -1, SQLITE_STATIC); sqlite3_bind_text(db->insert_index_job_stmt, 1, job->bulk_line->sid, -1, SQLITE_STATIC);
sqlite3_bind_int(db->insert_index_job_stmt, 2, job->bulk_line->type); sqlite3_bind_int(db->insert_index_job_stmt, 2, job->bulk_line->type);
if (job->bulk_line->type != ES_BULK_LINE_DELETE) { if (job->bulk_line->type != ES_BULK_LINE_DELETE) {
sqlite3_bind_text(db->insert_index_job_stmt, 3, job->bulk_line->line, -1, SQLITE_STATIC); sqlite3_bind_text(db->insert_index_job_stmt, 3, job->bulk_line->line, -1, SQLITE_STATIC);
@@ -808,24 +840,25 @@ void database_add_work(database_t *db, job_t *job) {
pthread_mutex_unlock(&db->ipc_ctx->mutex); pthread_mutex_unlock(&db->ipc_ctx->mutex);
} }
void database_write_tag(database_t *db, char *doc_id, char *tag) { void database_write_tag(database_t *db, long sid, char *tag) {
sqlite3_bind_text(db->write_tag_stmt, 1, doc_id, -1, SQLITE_STATIC); sqlite3_bind_int64(db->write_tag_stmt, 1, sid);
sqlite3_bind_text(db->write_tag_stmt, 2, tag, -1, SQLITE_STATIC); sqlite3_bind_int(db->write_tag_stmt, 2, (int) (sid >> 32));
sqlite3_bind_text(db->write_tag_stmt, 3, tag, -1, SQLITE_STATIC);
CRASH_IF_STMT_FAIL(sqlite3_step(db->write_tag_stmt)); CRASH_IF_STMT_FAIL(sqlite3_step(db->write_tag_stmt));
CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->write_tag_stmt)); CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->write_tag_stmt));
} }
void database_delete_tag(database_t *db, char *doc_id, char *tag) { void database_delete_tag(database_t *db, long sid, char *tag) {
sqlite3_bind_text(db->delete_tag_stmt, 1, doc_id, -1, SQLITE_STATIC); sqlite3_bind_int64(db->delete_tag_stmt, 1, sid);
sqlite3_bind_text(db->delete_tag_stmt, 2, tag, -1, SQLITE_STATIC); sqlite3_bind_text(db->delete_tag_stmt, 2, tag, -1, SQLITE_STATIC);
CRASH_IF_STMT_FAIL(sqlite3_step(db->delete_tag_stmt)); CRASH_IF_STMT_FAIL(sqlite3_step(db->delete_tag_stmt));
CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->delete_tag_stmt)); CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->delete_tag_stmt));
} }
cJSON *database_get_document(database_t *db, char *doc_id) { cJSON *database_get_document(database_t *db, int doc_id) {
sqlite3_bind_text(db->get_document, 1, doc_id, -1, SQLITE_STATIC); sqlite3_bind_int(db->get_document, 1, doc_id);
int ret = sqlite3_step(db->get_document); int ret = sqlite3_step(db->get_document);
CRASH_IF_STMT_FAIL(ret); CRASH_IF_STMT_FAIL(ret);
@@ -833,7 +866,7 @@ cJSON *database_get_document(database_t *db, char *doc_id) {
cJSON *json; cJSON *json;
if (ret == SQLITE_ROW) { if (ret == SQLITE_ROW) {
const char *json_str = sqlite3_column_text(db->get_document, 0); const char *json_str = (char *) sqlite3_column_text(db->get_document, 0);
json = cJSON_Parse(json_str); json = cJSON_Parse(json_str);
} else { } else {
json = NULL; json = NULL;
@@ -847,4 +880,24 @@ cJSON *database_get_document(database_t *db, char *doc_id) {
void database_increment_version(database_t *db) { void database_increment_version(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, "INSERT INTO version DEFAULT VALUES", NULL, NULL, NULL)); db->db, "INSERT INTO version DEFAULT VALUES", NULL, NULL, NULL));
}
void database_sync_mime_table(database_t *db) {
unsigned int *cur = get_mime_ids();
sqlite3_stmt *stmt;
CRASH_IF_NOT_SQLITE_OK(sqlite3_prepare(
db->db,
"REPLACE INTO mime (id, name) VALUES (?,?)", -1, &stmt, NULL));
while (*cur != 0) {
sqlite3_bind_int64(stmt, 1, (long) *cur);
sqlite3_bind_text(stmt, 2, mime_get_mime_text(*cur), -1, NULL);
CRASH_IF_STMT_FAIL(sqlite3_step(stmt));
sqlite3_reset(stmt);
cur += 1;
}
sqlite3_finalize(stmt);
} }

View File

@@ -81,7 +81,6 @@ typedef struct database {
sqlite3_stmt *mark_document_stmt; sqlite3_stmt *mark_document_stmt;
sqlite3_stmt *write_document_stmt; sqlite3_stmt *write_document_stmt;
sqlite3_stmt *write_document_sidecar_stmt;
sqlite3_stmt *write_thumbnail_stmt; sqlite3_stmt *write_thumbnail_stmt;
sqlite3_stmt *get_document; sqlite3_stmt *get_document;
sqlite3_stmt *get_models; sqlite3_stmt *get_models;
@@ -103,9 +102,9 @@ typedef struct database {
sqlite3_stmt *fts_get_document; sqlite3_stmt *fts_get_document;
sqlite3_stmt *fts_suggest_tag; sqlite3_stmt *fts_suggest_tag;
sqlite3_stmt *fts_get_tags; sqlite3_stmt *fts_get_tags;
sqlite3_stmt *fts_write_tag_stmt;
sqlite3_stmt *fts_model_size; sqlite3_stmt *fts_model_size;
char **tag_array; char **tag_array;
database_ipc_ctx_t *ipc_ctx; database_ipc_ctx_t *ipc_ctx;
@@ -133,15 +132,15 @@ void database_close(database_t *, int optimize);
void database_increment_version(database_t *db); void database_increment_version(database_t *db);
void database_write_thumbnail(database_t *db, const char *id, int num, void *data, size_t data_size); void database_write_thumbnail(database_t *db, int doc_id, int num, void *data, size_t data_size);
void *database_read_thumbnail(database_t *db, const char *id, int num, size_t *return_value_len); void *database_read_thumbnail(database_t *db, int doc_id, int num, size_t *return_value_len);
void database_write_index_descriptor(database_t *db, index_descriptor_t *desc); void database_write_index_descriptor(database_t *db, index_descriptor_t *desc);
index_descriptor_t *database_read_index_descriptor(database_t *db); index_descriptor_t *database_read_index_descriptor(database_t *db);
void database_write_document(database_t *db, document_t *doc, const char *json_data); int database_write_document(database_t *db, document_t *doc, const char *json_data);
database_iterator_t *database_create_document_iterator(database_t *db); database_iterator_t *database_create_document_iterator(database_t *db);
@@ -154,10 +153,10 @@ cJSON *database_document_iter(database_iterator_t *);
database_iterator_t *database_create_delete_list_iterator(database_t *db); database_iterator_t *database_create_delete_list_iterator(database_t *db);
char *database_delete_list_iter(database_iterator_t *iter); int database_delete_list_iter(database_iterator_t *iter);
#define database_delete_list_iter_foreach(element, iter) \ #define database_delete_list_iter_foreach(element, iter) \
for (char *(element) = database_delete_list_iter(iter); (element) != NULL; (element) = database_delete_list_iter(iter)) for (int (element) = database_delete_list_iter(iter); (element) != 0; (element) = database_delete_list_iter(iter))
cJSON *database_incremental_scan_begin(database_t *db); cJSON *database_incremental_scan_begin(database_t *db);
@@ -166,8 +165,6 @@ cJSON *database_incremental_scan_end(database_t *db);
int database_mark_document(database_t *db, const char *id, int mtime); int database_mark_document(database_t *db, const char *id, int mtime);
void database_write_document_sidecar(database_t *db, const char *id, const char *json_data);
database_iterator_t *database_create_treemap_iterator(database_t *db, long threshold); database_iterator_t *database_create_treemap_iterator(database_t *db, long threshold);
treemap_row_t database_treemap_iter(database_iterator_t *iter); treemap_row_t database_treemap_iter(database_iterator_t *iter);
@@ -206,7 +203,7 @@ void database_fts_index(database_t *db);
void database_fts_optimize(database_t *db); void database_fts_optimize(database_t *db);
cJSON *database_fts_get_paths(database_t *db, const char *index_id, int depth_min, int depth_max, const char *prefix, cJSON *database_fts_get_paths(database_t *db, int index_id, int depth_min, int depth_max, const char *prefix,
int suggest); int suggest);
cJSON *database_fts_get_mimetypes(database_t *db); cJSON *database_fts_get_mimetypes(database_t *db);
@@ -215,18 +212,20 @@ database_summary_stats_t database_fts_get_date_range(database_t *db);
cJSON *database_fts_search(database_t *db, const char *query, const char *path, long size_min, cJSON *database_fts_search(database_t *db, const char *query, const char *path, long size_min,
long size_max, long date_min, long date_max, int page_size, long size_max, long date_min, long date_max, int page_size,
char **index_ids, char **mime_types, char **tags, int sort_asc, int *index_ids, char **mime_types, char **tags, int sort_asc,
fts_sort_t sort, int seed, char **after, int fetch_aggregations, fts_sort_t sort, int seed, char **after, int fetch_aggregations,
int highlight, int highlight_context_size, int model, int highlight, int highlight_context_size, int model,
const float *embedding, int embedding_size); const float *embedding, int embedding_size);
void database_write_tag(database_t *db, char *doc_id, char *tag); void database_write_tag(database_t *db, long sid, char *tag);
void database_delete_tag(database_t *db, char *doc_id, char *tag); void database_fts_write_tag(database_t *db, long sid, char *tag);
void database_delete_tag(database_t *db, long sid, char *tag);
void database_fts_detach(database_t *db); void database_fts_detach(database_t *db);
cJSON *database_fts_get_document(database_t *db, char *doc_id); cJSON *database_fts_get_document(database_t *db, long sid);
database_summary_stats_t database_fts_sync_tags(database_t *db); database_summary_stats_t database_fts_sync_tags(database_t *db);
@@ -234,7 +233,7 @@ cJSON *database_fts_suggest_tag(database_t *db, char *prefix);
cJSON *database_fts_get_tags(database_t *db); cJSON *database_fts_get_tags(database_t *db);
cJSON *database_get_document(database_t *db, char *doc_id); cJSON *database_get_document(database_t *db, int doc_id);
void cosine_sim_func(sqlite3_context *ctx, int argc, sqlite3_value **argv); void cosine_sim_func(sqlite3_context *ctx, int argc, sqlite3_value **argv);
@@ -242,6 +241,8 @@ cJSON *database_get_models(database_t *db);
int database_fts_get_model_size(database_t *db, int model_id); int database_fts_get_model_size(database_t *db, int model_id);
cJSON *database_get_embedding(database_t *db, char *doc_id, int model_id); cJSON *database_get_embedding(database_t *db, int doc_id, int model_id);
void database_sync_mime_table(database_t *db);
#endif #endif

View File

@@ -69,9 +69,9 @@ cJSON *database_get_models(database_t *db) {
return json; return json;
} }
cJSON *database_get_embedding(database_t *db, char *doc_id, int model_id) { cJSON *database_get_embedding(database_t *db, int doc_id, int model_id) {
sqlite3_bind_text(db->get_embedding, 1, doc_id, -1, SQLITE_STATIC); sqlite3_bind_int(db->get_embedding, 1, doc_id);
sqlite3_bind_int(db->get_embedding, 2, model_id); sqlite3_bind_int(db->get_embedding, 2, model_id);
int ret = sqlite3_step(db->get_embedding); int ret = sqlite3_step(db->get_embedding);
CRASH_IF_STMT_FAIL(ret); CRASH_IF_STMT_FAIL(ret);

View File

@@ -42,21 +42,23 @@ void database_fts_index(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"WITH docs AS (" "WITH docs AS ("
" SELECT document.id as id, (SELECT id FROM descriptor) as index_id, size," " SELECT "
" ((SELECT id FROM descriptor) << 32) | document.id as id,"
" (SELECT id FROM descriptor) as index_id,"
" size,"
" document.json_data ->> 'name' as name," " document.json_data ->> 'name' as name,"
" document.json_data ->> 'path' as path," " document.json_data ->> 'path' as path,"
" mtime," " mtime,"
" document.json_data ->> 'mime' as mime," " m.name as mime,"
" json_set(document.json_data, " " thumbnail_count,"
" '$._id',document.id," " document.json_data"
" '$.size',document.size, "
" '$.mtime',document.mtime)"
" FROM document" " FROM document"
" LEFT JOIN mime m ON m.id=document.mime"
" )" " )"
" INSERT" " INSERT"
" INTO fts.document_index (id, index_id, size, name, path, mtime, mime, json_data)" " INTO fts.document_index (id, index_id, size, name, path, mtime, mime, thumbnail_count, json_data)"
" SELECT * FROM docs WHERE true" " SELECT * FROM docs WHERE true"
" on conflict (id, index_id) do update set " " on conflict (id) do update set "
" size=excluded.size, mtime=excluded.mtime, mime=excluded.mime, json_data=excluded.json_data;", " size=excluded.size, mtime=excluded.mtime, mime=excluded.mime, json_data=excluded.json_data;",
NULL, NULL, NULL)); NULL, NULL, NULL));
@@ -64,13 +66,14 @@ void database_fts_index(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"REPLACE INTO fts.embedding (id, model_id, start, end, embedding)" "REPLACE INTO fts.model (id, size)"
" SELECT id, model_id, start, end, embedding FROM embedding", NULL, NULL, NULL)); " SELECT id, size FROM model", NULL, NULL, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"INSERT INTO fts.model (id, size)" "REPLACE INTO fts.embedding (id, model_id, start, end, embedding)"
" SELECT id, size FROM model WHERE TRUE ON CONFLICT (id) DO NOTHING", NULL, NULL, NULL)); " SELECT (SELECT id FROM descriptor) << 32 | id, model_id, start, end, embedding FROM embedding "
" WHERE TRUE ON CONFLICT (id, model_id, start) DO NOTHING;", NULL, NULL, NULL));
// TODO: delete old embeddings // TODO: delete old embeddings
@@ -157,7 +160,8 @@ void database_fts_index(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"INSERT INTO search(rowid, name, content, title) SELECT id, name, content, title from document_view", "INSERT INTO search(rowid, name, content, title, path) "
"SELECT id, name, content, title, path from document_view",
NULL, NULL, NULL)); NULL, NULL, NULL));
} }
@@ -172,7 +176,7 @@ void database_fts_optimize(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "PRAGMA fts.optimize;", NULL, NULL, NULL)); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "PRAGMA fts.optimize;", NULL, NULL, NULL));
} }
cJSON *database_fts_get_paths(database_t *db, const char *index_id, int depth_min, int depth_max, const char *prefix, cJSON *database_fts_get_paths(database_t *db, int index_id, int depth_min, int depth_max, const char *prefix,
int suggest) { int suggest) {
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
@@ -192,7 +196,7 @@ cJSON *database_fts_get_paths(database_t *db, const char *index_id, int depth_mi
} else if (prefix) { } else if (prefix) {
stmt = db->fts_search_paths_w_prefix; stmt = db->fts_search_paths_w_prefix;
if (index_id) { if (index_id) {
sqlite3_bind_text(stmt, 1, index_id, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 1, index_id);
} else { } else {
sqlite3_bind_null(stmt, 1); sqlite3_bind_null(stmt, 1);
} }
@@ -207,7 +211,7 @@ cJSON *database_fts_get_paths(database_t *db, const char *index_id, int depth_mi
} else { } else {
stmt = db->fts_search_paths; stmt = db->fts_search_paths;
if (index_id) { if (index_id) {
sqlite3_bind_text(stmt, 1, index_id, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 1, index_id);
} else { } else {
sqlite3_bind_null(stmt, 1); sqlite3_bind_null(stmt, 1);
} }
@@ -290,7 +294,6 @@ const char *date_where_clause(long date_min, long date_max) {
} }
int array_length(char **arr) { int array_length(char **arr) {
if (arr == NULL) { if (arr == NULL) {
return 0; return 0;
} }
@@ -301,6 +304,17 @@ int array_length(char **arr) {
return count; return count;
} }
int int_array_length(const int *arr) {
if (arr == NULL) {
return 0;
}
int count = -1;
while (arr[++count] != 0);
return count;
}
#define INDEX_ID_PARAM_OFFSET (10) #define INDEX_ID_PARAM_OFFSET (10)
#define MIME_PARAM_OFFSET (INDEX_ID_PARAM_OFFSET + 1000) #define MIME_PARAM_OFFSET (INDEX_ID_PARAM_OFFSET + 1000)
@@ -351,8 +365,8 @@ char *build_where_clause(const char *path_where, const char *size_where, const c
return where; return where;
} }
char *index_ids_where_clause(char **index_ids) { char *index_ids_where_clause(int *index_ids) {
int param_count = array_length(index_ids); int param_count = int_array_length(index_ids);
char *clause = malloc(13 + 2 + 6 * param_count); char *clause = malloc(13 + 2 + 6 * param_count);
@@ -483,7 +497,7 @@ int database_fts_get_model_size(database_t *db, int model_id) {
cJSON *database_fts_search(database_t *db, const char *query, const char *path, long size_min, cJSON *database_fts_search(database_t *db, const char *query, const char *path, long size_min,
long size_max, long date_min, long date_max, int page_size, long size_max, long date_min, long date_max, int page_size,
char **index_ids, char **mime_types, char **tags, int sort_asc, int *index_ids, char **mime_types, char **tags, int sort_asc,
fts_sort_t sort, int seed, char **after, int fetch_aggregations, fts_sort_t sort, int seed, char **after, int fetch_aggregations,
int highlight, int highlight_context_size, int model, int highlight, int highlight_context_size, int model,
const float *embedding, int embedding_size) { const float *embedding, int embedding_size) {
@@ -524,13 +538,21 @@ cJSON *database_fts_search(database_t *db, const char *query, const char *path,
const char *json_object_sql; const char *json_object_sql;
if (highlight && query_where != NULL) { if (highlight && query_where != NULL) {
json_object_sql = "json_set(json_remove(doc.json_data, '$.content')," json_object_sql = "json_set(json_remove(doc.json_data, '$.content'),"
"'$._id', CAST(doc.id AS TEXT),"
"'$.index', doc.index_id," "'$.index', doc.index_id,"
"'$.thumbnail', doc.thumbnail_count,"
"'$.mime', doc.mime,"
"'$.size', doc.size,"
"'$.embedding', (CASE WHEN emb.id IS NOT NULL THEN 1 ELSE 0 END)," "'$.embedding', (CASE WHEN emb.id IS NOT NULL THEN 1 ELSE 0 END),"
"'$._highlight.name', snippet(search, 0, '<mark>', '</mark>', '', ?6)," "'$._highlight.name', snippet(search, 0, '<mark>', '</mark>', '', ?6),"
"'$._highlight.content', snippet(search, 1, '<mark>', '</mark>', '', ?6))"; "'$._highlight.content', snippet(search, 1, '<mark>', '</mark>', '', ?6))";
} else { } else {
json_object_sql = "json_set(json_remove(doc.json_data, '$.content')," json_object_sql = "json_set(json_remove(doc.json_data, '$.content'),"
"'$._id', CAST(doc.id AS TEXT),"
"'$.index', doc.index_id," "'$.index', doc.index_id,"
"'$.thumbnail', doc.thumbnail_count,"
"'$.mime', doc.mime,"
"'$.size', doc.size,"
"'$.embedding', (CASE WHEN emb.id IS NOT NULL THEN 1 ELSE 0 END))"; "'$.embedding', (CASE WHEN emb.id IS NOT NULL THEN 1 ELSE 0 END))";
} }
@@ -592,7 +614,7 @@ cJSON *database_fts_search(database_t *db, const char *query, const char *path,
if (index_ids) { if (index_ids) {
array_foreach(index_ids) { array_foreach(index_ids) {
sqlite3_bind_text(stmt, INDEX_ID_PARAM_OFFSET + i, index_ids[i], -1, SQLITE_STATIC); sqlite3_bind_int(stmt, INDEX_ID_PARAM_OFFSET + i, index_ids[i]);
} }
} }
if (mime_types) { if (mime_types) {
@@ -692,7 +714,7 @@ cJSON *database_fts_search(database_t *db, const char *query, const char *path,
if (index_ids) { if (index_ids) {
array_foreach(index_ids) { array_foreach(index_ids) {
sqlite3_bind_text(agg_stmt, INDEX_ID_PARAM_OFFSET + i, index_ids[i], -1, SQLITE_STATIC); sqlite3_bind_int(agg_stmt, INDEX_ID_PARAM_OFFSET + i, index_ids[i]);
} }
} }
if (mime_types) { if (mime_types) {
@@ -764,19 +786,20 @@ database_summary_stats_t database_fts_sync_tags(database_t *db) {
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"DELETE FROM fts.tag WHERE" "DELETE FROM fts.tag WHERE"
" (id, tag) NOT IN (SELECT id, tag FROM tag)", " (id, index_id, tag) NOT IN (SELECT ((SELECT id FROM descriptor) << 32) | id, (SELECT id FROM descriptor), tag FROM tag)"
" AND index_id = (SELECT id FROM descriptor)",
NULL, NULL, NULL)); NULL, NULL, NULL));
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec( CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(
db->db, db->db,
"INSERT INTO fts.tag (id, tag) " "INSERT INTO fts.tag (id, index_id, tag) "
" SELECT id, tag FROM tag " " SELECT (((SELECT id FROM descriptor) << 32) | id) as sid, (SELECT id FROM descriptor), tag FROM tag "
" WHERE (id, tag) NOT IN (SELECT * FROM fts.tag)", " WHERE (sid, tag) NOT IN (SELECT id, tag FROM fts.tag)",
NULL, NULL, NULL)); NULL, NULL, NULL));
} }
cJSON *database_fts_get_document(database_t *db, char *doc_id) { cJSON *database_fts_get_document(database_t *db, long sid) {
sqlite3_bind_text(db->fts_get_document, 1, doc_id, -1, NULL); sqlite3_bind_int64(db->fts_get_document, 1, sid);
int ret = sqlite3_step(db->fts_get_document); int ret = sqlite3_step(db->fts_get_document);
cJSON *json = NULL; cJSON *json = NULL;
@@ -844,3 +867,11 @@ cJSON *database_fts_get_tags(database_t *db) {
return json; return json;
} }
void database_fts_write_tag(database_t *db, long sid, char *tag) {
sqlite3_bind_int64(db->fts_write_tag_stmt, 1, sid);
sqlite3_bind_int(db->fts_write_tag_stmt, 2, (int) (sid >> 32));
sqlite3_bind_text(db->fts_write_tag_stmt, 3, tag, -1, SQLITE_STATIC);
CRASH_IF_STMT_FAIL(sqlite3_step(db->fts_write_tag_stmt));
CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->fts_write_tag_stmt));
}

View File

@@ -1,57 +1,63 @@
#ifdef SIST_DEBUG
#define STRICT " STRICT"
#else
#define STRICT ""
#endif
const char *FtsDatabaseSchema = const char *FtsDatabaseSchema =
"CREATE TABLE IF NOT EXISTS document_index (" "CREATE TABLE IF NOT EXISTS document_index ("
" id TEXT NOT NULL," " id INTEGER PRIMARY KEY,"
" index_id TEXT NOT NULL," " index_id INTEGER NOT NULL,"
" size INTEGER NOT NULL," " size INTEGER NOT NULL,"
" name TEXT NOT NULL," " name TEXT NOT NULL,"
" path TEXT NOT NULL," " path TEXT NOT NULL,"
" mtime INTEGER NOT NULL," " mtime INTEGER NOT NULL,"
" mime TEXT," " mime TEXT,"
" json_data TEXT NOT NULL," " thumbnail_count INTEGER NOT NULL,"
" PRIMARY KEY (id, index_id)" " json_data TEXT NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TABLE IF NOT EXISTS stats (" "CREATE TABLE IF NOT EXISTS stats ("
" mtime_min INTEGER," " mtime_min INTEGER,"
" mtime_max INTEGER" " mtime_max INTEGER"
");" ")"STRICT";"
"" ""
"CREATE TABLE IF NOT EXISTS path_index (" "CREATE TABLE IF NOT EXISTS path_index ("
" path TEXT," " path TEXT,"
" index_id TEXT," " index_id INTEGER,"
" count INTEGER NOT NULL," " count INTEGER NOT NULL,"
" depth INTEGER NOT NULL," " depth INTEGER NOT NULL,"
" PRIMARY KEY (path, index_id)" " PRIMARY KEY (path, index_id)"
");" ")"STRICT";"
"" ""
"CREATE TABLE IF NOT EXISTS mime_index (" "CREATE TABLE IF NOT EXISTS mime_index ("
" index_id TEXT," " index_id INTEGER,"
" mime TEXT," " mime TEXT,"
" count INT," " count INTEGER,"
" PRIMARY KEY(index_id, mime)" " PRIMARY KEY(index_id, mime)"
");" ")"STRICT";"
"" ""
"CREATE TABLE IF NOT EXISTS tag (" "CREATE TABLE IF NOT EXISTS tag ("
" id TEXT NOT NULL," " id INTEGER NOT NULL,"
" index_id INTEGER NOT NULL,"
" tag TEXT NOT NULL," " tag TEXT NOT NULL,"
" PRIMARY KEY (id, tag)" " PRIMARY KEY (id, tag)"
");" ")"STRICT";"
"CREATE INDEX IF NOT EXISTS tag_tag_idx ON tag(tag);" "CREATE INDEX IF NOT EXISTS tag_tag_idx ON tag(tag);"
"CREATE INDEX IF NOT EXISTS tag_id_idx ON tag(id);"
"" ""
"CREATE TABLE IF NOT EXISTS embedding (" "CREATE TABLE IF NOT EXISTS embedding ("
" id TEXT REFERENCES document(id)," " id INTEGER REFERENCES document_index(id),"
" model_id INTEGER NOT NULL REFERENCES model(id)," " model_id INTEGER NOT NULL REFERENCES model(id),"
" start INTEGER NOT NULL," " start INTEGER NOT NULL,"
" end INTEGER," " end INTEGER,"
" embedding BLOB NOT NULL," " embedding BLOB NOT NULL,"
" PRIMARY KEY (id, model_id, start)" " PRIMARY KEY (id, model_id, start)"
");" ")"STRICT";"
"" ""
"CREATE TABLE IF NOT EXISTS model (" "CREATE TABLE IF NOT EXISTS model ("
" id INTEGER PRIMARY KEY CHECK (id > 0 AND id < 1000)," " id INTEGER PRIMARY KEY CHECK (id > 0 AND id < 1000),"
" size INTEGER NOT NULL" " size INTEGER NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TRIGGER IF NOT EXISTS tag_write_trigger" "CREATE TRIGGER IF NOT EXISTS tag_write_trigger"
" AFTER INSERT ON tag" " AFTER INSERT ON tag"
@@ -69,23 +75,25 @@ const char *FtsDatabaseSchema =
" WHERE id = OLD.id;" " WHERE id = OLD.id;"
" END;" " END;"
"" ""
"CREATE VIEW IF NOT EXISTS document_view (id, name, content, title)" "CREATE VIEW IF NOT EXISTS document_view (id, name, content, title, path)"
" AS" " AS"
" SELECT rowid," " SELECT id,"
" json_data->>'name'," " json_data->>'name',"
" json_data->>'content'," " json_data->>'content',"
" json_data->>'title'" " json_data->>'title',"
" json_data->>'path'"
" FROM document_index;" " FROM document_index;"
"" ""
"CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5 (" "CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5 ("
" name," " name,"
" content," " content,"
" title," " title,"
" path,"
" content='document_view'," " content='document_view',"
" content_rowid='id'" " content_rowid='id'"
");" ");"
// name^8, content^3, title^8 // name^8, content^3, title^8, path^5
"INSERT INTO search(search, rank) VALUES('rank', 'bm25(8, 3, 8)');" "INSERT INTO search(search, rank) VALUES('rank', 'bm25(8, 3, 8, 5)');"
""; "";
const char *IpcDatabaseSchema = const char *IpcDatabaseSchema =
@@ -94,18 +102,18 @@ const char *IpcDatabaseSchema =
" filepath TEXT NOT NULL," " filepath TEXT NOT NULL,"
" mtime INTEGER NOT NULL," " mtime INTEGER NOT NULL,"
" st_size INTEGER NOT NULL" " st_size INTEGER NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TABLE index_job (" "CREATE TABLE index_job ("
" id INTEGER PRIMARY KEY," " id INTEGER PRIMARY KEY,"
" doc_id TEXT NOT NULL CHECK ( length(doc_id) = 32 )," " sid TEXT NOT NULL,"
" type INTEGER NOT NULL," " type INTEGER NOT NULL,"
" line TEXT" " line TEXT"
");"; ")"STRICT";";
const char *IndexDatabaseSchema = const char *IndexDatabaseSchema =
"CREATE TABLE thumbnail (" "CREATE TABLE thumbnail ("
" id TEXT NOT NULL CHECK ( length(id) = 32 )," " id INTEGER REFERENCES document(id),"
" num INTEGER NOT NULL," " num INTEGER NOT NULL,"
" data BLOB NOT NULL," " data BLOB NOT NULL,"
" PRIMARY KEY(id, num)" " PRIMARY KEY(id, num)"
@@ -114,34 +122,46 @@ const char *IndexDatabaseSchema =
"CREATE TABLE version (" "CREATE TABLE version ("
" id INTEGER PRIMARY KEY AUTOINCREMENT," " id INTEGER PRIMARY KEY AUTOINCREMENT,"
" date TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)" " date TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)"
");" ")"STRICT";"
""
"CREATE TABLE mime("
" id INTEGER PRIMARY KEY,"
" name TEXT"
")"STRICT";"
"CREATE UNIQUE INDEX mime_name_idx ON mime(name);"
"" ""
"CREATE TABLE document (" "CREATE TABLE document ("
" id TEXT PRIMARY KEY CHECK ( length(id) = 32 )," " id INTEGER PRIMARY KEY,"
" marked INTEGER NOT NULL DEFAULT (1)," " parent INTEGER REFERENCES document(id),"
" mime INTEGER REFERENCES mime(id),"
" path TEXT NOT NULL,"
" version INTEGER NOT NULL REFERENCES version(id)," " version INTEGER NOT NULL REFERENCES version(id),"
" mtime INTEGER NOT NULL," " mtime INTEGER NOT NULL,"
" size INTEGER NOT NULL," " size INTEGER NOT NULL,"
" json_data TEXT NOT NULL CHECK ( json_valid(json_data) )" " thumbnail_count INTEGER NOT NULL,"
") WITHOUT ROWID;" " json_data TEXT CHECK ( json_data IS NULL OR json_valid(json_data) )"
")"STRICT";"
"CREATE UNIQUE INDEX document_path_idx ON document(path);"
"CREATE TABLE marked ("
" id INTEGER PRIMARY KEY,"
" marked INTEGER NOT NULL,"
" mtime INTEGER NOT NULL"
")"STRICT";"
""
"CREATE INDEX marked_marked ON marked(marked);"
"" ""
"CREATE TABLE delete_list (" "CREATE TABLE delete_list ("
" id TEXT PRIMARY KEY CHECK ( length(id) = 32 )" " id INTEGER PRIMARY KEY"
") WITHOUT ROWID;" ")"STRICT";"
"" ""
"CREATE TABLE tag (" "CREATE TABLE tag ("
" id TEXT NOT NULL," " id INTEGER NOT NULL REFERENCES document(id),"
" tag TEXT NOT NULL," " tag TEXT NOT NULL,"
" PRIMARY KEY (id, tag)" " PRIMARY KEY (id, tag)"
");" ")"STRICT";"
""
"CREATE TABLE document_sidecar ("
" id TEXT PRIMARY KEY NOT NULL,"
" json_data TEXT NOT NULL"
") WITHOUT ROWID;"
"" ""
"CREATE TABLE descriptor (" "CREATE TABLE descriptor ("
" id TEXT NOT NULL," " id INTEGER PRIMARY KEY,"
" version_major INTEGER NOT NULL," " version_major INTEGER NOT NULL,"
" version_minor INTEGER NOT NULL," " version_minor INTEGER NOT NULL,"
" version_patch INTEGER NOT NULL," " version_patch INTEGER NOT NULL,"
@@ -149,37 +169,37 @@ const char *IndexDatabaseSchema =
" name TEXT NOT NULL," " name TEXT NOT NULL,"
" rewrite_url TEXT," " rewrite_url TEXT,"
" timestamp INTEGER NOT NULL" " timestamp INTEGER NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TABLE stats_treemap (" "CREATE TABLE stats_treemap ("
" path TEXT NOT NULL," " path TEXT NOT NULL,"
" size INTEGER NOT NULL" " size INTEGER NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TABLE stats_size_agg (" "CREATE TABLE stats_size_agg ("
" bucket INTEGER NOT NULL," " bucket INTEGER NOT NULL,"
" count INTEGER NOT NULL" " count INTEGER NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TABLE stats_date_agg (" "CREATE TABLE stats_date_agg ("
" bucket INTEGER NOT NULL," " bucket INTEGER NOT NULL,"
" count INTEGER NOT NULL" " count INTEGER NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TABLE stats_mime_agg (" "CREATE TABLE stats_mime_agg ("
" mime TEXT NOT NULL," " mime TEXT NOT NULL,"
" size INTEGER NOT NULL," " size INTEGER NOT NULL,"
" count INTEGER NOT NULL" " count INTEGER NOT NULL"
");" ")"STRICT";"
"" ""
"CREATE TABLE embedding (" "CREATE TABLE embedding ("
" id TEXT REFERENCES document(id)," " id INTEGER REFERENCES document(id),"
" model_id INTEGER NOT NULL references model(id)," " model_id INTEGER NOT NULL references model(id),"
" start INTEGER NOT NULL," " start INTEGER NOT NULL,"
" end INTEGER," " end INTEGER,"
" embedding BLOB NOT NULL," " embedding BLOB NOT NULL,"
" PRIMARY KEY (id, model_id, start)" " PRIMARY KEY (id, model_id, start)"
");" ")"STRICT";"
"" ""
"CREATE TABLE model (" "CREATE TABLE model ("
" id INTEGER PRIMARY KEY CHECK (id > 0 AND id < 1000)," " id INTEGER PRIMARY KEY CHECK (id > 0 AND id < 1000),"
@@ -188,5 +208,5 @@ const char *IndexDatabaseSchema =
" path TEXT NOT NULL UNIQUE," " path TEXT NOT NULL UNIQUE,"
" size INTEGER NOT NULL," " size INTEGER NOT NULL,"
" type TEXT NOT NULL CHECK ( type IN ('flat', 'nested') )" " type TEXT NOT NULL CHECK ( type IN ('flat', 'nested') )"
");"; ")"STRICT";";

View File

@@ -98,10 +98,10 @@ void database_generate_stats(database_t *db, double treemap_threshold) {
// mime aggregation // mime aggregation
sqlite3_prepare_v2(db->db, "INSERT INTO stats_mime_agg" sqlite3_prepare_v2(db->db, "INSERT INTO stats_mime_agg"
" SELECT" " SELECT"
" (json_data->>'mime') as bucket," " m.name as bucket,"
" sum(size)," " sum(size),"
" count(*)" " count(*)"
" FROM document" " FROM document INNER JOIN mime m ON m.id=document.mime"
" WHERE bucket IS NOT NULL" " WHERE bucket IS NOT NULL"
" GROUP BY bucket", -1, &stmt, NULL); " GROUP BY bucket", -1, &stmt, NULL);
CRASH_IF_STMT_FAIL(sqlite3_step(stmt)); CRASH_IF_STMT_FAIL(sqlite3_step(stmt));
@@ -117,8 +117,8 @@ void database_generate_stats(database_t *db, double treemap_threshold) {
// flat map // flat map
CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db,
"INSERT INTO tm (path, size) SELECT json_data->>'path' as path, sum(size)" "INSERT INTO tm (path, size) SELECT path, sum(size)"
" FROM document WHERE json_data->>'parent' IS NULL GROUP BY path;", " FROM document WHERE parent IS NULL GROUP BY path;",
NULL, NULL, NULL)); NULL, NULL, NULL));
// Merge up // Merge up

View File

@@ -47,7 +47,7 @@ void elastic_cleanup() {
destroy_indexer(Indexer); destroy_indexer(Indexer);
} }
void print_json(cJSON *document, const char id_str[SIST_DOC_ID_LEN]) { void print_json(cJSON *document, const char id_str[SIST_SID_LEN]) {
cJSON *line = cJSON_CreateObject(); cJSON *line = cJSON_CreateObject();
@@ -64,12 +64,12 @@ void print_json(cJSON *document, const char id_str[SIST_DOC_ID_LEN]) {
cJSON_Delete(line); cJSON_Delete(line);
} }
void delete_document(const char *document_id) { void delete_document(const char *sid) {
es_bulk_line_t bulk_line; es_bulk_line_t bulk_line;
bulk_line.type = ES_BULK_LINE_DELETE; bulk_line.type = ES_BULK_LINE_DELETE;
bulk_line.next = NULL; bulk_line.next = NULL;
strcpy(bulk_line.doc_id, document_id); strcpy(bulk_line.sid, sid);
tpool_add_work(IndexCtx.pool, &(job_t) { tpool_add_work(IndexCtx.pool, &(job_t) {
.type = JOB_BULK_LINE, .type = JOB_BULK_LINE,
@@ -78,14 +78,14 @@ void delete_document(const char *document_id) {
} }
void index_json(cJSON *document, const char doc_id[SIST_DOC_ID_LEN]) { void index_json(cJSON *document, const char doc_id[SIST_SID_LEN]) {
char *json = cJSON_PrintUnformatted(document); char *json = cJSON_PrintUnformatted(document);
size_t json_len = strlen(json); size_t json_len = strlen(json);
es_bulk_line_t *bulk_line = malloc(sizeof(es_bulk_line_t) + json_len + 2); es_bulk_line_t *bulk_line = malloc(sizeof(es_bulk_line_t) + json_len + 2);
bulk_line->type = ES_BULK_LINE_INDEX; bulk_line->type = ES_BULK_LINE_INDEX;
memcpy(bulk_line->line, json, json_len); memcpy(bulk_line->line, json, json_len);
strcpy(bulk_line->doc_id, doc_id); strcpy(bulk_line->sid, doc_id);
*(bulk_line->line + json_len) = '\n'; *(bulk_line->line + json_len) = '\n';
*(bulk_line->line + json_len + 1) = '\0'; *(bulk_line->line + json_len + 1) = '\0';
bulk_line->next = NULL; bulk_line->next = NULL;
@@ -124,13 +124,13 @@ void *create_bulk_buffer(int max, int *count, size_t *buf_len, int legacy) {
snprintf( snprintf(
action_str, sizeof(action_str), action_str, sizeof(action_str),
"{\"index\":{\"_id\":\"%s\",\"_type\":\"_doc\",\"_index\":\"%s\"}}\n", "{\"index\":{\"_id\":\"%s\",\"_type\":\"_doc\",\"_index\":\"%s\"}}\n",
line->doc_id, Indexer->es_index line->sid, Indexer->es_index
); );
} else { } else {
snprintf( snprintf(
action_str, sizeof(action_str), action_str, sizeof(action_str),
"{\"index\":{\"_id\":\"%s\",\"_index\":\"%s\"}}\n", "{\"index\":{\"_id\":\"%s\",\"_index\":\"%s\"}}\n",
line->doc_id, Indexer->es_index line->sid, Indexer->es_index
); );
} }
@@ -148,7 +148,7 @@ void *create_bulk_buffer(int max, int *count, size_t *buf_len, int legacy) {
snprintf( snprintf(
action_str, sizeof(action_str), action_str, sizeof(action_str),
"{\"delete\":{\"_id\":\"%s\",\"_index\":\"%s\"}}\n", "{\"delete\":{\"_id\":\"%s\",\"_index\":\"%s\"}}\n",
line->doc_id, Indexer->es_index line->sid, Indexer->es_index
); );
size_t action_str_len = strlen(action_str); size_t action_str_len = strlen(action_str);
@@ -236,7 +236,7 @@ void _elastic_flush(int max) {
if (r->status_code == 413) { if (r->status_code == 413) {
if (max <= 1) { if (max <= 1) {
LOG_ERRORF("elastic.c", "Single document too large, giving up: {%s}", Indexer->line_head->doc_id); LOG_ERRORF("elastic.c", "Single document too large, giving up: {%s}", Indexer->line_head->sid);
free_response(r); free_response(r);
free(buf); free(buf);
free_queue(1); free_queue(1);
@@ -348,7 +348,7 @@ es_indexer_t *create_indexer(const char *url, const char *index) {
return indexer; return indexer;
} }
void finish_indexer(char *index_id) { void finish_indexer(int index_id) {
char url[4096]; char url[4096];

View File

@@ -8,7 +8,7 @@
typedef struct es_bulk_line { typedef struct es_bulk_line {
struct es_bulk_line *next; struct es_bulk_line *next;
char doc_id[SIST_DOC_ID_LEN]; char sid[SIST_SID_LEN];
int type; int type;
char line[0]; char line[0];
} es_bulk_line_t; } es_bulk_line_t;
@@ -44,16 +44,16 @@ typedef struct es_indexer es_indexer_t;
void elastic_index_line(es_bulk_line_t *line); void elastic_index_line(es_bulk_line_t *line);
void print_json(cJSON *document, const char index_id_str[SIST_INDEX_ID_LEN]); void print_json(cJSON *document, const char doc_id[SIST_SID_LEN]);
void index_json(cJSON *document, const char doc_id[SIST_INDEX_ID_LEN]); void index_json(cJSON *document, const char doc_id[SIST_SID_LEN]);
void delete_document(const char *document_id); void delete_document(const char *sid);
es_indexer_t *create_indexer(const char *url, const char *index); es_indexer_t *create_indexer(const char *url, const char *index);
void elastic_cleanup(); void elastic_cleanup();
void finish_indexer(char *index_id); void finish_indexer(int index_id);
void elastic_init(int force_reset, const char* user_mappings, const char* user_settings); void elastic_init(int force_reset, const char* user_mappings, const char* user_settings);

View File

@@ -90,6 +90,7 @@ subreq_ctx_t *web_post_async(const char *url, char *data, int insecure) {
curl_easy_setopt(curl, CURLOPT_USERAGENT, "sist2"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "sist2");
if (insecure) { if (insecure) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
} }
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, req->curl_err_buffer); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, req->curl_err_buffer);
@@ -123,6 +124,7 @@ response_t *web_get(const char *url, int timeout, int insecure) {
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
if (insecure) { if (insecure) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
} }
struct curl_slist *headers = NULL; struct curl_slist *headers = NULL;
@@ -162,6 +164,7 @@ response_t *web_post(const char *url, const char *data, int insecure) {
curl_easy_setopt(curl, CURLOPT_USERAGENT, "sist2"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "sist2");
if (insecure) { if (insecure) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
} }
char err_buffer[CURL_ERROR_SIZE + 1] = {}; char err_buffer[CURL_ERROR_SIZE + 1] = {};
@@ -207,6 +210,7 @@ response_t *web_put(const char *url, const char *data, int insecure) {
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURLOPT_DNS_LOCAL_IP4); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURLOPT_DNS_LOCAL_IP4);
if (insecure) { if (insecure) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
} }
struct curl_slist *headers = NULL; struct curl_slist *headers = NULL;
@@ -241,6 +245,7 @@ response_t *web_delete(const char *url, int insecure) {
curl_easy_setopt(curl, CURLOPT_USERAGENT, "sist2"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "sist2");
if (insecure) { if (insecure) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
} }
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ""); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");

View File

@@ -32,8 +32,6 @@ char *get_meta_key_text(enum metakey meta_key) {
return "title"; return "title";
case MetaFontName: case MetaFontName:
return "font_name"; return "font_name";
case MetaParent:
return "parent";
case MetaExifMake: case MetaExifMake:
return "exif_make"; return "exif_make";
case MetaExifDescription: case MetaExifDescription:
@@ -58,8 +56,6 @@ char *get_meta_key_text(enum metakey meta_key) {
return "author"; return "author";
case MetaModifiedBy: case MetaModifiedBy:
return "modified_by"; return "modified_by";
case MetaThumbnail:
return "thumbnail";
case MetaPages: case MetaPages:
return "pages"; return "pages";
case MetaExifGpsLongitudeRef: case MetaExifGpsLongitudeRef:
@@ -81,21 +77,23 @@ char *get_meta_key_text(enum metakey meta_key) {
} }
} }
char *build_json_string(document_t *doc) { typedef struct {
meta_line_t *meta_head;
meta_line_t *meta_tail;
} linked_list_t;
void write_document(document_t *doc) {
linked_list_t thumbnails_to_write = {.meta_head = NULL, .meta_tail = NULL};
cJSON *json = cJSON_CreateObject(); cJSON *json = cJSON_CreateObject();
int buffer_size_guess = 8192; int buffer_size_guess = 8192;
const char *mime_text = mime_get_mime_text(doc->mime);
if (mime_text == NULL) {
cJSON_AddNullToObject(json, "mime");
} else {
cJSON_AddStringToObject(json, "mime", mime_text);
}
// Ignore root directory in the file path // Ignore root directory in the file path
doc->ext = (short) (doc->ext - 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->base = (short) (doc->base - ScanCtx.index.desc.root_len);
char *filepath = doc->filepath + ScanCtx.index.desc.root_len; char filepath[PATH_MAX * 3];
strcpy(filepath, doc->filepath + ScanCtx.index.desc.root_len);
cJSON_AddStringToObject(json, "extension", filepath + doc->ext); cJSON_AddStringToObject(json, "extension", filepath + doc->ext);
@@ -125,7 +123,6 @@ char *build_json_string(document_t *doc) {
while (meta != NULL) { while (meta != NULL) {
switch (meta->key) { switch (meta->key) {
case MetaThumbnail:
case MetaPages: case MetaPages:
case MetaWidth: case MetaWidth:
case MetaHeight: case MetaHeight:
@@ -143,7 +140,6 @@ char *build_json_string(document_t *doc) {
case MetaAlbumArtist: case MetaAlbumArtist:
case MetaGenre: case MetaGenre:
case MetaFontName: case MetaFontName:
case MetaParent:
case MetaExifMake: case MetaExifMake:
case MetaExifDescription: case MetaExifDescription:
case MetaExifSoftware: case MetaExifSoftware:
@@ -168,6 +164,11 @@ char *build_json_string(document_t *doc) {
buffer_size_guess += (int) strlen(meta->str_val); buffer_size_guess += (int) strlen(meta->str_val);
break; break;
} }
case MetaThumbnail: {
// Keep a list of thumbnails to write after we know what the sid is
APPEND_THUMBNAIL(&thumbnails_to_write, meta->str_val, meta->size);
break;
}
default: default:
LOG_FATALF("serialize.c", "Invalid meta key: %x %s", meta->key, get_meta_key_text(meta->key)); LOG_FATALF("serialize.c", "Invalid meta key: %x %s", meta->key, get_meta_key_text(meta->key));
} }
@@ -180,13 +181,19 @@ char *build_json_string(document_t *doc) {
char *json_str = cJSON_PrintBuffered(json, buffer_size_guess, FALSE); char *json_str = cJSON_PrintBuffered(json, buffer_size_guess, FALSE);
cJSON_Delete(json); cJSON_Delete(json);
return json_str; int doc_id = database_write_document(ProcData.index_db, doc, json_str);
}
void write_document(document_t *doc) {
char *json_str = build_json_string(doc);
database_write_document(ProcData.index_db, doc, json_str);
free(doc); free(doc);
free(json_str); free(json_str);
// Write thumbnails
meta = thumbnails_to_write.meta_head;
int index_num = 0;
while (meta != NULL) {
database_write_thumbnail(ProcData.index_db, doc_id, index_num, meta->str_val, meta->size);
meta_line_t *tmp = meta;
meta = meta->next;
free(tmp);
index_num += 1;
}
} }

View File

@@ -39,7 +39,7 @@ void database_scan_begin(scan_args_t *args) {
index_descriptor_t *original_desc = database_read_index_descriptor(db); index_descriptor_t *original_desc = database_read_index_descriptor(db);
// copy original index id // copy original index id
strcpy(desc->id, original_desc->id); desc->id = original_desc->id;
if (original_desc->version_major != VersionMajor) { if (original_desc->version_major != VersionMajor) {
LOG_FATALF("main.c", "Version mismatch! Index is %s but executable is %s", original_desc->version, Version); LOG_FATALF("main.c", "Version mismatch! Index is %s but executable is %s", original_desc->version, Version);
@@ -67,7 +67,7 @@ void database_scan_begin(scan_args_t *args) {
desc->version_patch = VersionPatch; desc->version_patch = VersionPatch;
// generate new index id based on timestamp // generate new index id based on timestamp
md5_hexdigest(&ScanCtx.index.desc.timestamp, sizeof(ScanCtx.index.desc.timestamp), ScanCtx.index.desc.id); desc->id = (int) ScanCtx.index.desc.timestamp;
database_initialize(db); database_initialize(db);
database_open(db); database_open(db);
@@ -75,14 +75,11 @@ void database_scan_begin(scan_args_t *args) {
} }
database_increment_version(db); database_increment_version(db);
database_sync_mime_table(db);
database_close(db, FALSE); database_close(db, FALSE);
} }
void write_thumbnail_callback(char *key, int num, void *buf, size_t buf_len) {
database_write_thumbnail(ProcData.index_db, key, num, buf, buf_len);
}
void log_callback(const char *filepath, int level, char *str) { void log_callback(const char *filepath, int level, char *str) {
if (level == LEVEL_FATAL) { if (level == LEVEL_FATAL) {
sist_log(filepath, level, str); sist_log(filepath, level, str);
@@ -140,7 +137,6 @@ void initialize_scan_context(scan_args_t *args) {
// Comic // Comic
ScanCtx.comic_ctx.log = log_callback; ScanCtx.comic_ctx.log = log_callback;
ScanCtx.comic_ctx.logf = logf_callback; ScanCtx.comic_ctx.logf = logf_callback;
ScanCtx.comic_ctx.store = write_thumbnail_callback;
ScanCtx.comic_ctx.enable_tn = args->tn_count > 0; ScanCtx.comic_ctx.enable_tn = args->tn_count > 0;
ScanCtx.comic_ctx.tn_size = args->tn_size; ScanCtx.comic_ctx.tn_size = args->tn_size;
ScanCtx.comic_ctx.tn_qscale = args->tn_quality; ScanCtx.comic_ctx.tn_qscale = args->tn_quality;
@@ -157,7 +153,6 @@ void initialize_scan_context(scan_args_t *args) {
} }
ScanCtx.ebook_ctx.log = log_callback; ScanCtx.ebook_ctx.log = log_callback;
ScanCtx.ebook_ctx.logf = logf_callback; ScanCtx.ebook_ctx.logf = logf_callback;
ScanCtx.ebook_ctx.store = write_thumbnail_callback;
ScanCtx.ebook_ctx.fast_epub_parse = args->fast_epub; ScanCtx.ebook_ctx.fast_epub_parse = args->fast_epub;
ScanCtx.ebook_ctx.tn_qscale = args->tn_quality; ScanCtx.ebook_ctx.tn_qscale = args->tn_quality;
@@ -165,7 +160,6 @@ void initialize_scan_context(scan_args_t *args) {
ScanCtx.font_ctx.enable_tn = args->tn_count > 0; ScanCtx.font_ctx.enable_tn = args->tn_count > 0;
ScanCtx.font_ctx.log = log_callback; ScanCtx.font_ctx.log = log_callback;
ScanCtx.font_ctx.logf = logf_callback; ScanCtx.font_ctx.logf = logf_callback;
ScanCtx.font_ctx.store = write_thumbnail_callback;
// Media // Media
ScanCtx.media_ctx.tn_qscale = args->tn_quality; ScanCtx.media_ctx.tn_qscale = args->tn_quality;
@@ -173,7 +167,6 @@ void initialize_scan_context(scan_args_t *args) {
ScanCtx.media_ctx.tn_count = args->tn_count; ScanCtx.media_ctx.tn_count = args->tn_count;
ScanCtx.media_ctx.log = log_callback; ScanCtx.media_ctx.log = log_callback;
ScanCtx.media_ctx.logf = logf_callback; ScanCtx.media_ctx.logf = logf_callback;
ScanCtx.media_ctx.store = write_thumbnail_callback;
ScanCtx.media_ctx.max_media_buffer = (long) args->max_memory_buffer_mib * 1024 * 1024; ScanCtx.media_ctx.max_media_buffer = (long) args->max_memory_buffer_mib * 1024 * 1024;
ScanCtx.media_ctx.read_subtitles = args->read_subtitles; ScanCtx.media_ctx.read_subtitles = args->read_subtitles;
ScanCtx.media_ctx.read_subtitles = args->tn_count; ScanCtx.media_ctx.read_subtitles = args->tn_count;
@@ -189,13 +182,11 @@ void initialize_scan_context(scan_args_t *args) {
ScanCtx.ooxml_ctx.content_size = args->content_size; ScanCtx.ooxml_ctx.content_size = args->content_size;
ScanCtx.ooxml_ctx.log = log_callback; ScanCtx.ooxml_ctx.log = log_callback;
ScanCtx.ooxml_ctx.logf = logf_callback; ScanCtx.ooxml_ctx.logf = logf_callback;
ScanCtx.ooxml_ctx.store = write_thumbnail_callback;
// MOBI // MOBI
ScanCtx.mobi_ctx.content_size = args->content_size; ScanCtx.mobi_ctx.content_size = args->content_size;
ScanCtx.mobi_ctx.log = log_callback; ScanCtx.mobi_ctx.log = log_callback;
ScanCtx.mobi_ctx.logf = logf_callback; ScanCtx.mobi_ctx.logf = logf_callback;
ScanCtx.mobi_ctx.store = write_thumbnail_callback;
ScanCtx.mobi_ctx.enable_tn = args->tn_count > 0; ScanCtx.mobi_ctx.enable_tn = args->tn_count > 0;
ScanCtx.mobi_ctx.tn_size = args->tn_size; ScanCtx.mobi_ctx.tn_size = args->tn_size;
ScanCtx.mobi_ctx.tn_qscale = args->tn_quality; ScanCtx.mobi_ctx.tn_qscale = args->tn_quality;
@@ -209,7 +200,6 @@ void initialize_scan_context(scan_args_t *args) {
ScanCtx.msdoc_ctx.content_size = args->content_size; ScanCtx.msdoc_ctx.content_size = args->content_size;
ScanCtx.msdoc_ctx.log = log_callback; ScanCtx.msdoc_ctx.log = log_callback;
ScanCtx.msdoc_ctx.logf = logf_callback; ScanCtx.msdoc_ctx.logf = logf_callback;
ScanCtx.msdoc_ctx.store = write_thumbnail_callback;
ScanCtx.msdoc_ctx.msdoc_mime = mime_get_mime_by_string("application/msword"); ScanCtx.msdoc_ctx.msdoc_mime = mime_get_mime_by_string("application/msword");
ScanCtx.threads = args->threads; ScanCtx.threads = args->threads;
@@ -228,7 +218,6 @@ void initialize_scan_context(scan_args_t *args) {
ScanCtx.raw_ctx.tn_size = args->tn_size; ScanCtx.raw_ctx.tn_size = args->tn_size;
ScanCtx.raw_ctx.log = log_callback; ScanCtx.raw_ctx.log = log_callback;
ScanCtx.raw_ctx.logf = logf_callback; ScanCtx.raw_ctx.logf = logf_callback;
ScanCtx.raw_ctx.store = write_thumbnail_callback;
// Wpd // Wpd
ScanCtx.wpd_ctx.content_size = args->content_size; ScanCtx.wpd_ctx.content_size = args->content_size;
@@ -271,9 +260,6 @@ void sist2_scan(scan_args_t *args) {
tpool_wait(ScanCtx.pool); tpool_wait(ScanCtx.pool);
tpool_destroy(ScanCtx.pool); tpool_destroy(ScanCtx.pool);
LOG_DEBUGF("main.c", "Thumbnail store size: %lu", ScanCtx.stat_tn_size);
LOG_DEBUGF("main.c", "Index size: %lu", ScanCtx.stat_index_size);
database_t *db = database_create(args->output, INDEX_DATABASE); database_t *db = database_create(args->output, INDEX_DATABASE);
database_open(db); database_open(db);
@@ -316,16 +302,15 @@ void sist2_index(index_args_t *args) {
database_open(db); database_open(db);
database_iterator_t *iterator = database_create_document_iterator(db); database_iterator_t *iterator = database_create_document_iterator(db);
database_document_iter_foreach(json, iterator) { database_document_iter_foreach(json, iterator) {
char doc_id[SIST_DOC_ID_LEN]; char sid[SIST_SID_LEN];
strcpy(doc_id, cJSON_GetObjectItem(json, "_id")->valuestring); int doc_id = cJSON_GetObjectItem(json, "_id")->valueint;
cJSON_DeleteItemFromObject(json, "_id"); cJSON_DeleteItemFromObject(json, "_id");
format_sid(sid, desc->id, doc_id);
// TODO: delete tag if empty
if (args->print) { if (args->print) {
print_json(json, doc_id); print_json(json, sid);
} else { } else {
index_json(json, doc_id); index_json(json, sid);
cnt += 1; cnt += 1;
} }
cJSON_Delete(json); cJSON_Delete(json);
@@ -334,10 +319,12 @@ void sist2_index(index_args_t *args) {
free(iterator); free(iterator);
if (!args->print) { if (!args->print) {
char sid[SIST_SID_LEN];
database_iterator_t *del_iter = database_create_delete_list_iterator(db); database_iterator_t *del_iter = database_create_delete_list_iterator(db);
database_delete_list_iter_foreach(id, del_iter) { database_delete_list_iter_foreach(doc_id, del_iter) {
delete_document(id); format_sid(sid, desc->id, doc_id);
free(id); delete_document(sid);
} }
free(del_iter); free(del_iter);
} }
@@ -366,7 +353,6 @@ void sist2_sqlite_index(sqlite_index_args_t *args) {
database_fts_optimize(db); database_fts_optimize(db);
database_close(db, FALSE); database_close(db, FALSE);
database_close(search_db, FALSE);
} }
void sist2_web(web_args_t *args) { void sist2_web(web_args_t *args) {
@@ -533,7 +519,8 @@ int main(int argc, const char *argv[]) {
OPT_BOOLEAN('f', "force-reset", &index_args->force_reset, "Reset Elasticsearch mappings and settings."), OPT_BOOLEAN('f', "force-reset", &index_args->force_reset, "Reset Elasticsearch mappings and settings."),
OPT_GROUP("sqlite-index options"), OPT_GROUP("sqlite-index options"),
OPT_STRING(0, "search-index", &common_search_index, "Path to search index. Will be created if it does not exist yet."), OPT_STRING(0, "search-index", &common_search_index,
"Path to search index. Will be created if it does not exist yet."),
OPT_GROUP("Web options"), OPT_GROUP("Web options"),
OPT_STRING(0, "es-url", &common_es_url, "Elasticsearch url. DEFAULT: http://localhost:9200"), OPT_STRING(0, "es-url", &common_es_url, "Elasticsearch url. DEFAULT: http://localhost:9200"),

View File

@@ -61,4 +61,6 @@ unsigned int mime_get_mime_by_ext(const char *ext);
unsigned int mime_get_mime_by_string(const char *str); unsigned int mime_get_mime_by_string(const char *str);
unsigned int* get_mime_ids();
#endif #endif

View File

@@ -365,7 +365,6 @@ model_vnd_gdl=65893,
model_vnd_gs_gdl=65894, model_vnd_gs_gdl=65894,
model_vrml=65895, model_vrml=65895,
model_x_pov=65896, model_x_pov=65896,
sist2_sidecar=2,
text_PGP=590185, text_PGP=590185,
text_asp=590186, text_asp=590186,
text_css=590187, text_css=590187,
@@ -909,7 +908,6 @@ case image_x_sony_arw: return "image/x-sony-arw";
case image_x_sony_sr2: return "image/x-sony-sr2"; case image_x_sony_sr2: return "image/x-sony-sr2";
case image_x_sony_srf: return "image/x-sony-srf"; case image_x_sony_srf: return "image/x-sony-srf";
case image_x_epson_erf: return "image/x-epson-erf"; case image_x_epson_erf: return "image/x-epson-erf";
case sist2_sidecar: return "sist2/sidecar";
default: return NULL;}} default: return NULL;}}
unsigned int mime_extension_lookup(unsigned long extension_crc32) {switch (extension_crc32) { unsigned int mime_extension_lookup(unsigned long extension_crc32) {switch (extension_crc32) {
case 2495639202:return application_x_matlab_data; case 2495639202:return application_x_matlab_data;
@@ -1293,7 +1291,6 @@ case 1698465774:return image_x_sony_arw;
case 2083014127:return image_x_sony_sr2; case 2083014127:return image_x_sony_sr2;
case 271503362:return image_x_sony_srf; case 271503362:return image_x_sony_srf;
case 142938048:return image_x_epson_erf; case 142938048:return image_x_epson_erf;
case 287571459:return sist2_sidecar;
default: return 0;}} default: return 0;}}
unsigned int mime_name_lookup(unsigned long mime_crc32) {switch (mime_crc32) { unsigned int mime_name_lookup(unsigned long mime_crc32) {switch (mime_crc32) {
case 3272851765: return application_x_matlab_data; case 3272851765: return application_x_matlab_data;
@@ -1747,6 +1744,7 @@ case 3060720351: return image_x_sony_arw;
case 2944016606: return image_x_sony_sr2; case 2944016606: return image_x_sony_sr2;
case 3279729971: return image_x_sony_srf; case 3279729971: return image_x_sony_srf;
case 1665206815: return image_x_epson_erf; case 1665206815: return image_x_epson_erf;
case 521139448: return sist2_sidecar;
default: return 0;}} default: return 0;}}
unsigned int mime_ids[] = {655530,655363,655364,655365,655366,655362,655361,655367,655368,655369,655370,655371,655372 | 0x40000000,655373,655374,655375,655376 | 0x08000000,655377,655378,655379,655380,655382,655381,655383,655384,655390,655385,655386,655387,655388,655389,655391,655392,655393,655394,655395 | 0x40000000,655396,655397,655398,655399,655400,655401,655402,655403,655404,655405,655406,655407,655408,655411,655412,655413,655414,655415,655416,655417,655418,655419 | 0x20000000,655421,655422,655423,655424,655425,655426,655427,655428,655429,655430,655431,655432 | 0x04000000,655433 | 0x04000000,655434 | 0x04000000,655435,655436,655437,655438,655439,655440,655441,655442,655443,655444,655445,655446 | 0x10000000,655447,655448,655449 | 0x10000000,655450,655451,655452,655453,655454,655455,655456,655457,655458,655459,655461 | 0x08000000,655460,655462,655463,655464,655465,655466,655467,655468,655469,655470,655471,655472,655473,655474,655475,655476,655477,655478,655479,655480,1,655481,655482,655483,655484,655485,655486,655487,655488,655489 | 0x20000000,655490,655491,655492,655493,655494,655495,655496,655497,655498,655499,655500,655501,655502,655503,655504,655505,655506,655507,655508,655509,655510,655511,655512,655513,655514,655515,655516,655517,655519,655518 | 0x08000000,655521,655520,655522 | 0x08000000,655523 | 0x08000000,655524 | 0x08000000,655525,655526,655527,655528,655529,655531,655532,655533,655534,655535,655599,655536 | 0x02000000,655409 | 0x02000000,655540,655537,655538,655539,655541,655542,655543,655544,655545,655546,655547,655548,655549,655550,655552,655551,655553,655554,655555,655556,655557,655558,655559,655560,655561,655562 | 0x10000000,655563,655564,655565,655566,655567,655569,655568,655570,655571,655572,655573,655574,655575,655576,655577,655578 | 0x10000000,655579,655580,655581,655583,655582,655584,655585,655586,655587,655588,655589,655590,655591,655592,655593,655594,655595 | 0x08000000,655596,655597 | 0x08000000,655600 | 0x10000000,655601,458994 | 0x80000000,458995,458996,458998,458997,458999,459000,459001,459002,459003,459004,459005,459006,459007,459008,459009,459010,459011,459012,459013,459014,459015,459016,459017,459018,459030,459019,459020,459021,459022,459023,459025,459024,459026,459027,459029 | 0x80000000,459028 | 0x80000000,327959 | 0x20000000,327960 | 0x20000000,327962 | 0x20000000,327961 | 0x20000000,524571,524572,524573,524574,524575,524576,524577,524578,524579,524580,524581,524582,524583,524584 | 0x80000000,524585 | 0x80000000,524586,524587 | 0x80000000,524588 | 0x80000000,524589,524590,524591,524592,524593,524594,524595,524596,524597,524599,524602,524603,524605,524606,524608,524610,524611,524612 | 0x80000000,524613,524614,524619,524620,524624,524626,524627,524628,524629,524630,524631,524636,524637,524638,524639 | 0x80000000,524640 | 0x80000000,524641,196962,196963,65892,65893,65894,65895,65896,590186,590187,590189 | 0x01000000,590190,590191,590192,590185,590193,590231,590188,655410,590194,590195,590196,590197,590198,590199,590200,590201,590203,590202,590204,590205,590206,590207,590208,590209,590210,590211,590212,590213,590214,590215,590216,590217,590219,590220,590244 | 0x01000000,590218,590222,590221,590223,590224,590225,590226,590227,590228,590229,590230,590232,590233,590234,590235 | 0x01000000,590236,590237,590238,590239,590240,590241,590242,590243,393638,393639,393640,393637,393641,393642,393643,393644,393645,393646,393647,393648,393649,393650,393651,393652,393653,393654,393655,393656,393657 | 0x80000000,393658,393659,393660,393661,393662,393663,393664,393665,721346,655598,655420,524622 | 0x00800000,524621 | 0x00800000,524609 | 0x00800000,524623 | 0x00800000,524598 | 0x00800000,524600 | 0x00800000,524601 | 0x00800000,524604 | 0x00800000,524615 | 0x00800000,524616 | 0x00800000,524617 | 0x00800000,524618 | 0x00800000,524625 | 0x00800000,524632 | 0x00800000,524633 | 0x00800000,524634 | 0x00800000,524635 | 0x00800000,524607 | 0x00800000,0};
unsigned int* get_mime_ids() { return mime_ids; }
#endif #endif

View File

@@ -4,10 +4,8 @@
#include "src/ctx.h" #include "src/ctx.h"
#include "mime.h" #include "mime.h"
#include "src/io/serialize.h" #include "src/io/serialize.h"
#include "src/parsing/sidecar.h"
#include "src/parsing/fs_util.h" #include "src/parsing/fs_util.h"
#include "src/parsing/magic_util.h" #include "src/parsing/magic_util.h"
#include <pthread.h>
#define MIN_VIDEO_SIZE (1024 * 64) #define MIN_VIDEO_SIZE (1024 * 64)
@@ -27,7 +25,6 @@ typedef enum {
FILETYPE_OOXML, FILETYPE_OOXML,
FILETYPE_COMIC, FILETYPE_COMIC,
FILETYPE_MOBI, FILETYPE_MOBI,
FILETYPE_SIST2_SIDECAR,
FILETYPE_MSDOC, FILETYPE_MSDOC,
FILETYPE_JSON, FILETYPE_JSON,
FILETYPE_NDJSON, FILETYPE_NDJSON,
@@ -63,8 +60,6 @@ file_type_t get_file_type(unsigned int mime, size_t size, const char *filepath)
return FILETYPE_COMIC; return FILETYPE_COMIC;
} else if (IS_MOBI(mime)) { } else if (IS_MOBI(mime)) {
return FILETYPE_MOBI; return FILETYPE_MOBI;
} else if (mime == MIME_SIST2_SIDECAR) {
return FILETYPE_SIST2_SIDECAR;
} else if (is_msdoc(&ScanCtx.msdoc_ctx, mime)) { } else if (is_msdoc(&ScanCtx.msdoc_ctx, mime)) {
return FILETYPE_MSDOC; return FILETYPE_MSDOC;
} else if (is_json(&ScanCtx.json_ctx, mime)) { } else if (is_json(&ScanCtx.json_ctx, mime)) {
@@ -157,7 +152,8 @@ void parse(parse_job_t *job) {
doc->size = job->vfile.st_size; doc->size = job->vfile.st_size;
doc->mtime = MAX(job->vfile.mtime, 0); doc->mtime = MAX(job->vfile.mtime, 0);
doc->mime = get_mime(job); doc->mime = get_mime(job);
generate_doc_id(doc->filepath + ScanCtx.index.desc.root_len, doc->doc_id); doc->thumbnail_count = 0;
strcpy(doc->parent, job->parent);
if (doc->mime == GET_MIME_ERROR_FATAL) { if (doc->mime == GET_MIME_ERROR_FATAL) {
CLOSE_FILE(job->vfile) CLOSE_FILE(job->vfile)
@@ -165,16 +161,12 @@ void parse(parse_job_t *job) {
return; return;
} }
if (database_mark_document(ProcData.index_db, doc->doc_id, doc->mtime)) { if (database_mark_document(ProcData.index_db, doc->filepath + ScanCtx.index.desc.root_len, doc->mtime)) {
CLOSE_FILE(job->vfile) CLOSE_FILE(job->vfile)
free(doc); free(doc);
return; return;
} }
if (LogCtx.very_verbose) {
LOG_DEBUGF(job->filepath, "Starting parse job {%s}", doc->doc_id);
}
switch (get_file_type(doc->mime, doc->size, doc->filepath)) { switch (get_file_type(doc->mime, doc->size, doc->filepath)) {
case FILETYPE_RAW: case FILETYPE_RAW:
parse_raw(&ScanCtx.raw_ctx, &job->vfile, doc); parse_raw(&ScanCtx.raw_ctx, &job->vfile, doc);
@@ -195,6 +187,10 @@ void parse(parse_job_t *job) {
parse_font(&ScanCtx.font_ctx, &job->vfile, doc); parse_font(&ScanCtx.font_ctx, &job->vfile, doc);
break; break;
case FILETYPE_ARCHIVE: case FILETYPE_ARCHIVE:
// Insert the document now so that the children documents can link to an existing ID
database_write_document(ProcData.index_db, doc, NULL);
parse_archive(&ScanCtx.arc_ctx, &job->vfile, doc, ScanCtx.exclude, ScanCtx.exclude_extra); parse_archive(&ScanCtx.arc_ctx, &job->vfile, doc, ScanCtx.exclude, ScanCtx.exclude_extra);
break; break;
case FILETYPE_OOXML: case FILETYPE_OOXML:
@@ -206,11 +202,6 @@ void parse(parse_job_t *job) {
case FILETYPE_MOBI: case FILETYPE_MOBI:
parse_mobi(&ScanCtx.mobi_ctx, &job->vfile, doc); parse_mobi(&ScanCtx.mobi_ctx, &job->vfile, doc);
break; break;
case FILETYPE_SIST2_SIDECAR:
parse_sidecar(&job->vfile, doc);
CLOSE_FILE(job->vfile)
free(doc);
return;
case FILETYPE_MSDOC: case FILETYPE_MSDOC:
parse_msdoc(&ScanCtx.msdoc_ctx, &job->vfile, doc); parse_msdoc(&ScanCtx.msdoc_ctx, &job->vfile, doc);
break; break;
@@ -225,14 +216,6 @@ void parse(parse_job_t *job) {
break; break;
} }
//Parent meta
if (job->parent[0] != '\0') {
meta_line_t *meta_parent = malloc(sizeof(meta_line_t) + SIST_INDEX_ID_LEN);
meta_parent->key = MetaParent;
strcpy(meta_parent->str_val, job->parent);
APPEND_META((doc), meta_parent);
}
CLOSE_FILE(job->vfile) CLOSE_FILE(job->vfile)
if (job->vfile.has_checksum) { if (job->vfile.has_checksum) {

View File

@@ -1,40 +0,0 @@
#include "sidecar.h"
#include "src/ctx.h"
void parse_sidecar(vfile_t *vfile, document_t *doc) {
LOG_DEBUGF("sidecar.c", "Parsing sidecar file %s", vfile->filepath);
size_t size;
char *buf = read_all(vfile, &size);
if (buf == NULL) {
LOG_ERRORF("sidecar.c", "Read error for %s", vfile->filepath);
return;
}
buf = realloc(buf, size + 1);
*(buf + size) = '\0';
cJSON *json = cJSON_Parse(buf);
if (json == NULL) {
LOG_ERRORF("sidecar.c", "Could not parse JSON sidecar %s", vfile->filepath);
return;
}
char *json_str = cJSON_PrintUnformatted(json);
char assoc_doc_id[SIST_DOC_ID_LEN];
char rel_path[PATH_MAX];
size_t rel_path_len = doc->ext - 1 - ScanCtx.index.desc.root_len;
memcpy(rel_path, vfile->filepath + ScanCtx.index.desc.root_len, rel_path_len);
*(rel_path + rel_path_len) = '\0';
generate_doc_id(rel_path, assoc_doc_id);
database_write_document_sidecar(ProcData.index_db, assoc_doc_id, json_str);
cJSON_Delete(json);
free(json_str);
free(buf);
}

View File

@@ -1,8 +0,0 @@
#ifndef SIST2_SIDECAR_H
#define SIST2_SIDECAR_H
#include "src/sist.h"
void parse_sidecar(vfile_t *vfile, document_t *doc);
#endif

View File

@@ -3,19 +3,19 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#ifndef FALSE #ifndef FALSE
#define FALSE (0) #define FALSE (0)
#define BOOL int #define BOOL int
#endif #endif
#ifndef TRUE #ifndef TRUE
#define TRUE (!FALSE) #define TRUE (!FALSE)
#endif #endif
#undef MAX #undef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MAX(a, b) (((a) > (b)) ? (a) : (b))
#undef MIN #undef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b))
#ifndef PATH_MAX #ifndef PATH_MAX
@@ -23,7 +23,7 @@
#endif #endif
#undef ABS #undef ABS
#define ABS(a) (((a) < 0) ? -(a) : (a)) #define ABS(a) (((a) < 0) ? -(a) : (a))
#define UNUSED(x) __attribute__((__unused__)) x #define UNUSED(x) __attribute__((__unused__)) x
@@ -51,10 +51,10 @@
#include <ctype.h> #include <ctype.h>
#include "git_hash.h" #include "git_hash.h"
#define VERSION "3.2.0" #define VERSION "3.4.0"
static const char *const Version = VERSION; static const char *const Version = VERSION;
static const int VersionMajor = 3; static const int VersionMajor = 3;
static const int VersionMinor = 2; static const int VersionMinor = 4;
static const int VersionPatch = 0; static const int VersionPatch = 0;
#ifndef SIST_PLATFORM #ifndef SIST_PLATFORM

View File

@@ -77,14 +77,14 @@ static void worker_thread_loop(tpool_t *pool) {
job_t *job = database_get_work(ProcData.ipc_db, pool->shm->job_type); job_t *job = database_get_work(ProcData.ipc_db, pool->shm->job_type);
if (job != NULL) { if (job != NULL) {
pthread_mutex_lock(&(pool->shm->data_mutex));
pool->shm->busy_count += 1;
pthread_mutex_unlock(&(pool->shm->data_mutex));
if (pool->shm->stop) { if (pool->shm->stop) {
break; break;
} }
pthread_mutex_lock(&(pool->shm->data_mutex));
pool->shm->busy_count += 1;
pthread_mutex_unlock(&(pool->shm->data_mutex));
if (job->type == JOB_PARSE_JOB) { if (job->type == JOB_PARSE_JOB) {
parse(job->parse_job); parse(job->parse_job);
} else if (job->type == JOB_BULK_LINE) { } else if (job->type == JOB_BULK_LINE) {
@@ -110,11 +110,11 @@ static void worker_thread_loop(tpool_t *pool) {
if (LogCtx.json_logs) { if (LogCtx.json_logs) {
progress_bar_print_json(done, progress_bar_print_json(done,
count, count,
ScanCtx.stat_tn_size, 0,
ScanCtx.stat_index_size, pool->shm->waiting); 0, pool->shm->waiting);
} else { } else {
progress_bar_print((double) done / count, progress_bar_print((double) done / count,
ScanCtx.stat_tn_size, ScanCtx.stat_index_size); 0, 0);
} }
} }
@@ -200,11 +200,11 @@ static void *tpool_worker(void *arg) {
pool->shm->ipc_ctx.completed_job_count += 1; pool->shm->ipc_ctx.completed_job_count += 1;
pthread_mutex_unlock(&(pool->shm->ipc_ctx.mutex)); pthread_mutex_unlock(&(pool->shm->ipc_ctx.mutex));
pthread_mutex_lock(&(pool->shm->data_mutex));
pool->shm->busy_count -= 1;
pthread_mutex_unlock(&(pool->shm->data_mutex));
if (WIFSIGNALED(status)) { if (WIFSIGNALED(status)) {
pthread_mutex_lock(&(pool->shm->data_mutex));
pool->shm->busy_count -= 1;
pthread_mutex_unlock(&(pool->shm->data_mutex));
int crashed_thread_id = -1; int crashed_thread_id = -1;
for (int i = 0; i < MAX_THREADS; i++) { for (int i = 0; i < MAX_THREADS; i++) {
if (pool->shm->thread_id_to_pid_mapping[i] == pid) { if (pool->shm->thread_id_to_pid_mapping[i] == pid) {
@@ -265,14 +265,14 @@ void tpool_wait(tpool_t *pool) {
if (pool->shm->ipc_ctx.job_count > 0) { if (pool->shm->ipc_ctx.job_count > 0) {
pthread_cond_wait(&(pool->shm->done_working_cond), &pool->shm->mutex); pthread_cond_wait(&(pool->shm->done_working_cond), &pool->shm->mutex);
} else { } else {
if (pool->shm->ipc_ctx.job_count == 0 && pool->shm->busy_count == 0) { if (pool->shm->ipc_ctx.job_count == 0 && pool->shm->busy_count <= 0) {
pool->shm->stop = TRUE; pool->shm->stop = TRUE;
break; break;
} }
} }
} }
if (pool->print_progress && !LogCtx.json_logs) { if (pool->print_progress && !LogCtx.json_logs) {
progress_bar_print(1.0, ScanCtx.stat_tn_size, ScanCtx.stat_index_size); progress_bar_print(1.0, 0, 0);
} }
pthread_mutex_unlock(&pool->shm->mutex); pthread_mutex_unlock(&pool->shm->mutex);

View File

@@ -4,7 +4,7 @@
typedef struct database database_t; typedef struct database database_t;
typedef struct index_descriptor { typedef struct index_descriptor {
char id[SIST_INDEX_ID_LEN]; int id;
char version[64]; char version[64];
int version_major; int version_major;
int version_minor; int version_minor;
@@ -24,4 +24,11 @@ typedef struct index_t {
char path[PATH_MAX]; char path[PATH_MAX];
} index_t; } index_t;
typedef struct {
int doc_id;
int index_id;
long sid_int64;
char sid_str[SIST_SID_LEN];
} sist_id_t;
#endif #endif

View File

@@ -7,6 +7,7 @@
#include "third-party/utf8.h/utf8.h" #include "third-party/utf8.h/utf8.h"
#include "libscan/scan.h" #include "libscan/scan.h"
#include "types.h"
#include <openssl/evp.h> #include <openssl/evp.h>
@@ -18,7 +19,8 @@ dyn_buffer_t url_escape(char *str);
extern int PrintingProgressBar; extern int PrintingProgressBar;
void progress_bar_print_json(size_t done, size_t count, size_t tn_size, size_t index_size, int waiting); void progress_bar_print_json(size_t done, size_t count, size_t tn_size, size_t index_size, int waiting);
void progress_bar_print(double percentage, size_t tn_size, size_t index_size); void progress_bar_print(double percentage, size_t tn_size, size_t index_size);
const char *find_file_in_paths(const char **paths, const char *filename); const char *find_file_in_paths(const char **paths, const char *filename);
@@ -87,24 +89,6 @@ static void buf2hex(const unsigned char *buf, size_t buflen, char *hex_string) {
*s = '\0'; *s = '\0';
} }
static void md5_hexdigest(const void *data, size_t size, char *output) {
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(md_ctx, EVP_md5(), NULL);
EVP_DigestUpdate(md_ctx, data, size);
unsigned char digest[MD5_DIGEST_LENGTH];
EVP_DigestFinal_ex(md_ctx, digest, NULL);
EVP_MD_CTX_free(md_ctx);
buf2hex(digest, MD5_DIGEST_LENGTH, output);
}
__always_inline
static void generate_doc_id(const char *rel_path, char *doc_id) {
md5_hexdigest(rel_path, strlen(rel_path), doc_id);
}
#define MILLISECOND 1000 #define MILLISECOND 1000
struct timespec timespec_add(struct timespec ts1, long usec); struct timespec timespec_add(struct timespec ts1, long usec);
@@ -125,6 +109,29 @@ struct timespec timespec_add(struct timespec ts1, long usec);
} while (0) } while (0)
#define array_foreach(arr) \ #define array_foreach(arr) \
for (int i = 0; (arr)[i] != NULL; i++) for (int i = 0; (arr)[i] != 0; i++)
#define format_sid(out, index_id, doc_id) \
sprintf((out), "%08x.%08x", (index_id), (doc_id))
static int parse_sid(sist_id_t *sid, const char doc_sid_str[SIST_SID_LEN]) {
if (doc_sid_str[8] != '.') {
return FALSE;
}
char tmp[9];
memcpy(tmp, doc_sid_str, 8);
sid->index_id = (int) strtol(tmp, NULL, 16);
memcpy(tmp, doc_sid_str + 9, 8);
sid->doc_id = (int) strtol(tmp, NULL, 16);
memcpy(sid->sid_str, doc_sid_str, SIST_SID_LEN - 1);
*(sid->sid_str + SIST_SID_LEN - 1) = '\0';
sid->sid_int64 = ((long) sid->index_id << 32) | sid->doc_id;
return TRUE;
}
#endif #endif

View File

@@ -48,30 +48,24 @@ void get_embedding(struct mg_connection *nc, struct mg_http_message *hm) {
WebCtx.es_version->major, WebCtx.es_version->minor, WebCtx.es_version->patch); WebCtx.es_version->major, WebCtx.es_version->minor, WebCtx.es_version->patch);
} }
if (hm->uri.len != SIST_INDEX_ID_LEN + SIST_DOC_ID_LEN + 2 + 4) { sist_id_t sid;
LOG_DEBUGF("serve.c", "Invalid thumbnail path: %.*s", (int) hm->uri.len, hm->uri.ptr);
if (hm->uri.len != SIST_SID_LEN + 2 + 4 || !parse_sid(&sid, hm->uri.ptr + 3)) {
LOG_DEBUGF("serve.c", "Invalid embedding path: %.*s", (int) hm->uri.len, hm->uri.ptr);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
char doc_id[SIST_DOC_ID_LEN]; int model_id = (int) strtol(hm->uri.ptr + SIST_SID_LEN + 3, NULL, 10);
char index_id[SIST_INDEX_ID_LEN];
memcpy(index_id, hm->uri.ptr + 3, SIST_INDEX_ID_LEN); database_t *db = web_get_database(sid.index_id);
*(index_id + SIST_INDEX_ID_LEN - 1) = '\0';
memcpy(doc_id, hm->uri.ptr + 3 + SIST_INDEX_ID_LEN, SIST_DOC_ID_LEN);
*(doc_id + SIST_DOC_ID_LEN - 1) = '\0';
int model_id = (int) strtol(hm->uri.ptr + SIST_INDEX_ID_LEN + SIST_DOC_ID_LEN + 3, NULL, 10);
database_t *db = web_get_database(index_id);
if (db == NULL) { if (db == NULL) {
LOG_DEBUGF("serve.c", "Could not get database for index: %s", index_id); LOG_DEBUGF("serve.c", "Could not get database for index: %s", sid.index_id);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
cJSON *json = database_get_embedding(db, doc_id, model_id); cJSON *json = database_get_embedding(db, sid.doc_id, model_id);
if (json == NULL) { if (json == NULL) {
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
@@ -84,17 +78,19 @@ void get_embedding(struct mg_connection *nc, struct mg_http_message *hm) {
void stats_files(struct mg_connection *nc, struct mg_http_message *hm) { void stats_files(struct mg_connection *nc, struct mg_http_message *hm) {
if (hm->uri.len != SIST_INDEX_ID_LEN + 7) { if (hm->uri.len != 17) {
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
char arg_index_id[SIST_INDEX_ID_LEN]; char index_id_str[9];
char arg_stat_type[5]; char arg_stat_type[5];
memcpy(arg_index_id, hm->uri.ptr + 3, SIST_INDEX_ID_LEN); memcpy(index_id_str, hm->uri.ptr + 3, 8);
*(arg_index_id + SIST_INDEX_ID_LEN - 1) = '\0'; *(index_id_str + 8) = '\0';
memcpy(arg_stat_type, hm->uri.ptr + 3 + SIST_INDEX_ID_LEN, 4); int index_id = (int) strtol(index_id_str, NULL, 16);
memcpy(arg_stat_type, hm->uri.ptr + 3 + 9, 4);
*(arg_stat_type + sizeof(arg_stat_type) - 1) = '\0'; *(arg_stat_type + sizeof(arg_stat_type) - 1) = '\0';
database_stat_type_d stat_type = database_get_stat_type_by_mnemonic(arg_stat_type); database_stat_type_d stat_type = database_get_stat_type_by_mnemonic(arg_stat_type);
@@ -103,9 +99,9 @@ void stats_files(struct mg_connection *nc, struct mg_http_message *hm) {
return; return;
} }
database_t *db = web_get_database(arg_index_id); database_t *db = web_get_database(index_id);
if (db == NULL) { if (db == NULL) {
LOG_DEBUGF("serve.c", "Could not get database for index: %s", arg_index_id); LOG_DEBUGF("serve.c", "Could not get database for index: %d", index_id);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
@@ -152,19 +148,19 @@ void serve_chunk_vendors_css(struct mg_connection *nc, struct mg_http_message *h
web_serve_asset_chunk_vendors_css(nc); web_serve_asset_chunk_vendors_css(nc);
} }
void serve_thumbnail(struct mg_connection *nc, struct mg_http_message *hm, const char *arg_index, void serve_thumbnail(struct mg_connection *nc, struct mg_http_message *hm, int index_id,
const char *arg_doc_id, int arg_num) { int doc_id, int arg_num) {
database_t *db = web_get_database(arg_index); database_t *db = web_get_database(index_id);
if (db == NULL) { if (db == NULL) {
LOG_DEBUGF("serve.c", "Could not get database for index: %s", arg_index); LOG_DEBUGF("serve.c", "Could not get database for index: %d", index_id);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
size_t data_len = 0; size_t data_len = 0;
void *data = database_read_thumbnail(db, arg_doc_id, arg_num, &data_len); void *data = database_read_thumbnail(db, doc_id, arg_num, &data_len);
if (data_len != 0) { if (data_len != 0) {
web_send_headers( web_send_headers(
@@ -181,44 +177,29 @@ void serve_thumbnail(struct mg_connection *nc, struct mg_http_message *hm, const
} }
void thumbnail_with_num(struct mg_connection *nc, struct mg_http_message *hm) { void thumbnail_with_num(struct mg_connection *nc, struct mg_http_message *hm) {
if (hm->uri.len != SIST_INDEX_ID_LEN + SIST_DOC_ID_LEN + 2 + 5) { sist_id_t sid;
if (hm->uri.len != SIST_SID_LEN + 2 + 4 || !parse_sid(&sid, hm->uri.ptr + 3)) {
LOG_DEBUGF("serve.c", "Invalid thumbnail path: %.*s", (int) hm->uri.len, hm->uri.ptr); LOG_DEBUGF("serve.c", "Invalid thumbnail path: %.*s", (int) hm->uri.len, hm->uri.ptr);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
char arg_doc_id[SIST_DOC_ID_LEN]; int num = (int) strtol(hm->uri.ptr + SIST_SID_LEN + 3, NULL, 10);
char arg_index[SIST_INDEX_ID_LEN];
char arg_num[5] = {0};
memcpy(arg_index, hm->uri.ptr + 3, SIST_INDEX_ID_LEN); serve_thumbnail(nc, hm, sid.index_id, sid.doc_id, num);
*(arg_index + SIST_INDEX_ID_LEN - 1) = '\0';
memcpy(arg_doc_id, hm->uri.ptr + 3 + SIST_INDEX_ID_LEN, SIST_DOC_ID_LEN);
*(arg_doc_id + SIST_DOC_ID_LEN - 1) = '\0';
memcpy(arg_num, hm->uri.ptr + SIST_INDEX_ID_LEN + SIST_DOC_ID_LEN + 3, 4);
int num = (int) strtol(arg_num, NULL, 10);
serve_thumbnail(nc, hm, arg_index, arg_doc_id, num);
} }
void thumbnail(struct mg_connection *nc, struct mg_http_message *hm) { void thumbnail(struct mg_connection *nc, struct mg_http_message *hm) {
sist_id_t sid;
if (hm->uri.len != SIST_INDEX_ID_LEN + SIST_DOC_ID_LEN + 2) { if (hm->uri.len != 20 || !parse_sid(&sid, hm->uri.ptr + 3)) {
LOG_DEBUGF("serve.c", "Invalid thumbnail path: %.*s", (int) hm->uri.len, hm->uri.ptr); LOG_DEBUGF("serve.c", "Invalid thumbnail path: %.*s", (int) hm->uri.len, hm->uri.ptr);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
char arg_doc_id[SIST_DOC_ID_LEN]; serve_thumbnail(nc, hm, sid.index_id, sid.doc_id, 0);
char arg_index[SIST_INDEX_ID_LEN];
memcpy(arg_index, hm->uri.ptr + 3, SIST_INDEX_ID_LEN);
*(arg_index + SIST_INDEX_ID_LEN - 1) = '\0';
memcpy(arg_doc_id, hm->uri.ptr + 3 + SIST_INDEX_ID_LEN, SIST_DOC_ID_LEN);
*(arg_doc_id + SIST_DOC_ID_LEN - 1) = '\0';
serve_thumbnail(nc, hm, arg_index, arg_doc_id, 0);
} }
void search(struct mg_connection *nc, struct mg_http_message *hm) { void search(struct mg_connection *nc, struct mg_http_message *hm) {
@@ -382,11 +363,15 @@ void index_info(struct mg_connection *nc) {
cJSON *idx_json = cJSON_CreateObject(); cJSON *idx_json = cJSON_CreateObject();
cJSON_AddStringToObject(idx_json, "name", idx->desc.name); cJSON_AddStringToObject(idx_json, "name", idx->desc.name);
cJSON_AddStringToObject(idx_json, "version", idx->desc.version); cJSON_AddStringToObject(idx_json, "version", idx->desc.version);
cJSON_AddStringToObject(idx_json, "id", idx->desc.id); cJSON_AddNumberToObject(idx_json, "id", idx->desc.id);
cJSON_AddStringToObject(idx_json, "rewriteUrl", idx->desc.rewrite_url); cJSON_AddStringToObject(idx_json, "rewriteUrl", idx->desc.rewrite_url);
cJSON_AddNumberToObject(idx_json, "timestamp", (double) idx->desc.timestamp); cJSON_AddNumberToObject(idx_json, "timestamp", (double) idx->desc.timestamp);
cJSON_AddItemToArray(arr, idx_json); cJSON_AddItemToArray(arr, idx_json);
#ifdef SIST_DEBUG_INFO
cJSON_AddStringToObject(idx_json, "root", idx->desc.root);
#endif
cJSON *models = database_get_models(idx->db); cJSON *models = database_get_models(idx->db);
cJSON_AddItemToObject(idx_json, "models", models); cJSON_AddItemToObject(idx_json, "models", models);
} }
@@ -405,15 +390,14 @@ void index_info(struct mg_connection *nc) {
cJSON_Delete(json); cJSON_Delete(json);
} }
cJSON *get_root_document_by_id(const char *index_id, const char *doc_id) { cJSON *get_root_document_by_id(int index_id, int doc_id) {
database_t *db = web_get_database(index_id); database_t *db = web_get_database(index_id);
if (!db) { if (!db) {
return NULL; return NULL;
} }
char next_id[SIST_DOC_ID_LEN]; int next_id = doc_id;
strcpy(next_id, doc_id);
while (TRUE) { while (TRUE) {
cJSON *doc = database_get_document(db, next_id); cJSON *doc = database_get_document(db, next_id);
@@ -423,38 +407,31 @@ cJSON *get_root_document_by_id(const char *index_id, const char *doc_id) {
} }
cJSON *parent = cJSON_GetObjectItem(doc, "parent"); cJSON *parent = cJSON_GetObjectItem(doc, "parent");
if (parent == NULL || cJSON_IsNull(parent)) { if (parent == NULL || !cJSON_IsNumber(parent)) {
return doc; return doc;
} }
strcpy(next_id, parent->valuestring); next_id = parent->valueint;
cJSON_Delete(parent); cJSON_Delete(doc);
} }
} }
void file(struct mg_connection *nc, struct mg_http_message *hm) { void file(struct mg_connection *nc, struct mg_http_message *hm) {
sist_id_t sid;
if (hm->uri.len != SIST_INDEX_ID_LEN + SIST_DOC_ID_LEN + 2) { if (hm->uri.len != 20 || !parse_sid(&sid, hm->uri.ptr + 3)) {
LOG_DEBUGF("serve.c", "Invalid file path: %.*s", (int) hm->uri.len, hm->uri.ptr); LOG_DEBUGF("serve.c", "Invalid file path: %.*s", (int) hm->uri.len, hm->uri.ptr);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
char arg_doc_id[SIST_DOC_ID_LEN]; index_t *idx = web_get_index_by_id(sid.index_id);
char arg_index[SIST_INDEX_ID_LEN];
memcpy(arg_index, hm->uri.ptr + 3, SIST_INDEX_ID_LEN);
*(arg_index + SIST_INDEX_ID_LEN - 1) = '\0';
memcpy(arg_doc_id, hm->uri.ptr + 3 + SIST_INDEX_ID_LEN, SIST_DOC_ID_LEN);
*(arg_doc_id + SIST_DOC_ID_LEN - 1) = '\0';
index_t *idx = web_get_index_by_id(arg_index);
if (idx == NULL) { if (idx == NULL) {
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
cJSON *source = get_root_document_by_id(arg_index, arg_doc_id); cJSON *source = get_root_document_by_id(sid.index_id, sid.doc_id);
if (strlen(idx->desc.rewrite_url) == 0) { if (strlen(idx->desc.rewrite_url) == 0) {
serve_file_from_disk(source, idx, nc, hm); serve_file_from_disk(source, idx, nc, hm);
@@ -478,7 +455,6 @@ void status(struct mg_connection *nc) {
typedef struct { typedef struct {
char *name; char *name;
int delete; int delete;
char *doc_id;
} tag_req_t; } tag_req_t;
tag_req_t *parse_tag_request(cJSON *json) { tag_req_t *parse_tag_request(cJSON *json) {
@@ -501,20 +477,14 @@ tag_req_t *parse_tag_request(cJSON *json) {
return NULL; return NULL;
} }
cJSON *arg_doc_id = cJSON_GetObjectItem(json, "doc_id");
if (arg_doc_id == NULL || !cJSON_IsString(arg_doc_id)) {
return NULL;
}
tag_req_t *req = malloc(sizeof(tag_req_t)); tag_req_t *req = malloc(sizeof(tag_req_t));
req->delete = arg_delete->valueint; req->delete = arg_delete->valueint;
req->name = arg_name->valuestring; req->name = arg_name->valuestring;
req->doc_id = arg_doc_id->valuestring;
return req; return req;
} }
subreq_ctx_t *elastic_delete_tag(const tag_req_t *req) { subreq_ctx_t *elastic_delete_tag(const char *sid, const tag_req_t *req) {
char *buf = malloc(sizeof(char) * 8192); char *buf = malloc(sizeof(char) * 8192);
snprintf(buf, 8192, snprintf(buf, 8192,
"{" "{"
@@ -529,12 +499,12 @@ subreq_ctx_t *elastic_delete_tag(const tag_req_t *req) {
); );
char url[4096]; char url[4096];
snprintf(url, sizeof(url), "%s/%s/_update/%s", WebCtx.es_url, WebCtx.es_index, req->doc_id); snprintf(url, sizeof(url), "%s/%s/_update/%s", WebCtx.es_url, WebCtx.es_index, sid);
return web_post_async(url, buf, WebCtx.es_insecure_ssl); return web_post_async(url, buf, WebCtx.es_insecure_ssl);
} }
subreq_ctx_t *elastic_write_tag(const tag_req_t *req) { subreq_ctx_t *elastic_write_tag(const char *sid, const tag_req_t *req) {
char *buf = malloc(sizeof(char) * 8192); char *buf = malloc(sizeof(char) * 8192);
snprintf(buf, 8192, snprintf(buf, 8192,
"{" "{"
@@ -549,21 +519,18 @@ subreq_ctx_t *elastic_write_tag(const tag_req_t *req) {
); );
char url[4096]; char url[4096];
snprintf(url, sizeof(url), "%s/%s/_update/%s", WebCtx.es_url, WebCtx.es_index, req->doc_id); snprintf(url, sizeof(url), "%s/%s/_update/%s", WebCtx.es_url, WebCtx.es_index, sid);
return web_post_async(url, buf, WebCtx.es_insecure_ssl); return web_post_async(url, buf, WebCtx.es_insecure_ssl);
} }
void tag(struct mg_connection *nc, struct mg_http_message *hm) { void tag(struct mg_connection *nc, struct mg_http_message *hm) {
if (hm->uri.len != SIST_INDEX_ID_LEN + 4) { sist_id_t sid;
if (hm->uri.len != 22 || !parse_sid(&sid, hm->uri.ptr + 5)) {
LOG_DEBUGF("serve.c", "Invalid tag path: %.*s", (int) hm->uri.len, hm->uri.ptr); LOG_DEBUGF("serve.c", "Invalid tag path: %.*s", (int) hm->uri.len, hm->uri.ptr);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
char arg_index[SIST_INDEX_ID_LEN];
memcpy(arg_index, hm->uri.ptr + 5, SIST_INDEX_ID_LEN);
*(arg_index + SIST_INDEX_ID_LEN - 1) = '\0';
char *body = malloc(hm->body.len + 1); char *body = malloc(hm->body.len + 1);
memcpy(body, hm->body.ptr, hm->body.len); memcpy(body, hm->body.ptr, hm->body.len);
*(body + hm->body.len) = '\0'; *(body + hm->body.len) = '\0';
@@ -575,36 +542,36 @@ void tag(struct mg_connection *nc, struct mg_http_message *hm) {
return; return;
} }
database_t *db = web_get_database(arg_index); database_t *db = web_get_database(sid.index_id);
if (db == NULL) { if (db == NULL) {
LOG_DEBUGF("serve.c", "Could not get database for index: %s", arg_index); LOG_DEBUGF("serve.c", "Could not get database for index: %d", sid.index_id);
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND
return; return;
} }
tag_req_t *req = parse_tag_request(json); tag_req_t *req = parse_tag_request(json);
if (req == NULL) { if (req == NULL) {
LOG_DEBUGF("serve.c", "Could not parse tag request", arg_index); LOG_DEBUG("serve.c", "Could not parse tag request");
cJSON_Delete(json); cJSON_Delete(json);
HTTP_REPLY_BAD_REQUEST HTTP_REPLY_BAD_REQUEST
return; return;
} }
if (req->delete) { if (req->delete) {
database_delete_tag(db, req->doc_id, req->name); database_delete_tag(db, sid.doc_id, req->name);
if (WebCtx.search_backend == SQLITE_SEARCH_BACKEND) { if (WebCtx.search_backend == SQLITE_SEARCH_BACKEND) {
database_delete_tag(WebCtx.search_db, req->doc_id, req->name); database_delete_tag(WebCtx.search_db, sid.sid_int64, req->name);
HTTP_REPLY_OK HTTP_REPLY_OK
} else { } else {
nc->fn_data = elastic_delete_tag(req); nc->fn_data = elastic_delete_tag(sid.sid_str, req);
} }
} else { } else {
database_write_tag(db, req->doc_id, req->name); database_write_tag(db, sid.doc_id, req->name);
if (WebCtx.search_backend == SQLITE_SEARCH_BACKEND) { if (WebCtx.search_backend == SQLITE_SEARCH_BACKEND) {
database_write_tag(WebCtx.search_db, req->doc_id, req->name); database_fts_write_tag(WebCtx.search_db, sid.sid_int64, req->name);
HTTP_REPLY_OK HTTP_REPLY_OK
} else { } else {
nc->fn_data = elastic_write_tag(req); nc->fn_data = elastic_write_tag(sid.sid_str, req);
} }
} }
@@ -739,11 +706,11 @@ static void ev_router(struct mg_connection *nc, int ev, void *ev_data, UNUSED(vo
if (mg_http_match_uri(hm, "/status")) { if (mg_http_match_uri(hm, "/status")) {
status(nc); status(nc);
} else if (mg_http_match_uri(hm, "/f/*/*")) { } else if (mg_http_match_uri(hm, "/f/*")) {
file(nc, hm); file(nc, hm);
} else if (mg_http_match_uri(hm, "/t/*/*/*")) {
thumbnail_with_num(nc, hm);
} else if (mg_http_match_uri(hm, "/t/*/*")) { } else if (mg_http_match_uri(hm, "/t/*/*")) {
thumbnail_with_num(nc, hm);
} else if (mg_http_match_uri(hm, "/t/*")) {
thumbnail(nc, hm); thumbnail(nc, hm);
} else if (mg_http_match_uri(hm, "/s/*/*")) { } else if (mg_http_match_uri(hm, "/s/*/*")) {
stats_files(nc, hm); stats_files(nc, hm);
@@ -752,7 +719,7 @@ static void ev_router(struct mg_connection *nc, int ev, void *ev_data, UNUSED(vo
return; return;
} }
tag(nc, hm); tag(nc, hm);
} else if (mg_http_match_uri(hm, "/e/*/*/*")) { } else if (mg_http_match_uri(hm, "/e/*/*")) {
get_embedding(nc, hm); get_embedding(nc, hm);
return; return;
} else { } else {

View File

@@ -3,7 +3,7 @@
#include "src/web/web_util.h" #include "src/web/web_util.h"
typedef struct { typedef struct {
char *index_id; int index_id;
char *prefix; char *prefix;
int min_depth; int min_depth;
int max_depth; int max_depth;
@@ -23,7 +23,7 @@ typedef struct {
double date_min; double date_min;
double date_max; double date_max;
int page_size; int page_size;
char **index_ids; int *index_ids;
char **mime_types; char **mime_types;
char **tags; char **tags;
int sort_asc; int sort_asc;
@@ -108,7 +108,7 @@ static json_value get_json_bool(cJSON *object, const char *name) {
return (json_value) {item, FALSE}; return (json_value) {item, FALSE};
} }
static json_value get_json_float_array(cJSON *object, const char *name) { static json_value get_json_number_array(cJSON *object, const char *name) {
cJSON *item = cJSON_GetObjectItem(object, name); cJSON *item = cJSON_GetObjectItem(object, name);
if (item == NULL || cJSON_IsNull(item)) { if (item == NULL || cJSON_IsNull(item)) {
return (json_value) {NULL, FALSE}; return (json_value) {NULL, FALSE};
@@ -147,7 +147,6 @@ static json_value get_json_array(cJSON *object, const char *name) {
} }
char **json_array_to_c_array(cJSON *json) { char **json_array_to_c_array(cJSON *json) {
cJSON *element; cJSON *element;
char **arr = calloc(cJSON_GetArraySize(json) + 1, sizeof(char *)); char **arr = calloc(cJSON_GetArraySize(json) + 1, sizeof(char *));
int i = 0; int i = 0;
@@ -158,6 +157,17 @@ char **json_array_to_c_array(cJSON *json) {
return arr; return arr;
} }
int *json_number_array_to_c_array(cJSON *json) {
cJSON *element;
int *arr = calloc(cJSON_GetArraySize(json) + 1, sizeof(int));
int i = 0;
cJSON_ArrayForEach(element, json) {
arr[i++] = (int) element->valuedouble;
}
return arr;
}
#define DEFAULT_HIGHLIGHT_CONTEXT_SIZE 20 #define DEFAULT_HIGHLIGHT_CONTEXT_SIZE 20
fts_search_req_t *get_search_req(struct mg_http_message *hm) { fts_search_req_t *get_search_req(struct mg_http_message *hm) {
@@ -169,7 +179,8 @@ fts_search_req_t *get_search_req(struct mg_http_message *hm) {
json_value req_query, req_path, req_size_min, req_size_max, req_date_min, req_date_max, req_page_size, json_value req_query, req_path, req_size_min, req_size_max, req_date_min, req_date_max, req_page_size,
req_index_ids, req_mime_types, req_tags, req_sort_asc, req_sort, req_seed, req_after, req_index_ids, req_mime_types, req_tags, req_sort_asc, req_sort, req_seed, req_after,
req_fetch_aggregations, req_highlight, req_highlight_context_size, req_embedding, req_model; req_fetch_aggregations, req_highlight, req_highlight_context_size, req_embedding, req_model,
req_search_in_path;
if (!cJSON_IsObject(json) || if (!cJSON_IsObject(json) ||
(req_query = get_json_string(json, "query")).invalid || (req_query = get_json_string(json, "query")).invalid ||
@@ -184,11 +195,12 @@ fts_search_req_t *get_search_req(struct mg_http_message *hm) {
(req_seed = get_json_number(json, "seed")).invalid || (req_seed = get_json_number(json, "seed")).invalid ||
(req_fetch_aggregations = get_json_bool(json, "fetchAggregations")).invalid || (req_fetch_aggregations = get_json_bool(json, "fetchAggregations")).invalid ||
(req_sort_asc = get_json_bool(json, "sortAsc")).invalid || (req_sort_asc = get_json_bool(json, "sortAsc")).invalid ||
(req_index_ids = get_json_array(json, "indexIds")).invalid || (req_index_ids = get_json_number_array(json, "indexIds")).invalid ||
(req_mime_types = get_json_array(json, "mimeTypes")).invalid || (req_mime_types = get_json_array(json, "mimeTypes")).invalid ||
(req_highlight = get_json_bool(json, "highlight")).invalid || (req_highlight = get_json_bool(json, "highlight")).invalid ||
(req_search_in_path = get_json_bool(json, "searchInPath")).invalid ||
(req_highlight_context_size = get_json_number(json, "highlightContextSize")).invalid || (req_highlight_context_size = get_json_number(json, "highlightContextSize")).invalid ||
(req_embedding = get_json_float_array(json, "embedding")).invalid || (req_embedding = get_json_number_array(json, "embedding")).invalid ||
(req_model = get_json_number(json, "model")).invalid || (req_model = get_json_number(json, "model")).invalid ||
(req_tags = get_json_array(json, "tags")).invalid) { (req_tags = get_json_array(json, "tags")).invalid) {
cJSON_Delete(json); cJSON_Delete(json);
@@ -242,7 +254,6 @@ fts_search_req_t *get_search_req(struct mg_http_message *hm) {
fts_search_req_t *req = malloc(sizeof(fts_search_req_t)); fts_search_req_t *req = malloc(sizeof(fts_search_req_t));
req->sort = sort; req->sort = sort;
req->query = req_query.val ? strdup(req_query.val->valuestring) : NULL;
req->path = req_path.val ? strdup(req_path.val->valuestring) : NULL; req->path = req_path.val ? strdup(req_path.val->valuestring) : NULL;
req->size_min = req_size_min.val ? req_size_min.val->valuedouble : 0; req->size_min = req_size_min.val ? req_size_min.val->valuedouble : 0;
req->size_max = req_size_max.val ? req_size_max.val->valuedouble : 0; req->size_max = req_size_max.val ? req_size_max.val->valuedouble : 0;
@@ -251,7 +262,7 @@ fts_search_req_t *get_search_req(struct mg_http_message *hm) {
req->date_max = req_date_max.val ? req_date_max.val->valuedouble : 0; req->date_max = req_date_max.val ? req_date_max.val->valuedouble : 0;
req->page_size = (int) req_page_size.val->valuedouble; req->page_size = (int) req_page_size.val->valuedouble;
req->sort_asc = req_sort_asc.val ? req_sort_asc.val->valueint : TRUE; req->sort_asc = req_sort_asc.val ? req_sort_asc.val->valueint : TRUE;
req->index_ids = req_index_ids.val ? json_array_to_c_array(req_index_ids.val) : NULL; req->index_ids = req_index_ids.val ? json_number_array_to_c_array(req_index_ids.val) : NULL;
req->after = req_after.val ? json_array_to_c_array(req_after.val) : NULL; req->after = req_after.val ? json_array_to_c_array(req_after.val) : NULL;
req->mime_types = req_mime_types.val ? json_array_to_c_array(req_mime_types.val) : NULL; req->mime_types = req_mime_types.val ? json_array_to_c_array(req_mime_types.val) : NULL;
req->tags = req_tags.val ? json_array_to_c_array(req_tags.val) : NULL; req->tags = req_tags.val ? json_array_to_c_array(req_tags.val) : NULL;
@@ -261,6 +272,16 @@ fts_search_req_t *get_search_req(struct mg_http_message *hm) {
? req_highlight_context_size.val->valueint ? req_highlight_context_size.val->valueint
: DEFAULT_HIGHLIGHT_CONTEXT_SIZE; : DEFAULT_HIGHLIGHT_CONTEXT_SIZE;
req->model = req_model.val ? req_model.val->valueint : 0; req->model = req_model.val ? req_model.val->valueint : 0;
if (req_search_in_path.val->valueint == FALSE && req_query.val) {
if (asprintf(&req->query, "- path : %s", req_query.val->valuestring) == -1) {
cJSON_Delete(json);
return NULL;
}
} else {
req->query = req_query.val ? strdup(req_query.val->valuestring) : NULL;
}
req->embedding = req_model.val req->embedding = req_model.val
? get_float_buffer(req_embedding.val, &req->embedding_size) ? get_float_buffer(req_embedding.val, &req->embedding_size)
: NULL; : NULL;
@@ -282,7 +303,9 @@ void destroy_search_req(fts_search_req_t *req) {
free(req->query); free(req->query);
free(req->path); free(req->path);
destroy_array(req->index_ids); if (req->index_ids) {
free(req->index_ids);
}
destroy_array(req->mime_types); destroy_array(req->mime_types);
destroy_array(req->tags); destroy_array(req->tags);
@@ -303,7 +326,7 @@ fts_search_paths_req_t *get_search_paths_req(struct mg_http_message *hm) {
json_value req_index_id, req_min_depth, req_max_depth, req_prefix; json_value req_index_id, req_min_depth, req_max_depth, req_prefix;
if (!cJSON_IsObject(json) || if (!cJSON_IsObject(json) ||
(req_index_id = get_json_string(json, "indexId")).invalid || (req_index_id = get_json_number(json, "indexId")).invalid ||
(req_prefix = get_json_string(json, "prefix")).invalid || (req_prefix = get_json_string(json, "prefix")).invalid ||
(req_min_depth = get_json_number(json, "minDepth")).val == NULL || (req_min_depth = get_json_number(json, "minDepth")).val == NULL ||
(req_max_depth = get_json_number(json, "maxDepth")).val == NULL) { (req_max_depth = get_json_number(json, "maxDepth")).val == NULL) {
@@ -313,19 +336,16 @@ fts_search_paths_req_t *get_search_paths_req(struct mg_http_message *hm) {
fts_search_paths_req_t *req = malloc(sizeof(fts_search_paths_req_t)); fts_search_paths_req_t *req = malloc(sizeof(fts_search_paths_req_t));
req->index_id = req_index_id.val ? strdup(req_index_id.val->valuestring) : NULL; req->index_id = req_index_id.val ? req_index_id.val->valueint : 0;
req->prefix = req_prefix.val ? strdup(req_prefix.val->valuestring) : NULL;
req->min_depth = req_min_depth.val->valueint; req->min_depth = req_min_depth.val->valueint;
req->max_depth = req_max_depth.val->valueint; req->max_depth = req_max_depth.val->valueint;
req->prefix = req_prefix.val ? strdup(req_prefix.val->valuestring) : NULL;
cJSON_Delete(json); cJSON_Delete(json);
return req; return req;
} }
void destroy_search_paths_req(fts_search_paths_req_t *req) { void destroy_search_paths_req(fts_search_paths_req_t *req) {
if (req->index_id) {
free(req->index_id);
}
if (req->prefix) { if (req->prefix) {
free(req->prefix); free(req->prefix);
} }
@@ -398,11 +418,15 @@ void fts_search(struct mg_connection *nc, struct mg_http_message *hm) {
void fts_get_document(struct mg_connection *nc, struct mg_http_message *hm) { void fts_get_document(struct mg_connection *nc, struct mg_http_message *hm) {
char doc_id[SIST_DOC_ID_LEN]; sist_id_t sid;
memcpy(doc_id, hm->uri.ptr + 7, SIST_INDEX_ID_LEN);
*(doc_id + SIST_INDEX_ID_LEN - 1) = '\0';
cJSON *json = database_fts_get_document(WebCtx.search_db, doc_id); if (hm->uri.len != 24 || !parse_sid(&sid, hm->uri.ptr + 7)) {
LOG_DEBUGF("serve.c", "Invalid /fts/d/ path: %.*s", (int) hm->uri.len, hm->uri.ptr);
HTTP_REPLY_NOT_FOUND
return;
}
cJSON *json = database_fts_get_document(WebCtx.search_db, sid.sid_int64);
if (!json) { if (!json) {
HTTP_REPLY_NOT_FOUND HTTP_REPLY_NOT_FOUND

View File

@@ -32,16 +32,16 @@ void web_serve_asset_chunk_vendors_css(struct mg_connection *nc) {
mg_send(nc, chunk_vendors_css, sizeof(chunk_vendors_css)); mg_send(nc, chunk_vendors_css, sizeof(chunk_vendors_css));
} }
index_t *web_get_index_by_id(const char *index_id) { index_t *web_get_index_by_id(int index_id) {
for (int i = WebCtx.index_count; i >= 0; i--) { for (int i = WebCtx.index_count; i >= 0; i--) {
if (strncmp(index_id, WebCtx.indices[i].desc.id, SIST_INDEX_ID_LEN) == 0) { if (index_id == WebCtx.indices[i].desc.id) {
return &WebCtx.indices[i]; return &WebCtx.indices[i];
} }
} }
return NULL; return NULL;
} }
database_t *web_get_database(const char *index_id) { database_t *web_get_database(int index_id) {
index_t *idx = web_get_index_by_id(index_id); index_t *idx = web_get_index_by_id(index_id);
if (idx != NULL) { if (idx != NULL) {
return idx->db; return idx->db;

View File

@@ -10,9 +10,9 @@
// See https://web.dev/coop-coep/ // See https://web.dev/coop-coep/
#define HTTP_CROSS_ORIGIN_HEADERS "Cross-Origin-Embedder-Policy: require-corp\r\nCross-Origin-Opener-Policy: same-origin\r\n" #define HTTP_CROSS_ORIGIN_HEADERS "Cross-Origin-Embedder-Policy: require-corp\r\nCross-Origin-Opener-Policy: same-origin\r\n"
index_t *web_get_index_by_id(const char *index_id); index_t *web_get_index_by_id(int index_id);
database_t *web_get_database(const char *index_id); database_t *web_get_database(int index_id);
__always_inline __always_inline
static char *web_address_to_string(struct mg_addr *addr) { static char *web_address_to_string(struct mg_addr *addr) {

View File

@@ -191,7 +191,7 @@ scan_code_t parse_archive(scan_arc_ctx_t *ctx, vfile_t *f, document_t *doc, pcre
sub_job->vfile.logf = ctx->logf; sub_job->vfile.logf = ctx->logf;
sub_job->vfile.has_checksum = FALSE; sub_job->vfile.has_checksum = FALSE;
sub_job->vfile.calculate_checksum = f->calculate_checksum; sub_job->vfile.calculate_checksum = f->calculate_checksum;
strcpy(sub_job->parent, doc->doc_id); strcpy(sub_job->parent, doc->filepath);
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
struct stat entry_stat = *archive_entry_stat(entry); struct stat entry_stat = *archive_entry_stat(entry);

View File

@@ -20,7 +20,6 @@ typedef struct {
parse_callback_t parse; parse_callback_t parse;
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
char passphrase[4096]; char passphrase[4096];
} scan_arc_ctx_t; } scan_arc_ctx_t;

View File

@@ -54,7 +54,6 @@ void parse_comic(scan_comic_ctx_t *ctx, vfile_t *f, document_t *doc) {
.max_media_buffer = 0, .max_media_buffer = 0,
.log = ctx->log, .log = ctx->log,
.logf = ctx->logf, .logf = ctx->logf,
.store = ctx->store,
}; };
ret = store_image_thumbnail(&media_ctx, buf, entry_size, doc, file_path); ret = store_image_thumbnail(&media_ctx, buf, entry_size, doc, file_path);

View File

@@ -7,7 +7,6 @@
typedef struct { typedef struct {
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
int enable_tn; int enable_tn;
int tn_size; int tn_size;

View File

@@ -160,8 +160,8 @@ int render_cover(scan_ebook_ctx_t *ctx, fz_context *fzctx, document_t *doc, fz_d
av_init_packet(&thumbnail_packet); av_init_packet(&thumbnail_packet);
avcodec_receive_packet(thumbnail_encoder, &thumbnail_packet); avcodec_receive_packet(thumbnail_encoder, &thumbnail_packet);
APPEND_LONG_META(doc, MetaThumbnail, 1); doc->thumbnail_count = 1;
ctx->store(doc->doc_id, 0, (char *) thumbnail_packet.data, thumbnail_packet.size); APPEND_THUMBNAIL(doc, (char *) thumbnail_packet.data, thumbnail_packet.size);
free(samples); free(samples);
av_packet_unref(&thumbnail_packet); av_packet_unref(&thumbnail_packet);

View File

@@ -12,7 +12,6 @@ typedef struct {
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
int fast_epub_parse; int fast_epub_parse;
int tn_qscale; int tn_qscale;
} scan_ebook_ctx_t; } scan_ebook_ctx_t;

View File

@@ -231,8 +231,8 @@ void parse_font(scan_font_ctx_t *ctx, vfile_t *f, document_t *doc) {
dyn_buffer_t bmp_data = dyn_buffer_create(); dyn_buffer_t bmp_data = dyn_buffer_create();
bmp_format(&bmp_data, dimensions, bitmap); bmp_format(&bmp_data, dimensions, bitmap);
APPEND_LONG_META(doc, MetaThumbnail, 1); doc->thumbnail_count = 1;
ctx->store(doc->doc_id, 0, bmp_data.buf, bmp_data.cur); APPEND_THUMBNAIL(doc, bmp_data.buf, bmp_data.cur);
dyn_buffer_destroy(&bmp_data); dyn_buffer_destroy(&bmp_data);
free(bitmap); free(bitmap);

View File

@@ -8,7 +8,6 @@ typedef struct {
int enable_tn; int enable_tn;
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
} scan_font_ctx_t; } scan_font_ctx_t;
void parse_font(scan_font_ctx_t *ctx, vfile_t *f, document_t *doc); void parse_font(scan_font_ctx_t *ctx, vfile_t *f, document_t *doc);

View File

@@ -8,7 +8,6 @@ typedef struct {
long content_size; long content_size;
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
unsigned int json_mime; unsigned int json_mime;
unsigned int ndjson_mime; unsigned int ndjson_mime;
} scan_json_ctx_t; } scan_json_ctx_t;

View File

@@ -37,15 +37,22 @@
meta_long->long_val = value; \ meta_long->long_val = value; \
APPEND_META(doc, meta_long);}} while(0) APPEND_META(doc, meta_long);}} while(0)
#define APPEND_THUMBNAIL(doc, data, data_len) do{ \
{meta_line_t *meta_tn = malloc(sizeof(meta_line_t) + (data_len)); \
meta_tn->key = MetaThumbnail; \
meta_tn->size = data_len; \
memcpy(meta_tn->str_val, data, data_len); \
APPEND_META(doc, meta_tn);}} while(0)
#define APPEND_META(doc, meta) do {\ #define APPEND_META(doc, meta) do {\
meta->next = NULL;\ (meta)->next = NULL;\
if (doc->meta_head == NULL) {\ if ((doc)->meta_head == NULL) {\
doc->meta_head = meta;\ (doc)->meta_head = meta;\
doc->meta_tail = doc->meta_head;\ (doc)->meta_tail = (doc)->meta_head;\
} else {\ } else {\
doc->meta_tail->next = meta;\ (doc)->meta_tail->next = meta;\
doc->meta_tail = meta;\ (doc)->meta_tail = meta;\
}}while(0) }}while(0)
#define APPEND_UTF8_META(doc, keyname, str) \ #define APPEND_UTF8_META(doc, keyname, str) \

View File

@@ -466,7 +466,7 @@ int decode_frame_and_save_thumbnail(scan_media_ctx_t *ctx, AVFormatContext *pFor
if (scaled_frame == STORE_AS_IS) { if (scaled_frame == STORE_AS_IS) {
return_value = SAVE_THUMBNAIL_OK; return_value = SAVE_THUMBNAIL_OK;
ctx->store(doc->doc_id, 0, frame_and_packet->packet->data, frame_and_packet->packet->size); APPEND_THUMBNAIL(doc, frame_and_packet->packet->data, frame_and_packet->packet->size);
} else { } else {
// Encode frame // Encode frame
AVCodecContext *thumbnail_encoder = alloc_webp_encoder(scaled_frame->width, scaled_frame->height, AVCodecContext *thumbnail_encoder = alloc_webp_encoder(scaled_frame->width, scaled_frame->height,
@@ -477,9 +477,9 @@ int decode_frame_and_save_thumbnail(scan_media_ctx_t *ctx, AVFormatContext *pFor
AVPacket *thumbnail_packet = av_packet_alloc(); AVPacket *thumbnail_packet = av_packet_alloc();
avcodec_receive_packet(thumbnail_encoder, thumbnail_packet); avcodec_receive_packet(thumbnail_encoder, thumbnail_packet);
// Save thumbnail // Save thumbnail_count
if (thumbnail_index == 0) { if (thumbnail_index == 0) {
ctx->store(doc->doc_id, 0, thumbnail_packet->data, thumbnail_packet->size); APPEND_THUMBNAIL(doc, thumbnail_packet->data, thumbnail_packet->size);
return_value = SAVE_THUMBNAIL_OK; return_value = SAVE_THUMBNAIL_OK;
} else if (thumbnail_index > 1) { } else if (thumbnail_index > 1) {
@@ -487,7 +487,7 @@ int decode_frame_and_save_thumbnail(scan_media_ctx_t *ctx, AVFormatContext *pFor
// I figure out a better fix. // I figure out a better fix.
thumbnail_index -= 1; thumbnail_index -= 1;
ctx->store(doc->doc_id, thumbnail_index, thumbnail_packet->data, thumbnail_packet->size); APPEND_THUMBNAIL(doc, thumbnail_packet->data, thumbnail_packet->size);
return_value = SAVE_THUMBNAIL_OK; return_value = SAVE_THUMBNAIL_OK;
} else { } else {
@@ -584,7 +584,7 @@ void parse_media_format_ctx(scan_media_ctx_t *ctx, AVFormatContext *pFormatCtx,
int thumbnails_to_generate = (IS_VIDEO(pFormatCtx) && stream->codecpar->codec_id != AV_CODEC_ID_GIF && int thumbnails_to_generate = (IS_VIDEO(pFormatCtx) && stream->codecpar->codec_id != AV_CODEC_ID_GIF &&
video_duration_in_seconds >= 15) video_duration_in_seconds >= 15)
// Limit to ~1 thumbnail every 7s // Limit to ~1 thumbnail_count every 7s
? MAX(MIN(ctx->tn_count, video_duration_in_seconds / 7 + 1), 1) + 1 ? MAX(MIN(ctx->tn_count, video_duration_in_seconds / 7 + 1), 1) + 1
: 1; : 1;
@@ -610,7 +610,7 @@ void parse_media_format_ctx(scan_media_ctx_t *ctx, AVFormatContext *pFormatCtx,
} }
if (number_of_thumbnails_generated > 0) { if (number_of_thumbnails_generated > 0) {
APPEND_LONG_META(doc, MetaThumbnail, number_of_thumbnails_generated); doc->thumbnail_count = number_of_thumbnails_generated;
} }
avcodec_free_context(&decoder); avcodec_free_context(&decoder);
@@ -859,8 +859,8 @@ int store_image_thumbnail(scan_media_ctx_t *ctx, void *buf, size_t buf_len, docu
} }
if (scaled_frame == STORE_AS_IS) { if (scaled_frame == STORE_AS_IS) {
APPEND_LONG_META(doc, MetaThumbnail, 1); doc->thumbnail_count = 1;
ctx->store(doc->doc_id, 0, frame_and_packet->packet->data, frame_and_packet->packet->size); APPEND_THUMBNAIL(doc, frame_and_packet->packet->data, frame_and_packet->packet->size);
} else { } else {
// Encode frame to jpeg // Encode frame to jpeg
AVCodecContext *jpeg_encoder = alloc_webp_encoder(scaled_frame->width, scaled_frame->height, AVCodecContext *jpeg_encoder = alloc_webp_encoder(scaled_frame->width, scaled_frame->height,
@@ -871,9 +871,9 @@ int store_image_thumbnail(scan_media_ctx_t *ctx, void *buf, size_t buf_len, docu
AVPacket *jpeg_packet = av_packet_alloc(); AVPacket *jpeg_packet = av_packet_alloc();
avcodec_receive_packet(jpeg_encoder, jpeg_packet); avcodec_receive_packet(jpeg_encoder, jpeg_packet);
// Save thumbnail // Save thumbnail_count
APPEND_LONG_META(doc, MetaThumbnail, 1); doc->thumbnail_count = 1;
ctx->store(doc->doc_id, 0, jpeg_packet->data, jpeg_packet->size); APPEND_THUMBNAIL(doc, jpeg_packet->data, jpeg_packet->size);
av_packet_free(&jpeg_packet); av_packet_free(&jpeg_packet);
avcodec_free_context(&jpeg_encoder); avcodec_free_context(&jpeg_encoder);

View File

@@ -13,7 +13,6 @@
typedef struct { typedef struct {
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
int tn_size; int tn_size;
int tn_qscale; int tn_qscale;

View File

@@ -31,7 +31,6 @@ int store_cover(scan_mobi_ctx_t *ctx, document_t *doc, MOBIData *m) {
.max_media_buffer = 0, .max_media_buffer = 0,
.log = ctx->log, .log = ctx->log,
.logf = ctx->logf, .logf = ctx->logf,
.store = ctx->store,
}; };
store_image_thumbnail(&media_ctx, record->data, record->size, doc, "img.jpg"); store_image_thumbnail(&media_ctx, record->data, record->size, doc, "img.jpg");

View File

@@ -7,7 +7,6 @@ typedef struct {
long content_size; long content_size;
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
int tn_qscale; int tn_qscale;
int tn_size; int tn_size;

View File

@@ -7,7 +7,6 @@ typedef struct {
long content_size; long content_size;
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
unsigned int msdoc_mime; unsigned int msdoc_mime;
} scan_msdoc_ctx_t; } scan_msdoc_ctx_t;

View File

@@ -190,8 +190,7 @@ void read_thumbnail(scan_ooxml_ctx_t *ctx, document_t *doc, struct archive *a, s
char *buf = malloc(entry_size); char *buf = malloc(entry_size);
archive_read_data(a, buf, entry_size); archive_read_data(a, buf, entry_size);
APPEND_LONG_META(doc, MetaThumbnail, 1); doc->thumbnail_count = 1;
ctx->store(doc->doc_id, 1, buf, entry_size);
free(buf); free(buf);
} }
@@ -238,7 +237,7 @@ void parse_ooxml(scan_ooxml_ctx_t *ctx, vfile_t *f, document_t *doc) {
if (read_doc_props(ctx, a, doc) != 0) { if (read_doc_props(ctx, a, doc) != 0) {
break; break;
} }
} else if (ctx->enable_tn && strcmp(path, "docProps/thumbnail.jpeg") == 0) { } else if (ctx->enable_tn && strcmp(path, "docProps/thumbnail_count.jpeg") == 0) {
read_thumbnail(ctx, doc, a, entry); read_thumbnail(ctx, doc, a, entry);
} }
} }

View File

@@ -9,7 +9,6 @@ typedef struct {
long content_size; long content_size;
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
} scan_ooxml_ctx_t; } scan_ooxml_ctx_t;
void parse_ooxml(scan_ooxml_ctx_t *ctx, vfile_t *f, document_t *doc); void parse_ooxml(scan_ooxml_ctx_t *ctx, vfile_t *f, document_t *doc);

View File

@@ -13,7 +13,6 @@ int store_thumbnail_jpeg(scan_raw_ctx_t *ctx, libraw_thumbnail_t img, document_t
.read_subtitles = FALSE, .read_subtitles = FALSE,
.tn_count = 1, .tn_count = 1,
.max_media_buffer = 0, .max_media_buffer = 0,
.store = ctx->store,
.log = ctx->log, .log = ctx->log,
.logf = ctx->logf, .logf = ctx->logf,
.tn_size = ctx->tn_size, .tn_size = ctx->tn_size,
@@ -84,8 +83,8 @@ int store_thumbnail_rgb24(scan_raw_ctx_t *ctx, libraw_processed_image_t *img, do
av_init_packet(&thumbnail_packet); av_init_packet(&thumbnail_packet);
avcodec_receive_packet(thumbnail_encoder, &thumbnail_packet); avcodec_receive_packet(thumbnail_encoder, &thumbnail_packet);
APPEND_LONG_META(doc, MetaThumbnail, 1); doc->thumbnail_count = 1;
ctx->store((char *) doc->doc_id, sizeof(doc->doc_id), (char *) thumbnail_packet.data, thumbnail_packet.size); APPEND_THUMBNAIL(doc, (char *) thumbnail_packet.data, thumbnail_packet.size);
av_packet_unref(&thumbnail_packet); av_packet_unref(&thumbnail_packet);
av_free(*scaled_frame->data); av_free(*scaled_frame->data);

View File

@@ -6,7 +6,6 @@
typedef struct { typedef struct {
log_callback_t log; log_callback_t log;
logf_callback_t logf; logf_callback_t logf;
store_callback_t store;
int enable_tn; int enable_tn;
int tn_size; int tn_size;

View File

@@ -18,8 +18,6 @@
#define UNUSED(x) __attribute__((__unused__)) x #define UNUSED(x) __attribute__((__unused__)) x
typedef void (*store_callback_t)(char *key, int num, void *buf, size_t buf_len);
typedef void (*logf_callback_t)(const char *filepath, int level, char *format, ...); typedef void (*logf_callback_t)(const char *filepath, int level, char *format, ...);
typedef void (*log_callback_t)(const char *filepath, int level, char *str); typedef void (*log_callback_t)(const char *filepath, int level, char *str);
@@ -50,8 +48,8 @@ typedef int scan_code_t;
#define CTX_LOG_FATALF(filepath, fmt, ...) ctx->logf(filepath, LEVEL_FATAL, fmt, __VA_ARGS__); exit(-1) #define CTX_LOG_FATALF(filepath, fmt, ...) ctx->logf(filepath, LEVEL_FATAL, fmt, __VA_ARGS__); exit(-1)
#define CTX_LOG_FATAL(filepath, str) ctx->log(filepath, LEVEL_FATAL, str); exit(-1) #define CTX_LOG_FATAL(filepath, str) ctx->log(filepath, LEVEL_FATAL, str); exit(-1)
#define SIST_DOC_ID_LEN MD5_STR_LENGTH // 0000000.000000000
#define SIST_INDEX_ID_LEN MD5_STR_LENGTH #define SIST_SID_LEN 18
#define EBOOK_LOCKS 0 #define EBOOK_LOCKS 0
@@ -66,7 +64,6 @@ enum metakey {
MetaGenre, MetaGenre,
MetaTitle, MetaTitle,
MetaFontName, MetaFontName,
MetaParent,
MetaExifMake, MetaExifMake,
MetaExifDescription, MetaExifDescription,
MetaExifSoftware, MetaExifSoftware,
@@ -79,7 +76,6 @@ enum metakey {
MetaExifDateTime, MetaExifDateTime,
MetaAuthor, MetaAuthor,
MetaModifiedBy, MetaModifiedBy,
MetaThumbnail,
MetaChecksum, MetaChecksum,
// Number // Number
@@ -96,11 +92,15 @@ enum metakey {
MetaExifGpsLatitudeRef, MetaExifGpsLatitudeRef,
MetaExifGpsLatitudeDec, MetaExifGpsLatitudeDec,
MetaExifGpsLongitudeDec, MetaExifGpsLongitudeDec,
// other
MetaThumbnail,
}; };
typedef struct meta_line { typedef struct meta_line {
struct meta_line *next; struct meta_line *next;
enum metakey key; enum metakey key;
size_t size;
union { union {
char str_val[0]; char str_val[0];
unsigned long long_val; unsigned long long_val;
@@ -109,7 +109,6 @@ typedef struct meta_line {
typedef struct document { typedef struct document {
char doc_id[SIST_DOC_ID_LEN];
unsigned long size; unsigned long size;
unsigned int mime; unsigned int mime;
int mtime; int mtime;
@@ -117,7 +116,9 @@ typedef struct document {
int ext; int ext;
meta_line_t *meta_head; meta_line_t *meta_head;
meta_line_t *meta_tail; meta_line_t *meta_tail;
int thumbnail_count;
char filepath[PATH_MAX * 2 + 1]; char filepath[PATH_MAX * 2 + 1];
char parent[PATH_MAX * 2 + 1];
} document_t; } document_t;
typedef struct vfile vfile_t; typedef struct vfile vfile_t;
@@ -166,7 +167,7 @@ typedef struct {
int base; int base;
int ext; int ext;
struct vfile vfile; struct vfile vfile;
char parent[SIST_DOC_ID_LEN]; char parent[PATH_MAX * 2 + 1];
char filepath[PATH_MAX * 2 + 1]; char filepath[PATH_MAX * 2 + 1];
} parse_job_t; } parse_job_t;

View File

@@ -8,7 +8,7 @@
#include "macros.h" #include "macros.h"
#include <openssl/evp.h> #include <openssl/evp.h>
#define STR_STARTS_WITH_CONSTANT(x, y) (strncmp(y, x, sizeof(y) - 1) == 0) #define STR_STARTS_WITH_CONSTANT(x, y) ((x) != NULL && (y) != NULL && strncmp(y, x, sizeof(y) - 1) == 0)
#define TEXT_BUF_FULL (-1) #define TEXT_BUF_FULL (-1)
#define INITIAL_BUF_SIZE (1024 * 16) #define INITIAL_BUF_SIZE (1024 * 16)

View File

@@ -526,7 +526,7 @@ TEST(MediaVideo, Vid3Mp4) {
ASSERT_EQ(get_meta(&doc, MetaMediaBitrate)->long_val, 825169); ASSERT_EQ(get_meta(&doc, MetaMediaBitrate)->long_val, 825169);
ASSERT_EQ(get_meta(&doc, MetaMediaDuration)->long_val, 10); ASSERT_EQ(get_meta(&doc, MetaMediaDuration)->long_val, 10);
//TODO: Check that thumbnail was generated correctly //TODO: Check that thumbnail_count was generated correctly
cleanup(&doc, &f); cleanup(&doc, &f);
} }
@@ -541,7 +541,7 @@ TEST(MediaVideo, Vid3Ogv) {
ASSERT_EQ(get_meta(&doc, MetaMediaBitrate)->long_val, 590261); ASSERT_EQ(get_meta(&doc, MetaMediaBitrate)->long_val, 590261);
ASSERT_EQ(get_meta(&doc, MetaMediaDuration)->long_val, 10); ASSERT_EQ(get_meta(&doc, MetaMediaDuration)->long_val, 10);
//TODO: Check that thumbnail was generated correctly //TODO: Check that thumbnail_count was generated correctly
cleanup(&doc, &f); cleanup(&doc, &f);
} }
@@ -556,7 +556,7 @@ TEST(MediaVideo, Vid3Webm) {
ASSERT_EQ(get_meta(&doc, MetaMediaBitrate)->long_val, 343153); ASSERT_EQ(get_meta(&doc, MetaMediaBitrate)->long_val, 343153);
ASSERT_EQ(get_meta(&doc, MetaMediaDuration)->long_val, 10); ASSERT_EQ(get_meta(&doc, MetaMediaDuration)->long_val, 10);
//TODO: Check that thumbnail was generated correctly //TODO: Check that thumbnail_count was generated correctly
cleanup(&doc, &f); cleanup(&doc, &f);
} }