@camstack/addon-post-analysis 1.2.62 → 1.2.64
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/dist/{dist-D4fGaA71.js → dist-BQ-M7jn9.js} +301 -36
- package/dist/{dist-CdqESw05.mjs → dist-CSTtfEOC.mjs} +301 -36
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DrgptocJ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-lSMqbbY-.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DJwl-swi.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-EHTrRb5u.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-C8v4WVLy.mjs → hostInit-D5rpu5pY.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +281 -55
- package/dist/pipeline-analytics/index.mjs +281 -55
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +2 -2
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BQ-M7jn9.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -5602,6 +5602,12 @@ function frozenOccupancy(rule, occ) {
|
|
|
5602
5602
|
occupied: occ.occupied
|
|
5603
5603
|
};
|
|
5604
5604
|
}
|
|
5605
|
+
function notificationZones(rule, subject) {
|
|
5606
|
+
const configured = rule.conditions.zones?.ids;
|
|
5607
|
+
if (configured === void 0 || configured.length === 0) return subject.zones;
|
|
5608
|
+
const allowed = new Set(configured);
|
|
5609
|
+
return subject.zones.filter((zoneId) => allowed.has(zoneId));
|
|
5610
|
+
}
|
|
5605
5611
|
/** Classify an object-event row as a package delivery / pick-up, or `null` when
|
|
5606
5612
|
* it is an ordinary detection. */
|
|
5607
5613
|
function packagePhaseOf(ev) {
|
|
@@ -6574,6 +6580,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
6574
6580
|
const isTrackScoped = kind === "object-event" || kind === "track-end";
|
|
6575
6581
|
const isSystemEvent = kind === "system-event";
|
|
6576
6582
|
const direct = new Set(rule.targets.map((t) => t.targetId));
|
|
6583
|
+
const renderedZones = notificationZones(rule, subject);
|
|
6577
6584
|
return [...rule.targets, ...userTargets.filter((t) => !direct.has(t.targetId))].filter((target) => !rule.disabledTargetIds.includes(target.targetId)).map((target) => {
|
|
6578
6585
|
const payload = {
|
|
6579
6586
|
ruleName: rule.name,
|
|
@@ -6599,7 +6606,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
6599
6606
|
className: subject.classNames[0] ?? subject.sensorKind ?? "event",
|
|
6600
6607
|
...subject.label !== void 0 ? { label: subject.label } : {},
|
|
6601
6608
|
...subject.confidence !== void 0 ? { confidence: subject.confidence } : {},
|
|
6602
|
-
zones:
|
|
6609
|
+
zones: renderedZones,
|
|
6603
6610
|
timestamp: subject.timestamp,
|
|
6604
6611
|
...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {},
|
|
6605
6612
|
...subject.systemEvent !== void 0 ? { systemEvent: subject.systemEvent } : {}
|
|
@@ -15777,6 +15784,7 @@ function resolveFaceSettings(raw) {
|
|
|
15777
15784
|
*/
|
|
15778
15785
|
function createLocationAwareMediaStorage(deps) {
|
|
15779
15786
|
const roots = /* @__PURE__ */ new Map();
|
|
15787
|
+
let defaultBypassInvalidated = false;
|
|
15780
15788
|
const rootOf = async (locationId) => {
|
|
15781
15789
|
const cached = roots.get(locationId);
|
|
15782
15790
|
if (cached !== void 0) return cached;
|
|
@@ -15787,21 +15795,55 @@ function createLocationAwareMediaStorage(deps) {
|
|
|
15787
15795
|
const absOf = async (locationId, relativePath) => node_path.default.join(await rootOf(locationId), relativePath);
|
|
15788
15796
|
return {
|
|
15789
15797
|
write: async (input) => {
|
|
15790
|
-
if (input.location === deps.defaultLocation) return deps.base.write(input);
|
|
15798
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.write(input);
|
|
15791
15799
|
const abs = await absOf(input.location, input.relativePath);
|
|
15792
15800
|
await node_fs.promises.mkdir(node_path.default.dirname(abs), { recursive: true });
|
|
15793
15801
|
await node_fs.promises.writeFile(abs, input.data);
|
|
15794
15802
|
},
|
|
15795
15803
|
read: async (input) => {
|
|
15796
|
-
if (input.location === deps.defaultLocation) return deps.base.read(input);
|
|
15804
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.read(input);
|
|
15797
15805
|
return node_fs.promises.readFile(await absOf(input.location, input.relativePath));
|
|
15798
15806
|
},
|
|
15799
15807
|
delete: async (input) => {
|
|
15800
|
-
if (input.location === deps.defaultLocation) return deps.base.delete(input);
|
|
15808
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.delete(input);
|
|
15801
15809
|
await node_fs.promises.rm(await absOf(input.location, input.relativePath), { force: true });
|
|
15810
|
+
},
|
|
15811
|
+
invalidateRoots: () => {
|
|
15812
|
+
roots.clear();
|
|
15813
|
+
defaultBypassInvalidated = true;
|
|
15802
15814
|
}
|
|
15803
15815
|
};
|
|
15804
15816
|
}
|
|
15817
|
+
//#endregion
|
|
15818
|
+
//#region src/pipeline-analytics/media-write-gate.ts
|
|
15819
|
+
/**
|
|
15820
|
+
* Reversible writer gate for storage maintenance. `pause()` closes admission
|
|
15821
|
+
* first, then waits for writes already admitted to settle; callers never
|
|
15822
|
+
* mutate device bindings or analytics configuration to quiesce media.
|
|
15823
|
+
*/
|
|
15824
|
+
var MediaWriteGate = class {
|
|
15825
|
+
paused = false;
|
|
15826
|
+
active = 0;
|
|
15827
|
+
drainedWaiters = [];
|
|
15828
|
+
async run(write) {
|
|
15829
|
+
if (this.paused) throw new Error("event-media writes are paused for storage migration");
|
|
15830
|
+
this.active++;
|
|
15831
|
+
try {
|
|
15832
|
+
return await write();
|
|
15833
|
+
} finally {
|
|
15834
|
+
this.active--;
|
|
15835
|
+
if (this.active === 0) for (const resolve of this.drainedWaiters.splice(0)) resolve();
|
|
15836
|
+
}
|
|
15837
|
+
}
|
|
15838
|
+
pause() {
|
|
15839
|
+
this.paused = true;
|
|
15840
|
+
if (this.active === 0) return Promise.resolve();
|
|
15841
|
+
return new Promise((resolve) => this.drainedWaiters.push(resolve));
|
|
15842
|
+
}
|
|
15843
|
+
resume() {
|
|
15844
|
+
this.paused = false;
|
|
15845
|
+
}
|
|
15846
|
+
};
|
|
15805
15847
|
var MACRO_SET = new Set([
|
|
15806
15848
|
"person",
|
|
15807
15849
|
"vehicle",
|
|
@@ -16672,7 +16714,14 @@ var RETRAIN_FRAME_COLUMNS = [
|
|
|
16672
16714
|
name: "copiedAt",
|
|
16673
16715
|
type: "INTEGER",
|
|
16674
16716
|
notNull: true
|
|
16675
|
-
}
|
|
16717
|
+
},
|
|
16718
|
+
(
|
|
16719
|
+
/** NULL = current eventMedia default; stamped when migration moves a copy
|
|
16720
|
+
* before the logical default is repointed. */
|
|
16721
|
+
{
|
|
16722
|
+
name: "locationId",
|
|
16723
|
+
type: "TEXT"
|
|
16724
|
+
})
|
|
16676
16725
|
];
|
|
16677
16726
|
var RETRAIN_FRAME_INDEXES = [{
|
|
16678
16727
|
name: "idx_retrain_frames_track",
|
|
@@ -16701,12 +16750,14 @@ var RetrainFrameStore = class {
|
|
|
16701
16750
|
logger;
|
|
16702
16751
|
readSource;
|
|
16703
16752
|
now;
|
|
16753
|
+
writeGate;
|
|
16704
16754
|
constructor(deps) {
|
|
16705
16755
|
this.store = deps.store;
|
|
16706
16756
|
this.storage = deps.storage;
|
|
16707
16757
|
this.logger = deps.logger;
|
|
16708
16758
|
this.readSource = deps.readSource;
|
|
16709
16759
|
this.now = deps.now;
|
|
16760
|
+
this.writeGate = deps.writeGate;
|
|
16710
16761
|
}
|
|
16711
16762
|
static async declare(store) {
|
|
16712
16763
|
await store.declareCollection.mutate({
|
|
@@ -16725,7 +16776,8 @@ var RetrainFrameStore = class {
|
|
|
16725
16776
|
sizeBytes: Number(data["sizeBytes"] ?? 0),
|
|
16726
16777
|
width: Number(data["width"] ?? 0),
|
|
16727
16778
|
height: Number(data["height"] ?? 0),
|
|
16728
|
-
copiedAt: Number(data["copiedAt"] ?? 0)
|
|
16779
|
+
copiedAt: Number(data["copiedAt"] ?? 0),
|
|
16780
|
+
locationId: typeof data["locationId"] === "string" && data["locationId"].length > 0 ? data["locationId"] : null
|
|
16729
16781
|
};
|
|
16730
16782
|
}
|
|
16731
16783
|
/** The copy with this id, or `null`. Index read only — no blob is touched. */
|
|
@@ -16793,7 +16845,7 @@ var RetrainFrameStore = class {
|
|
|
16793
16845
|
async readFrame(frame) {
|
|
16794
16846
|
try {
|
|
16795
16847
|
return await this.storage.read({
|
|
16796
|
-
location:
|
|
16848
|
+
location: frame.locationId ?? "eventMedia",
|
|
16797
16849
|
relativePath: framePath(frame.deviceId, frame.trackId, frame.frameId)
|
|
16798
16850
|
});
|
|
16799
16851
|
} catch (err) {
|
|
@@ -16873,9 +16925,10 @@ var RetrainFrameStore = class {
|
|
|
16873
16925
|
sizeBytes: bytes.length,
|
|
16874
16926
|
width: size.width,
|
|
16875
16927
|
height: size.height,
|
|
16876
|
-
copiedAt: this.now()
|
|
16928
|
+
copiedAt: this.now(),
|
|
16929
|
+
locationId: null
|
|
16877
16930
|
};
|
|
16878
|
-
|
|
16931
|
+
const persist = async () => {
|
|
16879
16932
|
await this.storage.write({
|
|
16880
16933
|
location: RETRAIN_MEDIA_LOCATION,
|
|
16881
16934
|
relativePath: path,
|
|
@@ -16893,9 +16946,14 @@ var RetrainFrameStore = class {
|
|
|
16893
16946
|
sizeBytes: frame.sizeBytes,
|
|
16894
16947
|
width: frame.width,
|
|
16895
16948
|
height: frame.height,
|
|
16896
|
-
copiedAt: frame.copiedAt
|
|
16949
|
+
copiedAt: frame.copiedAt,
|
|
16950
|
+
locationId: null
|
|
16897
16951
|
}
|
|
16898
16952
|
});
|
|
16953
|
+
};
|
|
16954
|
+
try {
|
|
16955
|
+
if (this.writeGate) await this.writeGate.run(persist);
|
|
16956
|
+
else await persist();
|
|
16899
16957
|
} catch (err) {
|
|
16900
16958
|
this.logger.error("retrain copy failed to persist", {
|
|
16901
16959
|
tags: { deviceId: input.deviceId },
|
|
@@ -16936,26 +16994,30 @@ var RetrainFrameStore = class {
|
|
|
16936
16994
|
* best-effort write is how a half-deleted annotation set happens.
|
|
16937
16995
|
*/
|
|
16938
16996
|
async deleteFrame(frame) {
|
|
16939
|
-
|
|
16940
|
-
|
|
16941
|
-
|
|
16942
|
-
|
|
16943
|
-
|
|
16944
|
-
|
|
16945
|
-
|
|
16946
|
-
|
|
16947
|
-
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
16952
|
-
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
|
|
16956
|
-
|
|
16957
|
-
|
|
16958
|
-
|
|
16997
|
+
const remove = async () => {
|
|
16998
|
+
try {
|
|
16999
|
+
await this.storage.delete({
|
|
17000
|
+
location: frame.locationId ?? "eventMedia",
|
|
17001
|
+
relativePath: framePath(frame.deviceId, frame.trackId, frame.frameId)
|
|
17002
|
+
});
|
|
17003
|
+
} catch {}
|
|
17004
|
+
try {
|
|
17005
|
+
await this.store.delete.mutate({
|
|
17006
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
17007
|
+
key: frame.frameId
|
|
17008
|
+
});
|
|
17009
|
+
} catch (err) {
|
|
17010
|
+
this.logger.warn("RetrainFrameStore.deleteFrame row delete failed", {
|
|
17011
|
+
tags: { deviceId: frame.deviceId },
|
|
17012
|
+
meta: {
|
|
17013
|
+
frameId: frame.frameId,
|
|
17014
|
+
error: String(err)
|
|
17015
|
+
}
|
|
17016
|
+
});
|
|
17017
|
+
}
|
|
17018
|
+
};
|
|
17019
|
+
if (this.writeGate) await this.writeGate.run(remove);
|
|
17020
|
+
else await remove();
|
|
16959
17021
|
}
|
|
16960
17022
|
};
|
|
16961
17023
|
//#endregion
|
|
@@ -17994,10 +18056,15 @@ var MediaStore = class {
|
|
|
17994
18056
|
storage;
|
|
17995
18057
|
store;
|
|
17996
18058
|
logger;
|
|
18059
|
+
writeGate;
|
|
17997
18060
|
constructor(deps) {
|
|
17998
18061
|
this.storage = deps.storage;
|
|
17999
18062
|
this.store = deps.store;
|
|
18000
18063
|
this.logger = deps.logger;
|
|
18064
|
+
this.writeGate = deps.writeGate;
|
|
18065
|
+
}
|
|
18066
|
+
gated(mutation) {
|
|
18067
|
+
return this.writeGate ? this.writeGate.run(mutation) : mutation();
|
|
18001
18068
|
}
|
|
18002
18069
|
static async declare(store) {
|
|
18003
18070
|
await store.declareCollection.mutate({
|
|
@@ -18009,6 +18076,10 @@ var MediaStore = class {
|
|
|
18009
18076
|
/** Persist a media blob and register its metadata row. Returns the
|
|
18010
18077
|
* collection key (MediaFile.key) that callers store as reference. */
|
|
18011
18078
|
async put(params) {
|
|
18079
|
+
if (this.writeGate) return this.writeGate.run(() => this.putUnchecked(params));
|
|
18080
|
+
return this.putUnchecked(params);
|
|
18081
|
+
}
|
|
18082
|
+
async putUnchecked(params) {
|
|
18012
18083
|
const key = buildKey(params);
|
|
18013
18084
|
const path = buildPath(params);
|
|
18014
18085
|
const record = {
|
|
@@ -18079,6 +18150,9 @@ var MediaStore = class {
|
|
|
18079
18150
|
* scheme (transition back-compat); once migrated it is a no-op.
|
|
18080
18151
|
*/
|
|
18081
18152
|
async putReplacing(params) {
|
|
18153
|
+
return this.gated(() => this.putReplacingUnchecked(params));
|
|
18154
|
+
}
|
|
18155
|
+
async putReplacingUnchecked(params) {
|
|
18082
18156
|
const existing = await this.store.query.query({
|
|
18083
18157
|
collection: MEDIA_COLLECTION,
|
|
18084
18158
|
filter: { where: {
|
|
@@ -18087,7 +18161,7 @@ var MediaStore = class {
|
|
|
18087
18161
|
kind: params.kind
|
|
18088
18162
|
} }
|
|
18089
18163
|
});
|
|
18090
|
-
const newKey = await this.
|
|
18164
|
+
const newKey = await this.putUnchecked(params);
|
|
18091
18165
|
for (const row of existing) {
|
|
18092
18166
|
if (row.id === newKey) continue;
|
|
18093
18167
|
const rowData = row.data;
|
|
@@ -18130,8 +18204,11 @@ var MediaStore = class {
|
|
|
18130
18204
|
* own kind.
|
|
18131
18205
|
*/
|
|
18132
18206
|
async promoteToLastFrame(input) {
|
|
18207
|
+
return this.gated(() => this.promoteToLastFrameUnchecked(input));
|
|
18208
|
+
}
|
|
18209
|
+
async promoteToLastFrameUnchecked(input) {
|
|
18133
18210
|
const data = Buffer.from(input.snapshot.base64, "base64");
|
|
18134
|
-
const newKey = await this.
|
|
18211
|
+
const newKey = await this.putReplacingUnchecked({
|
|
18135
18212
|
deviceId: input.deviceId,
|
|
18136
18213
|
ownerKind: "track",
|
|
18137
18214
|
ownerId: input.trackId,
|
|
@@ -18139,7 +18216,7 @@ var MediaStore = class {
|
|
|
18139
18216
|
timestamp: input.snapshot.timestamp,
|
|
18140
18217
|
data
|
|
18141
18218
|
});
|
|
18142
|
-
if (input.keepSource !== true) await this.
|
|
18219
|
+
if (input.keepSource !== true) await this.deleteByKeyUnchecked(input.snapshot.key);
|
|
18143
18220
|
return newKey;
|
|
18144
18221
|
}
|
|
18145
18222
|
/**
|
|
@@ -18177,6 +18254,9 @@ var MediaStore = class {
|
|
|
18177
18254
|
* Deletes the blob from storage (best-effort) and removes the index row.
|
|
18178
18255
|
*/
|
|
18179
18256
|
async deleteByKey(key) {
|
|
18257
|
+
await this.gated(() => this.deleteByKeyUnchecked(key));
|
|
18258
|
+
}
|
|
18259
|
+
async deleteByKeyUnchecked(key) {
|
|
18180
18260
|
const row = await this.store.get.query({
|
|
18181
18261
|
collection: MEDIA_COLLECTION,
|
|
18182
18262
|
key
|
|
@@ -18287,6 +18367,9 @@ var MediaStore = class {
|
|
|
18287
18367
|
* Best-effort per row; returns the number of index rows removed.
|
|
18288
18368
|
*/
|
|
18289
18369
|
async deleteForOwner(ownerKind, ownerIds) {
|
|
18370
|
+
return this.gated(() => this.deleteForOwnerUnchecked(ownerKind, ownerIds));
|
|
18371
|
+
}
|
|
18372
|
+
async deleteForOwnerUnchecked(ownerKind, ownerIds) {
|
|
18290
18373
|
let removed = 0;
|
|
18291
18374
|
for (const ownerId of ownerIds) {
|
|
18292
18375
|
const rows = await this.store.query.query({
|
|
@@ -18368,6 +18451,9 @@ var MediaStore = class {
|
|
|
18368
18451
|
* The old-entry deletion is best-effort: on failure a warning is logged
|
|
18369
18452
|
* and the method still returns the new key. */
|
|
18370
18453
|
async reown(input) {
|
|
18454
|
+
return this.gated(() => this.reownUnchecked(input));
|
|
18455
|
+
}
|
|
18456
|
+
async reownUnchecked(input) {
|
|
18371
18457
|
const { fromOwnerKind, fromOwnerId, toOwnerKind, toOwnerId, key } = input;
|
|
18372
18458
|
const row = (await this.store.query.query({
|
|
18373
18459
|
collection: MEDIA_COLLECTION,
|
|
@@ -18401,7 +18487,7 @@ var MediaStore = class {
|
|
|
18401
18487
|
relativePath: oldPath
|
|
18402
18488
|
})
|
|
18403
18489
|
};
|
|
18404
|
-
const newKey = await this.
|
|
18490
|
+
const newKey = await this.putUnchecked(newParams);
|
|
18405
18491
|
try {
|
|
18406
18492
|
await this.storage.delete({
|
|
18407
18493
|
location: mediaRowLocation(meta),
|
|
@@ -18455,8 +18541,10 @@ var MediaStore = class {
|
|
|
18455
18541
|
* batch.
|
|
18456
18542
|
*/
|
|
18457
18543
|
async deleteByTracks(trackIds) {
|
|
18458
|
-
await this.
|
|
18459
|
-
|
|
18544
|
+
await this.gated(async () => {
|
|
18545
|
+
await this.deleteForOwnerUnchecked("track", trackIds);
|
|
18546
|
+
await this.deleteForOwnerUnchecked("face", trackIds.map((id) => FACE_MEDIA_OWNER_PREFIX + id));
|
|
18547
|
+
});
|
|
18460
18548
|
await this.deleteForOwner("plate", trackIds.map((id) => PLATE_MEDIA_OWNER_PREFIX + id));
|
|
18461
18549
|
}
|
|
18462
18550
|
/**
|
|
@@ -18498,7 +18586,7 @@ function snapshot(j) {
|
|
|
18498
18586
|
fromLocationId: j.fromLocationId,
|
|
18499
18587
|
toLocationId: j.toLocationId,
|
|
18500
18588
|
deviceId: j.deviceId,
|
|
18501
|
-
entities: ["media"],
|
|
18589
|
+
entities: ["media", "retrainFrames"],
|
|
18502
18590
|
filesMoved: j.filesMoved,
|
|
18503
18591
|
bytesMoved: j.bytesMoved,
|
|
18504
18592
|
filesTotal: null,
|
|
@@ -18529,7 +18617,7 @@ var MediaRelocateEngine = class {
|
|
|
18529
18617
|
state: "running",
|
|
18530
18618
|
fromLocationId: "*",
|
|
18531
18619
|
toLocationId: input.toLocationId,
|
|
18532
|
-
deviceId:
|
|
18620
|
+
deviceId: null,
|
|
18533
18621
|
filesMoved: 0,
|
|
18534
18622
|
bytesMoved: 0,
|
|
18535
18623
|
startedAt: this.deps.now(),
|
|
@@ -18546,24 +18634,25 @@ var MediaRelocateEngine = class {
|
|
|
18546
18634
|
const bytesPerMs = throttleMbps * 1024 * 1024 / 1e3;
|
|
18547
18635
|
try {
|
|
18548
18636
|
await this.deps.resolveTargetRoot(job.toLocationId);
|
|
18637
|
+
let failedRows = 0;
|
|
18549
18638
|
let cursor = 0;
|
|
18550
|
-
let
|
|
18639
|
+
let offsetAtCursor = 0;
|
|
18551
18640
|
for (;;) {
|
|
18552
18641
|
if (job.cancelRequested) break;
|
|
18553
|
-
const
|
|
18642
|
+
const rows = await this.deps.store.query.query({
|
|
18554
18643
|
collection: MEDIA_COLLECTION,
|
|
18555
18644
|
filter: {
|
|
18556
|
-
...job.deviceId !== null ? { where: { deviceId: job.deviceId } } : {},
|
|
18557
18645
|
whereBetween: { timestamp: [cursor, Number.MAX_SAFE_INTEGER] },
|
|
18558
18646
|
orderBy: {
|
|
18559
18647
|
field: "timestamp",
|
|
18560
18648
|
direction: "asc"
|
|
18561
18649
|
},
|
|
18562
|
-
limit: PAGE_SIZE
|
|
18650
|
+
limit: PAGE_SIZE,
|
|
18651
|
+
offset: offsetAtCursor
|
|
18563
18652
|
}
|
|
18564
|
-
})
|
|
18565
|
-
if (
|
|
18566
|
-
for (const row of
|
|
18653
|
+
});
|
|
18654
|
+
if (rows.length === 0) break;
|
|
18655
|
+
for (const row of rows) {
|
|
18567
18656
|
if (job.cancelRequested) break;
|
|
18568
18657
|
const data = row.data;
|
|
18569
18658
|
const from = mediaRowLocation(data);
|
|
@@ -18571,6 +18660,17 @@ var MediaRelocateEngine = class {
|
|
|
18571
18660
|
const relativePath = String(data["path"] ?? "");
|
|
18572
18661
|
if (relativePath.length === 0) continue;
|
|
18573
18662
|
try {
|
|
18663
|
+
if (await this.samePhysicalLocation(from, job.toLocationId)) {
|
|
18664
|
+
await this.deps.store.set.mutate({
|
|
18665
|
+
collection: MEDIA_COLLECTION,
|
|
18666
|
+
key: row.id,
|
|
18667
|
+
value: {
|
|
18668
|
+
...data,
|
|
18669
|
+
locationId: job.toLocationId
|
|
18670
|
+
}
|
|
18671
|
+
});
|
|
18672
|
+
continue;
|
|
18673
|
+
}
|
|
18574
18674
|
const bytes = await this.deps.storage.read({
|
|
18575
18675
|
location: from,
|
|
18576
18676
|
relativePath
|
|
@@ -18580,6 +18680,10 @@ var MediaRelocateEngine = class {
|
|
|
18580
18680
|
relativePath,
|
|
18581
18681
|
data: bytes
|
|
18582
18682
|
});
|
|
18683
|
+
if ((await this.deps.storage.read({
|
|
18684
|
+
location: job.toLocationId,
|
|
18685
|
+
relativePath
|
|
18686
|
+
})).length !== bytes.length) throw new Error(`relocated media size mismatch for ${relativePath}`);
|
|
18583
18687
|
await this.deps.store.set.mutate({
|
|
18584
18688
|
collection: MEDIA_COLLECTION,
|
|
18585
18689
|
key: row.id,
|
|
@@ -18596,20 +18700,99 @@ var MediaRelocateEngine = class {
|
|
|
18596
18700
|
job.bytesMoved += bytes.length;
|
|
18597
18701
|
await sleep(bytes.length / bytesPerMs);
|
|
18598
18702
|
} catch (err) {
|
|
18703
|
+
failedRows++;
|
|
18599
18704
|
this.deps.logger.debug("media relocate row failed", { meta: {
|
|
18600
18705
|
key: row.id,
|
|
18601
18706
|
error: String(err)
|
|
18602
18707
|
} });
|
|
18603
18708
|
}
|
|
18604
18709
|
}
|
|
18605
|
-
const last =
|
|
18710
|
+
const last = rows[rows.length - 1];
|
|
18606
18711
|
const lastTs = Number(last.data["timestamp"] ?? cursor);
|
|
18607
|
-
if (lastTs === cursor)
|
|
18712
|
+
if (lastTs === cursor) offsetAtCursor += rows.length;
|
|
18608
18713
|
else {
|
|
18609
18714
|
cursor = lastTs;
|
|
18610
|
-
|
|
18715
|
+
offsetAtCursor = rows.filter((row) => Number(row.data["timestamp"]) === lastTs).length;
|
|
18611
18716
|
}
|
|
18717
|
+
if (rows.length < PAGE_SIZE) break;
|
|
18612
18718
|
}
|
|
18719
|
+
let retrainOffset = 0;
|
|
18720
|
+
for (;;) {
|
|
18721
|
+
if (job.cancelRequested) break;
|
|
18722
|
+
const rows = await this.deps.store.query.query({
|
|
18723
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
18724
|
+
filter: {
|
|
18725
|
+
orderBy: {
|
|
18726
|
+
field: "copiedAt",
|
|
18727
|
+
direction: "asc"
|
|
18728
|
+
},
|
|
18729
|
+
limit: PAGE_SIZE,
|
|
18730
|
+
offset: retrainOffset
|
|
18731
|
+
}
|
|
18732
|
+
});
|
|
18733
|
+
if (rows.length === 0) break;
|
|
18734
|
+
for (const row of rows) {
|
|
18735
|
+
if (job.cancelRequested) break;
|
|
18736
|
+
const data = row.data;
|
|
18737
|
+
const from = mediaRowLocation(data);
|
|
18738
|
+
if (from === job.toLocationId) continue;
|
|
18739
|
+
const relativePath = String(data["path"] ?? "");
|
|
18740
|
+
if (relativePath.length === 0) {
|
|
18741
|
+
failedRows++;
|
|
18742
|
+
continue;
|
|
18743
|
+
}
|
|
18744
|
+
try {
|
|
18745
|
+
if (await this.samePhysicalLocation(from, job.toLocationId)) {
|
|
18746
|
+
await this.deps.store.set.mutate({
|
|
18747
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
18748
|
+
key: row.id,
|
|
18749
|
+
value: {
|
|
18750
|
+
...data,
|
|
18751
|
+
locationId: job.toLocationId
|
|
18752
|
+
}
|
|
18753
|
+
});
|
|
18754
|
+
continue;
|
|
18755
|
+
}
|
|
18756
|
+
const bytes = await this.deps.storage.read({
|
|
18757
|
+
location: from,
|
|
18758
|
+
relativePath
|
|
18759
|
+
});
|
|
18760
|
+
await this.deps.storage.write({
|
|
18761
|
+
location: job.toLocationId,
|
|
18762
|
+
relativePath,
|
|
18763
|
+
data: bytes
|
|
18764
|
+
});
|
|
18765
|
+
if ((await this.deps.storage.read({
|
|
18766
|
+
location: job.toLocationId,
|
|
18767
|
+
relativePath
|
|
18768
|
+
})).length !== bytes.length) throw new Error(`relocated retrain frame size mismatch for ${relativePath}`);
|
|
18769
|
+
await this.deps.store.set.mutate({
|
|
18770
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
18771
|
+
key: row.id,
|
|
18772
|
+
value: {
|
|
18773
|
+
...data,
|
|
18774
|
+
locationId: job.toLocationId
|
|
18775
|
+
}
|
|
18776
|
+
});
|
|
18777
|
+
await this.deps.storage.delete({
|
|
18778
|
+
location: from,
|
|
18779
|
+
relativePath
|
|
18780
|
+
});
|
|
18781
|
+
job.filesMoved++;
|
|
18782
|
+
job.bytesMoved += bytes.length;
|
|
18783
|
+
await sleep(bytes.length / bytesPerMs);
|
|
18784
|
+
} catch (err) {
|
|
18785
|
+
failedRows++;
|
|
18786
|
+
this.deps.logger.debug("retrain frame relocate row failed", { meta: {
|
|
18787
|
+
key: row.id,
|
|
18788
|
+
error: String(err)
|
|
18789
|
+
} });
|
|
18790
|
+
}
|
|
18791
|
+
}
|
|
18792
|
+
retrainOffset += rows.length;
|
|
18793
|
+
if (rows.length < PAGE_SIZE) break;
|
|
18794
|
+
}
|
|
18795
|
+
if (failedRows > 0) throw new Error(`event-media relocation left ${failedRows} row(s) unmoved`);
|
|
18613
18796
|
job.state = job.cancelRequested ? "cancelled" : "done";
|
|
18614
18797
|
} catch (err) {
|
|
18615
18798
|
job.state = "failed";
|
|
@@ -18623,6 +18806,18 @@ var MediaRelocateEngine = class {
|
|
|
18623
18806
|
this.deps.onFinished?.(snapshot(job));
|
|
18624
18807
|
}
|
|
18625
18808
|
}
|
|
18809
|
+
async samePhysicalLocation(from, to) {
|
|
18810
|
+
if (from === to) return true;
|
|
18811
|
+
try {
|
|
18812
|
+
const [fromRoot, toRoot] = await Promise.all([this.deps.resolveTargetRoot(from), this.deps.resolveTargetRoot(to)]);
|
|
18813
|
+
const [fromStat, toStat] = await Promise.all([node_fs.promises.stat(fromRoot), node_fs.promises.stat(toRoot)]);
|
|
18814
|
+
if (fromStat.dev === toStat.dev && fromStat.ino === toStat.ino) return true;
|
|
18815
|
+
const [fromReal, toReal] = await Promise.all([node_fs.promises.realpath(node_path.default.resolve(fromRoot)), node_fs.promises.realpath(node_path.default.resolve(toRoot))]);
|
|
18816
|
+
return fromReal === toReal;
|
|
18817
|
+
} catch {
|
|
18818
|
+
return false;
|
|
18819
|
+
}
|
|
18820
|
+
}
|
|
18626
18821
|
};
|
|
18627
18822
|
//#endregion
|
|
18628
18823
|
//#region src/pipeline-analytics/media-settings.ts
|
|
@@ -31175,6 +31370,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
31175
31370
|
eventsOpsLog = null;
|
|
31176
31371
|
/** Event-media relocation engine (entity-routing Phase 4). */
|
|
31177
31372
|
mediaRelocate = null;
|
|
31373
|
+
mediaLocationStorage = null;
|
|
31374
|
+
mediaWriteGate = new MediaWriteGate();
|
|
31375
|
+
storageMigrationLeaseId = null;
|
|
31178
31376
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
31179
31377
|
sensorEventStore = null;
|
|
31180
31378
|
/** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
|
|
@@ -31975,7 +32173,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
31975
32173
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
31976
32174
|
}
|
|
31977
32175
|
if (!storage) throw new Error("pipeline-analytics requires ctx.kernel.storage");
|
|
31978
|
-
|
|
32176
|
+
const routed = createLocationAwareMediaStorage({
|
|
31979
32177
|
base: storage,
|
|
31980
32178
|
defaultLocation: "eventMedia",
|
|
31981
32179
|
resolveRoot: (locationId) => this.ctx.api.storage.resolve.query({
|
|
@@ -31983,6 +32181,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
31983
32181
|
relativePath: ""
|
|
31984
32182
|
})
|
|
31985
32183
|
});
|
|
32184
|
+
this.mediaLocationStorage = routed;
|
|
32185
|
+
return routed;
|
|
31986
32186
|
}
|
|
31987
32187
|
/** Constructs every SQLite-backed store plus the stationary/package-drop/
|
|
31988
32188
|
* sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
|
|
@@ -32027,7 +32227,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
32027
32227
|
const mediaStore = new MediaStore({
|
|
32028
32228
|
storage,
|
|
32029
32229
|
store: api.settingsStore,
|
|
32030
|
-
logger: logger.child("MediaStore")
|
|
32230
|
+
logger: logger.child("MediaStore"),
|
|
32231
|
+
writeGate: this.mediaWriteGate
|
|
32031
32232
|
});
|
|
32032
32233
|
this.mediaStore = mediaStore;
|
|
32033
32234
|
const retrainFrames = new RetrainFrameStore({
|
|
@@ -32035,6 +32236,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
32035
32236
|
storage,
|
|
32036
32237
|
logger: logger.child("RetrainFrames"),
|
|
32037
32238
|
now: () => Date.now(),
|
|
32239
|
+
writeGate: this.mediaWriteGate,
|
|
32038
32240
|
readSource: async (mediaKey) => {
|
|
32039
32241
|
const file = await mediaStore.getByKey(mediaKey);
|
|
32040
32242
|
return file === null ? null : Buffer.from(file.base64, "base64");
|
|
@@ -36276,14 +36478,38 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
36276
36478
|
return this.queryFacade.deleteDeviceEvents(input);
|
|
36277
36479
|
}
|
|
36278
36480
|
/** The events ops-log rows (newest-first), optionally scoped to one camera. */
|
|
36279
|
-
async
|
|
36481
|
+
async pauseForStorageMigration(input) {
|
|
36482
|
+
if (this.storageMigrationLeaseId !== null && this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media writes are already paused by another storage migration");
|
|
36483
|
+
this.storageMigrationLeaseId = input.leaseId;
|
|
36484
|
+
try {
|
|
36485
|
+
await this.mediaWriteGate.pause();
|
|
36486
|
+
return { paused: true };
|
|
36487
|
+
} catch (err) {
|
|
36488
|
+
this.storageMigrationLeaseId = null;
|
|
36489
|
+
throw err;
|
|
36490
|
+
}
|
|
36491
|
+
}
|
|
36492
|
+
async resumeForStorageMigration(input) {
|
|
36493
|
+
if (this.storageMigrationLeaseId === null) return { resumed: true };
|
|
36494
|
+
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
36495
|
+
this.mediaWriteGate.resume();
|
|
36496
|
+
this.storageMigrationLeaseId = null;
|
|
36497
|
+
return { resumed: true };
|
|
36498
|
+
}
|
|
36499
|
+
async refreshStorageLocationsForMigration(input) {
|
|
36500
|
+
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
36501
|
+
this.mediaLocationStorage?.invalidateRoots();
|
|
36502
|
+
return { refreshed: true };
|
|
36503
|
+
}
|
|
36504
|
+
async startStorageMigrationMove(input) {
|
|
36280
36505
|
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
36506
|
+
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
36281
36507
|
return { jobId: this.mediaRelocate.start(input) };
|
|
36282
36508
|
}
|
|
36283
|
-
async
|
|
36284
|
-
return this.mediaRelocate?.list() ??
|
|
36509
|
+
async getStorageMigrationMoveStatus(input) {
|
|
36510
|
+
return this.mediaRelocate?.list().find((job) => job.jobId === input.jobId) ?? null;
|
|
36285
36511
|
}
|
|
36286
|
-
async
|
|
36512
|
+
async cancelStorageMigrationMove(input) {
|
|
36287
36513
|
return { cancelled: this.mediaRelocate?.cancel(input.jobId) ?? false };
|
|
36288
36514
|
}
|
|
36289
36515
|
async listOpsLog(input) {
|