pinokiod 8.0.71 → 8.0.72
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.
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
ENTRY_BATCH_SIZE
|
|
8
8
|
} = require("./constants")
|
|
9
9
|
const { statMany } = require("./walker")
|
|
10
|
-
const { sameSnapshot } = require("./snapshot")
|
|
10
|
+
const { fileSnapshot, sameSnapshot } = require("./snapshot")
|
|
11
11
|
const {
|
|
12
12
|
cancelledError,
|
|
13
13
|
isPathError
|
|
@@ -52,6 +52,7 @@ class AutomaticScans {
|
|
|
52
52
|
this.manualDepth = 0
|
|
53
53
|
this.observedApps = new Set()
|
|
54
54
|
this.changedPaths = new Map()
|
|
55
|
+
this.changedPathIndex = new Map()
|
|
55
56
|
this.pendingStops = new Map()
|
|
56
57
|
this.lifecycleWork = new Set()
|
|
57
58
|
this.stopSettleMs = STOP_SETTLE_MS
|
|
@@ -136,6 +137,7 @@ class AutomaticScans {
|
|
|
136
137
|
}
|
|
137
138
|
this.observedApps.clear()
|
|
138
139
|
this.changedPaths.clear()
|
|
140
|
+
this.changedPathIndex.clear()
|
|
139
141
|
if (watcherError) throw watcherError
|
|
140
142
|
}
|
|
141
143
|
|
|
@@ -152,7 +154,8 @@ class AutomaticScans {
|
|
|
152
154
|
if (!event || !["create", "update", "delete"].includes(event.type)) {
|
|
153
155
|
continue
|
|
154
156
|
}
|
|
155
|
-
this.
|
|
157
|
+
if (event.type === "delete") this.removeChangedPath(event.path)
|
|
158
|
+
else this.recordChangedPath(event.path)
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
161
|
|
|
@@ -171,19 +174,95 @@ class AutomaticScans {
|
|
|
171
174
|
paths = new Set()
|
|
172
175
|
this.changedPaths.set(app, paths)
|
|
173
176
|
}
|
|
177
|
+
let index = this.changedPathIndex.get(app)
|
|
178
|
+
if (!index) {
|
|
179
|
+
index = { exact: new Map(), descendants: new Map() }
|
|
180
|
+
this.changedPathIndex.set(app, index)
|
|
181
|
+
}
|
|
182
|
+
const key = pathKey(resolved)
|
|
183
|
+
if (index.exact.has(key)) return true
|
|
174
184
|
paths.add(resolved)
|
|
185
|
+
index.exact.set(key, resolved)
|
|
186
|
+
this.addChangedPathToIndex(index.descendants, appRoot, resolved)
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
changedPathParentKeys(appRoot, filePath) {
|
|
191
|
+
const rootKey = pathKey(appRoot)
|
|
192
|
+
const keys = []
|
|
193
|
+
let current = path.dirname(path.resolve(filePath))
|
|
194
|
+
while (true) {
|
|
195
|
+
const key = pathKey(current)
|
|
196
|
+
keys.push(key)
|
|
197
|
+
if (key === rootKey) return keys
|
|
198
|
+
current = path.dirname(current)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
addChangedPathToIndex(index, appRoot, filePath) {
|
|
203
|
+
for (const key of this.changedPathParentKeys(appRoot, filePath)) {
|
|
204
|
+
let descendants = index.get(key)
|
|
205
|
+
if (!descendants) {
|
|
206
|
+
descendants = new Set()
|
|
207
|
+
index.set(key, descendants)
|
|
208
|
+
}
|
|
209
|
+
descendants.add(filePath)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
removeChangedPathFromIndex(index, appRoot, filePath) {
|
|
214
|
+
index.exact.delete(pathKey(filePath))
|
|
215
|
+
for (const key of this.changedPathParentKeys(appRoot, filePath)) {
|
|
216
|
+
const descendants = index.descendants.get(key)
|
|
217
|
+
if (!descendants) continue
|
|
218
|
+
descendants.delete(filePath)
|
|
219
|
+
if (!descendants.size) index.descendants.delete(key)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
removeChangedPath(filePath) {
|
|
224
|
+
if (this.disposed || !this.supported ||
|
|
225
|
+
typeof filePath !== "string" || !filePath) {
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
const app = this.appForLaunchPath(filePath)
|
|
229
|
+
if (!app || !this.observedApps.has(app)) return false
|
|
230
|
+
const paths = this.changedPaths.get(app)
|
|
231
|
+
if (!paths) return false
|
|
232
|
+
const resolved = path.resolve(filePath)
|
|
233
|
+
const appRoot = path.resolve(this.vault.kernel.homedir, "api", app)
|
|
234
|
+
const index = this.changedPathIndex.get(app)
|
|
235
|
+
if (!index) return false
|
|
236
|
+
const key = pathKey(resolved)
|
|
237
|
+
const exact = index.exact.get(key)
|
|
238
|
+
const descendants = index.descendants.get(key)
|
|
239
|
+
if (!exact && !descendants) return false
|
|
240
|
+
const matches = new Set(descendants || [])
|
|
241
|
+
if (exact) matches.add(exact)
|
|
242
|
+
for (const candidate of matches) {
|
|
243
|
+
paths.delete(candidate)
|
|
244
|
+
this.removeChangedPathFromIndex(index, appRoot, candidate)
|
|
245
|
+
}
|
|
246
|
+
if (!paths.size) this.discardChangedPaths(app)
|
|
175
247
|
return true
|
|
176
248
|
}
|
|
177
249
|
|
|
178
250
|
discardChangedPaths(app) {
|
|
179
251
|
this.changedPaths.delete(app)
|
|
252
|
+
this.changedPathIndex.delete(app)
|
|
180
253
|
}
|
|
181
254
|
|
|
182
255
|
consumeChangedPaths(app, paths) {
|
|
183
256
|
const collected = this.changedPaths.get(app)
|
|
184
257
|
if (!collected) return
|
|
185
|
-
|
|
186
|
-
|
|
258
|
+
const appRoot = path.resolve(this.vault.kernel.homedir, "api", app)
|
|
259
|
+
const index = this.changedPathIndex.get(app)
|
|
260
|
+
for (const filePath of paths || []) {
|
|
261
|
+
const resolved = path.resolve(filePath)
|
|
262
|
+
if (!collected.delete(resolved) || !index) continue
|
|
263
|
+
this.removeChangedPathFromIndex(index, appRoot, resolved)
|
|
264
|
+
}
|
|
265
|
+
if (!collected.size) this.discardChangedPaths(app)
|
|
187
266
|
}
|
|
188
267
|
|
|
189
268
|
async candidateThreshold() {
|
|
@@ -799,37 +878,58 @@ class AutomaticScans {
|
|
|
799
878
|
current = await fs.promises.lstat(entry.path)
|
|
800
879
|
} catch (error) {
|
|
801
880
|
if (!isPathError(error)) throw error
|
|
881
|
+
if (entry.kind === "changed" && isMissing(error)) {
|
|
882
|
+
counts.path_errors += 1
|
|
883
|
+
return null
|
|
884
|
+
}
|
|
802
885
|
this.logVerificationSkip(active.app, entry, "unreadable", counts, error)
|
|
803
886
|
return null
|
|
804
887
|
}
|
|
805
888
|
this.checkpoint(active)
|
|
806
889
|
if (!current.isFile() || current.isSymbolicLink() ||
|
|
807
|
-
|
|
890
|
+
current.dev !== entry.dev || current.size !== entry.size) {
|
|
808
891
|
this.logVerificationSkip(
|
|
809
892
|
active.app, entry, "snapshot-changed", counts)
|
|
810
893
|
return null
|
|
811
894
|
}
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
:
|
|
895
|
+
const currentEntry = Object.assign({}, entry, fileSnapshot(current))
|
|
896
|
+
const key = this.automaticHashKey(currentEntry)
|
|
897
|
+
const cachedSnapshot = {
|
|
898
|
+
size: entry.cached_size,
|
|
899
|
+
mtime: entry.cached_mtime,
|
|
900
|
+
ctime: entry.cached_ctime,
|
|
901
|
+
dev: entry.cached_dev,
|
|
902
|
+
ino: entry.cached_ino
|
|
903
|
+
}
|
|
904
|
+
const reusable = memory.get(key) ||
|
|
905
|
+
(sameSnapshot(entry, current) && SHA256_RE.test(entry.hash || "")
|
|
906
|
+
? entry.hash
|
|
907
|
+
: null) ||
|
|
908
|
+
(sameSnapshot(cachedSnapshot, current) &&
|
|
909
|
+
SHA256_RE.test(entry.cached_hash || "")
|
|
910
|
+
? entry.cached_hash
|
|
911
|
+
: null)
|
|
816
912
|
if (reusable) {
|
|
817
913
|
counts.hash_reuses += 1
|
|
818
914
|
memory.set(key, reusable)
|
|
819
|
-
verifiedHashes.set(pathKey(entry.path), Object.assign({},
|
|
915
|
+
verifiedHashes.set(pathKey(entry.path), Object.assign({}, currentEntry, {
|
|
820
916
|
hash: reusable
|
|
821
917
|
}))
|
|
822
918
|
return reusable
|
|
823
919
|
}
|
|
824
920
|
let verified
|
|
825
921
|
try {
|
|
826
|
-
verified = await this.vault.scanner.hashStable(
|
|
922
|
+
verified = await this.vault.scanner.hashStable(currentEntry, {
|
|
827
923
|
signal: active.controller && active.controller.signal
|
|
828
924
|
})
|
|
829
925
|
} catch (error) {
|
|
830
926
|
if (error && error.code === "EVAULTCANCELLED") throw error
|
|
831
927
|
if (!isPathError(error)) throw error
|
|
832
928
|
counts.hash_failures += 1
|
|
929
|
+
if (entry.kind === "changed" && isMissing(error)) {
|
|
930
|
+
counts.path_errors += 1
|
|
931
|
+
return null
|
|
932
|
+
}
|
|
833
933
|
this.logVerificationSkip(active.app, entry, "hash-failed", counts, error)
|
|
834
934
|
return null
|
|
835
935
|
}
|
|
@@ -842,7 +942,7 @@ class AutomaticScans {
|
|
|
842
942
|
counts.hashed += 1
|
|
843
943
|
counts.hash_bytes += Math.max(0, Number(verified.result.size) || 0)
|
|
844
944
|
memory.set(key, verified.result.hash)
|
|
845
|
-
verifiedHashes.set(pathKey(entry.path), Object.assign({},
|
|
945
|
+
verifiedHashes.set(pathKey(entry.path), Object.assign({}, currentEntry, {
|
|
846
946
|
hash: verified.result.hash
|
|
847
947
|
}))
|
|
848
948
|
return verified.result.hash
|
|
@@ -888,6 +988,7 @@ class AutomaticScans {
|
|
|
888
988
|
onError: (error, filePath) => {
|
|
889
989
|
if (!isPathError(error)) return false
|
|
890
990
|
counts.path_errors += 1
|
|
991
|
+
if (isMissing(error)) return true
|
|
891
992
|
this.log("path-skipped", {
|
|
892
993
|
app,
|
|
893
994
|
path: filePath,
|
|
@@ -2867,23 +2867,30 @@ class RegistryCore {
|
|
|
2867
2867
|
rows = this.database.prepare(`
|
|
2868
2868
|
SELECT
|
|
2869
2869
|
'anchor' AS kind,
|
|
2870
|
-
path,
|
|
2871
|
-
size,
|
|
2872
|
-
mtime,
|
|
2873
|
-
ctime,
|
|
2874
|
-
dev,
|
|
2875
|
-
ino,
|
|
2876
|
-
nlink,
|
|
2877
|
-
mode,
|
|
2878
|
-
uid,
|
|
2879
|
-
gid,
|
|
2880
|
-
hash
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2870
|
+
anchor.path,
|
|
2871
|
+
anchor.size,
|
|
2872
|
+
anchor.mtime,
|
|
2873
|
+
anchor.ctime,
|
|
2874
|
+
anchor.dev,
|
|
2875
|
+
anchor.ino,
|
|
2876
|
+
anchor.nlink,
|
|
2877
|
+
anchor.mode,
|
|
2878
|
+
anchor.uid,
|
|
2879
|
+
anchor.gid,
|
|
2880
|
+
anchor.hash,
|
|
2881
|
+
cached.hash AS cached_hash,
|
|
2882
|
+
cached.size AS cached_size,
|
|
2883
|
+
cached.mtime AS cached_mtime,
|
|
2884
|
+
cached.ctime AS cached_ctime,
|
|
2885
|
+
cached.dev AS cached_dev,
|
|
2886
|
+
cached.ino AS cached_ino
|
|
2887
|
+
FROM anchors anchor
|
|
2888
|
+
LEFT JOIN hash_cache cached ON cached.path = anchor.path
|
|
2889
|
+
WHERE anchor.dev = @dev
|
|
2890
|
+
AND anchor.size = @size
|
|
2891
|
+
AND anchor.path > @path
|
|
2892
|
+
AND anchor.verified_at IS NOT NULL
|
|
2893
|
+
ORDER BY anchor.path
|
|
2887
2894
|
LIMIT @limit
|
|
2888
2895
|
`).all({ dev, size, path: afterPath, limit: pageSize })
|
|
2889
2896
|
} else {
|
|
@@ -2900,15 +2907,15 @@ class RegistryCore {
|
|
|
2900
2907
|
peer.mode,
|
|
2901
2908
|
peer.uid,
|
|
2902
2909
|
peer.gid,
|
|
2903
|
-
|
|
2910
|
+
peer.hash,
|
|
2911
|
+
cached.hash AS cached_hash,
|
|
2912
|
+
cached.size AS cached_size,
|
|
2913
|
+
cached.mtime AS cached_mtime,
|
|
2914
|
+
cached.ctime AS cached_ctime,
|
|
2915
|
+
cached.dev AS cached_dev,
|
|
2916
|
+
cached.ino AS cached_ino
|
|
2904
2917
|
FROM files peer
|
|
2905
|
-
LEFT JOIN hash_cache cached
|
|
2906
|
-
ON cached.path = peer.path
|
|
2907
|
-
AND cached.size = peer.size
|
|
2908
|
-
AND cached.mtime = peer.mtime
|
|
2909
|
-
AND cached.ctime = peer.ctime
|
|
2910
|
-
AND cached.dev = peer.dev
|
|
2911
|
-
AND cached.ino = peer.ino
|
|
2918
|
+
LEFT JOIN hash_cache cached ON cached.path = peer.path
|
|
2912
2919
|
WHERE peer.dev = @dev
|
|
2913
2920
|
AND peer.size = @size
|
|
2914
2921
|
AND peer.path > @path
|
package/package.json
CHANGED
|
@@ -226,6 +226,45 @@ describe("automatic app checks", () => {
|
|
|
226
226
|
await close(vault)
|
|
227
227
|
})
|
|
228
228
|
|
|
229
|
+
test("Windows watcher deletes remove retained paths and descendants", async () => {
|
|
230
|
+
const home = await makeHome()
|
|
231
|
+
const appRoot = path.join(home, "api", "windows-app")
|
|
232
|
+
const scriptPath = path.join(appRoot, "start.js")
|
|
233
|
+
const first = path.join(appRoot, "first.bin")
|
|
234
|
+
const nestedRoot = path.join(appRoot, "temporary")
|
|
235
|
+
const nested = path.join(nestedRoot, "nested.bin")
|
|
236
|
+
await fs.promises.mkdir(appRoot)
|
|
237
|
+
let callback = null
|
|
238
|
+
const vault = await makeVault(home, {
|
|
239
|
+
deferStorage: true,
|
|
240
|
+
platform: "win32",
|
|
241
|
+
automaticWatcher: {
|
|
242
|
+
subscribe: async (_root, listener) => {
|
|
243
|
+
callback = listener
|
|
244
|
+
return { unsubscribe: async () => {} }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
assert.equal(await vault.automaticScans.startWatcher(), true)
|
|
250
|
+
vault.automaticScans.handleStarted(scriptPath)
|
|
251
|
+
callback(null, [
|
|
252
|
+
{ type: "create", path: first },
|
|
253
|
+
{ type: "update", path: nestedRoot },
|
|
254
|
+
{ type: "update", path: nested }
|
|
255
|
+
])
|
|
256
|
+
assert.deepEqual([...vault.automaticScans.changedPaths.get(
|
|
257
|
+
"windows-app")].sort(), [first, nestedRoot, nested].sort())
|
|
258
|
+
|
|
259
|
+
callback(null, [{ type: "delete", path: nestedRoot }])
|
|
260
|
+
assert.deepEqual([...vault.automaticScans.changedPaths.get(
|
|
261
|
+
"windows-app")], [first])
|
|
262
|
+
callback(null, [{ type: "delete", path: first }])
|
|
263
|
+
assert.equal(vault.automaticScans.changedPaths.has("windows-app"), false)
|
|
264
|
+
assert.equal(vault.registry, null)
|
|
265
|
+
await close(vault)
|
|
266
|
+
})
|
|
267
|
+
|
|
229
268
|
test("disposing automatic checks releases the watcher and pending work", async () => {
|
|
230
269
|
const home = await makeHome()
|
|
231
270
|
const appRoot = path.join(home, "api", "disposed-app")
|
|
@@ -1150,6 +1189,31 @@ describe("automatic app checks", () => {
|
|
|
1150
1189
|
await close(vault)
|
|
1151
1190
|
})
|
|
1152
1191
|
|
|
1192
|
+
test("transient missing watcher paths do not emit per-path logs", async () => {
|
|
1193
|
+
const home = await makeHome()
|
|
1194
|
+
const app = "temporary-path-app"
|
|
1195
|
+
const appRoot = path.join(home, "api", app)
|
|
1196
|
+
const missing = [
|
|
1197
|
+
path.join(appRoot, "temporary-1.bin"),
|
|
1198
|
+
path.join(appRoot, "temporary-2.bin")
|
|
1199
|
+
]
|
|
1200
|
+
await fs.promises.mkdir(appRoot)
|
|
1201
|
+
const vault = await makeVault(home)
|
|
1202
|
+
const logs = []
|
|
1203
|
+
vault.automaticScans.log = (event, details) => {
|
|
1204
|
+
logs.push({ event, details })
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
vault.automaticScans.queueApp(app, missing)
|
|
1208
|
+
await waitFor(() => !vault.automaticScans.active &&
|
|
1209
|
+
!vault.automaticScans.entries.has(app))
|
|
1210
|
+
|
|
1211
|
+
assert.equal(logs.some((record) =>
|
|
1212
|
+
["path-skipped", "verification-skipped"].includes(record.event) &&
|
|
1213
|
+
missing.includes(record.details.path)), false)
|
|
1214
|
+
await close(vault)
|
|
1215
|
+
})
|
|
1216
|
+
|
|
1153
1217
|
test("stable published hashes are reused without rereading contents", async () => {
|
|
1154
1218
|
const home = await makeHome()
|
|
1155
1219
|
const app = "published-hash-app"
|
|
@@ -1203,6 +1267,102 @@ describe("automatic app checks", () => {
|
|
|
1203
1267
|
await close(vault)
|
|
1204
1268
|
})
|
|
1205
1269
|
|
|
1270
|
+
test("a metadata-changed Windows peer is freshly hashed and cached", async () => {
|
|
1271
|
+
const home = await makeHome()
|
|
1272
|
+
const app = "windows-peer-app"
|
|
1273
|
+
const appRoot = path.join(home, "api", app)
|
|
1274
|
+
const candidate = path.join(appRoot, "candidate.bin")
|
|
1275
|
+
const peer = path.join(home, "published-peer.bin")
|
|
1276
|
+
await fs.promises.mkdir(appRoot)
|
|
1277
|
+
await fs.promises.writeFile(candidate, "same-content")
|
|
1278
|
+
await fs.promises.writeFile(peer, "same-content")
|
|
1279
|
+
const vault = await makeVault(home, {
|
|
1280
|
+
deferStorage: true,
|
|
1281
|
+
platform: "win32"
|
|
1282
|
+
})
|
|
1283
|
+
await vault.ensureRegistryInitialized()
|
|
1284
|
+
vault.automaticScans.candidateThreshold = async () => 1
|
|
1285
|
+
const peerStat = await fs.promises.lstat(peer)
|
|
1286
|
+
await vault.registry.upsertFile({
|
|
1287
|
+
path: peer,
|
|
1288
|
+
hash: crypto.createHash("sha256").update("old-content!").digest("hex"),
|
|
1289
|
+
size: peerStat.size,
|
|
1290
|
+
mtime: peerStat.mtimeMs - 1,
|
|
1291
|
+
ctime: peerStat.ctimeMs - 1,
|
|
1292
|
+
dev: peerStat.dev,
|
|
1293
|
+
ino: peerStat.ino,
|
|
1294
|
+
mode: peerStat.mode,
|
|
1295
|
+
uid: peerStat.uid,
|
|
1296
|
+
gid: peerStat.gid,
|
|
1297
|
+
source_id: "app:peer",
|
|
1298
|
+
app: "peer",
|
|
1299
|
+
status: "reference",
|
|
1300
|
+
unavailable_reason: null,
|
|
1301
|
+
updated_at: Date.now()
|
|
1302
|
+
})
|
|
1303
|
+
const hashedPaths = []
|
|
1304
|
+
const originalHashFile = vault.hashFile.bind(vault)
|
|
1305
|
+
vault.hashFile = async (filePath, options) => {
|
|
1306
|
+
hashedPaths.push(filePath)
|
|
1307
|
+
return originalHashFile(filePath, options)
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
vault.automaticScans.queueApp(app, [candidate])
|
|
1311
|
+
await waitFor(() => !vault.automaticScans.active &&
|
|
1312
|
+
vault.automaticScans.entries.get(app)?.state === "result")
|
|
1313
|
+
assert.deepEqual(hashedPaths.sort(), [candidate, peer].sort())
|
|
1314
|
+
|
|
1315
|
+
await vault.registry.setAutomaticAppScanState(app, null)
|
|
1316
|
+
vault.automaticScans.entries.delete(app)
|
|
1317
|
+
vault.hashFile = async () => {
|
|
1318
|
+
throw new Error("Fresh automatic hashes must be reused.")
|
|
1319
|
+
}
|
|
1320
|
+
vault.automaticScans.queueApp(app, [candidate])
|
|
1321
|
+
await waitFor(() => !vault.automaticScans.active &&
|
|
1322
|
+
vault.automaticScans.entries.get(app)?.state === "result")
|
|
1323
|
+
|
|
1324
|
+
await close(vault)
|
|
1325
|
+
})
|
|
1326
|
+
|
|
1327
|
+
test("a stale peer hash cannot match different equal-size content", async () => {
|
|
1328
|
+
const home = await makeHome()
|
|
1329
|
+
const app = "stale-peer-hash-app"
|
|
1330
|
+
const appRoot = path.join(home, "api", app)
|
|
1331
|
+
const candidate = path.join(appRoot, "candidate.bin")
|
|
1332
|
+
const peer = path.join(home, "stale-peer.bin")
|
|
1333
|
+
await fs.promises.mkdir(appRoot)
|
|
1334
|
+
await fs.promises.writeFile(candidate, "same-content")
|
|
1335
|
+
await fs.promises.writeFile(peer, "other-bytes!")
|
|
1336
|
+
const vault = await makeVault(home, { deferStorage: true })
|
|
1337
|
+
await vault.ensureRegistryInitialized()
|
|
1338
|
+
vault.automaticScans.candidateThreshold = async () => 1
|
|
1339
|
+
const peerStat = await fs.promises.lstat(peer)
|
|
1340
|
+
await vault.registry.upsertFile({
|
|
1341
|
+
path: peer,
|
|
1342
|
+
hash: crypto.createHash("sha256").update("same-content").digest("hex"),
|
|
1343
|
+
size: peerStat.size,
|
|
1344
|
+
mtime: peerStat.mtimeMs - 1,
|
|
1345
|
+
ctime: peerStat.ctimeMs - 1,
|
|
1346
|
+
dev: peerStat.dev,
|
|
1347
|
+
ino: peerStat.ino,
|
|
1348
|
+
mode: peerStat.mode,
|
|
1349
|
+
uid: peerStat.uid,
|
|
1350
|
+
gid: peerStat.gid,
|
|
1351
|
+
source_id: "app:peer",
|
|
1352
|
+
app: "peer",
|
|
1353
|
+
status: "reference",
|
|
1354
|
+
unavailable_reason: null,
|
|
1355
|
+
updated_at: Date.now()
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
vault.automaticScans.queueApp(app, [candidate])
|
|
1359
|
+
await waitFor(() => !vault.automaticScans.active &&
|
|
1360
|
+
!vault.automaticScans.entries.has(app))
|
|
1361
|
+
|
|
1362
|
+
assert.deepEqual(vault.automaticScans.snapshot().rows, [])
|
|
1363
|
+
await close(vault)
|
|
1364
|
+
})
|
|
1365
|
+
|
|
1206
1366
|
test("stale file rows and known identical inodes are not peers", async () => {
|
|
1207
1367
|
const home = await makeHome()
|
|
1208
1368
|
const appRoot = path.join(home, "api", "no-peer-app")
|