feat(locate-db): enhance database build process with directory tracking and status updates
This commit is contained in:
parent
ecc54c1fc9
commit
bbd38746df
|
|
@ -6,6 +6,11 @@ import { getWebDAVBaseUrl, basename, listDirectory, putFileContents, deletePath
|
||||||
const LOCATE_PATH = '/sd/.locate';
|
const LOCATE_PATH = '/sd/.locate';
|
||||||
const LOCATE_GZ_PATH = '/sd/.locate.gz';
|
const LOCATE_GZ_PATH = '/sd/.locate.gz';
|
||||||
const SCAN_PROGRESS_INTERVAL = 50;
|
const SCAN_PROGRESS_INTERVAL = 50;
|
||||||
|
|
||||||
|
function dirPath(path: string): string {
|
||||||
|
const i = path.lastIndexOf('/');
|
||||||
|
return i > 0 ? path.slice(0, i) : '/';
|
||||||
|
}
|
||||||
// Vite rewrites this `?url` import to the hashed asset path
|
// Vite rewrites this `?url` import to the hashed asset path
|
||||||
// (e.g. /assets/sqlite3-BVKGSWc-.wasm) and respects the configured base path.
|
// (e.g. /assets/sqlite3-BVKGSWc-.wasm) and respects the configured base path.
|
||||||
const WASM_URL: string = wasmUrl;
|
const WASM_URL: string = wasmUrl;
|
||||||
|
|
@ -190,10 +195,12 @@ export async function buildLocateDb(
|
||||||
let totalBytes = 0;
|
let totalBytes = 0;
|
||||||
let dirCount = 0;
|
let dirCount = 0;
|
||||||
let fileCount = 0;
|
let fileCount = 0;
|
||||||
|
let lastFolder = '/sd';
|
||||||
|
|
||||||
while (queue.length > 0) {
|
while (queue.length > 0) {
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
const dir = queue.shift()!;
|
const dir = queue.shift()!;
|
||||||
|
lastFolder = dir;
|
||||||
let prevBytes = 0;
|
let prevBytes = 0;
|
||||||
const batch = await listDirectory(dir, false, bytes => {
|
const batch = await listDirectory(dir, false, bytes => {
|
||||||
totalBytes += bytes - prevBytes;
|
totalBytes += bytes - prevBytes;
|
||||||
|
|
@ -227,28 +234,77 @@ export async function buildLocateDb(
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
|
|
||||||
// ── 2. Build in-memory DB ───────────────────────────────────────────────
|
// ── 2. Build in-memory DB ───────────────────────────────────────────────
|
||||||
|
const scanStart = Date.now();
|
||||||
const db = new sqlite3.oo1.DB(':memory:', 'ct');
|
const db = new sqlite3.oo1.DB(':memory:', 'ct');
|
||||||
db.exec(
|
db.exec(
|
||||||
|
'CREATE TABLE dirs (' +
|
||||||
|
' id INTEGER PRIMARY KEY,' +
|
||||||
|
' path TEXT NOT NULL UNIQUE,' +
|
||||||
|
' scanned INTEGER NOT NULL DEFAULT 0' +
|
||||||
|
');' +
|
||||||
'CREATE TABLE files (' +
|
'CREATE TABLE files (' +
|
||||||
' path TEXT PRIMARY KEY,' +
|
' id INTEGER PRIMARY KEY,' +
|
||||||
|
' dir_id INTEGER NOT NULL,' +
|
||||||
|
' name TEXT NOT NULL,' +
|
||||||
' size INTEGER NOT NULL DEFAULT 0,' +
|
' size INTEGER NOT NULL DEFAULT 0,' +
|
||||||
' mtime INTEGER NOT NULL DEFAULT 0,' +
|
' mtime INTEGER NOT NULL DEFAULT 0,' +
|
||||||
' is_dir INTEGER NOT NULL DEFAULT 0' +
|
' is_dir INTEGER NOT NULL DEFAULT 0,' +
|
||||||
|
' UNIQUE(dir_id, name)' +
|
||||||
');' +
|
');' +
|
||||||
'CREATE INDEX IF NOT EXISTS idx_path ON files(path);',
|
'CREATE INDEX files_dir_idx ON files(dir_id);' +
|
||||||
|
'CREATE VIRTUAL TABLE files_fts USING fts5(' +
|
||||||
|
" path, content='', tokenize=\"unicode61\"" +
|
||||||
|
');' +
|
||||||
|
'CREATE TABLE status (' +
|
||||||
|
' id INTEGER PRIMARY KEY DEFAULT 1,' +
|
||||||
|
' total_dirs INTEGER NOT NULL DEFAULT 0,' +
|
||||||
|
' total_files INTEGER NOT NULL DEFAULT 0,' +
|
||||||
|
' last_scan INTEGER NOT NULL DEFAULT 0,' +
|
||||||
|
' duration INTEGER NOT NULL DEFAULT 0,' +
|
||||||
|
" last_folder TEXT NOT NULL DEFAULT ''" +
|
||||||
|
');',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ── 3. Insert all unique directory paths ────────────────────────────────
|
||||||
|
const allDirPaths = new Set<string>(['/sd']);
|
||||||
|
for (const e of entries) {
|
||||||
|
allDirPaths.add(dirPath(e.path));
|
||||||
|
if (e.type === 'folder') allDirPaths.add(e.path);
|
||||||
|
}
|
||||||
db.exec('BEGIN');
|
db.exec('BEGIN');
|
||||||
const stmt = db.prepare('INSERT OR REPLACE INTO files VALUES (?,?,?,?)');
|
const stmtDir = db.prepare('INSERT OR IGNORE INTO dirs(path, scanned) VALUES (?, 1)');
|
||||||
|
try {
|
||||||
|
for (const p of allDirPaths) stmtDir.bind([p]).stepReset();
|
||||||
|
} finally { stmtDir.finalize(); }
|
||||||
|
db.exec('COMMIT');
|
||||||
|
|
||||||
|
const dirIdMap = new Map<string, number>();
|
||||||
|
db.exec({
|
||||||
|
sql: 'SELECT id, path FROM dirs',
|
||||||
|
rowMode: 'array',
|
||||||
|
callback: (row: any[]) => dirIdMap.set(row[1] as string, row[0] as number),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 4. Insert files + FTS ────────────────────────────────────────────────
|
||||||
|
db.exec('BEGIN');
|
||||||
|
const stmtFile = db.prepare(
|
||||||
|
'INSERT INTO files(dir_id, name, size, mtime, is_dir) VALUES (?,?,?,?,?)',
|
||||||
|
);
|
||||||
|
const stmtFts = db.prepare('INSERT INTO files_fts(rowid, path) VALUES (?,?)');
|
||||||
try {
|
try {
|
||||||
for (let i = 0; i < entries.length; i++) {
|
for (let i = 0; i < entries.length; i++) {
|
||||||
const e = entries[i];
|
const e = entries[i];
|
||||||
stmt.bind([
|
const dirId = dirIdMap.get(dirPath(e.path));
|
||||||
e.path,
|
if (dirId === undefined) continue;
|
||||||
|
stmtFile.bind([
|
||||||
|
dirId,
|
||||||
|
basename(e.path) || e.path,
|
||||||
e.size,
|
e.size,
|
||||||
e.lastModified ? Math.floor(e.lastModified.getTime() / 1000) : 0,
|
e.lastModified ? Math.floor(e.lastModified.getTime() / 1000) : 0,
|
||||||
e.type === 'folder' ? 1 : 0,
|
e.type === 'folder' ? 1 : 0,
|
||||||
]).stepReset();
|
]).stepReset();
|
||||||
|
const rowid = sqlite3.capi.sqlite3_last_insert_rowid(db.pointer);
|
||||||
|
stmtFts.bind([rowid, e.path]).stepReset();
|
||||||
if (i % SCAN_PROGRESS_INTERVAL === 0) {
|
if (i % SCAN_PROGRESS_INTERVAL === 0) {
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
onProgress?.('building', i, e.path, { dirs: dirCount, files: fileCount });
|
onProgress?.('building', i, e.path, { dirs: dirCount, files: fileCount });
|
||||||
|
|
@ -256,11 +312,24 @@ export async function buildLocateDb(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
stmt.finalize();
|
stmtFile.finalize();
|
||||||
|
stmtFts.finalize();
|
||||||
}
|
}
|
||||||
db.exec('COMMIT');
|
db.exec('COMMIT');
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
onProgress?.('building', entries.length);
|
onProgress?.('building', entries.length, undefined, { dirs: dirCount, files: fileCount });
|
||||||
|
|
||||||
|
// ── 5. Write status ──────────────────────────────────────────────────────
|
||||||
|
db.exec({
|
||||||
|
sql: 'INSERT OR REPLACE INTO status(id,total_dirs,total_files,last_scan,duration,last_folder) VALUES (1,?,?,?,?,?)',
|
||||||
|
bind: [
|
||||||
|
dirCount,
|
||||||
|
fileCount,
|
||||||
|
Math.floor(Date.now() / 1000),
|
||||||
|
Math.floor((Date.now() - scanStart) / 1000),
|
||||||
|
lastFolder,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
// ── 3. Serialize to bytes ────────────────────────────────────────────────
|
// ── 3. Serialize to bytes ────────────────────────────────────────────────
|
||||||
const pSaved = sqlite3.wasm.pstack.pointer;
|
const pSaved = sqlite3.wasm.pstack.pointer;
|
||||||
|
|
@ -331,7 +400,9 @@ export function searchLocate(query: string, limit = 500): LocateEntry[] {
|
||||||
const needle = toSqlLike(query);
|
const needle = toSqlLike(query);
|
||||||
const rows: LocateEntry[] = [];
|
const rows: LocateEntry[] = [];
|
||||||
_db.exec({
|
_db.exec({
|
||||||
sql: "SELECT path, size, mtime, is_dir FROM files WHERE path LIKE ? ESCAPE '\\' LIMIT ?",
|
sql: "SELECT d.path || '/' || f.name, f.size, f.mtime, f.is_dir " +
|
||||||
|
"FROM files f JOIN dirs d ON f.dir_id = d.id " +
|
||||||
|
"WHERE (d.path || '/' || f.name) LIKE ? ESCAPE '\\' LIMIT ?",
|
||||||
bind: [needle, limit],
|
bind: [needle, limit],
|
||||||
rowMode: 'array',
|
rowMode: 'array',
|
||||||
callback: (row: any[]) => {
|
callback: (row: any[]) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user