@1-/scan 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -8
- package/_.js +23 -22
- package/dbInit.js +30 -0
- package/load.js +21 -12
- package/package.json +7 -7
- package/rm.js +10 -4
- package/scan.js +32 -6
- package/stat.js +6 -0
- package/upsert.js +20 -5
- package/save.js +0 -18
package/README.md
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
<a id="en"></a>
|
|
6
6
|
# @1-/scan : SQLite-backed incremental directory file scanner
|
|
7
7
|
|
|
8
|
+
- [@1-/scan : SQLite-backed incremental directory file scanner](#1-scan-sqlite-backed-incremental-directory-file-scanner)
|
|
9
|
+
- [1. Features](#1-features)
|
|
10
|
+
- [2. Usage](#2-usage)
|
|
11
|
+
- [Basic Incremental Scan](#basic-incremental-scan)
|
|
12
|
+
- [Bulk Storage Module](#bulk-storage-module)
|
|
13
|
+
- [3. Design](#3-design)
|
|
14
|
+
- [4. Tech Stack](#4-tech-stack)
|
|
15
|
+
- [5. Code Structure](#5-code-structure)
|
|
16
|
+
- [6. History](#6-history)
|
|
17
|
+
- [About](#about)
|
|
18
|
+
|
|
8
19
|
Incrementally scans directory files, compares file sizes and modification times to detect changes, synchronizes metadata to SQLite database, and returns list of changed relative paths.
|
|
9
20
|
|
|
10
21
|
## 1. Features
|
|
@@ -23,11 +34,11 @@ Incrementally scans directory files, compares file sizes and modification times
|
|
|
23
34
|
import scan from "@1-/scan";
|
|
24
35
|
|
|
25
36
|
const dir = "./data";
|
|
26
|
-
const
|
|
37
|
+
const db_dir = "./db";
|
|
27
38
|
const files = ["file1.txt", "file2.txt"];
|
|
28
39
|
|
|
29
40
|
// Scan file list, sync metadata to SQLite, return changed relative paths and upsert function
|
|
30
|
-
const [updated_paths, upsert] = await scan(dir,
|
|
41
|
+
const [updated_paths, upsert] = await scan(dir, db_dir, files);
|
|
31
42
|
|
|
32
43
|
// Close database automatically when exiting scope
|
|
33
44
|
using _ = upsert;
|
|
@@ -58,10 +69,10 @@ db.close();
|
|
|
58
69
|
|
|
59
70
|
Main entry orchestrates modules to scan directories and synchronize metadata.
|
|
60
71
|
|
|
61
|
-

|
|
62
73
|
|
|
63
74
|
1. **Initialize Connection**: Calls `@1-/sqlite` to open SQLite database. Updates `.gitignore` in the database directory if the database is newly created to prevent tracking.
|
|
64
|
-
2. **Load Records**: `load.js` checks and creates `
|
|
75
|
+
2. **Load Records**: `load.js` checks and creates `scan` table. Reads stored paths, sizes, and modification times to restore memory mappings inside `BinMap`.
|
|
65
76
|
3. **Compare Files**: `scan.js` iterates over input file list, calling `stat.js` for metadata and utilizing `@1-/hash` to map paths to 16-byte binary keys. Adds files with mismatched size or modification time to change list.
|
|
66
77
|
4. **Delete and Return**: `rm.js` deletes absent or unscanned records in transaction. Returns changed paths list and `upsert` function (provided by `upsert.js`) for persistence, supporting automatic resource disposal.
|
|
67
78
|
|
|
@@ -93,17 +104,30 @@ Main entry orchestrates modules to scan directories and synchronize metadata.
|
|
|
93
104
|
SQLite was created by D. Richard Hipp in 2000 while designing board software for guided-missile destroyers. The system originally depended on commercial database that required constant database administration; connection loss could stall the entire damage control application. Hipp designed serverless, zero-configuration embedded database that directly reads and writes local files, marking the birth of SQLite.
|
|
94
105
|
|
|
95
106
|
To conserve space and reduce latency, SQLite utilizes Varint (variable-length integer) encoding for metadata storage. Under this scheme, small integers consume only 1 byte, while larger numbers scale dynamically. This library inherits that design philosophy, compressing file metadata into varints for memory storage to ensure minimal footprint and high synchronization performance.
|
|
107
|
+
|
|
96
108
|
## About
|
|
97
109
|
|
|
98
110
|
This library is developed by [WebC.site](https://webc.site).
|
|
99
111
|
|
|
100
112
|
[WebC.site](https://webc.site): A new paradigm of web development for AI
|
|
101
113
|
|
|
114
|
+
|
|
102
115
|
---
|
|
103
116
|
|
|
104
117
|
<a id="zh"></a>
|
|
105
118
|
# @1-/scan : 基于 SQLite 的目录文件增量扫描器
|
|
106
119
|
|
|
120
|
+
- [@1-/scan : 基于 SQLite 的目录文件增量扫描器](#1-scan-基于-sqlite-的目录文件增量扫描器)
|
|
121
|
+
- [1. 功能介绍](#1-功能介绍)
|
|
122
|
+
- [2. 使用演示](#2-使用演示)
|
|
123
|
+
- [基础增量扫描](#基础增量扫描)
|
|
124
|
+
- [独立批量存储模块](#独立批量存储模块)
|
|
125
|
+
- [3. 设计思路](#3-设计思路)
|
|
126
|
+
- [4. 技术栈](#4-技术栈)
|
|
127
|
+
- [5. 代码结构](#5-代码结构)
|
|
128
|
+
- [6. 历史故事](#6-历史故事)
|
|
129
|
+
- [关于](#关于)
|
|
130
|
+
|
|
107
131
|
增量扫描目录文件,比对文件大小与修改时间检测变更,同步元数据至 SQLite 数据库,返回已变更相对路径列表。
|
|
108
132
|
|
|
109
133
|
## 1. 功能介绍
|
|
@@ -122,11 +146,11 @@ This library is developed by [WebC.site](https://webc.site).
|
|
|
122
146
|
import scan from "@1-/scan";
|
|
123
147
|
|
|
124
148
|
const dir = "./data";
|
|
125
|
-
const
|
|
149
|
+
const db_dir = "./db";
|
|
126
150
|
const files = ["file1.txt", "file2.txt"];
|
|
127
151
|
|
|
128
152
|
// 扫描文件列表并同步至 SQLite,返回已变更的相对路径列表与更新函数
|
|
129
|
-
const [updated_paths, upsert] = await scan(dir,
|
|
153
|
+
const [updated_paths, upsert] = await scan(dir, db_dir, files);
|
|
130
154
|
|
|
131
155
|
// 退出作用域自动关闭数据库
|
|
132
156
|
using _ = upsert;
|
|
@@ -157,10 +181,10 @@ db.close();
|
|
|
157
181
|
|
|
158
182
|
主入口调度各模块,协作完成目录扫描与数据同步。
|
|
159
183
|
|
|
160
|
-

|
|
161
185
|
|
|
162
186
|
1. **初始化连接**:调用 `@1-/sqlite` 打开 SQLite 数据库。若数据库为新创建,自动更新所在目录的 `.gitignore` 阻断提交。
|
|
163
|
-
2. **加载记录**:`load.js` 检查并创建 `
|
|
187
|
+
2. **加载记录**:`load.js` 检查并创建 `scan` 表。读取已记录的路径、大小及修改时间,恢复至内存映射 `BinMap` 中。
|
|
164
188
|
3. **比对文件**:`scan.js` 遍历输入文件列表,调用 `stat.js` 获取元数据,并利用 `@1-/hash` 将路径映射为 16 字节二进制键。比对大小或修改时间,差异项归入变更列表。
|
|
165
189
|
4. **删除与返回**:`rm.js` 在事务中批量删除物理移除或不再扫描的记录。返回变更路径列表与 `upsert` 函数(`upsert.js` 提供),用以更新数据库,支持自动释放。
|
|
166
190
|
|
|
@@ -192,8 +216,10 @@ db.close();
|
|
|
192
216
|
SQLite 的诞生源自导弹驱逐舰板载损害控制软件项目。2000 年,D. Richard Hipp 为美国海军设计该系统时,遭遇商业数据库因配置复杂、无法承受断连和崩溃之痛点。Hipp 随后设计出免服务器配置、直接读写本地文件之嵌入式数据库,即 SQLite。
|
|
193
217
|
|
|
194
218
|
为节省空间与降低读写延迟,SQLite 广泛应用了 Varint(可变字节整型)编码。在这种编码下,小整数仅占用 1 字节,只有大数值才占用更多字节。本项目在内存存储设计中对文件大小与修改时间采用同样的压缩设计,契合 SQLite 节省空间与高效之设计哲学。
|
|
219
|
+
|
|
195
220
|
## 关于
|
|
196
221
|
|
|
197
222
|
本库由 [WebC.site](https://webc.site) 开发。
|
|
198
223
|
|
|
199
224
|
[WebC.site](https://webc.site) : 面向人工智能的网站开发新范式
|
|
225
|
+
|
package/_.js
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
|
-
import sqlite from "@1-/sqlite";
|
|
2
|
-
import { BinMap } from "@3-/binmap";
|
|
3
|
-
import vbE from "@3-/vb/vbE.js";
|
|
4
1
|
import { availableParallelism } from "node:os";
|
|
5
2
|
import pLimit from "@3-/plimit";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import { join, dirname, basename } from "node:path";
|
|
9
|
-
import load from "./load.js";
|
|
3
|
+
import dbInit from "./dbInit.js";
|
|
4
|
+
import { mtime, md5 } from "./load.js";
|
|
10
5
|
import scan from "./scan.js";
|
|
11
6
|
import rm from "./rm.js";
|
|
12
7
|
import upsert from "./upsert.js";
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
/*
|
|
10
|
+
扫描指定目录下文件列表,比对缓存并做清理,返回更新列表和 upsert 存储函数
|
|
11
|
+
dir: 扫描的目标目录
|
|
12
|
+
db_dir: 数据库存放目录
|
|
13
|
+
files: 待扫描的文件列表
|
|
14
|
+
返回值: [update, upsert]
|
|
15
|
+
update: 发生变动需要更新的相对路径列表
|
|
16
|
+
upsert: 用于将新扫描记录保存至数据库的 dispose 异步函数
|
|
17
|
+
*/
|
|
18
|
+
export default async (dir, db_dir, files) => {
|
|
19
|
+
const [db_mtime, db_md5] = dbInit(db_dir),
|
|
20
|
+
existing_mtime = mtime(db_mtime),
|
|
21
|
+
existing_md5 = md5(db_md5),
|
|
22
|
+
limit = pLimit(availableParallelism()),
|
|
23
|
+
[scanned, update] = await scan(dir, files, existing_mtime, existing_md5, limit, db_mtime),
|
|
24
|
+
rmPaths = (existing) => [...existing.keys()].filter((path) => !scanned.has(path));
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
[
|
|
27
|
+
[db_mtime, existing_mtime],
|
|
28
|
+
[db_md5, existing_md5],
|
|
29
|
+
].forEach(([db, existing]) => rm(db, "scan", rmPaths(existing)));
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
rm_hashes = db_rows.filter(({ hash }) => !scanned.has(hash)).map(({ hash }) => hash);
|
|
27
|
-
|
|
28
|
-
rm(db, rm_hashes);
|
|
29
|
-
|
|
30
|
-
return [update, upsert(db, dir)];
|
|
31
|
+
return [update, upsert(db_mtime, db_md5, dir)];
|
|
31
32
|
};
|
package/dbInit.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import sqlite from "@1-/sqlite";
|
|
2
|
+
import upsertGitignore from "@1-/upsert_gitignore";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join, basename } from "node:path";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
初始化/打开修改时间和 md5 的 sqlite 数据库,并将数据库文件加入 gitignore
|
|
8
|
+
db_dir: 数据库存放目录
|
|
9
|
+
返回值: [db_mtime, db_md5]
|
|
10
|
+
*/
|
|
11
|
+
export default (db_dir) => {
|
|
12
|
+
const li = ["mtime", "md5"],
|
|
13
|
+
path_li = li.map((x) => join(db_dir, x + ".sqlite"));
|
|
14
|
+
|
|
15
|
+
if (path_li.some((x) => !existsSync(x))) {
|
|
16
|
+
upsertGitignore(
|
|
17
|
+
join(db_dir, ".gitignore"),
|
|
18
|
+
path_li.map((x) => basename(x)),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const [db_mtime, db_md5] = path_li.map((x) => sqlite(x));
|
|
23
|
+
|
|
24
|
+
db_mtime.exec(
|
|
25
|
+
"CREATE TABLE IF NOT EXISTS scan(path PRIMARY KEY,size INT UNSIGNED,mtime INT UNSIGNED)",
|
|
26
|
+
);
|
|
27
|
+
db_md5.exec("CREATE TABLE IF NOT EXISTS scan(path PRIMARY KEY,md5 BINARY(16))");
|
|
28
|
+
|
|
29
|
+
return [db_mtime, db_md5];
|
|
30
|
+
};
|
package/load.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
import { BinMap } from "@3-/binmap";
|
|
2
|
+
import vbE from "@3-/vb/vbE.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
/*
|
|
5
|
+
分别从修改时间数据库和 md5 数据库中加载记录,并创建内存 Map 缓存
|
|
6
|
+
db: 数据库实例
|
|
7
|
+
返回值: 包含记录的 Map 缓存
|
|
8
|
+
*/
|
|
9
|
+
export const mtime = (db) => {
|
|
10
|
+
const existing = new BinMap();
|
|
11
|
+
db.query("SELECT path,size,mtime FROM scan")
|
|
12
|
+
.all()
|
|
13
|
+
.forEach(({ path, size, mtime }) => existing.set(path, vbE([size, mtime])));
|
|
14
|
+
return existing;
|
|
15
|
+
},
|
|
16
|
+
md5 = (db) => {
|
|
17
|
+
const existing = new BinMap();
|
|
18
|
+
db.query("SELECT path,md5 FROM scan")
|
|
19
|
+
.all()
|
|
20
|
+
.forEach(({ path, md5 }) => existing.set(path, md5));
|
|
21
|
+
return existing;
|
|
22
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1-/scan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Incrementally scan directory files and track metadata in SQLite / 增量扫描目录文件并使用 SQLite 记录元数据",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directory",
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
".": "./_.js",
|
|
22
22
|
"./*": "./*"
|
|
23
23
|
},
|
|
24
|
-
"
|
|
24
|
+
"dependencies": {
|
|
25
25
|
"@1-/hash": "^0.1.0",
|
|
26
|
+
"@1-/md5": "^0.1.1",
|
|
26
27
|
"@1-/sqlite": "^0.1.1",
|
|
27
|
-
"@
|
|
28
|
-
"@3-/
|
|
28
|
+
"@1-/upsert_gitignore": "^0.1.6",
|
|
29
|
+
"@3-/binmap": "^0.1.21",
|
|
30
|
+
"@3-/binset": "^0.1.7",
|
|
29
31
|
"@3-/int": "^0.1.1",
|
|
30
32
|
"@3-/plimit": "^0.1.3",
|
|
31
33
|
"@3-/u8": "^0.1.2",
|
|
32
|
-
"@3-/
|
|
33
|
-
"@3-/vb": "^0.1.6",
|
|
34
|
-
"@1-/upsert_gitignore": "^0.1.3"
|
|
34
|
+
"@3-/vb": "^0.1.6"
|
|
35
35
|
}
|
|
36
36
|
}
|
package/rm.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import tx from "@1-/sqlite/tx.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/*
|
|
4
|
+
批量删除记录
|
|
5
|
+
db: sqlite 实例
|
|
6
|
+
table: 数据表名
|
|
7
|
+
rm: 待删除的路径 path 数组
|
|
8
|
+
*/
|
|
9
|
+
export default (db, table, rm) => {
|
|
10
|
+
if (rm.length) {
|
|
5
11
|
tx(db, () => {
|
|
6
|
-
const del = db.prepare("DELETE FROM
|
|
7
|
-
rm.forEach((
|
|
12
|
+
const del = db.prepare("DELETE FROM " + table + " WHERE path=?");
|
|
13
|
+
rm.forEach((path) => del.run(path));
|
|
8
14
|
});
|
|
9
15
|
}
|
|
10
16
|
};
|
package/scan.js
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
import { BinSet } from "@3-/binset";
|
|
2
2
|
import u8eq from "@3-/u8/u8eq.js";
|
|
3
3
|
import vbE from "@3-/vb/vbE.js";
|
|
4
|
+
import { join } from "node:path";
|
|
4
5
|
import stat from "./stat.js";
|
|
6
|
+
import pathMd5 from "@1-/md5/pathMd5.js";
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
/*
|
|
9
|
+
扫描指定目录下的文件,并与已有记录进行对比
|
|
10
|
+
dir: 扫描目录
|
|
11
|
+
files: 相对路径列表
|
|
12
|
+
existing: 已有的元数据 Map
|
|
13
|
+
existing_md5: 已有的 md5 Map
|
|
14
|
+
limit: 并发限制器
|
|
15
|
+
db_mtime: 修改时间数据库实例
|
|
16
|
+
返回值: [scanned, update]
|
|
17
|
+
scanned: 已扫描文件的 path 集合
|
|
18
|
+
update: 需要更新或新增的文件相对路径列表
|
|
19
|
+
*/
|
|
20
|
+
export default async (dir, files, existing, existing_md5, limit, db_mtime) => {
|
|
7
21
|
const scanned = new BinSet(),
|
|
8
|
-
update = []
|
|
22
|
+
update = [],
|
|
23
|
+
update_mtime = db_mtime.query("INSERT OR REPLACE INTO scan(path,size,mtime)VALUES(?,?,?)");
|
|
24
|
+
|
|
9
25
|
await Promise.all(
|
|
10
26
|
files.map((rel_path) =>
|
|
11
27
|
limit(async () => {
|
|
12
28
|
try {
|
|
13
|
-
const [size, mtime,
|
|
14
|
-
val = existing.get(
|
|
29
|
+
const [size, mtime, path] = await stat(dir, rel_path),
|
|
30
|
+
val = existing.get(path);
|
|
15
31
|
|
|
16
|
-
scanned.add(
|
|
32
|
+
scanned.add(path);
|
|
17
33
|
|
|
18
|
-
if (!val
|
|
34
|
+
if (!val) {
|
|
35
|
+
update.push(rel_path);
|
|
36
|
+
} else if (!u8eq(val, vbE([size, mtime]))) {
|
|
37
|
+
const old_md5 = existing_md5.get(path);
|
|
38
|
+
if (old_md5) {
|
|
39
|
+
const cur_md5 = await pathMd5(join(dir, rel_path));
|
|
40
|
+
if (u8eq(old_md5, cur_md5)) {
|
|
41
|
+
update_mtime.run(path, size, mtime);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
19
45
|
update.push(rel_path);
|
|
20
46
|
}
|
|
21
47
|
} catch {}
|
package/stat.js
CHANGED
|
@@ -3,6 +3,12 @@ import { join } from "node:path";
|
|
|
3
3
|
import int from "@3-/int";
|
|
4
4
|
import strmd5 from "@1-/hash/strmd5.js";
|
|
5
5
|
|
|
6
|
+
/*
|
|
7
|
+
获取文件的大小、修改时间(秒)以及路径哈希
|
|
8
|
+
dir: 基础目录
|
|
9
|
+
rel_path: 相对路径
|
|
10
|
+
返回值: [大小, 修改时间, 路径哈希]
|
|
11
|
+
*/
|
|
6
12
|
export default async (dir, rel_path) => {
|
|
7
13
|
const { size, mtimeMs: mtime_ms } = await fsStat(join(dir, rel_path));
|
|
8
14
|
return [size, int(mtime_ms), strmd5(rel_path)];
|
package/upsert.js
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import stat from "./stat.js";
|
|
3
|
+
import pathMd5 from "@1-/md5/pathMd5.js";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
/*
|
|
6
|
+
创建用于插入/更新文件元数据(大小、修改时间、md5)的函数,并在资源释放时关闭数据库
|
|
7
|
+
db_mtime: 修改时间数据库实例
|
|
8
|
+
db_md5: md5 数据库实例
|
|
9
|
+
dir: 扫描目录
|
|
10
|
+
返回值: upsert 异步函数 (带有 [Symbol.dispose] 方法)
|
|
11
|
+
*/
|
|
12
|
+
export default (db_mtime, db_md5, dir) => {
|
|
13
|
+
const insert_mtime = db_mtime.query("INSERT OR REPLACE INTO scan(path,size,mtime)VALUES(?,?,?)"),
|
|
14
|
+
insert_md5 = db_md5.query("INSERT OR REPLACE INTO scan(path,md5)VALUES(?,?)"),
|
|
5
15
|
upsert = async (rel_path) => {
|
|
6
16
|
try {
|
|
7
|
-
const [size, mtime,
|
|
8
|
-
|
|
17
|
+
const [size, mtime, path] = await stat(dir, rel_path),
|
|
18
|
+
md5 = await pathMd5(join(dir, rel_path));
|
|
19
|
+
insert_mtime.run(path, size, mtime);
|
|
20
|
+
insert_md5.run(path, md5);
|
|
9
21
|
} catch {}
|
|
10
22
|
};
|
|
11
|
-
upsert[Symbol.dispose] = () =>
|
|
23
|
+
upsert[Symbol.dispose] = () => {
|
|
24
|
+
db_mtime.close();
|
|
25
|
+
db_md5.close();
|
|
26
|
+
};
|
|
12
27
|
return upsert;
|
|
13
28
|
};
|
package/save.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import tx from "@1-/sqlite/tx.js";
|
|
2
|
-
|
|
3
|
-
export default (db, update, rm) => {
|
|
4
|
-
if (update.length > 0 || rm.length > 0) {
|
|
5
|
-
tx(db, () => {
|
|
6
|
-
if (update.length > 0) {
|
|
7
|
-
const insert = db.prepare(
|
|
8
|
-
"INSERT OR REPLACE INTO scanMtimeLen(hash,size,mtime)VALUES(?,?,?)",
|
|
9
|
-
);
|
|
10
|
-
update.forEach(([_, h, size, mtime]) => insert.run(h, size, mtime));
|
|
11
|
-
}
|
|
12
|
-
if (rm.length > 0) {
|
|
13
|
-
const del = db.prepare("DELETE FROM scanMtimeLen WHERE hash=?");
|
|
14
|
-
rm.forEach((h) => del.run(h));
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
};
|