@digitalpaws/starr 0.0.1
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.rst +75 -0
- package/data/.nfs.811bbb0d.43c34.4 +0 -0
- package/data/.nfs.81ba4cd0.ffff87804.4 +0 -0
- package/data/.nfs.82d127f7.fffff5704.4 +0 -0
- package/data/.nfs.82d127fe.fffff5704.4 +0 -0
- package/data/.nfs.82d159a5.fffff5aa4.4 +0 -0
- package/data/.nfs.82d159aa.fffff5aa4.4 +0 -0
- package/data/.nfs.82d15a9c.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15aa1.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15c65.fffff5c24.4 +0 -0
- package/data/.nfs.82d1852c.fffff5e24.4 +0 -0
- package/data/.nfs.82d18536.fffff5e24.4 +0 -0
- package/data/.nfs.82d18dbe.fffff5e44.4 +0 -0
- package/data/.nfs.82d18dc6.fffff5e44.4 +0 -0
- package/data/.nfs.8309a3f5.fffff82f4.4 +0 -0
- package/data/.nfs.8309a3fe.fffff82f4.4 +0 -0
- package/data/.nfs.8314122e.fffffb044.4 +0 -0
- package/data/.nfs.83141236.fffffb044.4 +0 -0
- package/data/.nfs.833c4fe9.fffffbc34.4 +0 -0
- package/data/.nfs.833c4ff3.fffffbc34.4 +0 -0
- package/data/logs/starr.log +66244 -0
- package/data/starr.db +0 -0
- package/data/starr.db-shm +0 -0
- package/data/starr.db-wal +0 -0
- package/data/starr.jwt +1 -0
- package/data/starr.key +1 -0
- package/dist/auth/auth-service.d.ts +42 -0
- package/dist/auth/auth-service.d.ts.map +1 -0
- package/dist/auth/auth-service.js +226 -0
- package/dist/auth/auth-service.js.map +1 -0
- package/dist/auth/http-server.d.ts +16 -0
- package/dist/auth/http-server.d.ts.map +1 -0
- package/dist/auth/http-server.js +207 -0
- package/dist/auth/http-server.js.map +1 -0
- package/dist/auth/jwt-secret.d.ts +6 -0
- package/dist/auth/jwt-secret.d.ts.map +1 -0
- package/dist/auth/jwt-secret.js +51 -0
- package/dist/auth/jwt-secret.js.map +1 -0
- package/dist/auth/oidc/oidc-flow.d.ts +60 -0
- package/dist/auth/oidc/oidc-flow.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-flow.js +201 -0
- package/dist/auth/oidc/oidc-flow.js.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts +59 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.js +124 -0
- package/dist/auth/oidc/oidc-provider-service.js.map +1 -0
- package/dist/auth/oidc/user-service.d.ts +67 -0
- package/dist/auth/oidc/user-service.d.ts.map +1 -0
- package/dist/auth/oidc/user-service.js +177 -0
- package/dist/auth/oidc/user-service.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +31 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +70 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/config/config-service.d.ts +83 -0
- package/dist/config/config-service.d.ts.map +1 -0
- package/dist/config/config-service.js +309 -0
- package/dist/config/config-service.js.map +1 -0
- package/dist/config/validate.d.ts +12 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +140 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/content-types/registry.d.ts +75 -0
- package/dist/content-types/registry.d.ts.map +1 -0
- package/dist/content-types/registry.js +171 -0
- package/dist/content-types/registry.js.map +1 -0
- package/dist/datastore/db.d.ts +9 -0
- package/dist/datastore/db.d.ts.map +1 -0
- package/dist/datastore/db.js +27 -0
- package/dist/datastore/db.js.map +1 -0
- package/dist/datastore/encrypt.d.ts +9 -0
- package/dist/datastore/encrypt.d.ts.map +1 -0
- package/dist/datastore/encrypt.js +84 -0
- package/dist/datastore/encrypt.js.map +1 -0
- package/dist/datastore/migrations.d.ts +10 -0
- package/dist/datastore/migrations.d.ts.map +1 -0
- package/dist/datastore/migrations.js +340 -0
- package/dist/datastore/migrations.js.map +1 -0
- package/dist/datastore/storage.d.ts +4 -0
- package/dist/datastore/storage.d.ts.map +1 -0
- package/dist/datastore/storage.js +60 -0
- package/dist/datastore/storage.js.map +1 -0
- package/dist/events/event-bus.d.ts +15 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +140 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/spine.d.ts +31 -0
- package/dist/events/spine.d.ts.map +1 -0
- package/dist/events/spine.js +68 -0
- package/dist/events/spine.js.map +1 -0
- package/dist/events/trace.d.ts +24 -0
- package/dist/events/trace.d.ts.map +1 -0
- package/dist/events/trace.js +33 -0
- package/dist/events/trace.js.map +1 -0
- package/dist/host.d.ts +65 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +94 -0
- package/dist/host.js.map +1 -0
- package/dist/library/importer.d.ts +29 -0
- package/dist/library/importer.d.ts.map +1 -0
- package/dist/library/importer.js +145 -0
- package/dist/library/importer.js.map +1 -0
- package/dist/library/library-cache.d.ts +46 -0
- package/dist/library/library-cache.d.ts.map +1 -0
- package/dist/library/library-cache.js +152 -0
- package/dist/library/library-cache.js.map +1 -0
- package/dist/library/library-config.d.ts +12 -0
- package/dist/library/library-config.d.ts.map +1 -0
- package/dist/library/library-config.js +10 -0
- package/dist/library/library-config.js.map +1 -0
- package/dist/library/library-scanner.d.ts +62 -0
- package/dist/library/library-scanner.d.ts.map +1 -0
- package/dist/library/library-scanner.js +336 -0
- package/dist/library/library-scanner.js.map +1 -0
- package/dist/library/library-service.d.ts +65 -0
- package/dist/library/library-service.d.ts.map +1 -0
- package/dist/library/library-service.js +197 -0
- package/dist/library/library-service.js.map +1 -0
- package/dist/library/metadata-matcher.d.ts +56 -0
- package/dist/library/metadata-matcher.d.ts.map +1 -0
- package/dist/library/metadata-matcher.js +207 -0
- package/dist/library/metadata-matcher.js.map +1 -0
- package/dist/library/naming.d.ts +8 -0
- package/dist/library/naming.d.ts.map +1 -0
- package/dist/library/naming.js +47 -0
- package/dist/library/naming.js.map +1 -0
- package/dist/library/scan-controller.d.ts +95 -0
- package/dist/library/scan-controller.d.ts.map +1 -0
- package/dist/library/scan-controller.js +379 -0
- package/dist/library/scan-controller.js.map +1 -0
- package/dist/library/source-resolver.d.ts +17 -0
- package/dist/library/source-resolver.d.ts.map +1 -0
- package/dist/library/source-resolver.js +75 -0
- package/dist/library/source-resolver.js.map +1 -0
- package/dist/logging/logger.d.ts +3 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +107 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/main.d.ts +36 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +229 -0
- package/dist/main.js.map +1 -0
- package/dist/plugins/context.d.ts +10 -0
- package/dist/plugins/context.d.ts.map +1 -0
- package/dist/plugins/context.js +110 -0
- package/dist/plugins/context.js.map +1 -0
- package/dist/plugins/discovery.d.ts +2 -0
- package/dist/plugins/discovery.d.ts.map +1 -0
- package/dist/plugins/discovery.js +32 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/loader.d.ts +9 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +245 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/state/request-intake.d.ts +26 -0
- package/dist/state/request-intake.d.ts.map +1 -0
- package/dist/state/request-intake.js +74 -0
- package/dist/state/request-intake.js.map +1 -0
- package/dist/state/state-store.d.ts +146 -0
- package/dist/state/state-store.d.ts.map +1 -0
- package/dist/state/state-store.js +465 -0
- package/dist/state/state-store.js.map +1 -0
- package/dist/state/trace-store.d.ts +12 -0
- package/dist/state/trace-store.d.ts.map +1 -0
- package/dist/state/trace-store.js +24 -0
- package/dist/state/trace-store.js.map +1 -0
- package/dist/types/plugin.d.ts +93 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +4 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/web/index.d.ts +10 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +153 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/routes/activity.d.ts +6 -0
- package/dist/web/routes/activity.d.ts.map +1 -0
- package/dist/web/routes/activity.js +31 -0
- package/dist/web/routes/activity.js.map +1 -0
- package/dist/web/routes/auth.d.ts +6 -0
- package/dist/web/routes/auth.d.ts.map +1 -0
- package/dist/web/routes/auth.js +142 -0
- package/dist/web/routes/auth.js.map +1 -0
- package/dist/web/routes/config.d.ts +7 -0
- package/dist/web/routes/config.d.ts.map +1 -0
- package/dist/web/routes/config.js +204 -0
- package/dist/web/routes/config.js.map +1 -0
- package/dist/web/routes/content-types.d.ts +6 -0
- package/dist/web/routes/content-types.d.ts.map +1 -0
- package/dist/web/routes/content-types.js +29 -0
- package/dist/web/routes/content-types.js.map +1 -0
- package/dist/web/routes/discovery.d.ts +6 -0
- package/dist/web/routes/discovery.d.ts.map +1 -0
- package/dist/web/routes/discovery.js +111 -0
- package/dist/web/routes/discovery.js.map +1 -0
- package/dist/web/routes/library-import.d.ts +6 -0
- package/dist/web/routes/library-import.d.ts.map +1 -0
- package/dist/web/routes/library-import.js +120 -0
- package/dist/web/routes/library-import.js.map +1 -0
- package/dist/web/routes/library-management.d.ts +6 -0
- package/dist/web/routes/library-management.d.ts.map +1 -0
- package/dist/web/routes/library-management.js +120 -0
- package/dist/web/routes/library-management.js.map +1 -0
- package/dist/web/routes/library.d.ts +6 -0
- package/dist/web/routes/library.d.ts.map +1 -0
- package/dist/web/routes/library.js +28 -0
- package/dist/web/routes/library.js.map +1 -0
- package/dist/web/routes/oidc.d.ts +9 -0
- package/dist/web/routes/oidc.d.ts.map +1 -0
- package/dist/web/routes/oidc.js +203 -0
- package/dist/web/routes/oidc.js.map +1 -0
- package/dist/web/routes/requests.d.ts +6 -0
- package/dist/web/routes/requests.d.ts.map +1 -0
- package/dist/web/routes/requests.js +183 -0
- package/dist/web/routes/requests.js.map +1 -0
- package/dist/web/routes/sse.d.ts +6 -0
- package/dist/web/routes/sse.d.ts.map +1 -0
- package/dist/web/routes/sse.js +54 -0
- package/dist/web/routes/sse.js.map +1 -0
- package/package.json +33 -0
- package/package.json.license +2 -0
- package/src/auth/auth-service.ts +333 -0
- package/src/auth/http-server.ts +249 -0
- package/src/auth/jwt-secret.ts +65 -0
- package/src/auth/oidc/oidc-flow.ts +266 -0
- package/src/auth/oidc/oidc-provider-service.ts +174 -0
- package/src/auth/oidc/user-service.ts +227 -0
- package/src/auth/rate-limiter.ts +85 -0
- package/src/config/config-service.ts +434 -0
- package/src/config/validate.ts +153 -0
- package/src/content-types/registry.ts +194 -0
- package/src/datastore/db.ts +38 -0
- package/src/datastore/encrypt.ts +107 -0
- package/src/datastore/migrations.ts +367 -0
- package/src/datastore/storage.ts +83 -0
- package/src/events/event-bus.ts +180 -0
- package/src/events/spine.ts +87 -0
- package/src/events/trace.ts +38 -0
- package/src/host.ts +130 -0
- package/src/library/importer.ts +174 -0
- package/src/library/library-cache.ts +212 -0
- package/src/library/library-config.ts +22 -0
- package/src/library/library-scanner.ts +404 -0
- package/src/library/library-service.ts +282 -0
- package/src/library/metadata-matcher.ts +285 -0
- package/src/library/naming.ts +59 -0
- package/src/library/scan-controller.ts +490 -0
- package/src/library/source-resolver.ts +92 -0
- package/src/logging/logger.ts +115 -0
- package/src/main.ts +364 -0
- package/src/plugins/context.ts +138 -0
- package/src/plugins/discovery.ts +29 -0
- package/src/plugins/loader.ts +309 -0
- package/src/state/request-intake.ts +101 -0
- package/src/state/state-store.ts +621 -0
- package/src/state/trace-store.ts +27 -0
- package/src/types/plugin.ts +110 -0
- package/src/web/index.ts +182 -0
- package/src/web/routes/activity.ts +41 -0
- package/src/web/routes/auth.ts +170 -0
- package/src/web/routes/config.ts +282 -0
- package/src/web/routes/content-types.ts +42 -0
- package/src/web/routes/discovery.ts +156 -0
- package/src/web/routes/library-import.ts +196 -0
- package/src/web/routes/library-management.ts +140 -0
- package/src/web/routes/library.ts +46 -0
- package/src/web/routes/oidc.ts +260 -0
- package/src/web/routes/requests.ts +250 -0
- package/src/web/routes/sse.ts +92 -0
- package/tests/auth.test.ts +510 -0
- package/tests/config-route.test.ts +345 -0
- package/tests/config.test.ts +612 -0
- package/tests/content-types.test.ts +376 -0
- package/tests/datastore.test.ts +345 -0
- package/tests/e2e.test.ts +430 -0
- package/tests/event-bus.test.ts +925 -0
- package/tests/host.test.ts +449 -0
- package/tests/library-cache.test.ts +171 -0
- package/tests/library-scanner.test.ts +177 -0
- package/tests/library.test.ts +210 -0
- package/tests/loader-context.test.ts +644 -0
- package/tests/logger-jsonl.test.ts +154 -0
- package/tests/metadata-matcher.test.ts +198 -0
- package/tests/oidc.test.ts +218 -0
- package/tests/scan-controller.test.ts +328 -0
- package/tests/source-resolver.test.ts +100 -0
- package/tests/starr-video-integration.test.ts +153 -0
- package/tests/state-store.test.ts +700 -0
- package/tests/trace-history.test.ts +356 -0
- package/tsconfig.json +8 -0
- package/tsconfig.json.license +2 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
export class LibraryCache {
|
|
4
|
+
db;
|
|
5
|
+
bus;
|
|
6
|
+
logger;
|
|
7
|
+
stmtInsert;
|
|
8
|
+
stmtDelete;
|
|
9
|
+
stmtGetAll;
|
|
10
|
+
stmtGetByType;
|
|
11
|
+
stmtCount;
|
|
12
|
+
constructor(db, bus, logger) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
this.bus = bus;
|
|
15
|
+
this.logger = logger.child("library-cache");
|
|
16
|
+
this.stmtInsert = db.prepare(`
|
|
17
|
+
INSERT INTO library_cache
|
|
18
|
+
(request_id, content_type_id, content_identity, title, sort_title,
|
|
19
|
+
summary, release_date, images, external_ids, fields,
|
|
20
|
+
library_path, file_size, imported_at, updated_at)
|
|
21
|
+
VALUES
|
|
22
|
+
(@requestId, @contentTypeId, @contentIdentity, @title, @sortTitle,
|
|
23
|
+
@summary, @releaseDate, @images, @externalIds, @fields,
|
|
24
|
+
@libraryPath, @fileSize, @importedAt, datetime('now'))
|
|
25
|
+
ON CONFLICT(request_id) DO UPDATE SET
|
|
26
|
+
title = excluded.title,
|
|
27
|
+
library_path = excluded.library_path,
|
|
28
|
+
file_size = excluded.file_size,
|
|
29
|
+
updated_at = datetime('now')
|
|
30
|
+
`);
|
|
31
|
+
this.stmtDelete = db.prepare("DELETE FROM library_cache WHERE request_id = @requestId");
|
|
32
|
+
this.stmtGetAll = db.prepare("SELECT * FROM library_cache ORDER BY imported_at DESC");
|
|
33
|
+
this.stmtGetByType = db.prepare("SELECT * FROM library_cache WHERE content_type_id = @type ORDER BY imported_at DESC");
|
|
34
|
+
this.stmtCount = db.prepare("SELECT COUNT(*) AS count FROM library_cache");
|
|
35
|
+
// Subscribe to events
|
|
36
|
+
bus.on("library.file_added", this.onFileAdded.bind(this));
|
|
37
|
+
bus.on("request.deleted", this.onRequestDeleted.bind(this));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Populate the cache from tracked_requests on startup.
|
|
41
|
+
*/
|
|
42
|
+
refreshFromDb() {
|
|
43
|
+
const rows = this.db
|
|
44
|
+
.prepare(`SELECT tr.request_id, tr.content_type_id, tr.content_identity,
|
|
45
|
+
tr.item, tr.updated_at,
|
|
46
|
+
lp.library_path, lp.file_size, lp.imported_at
|
|
47
|
+
FROM tracked_requests tr
|
|
48
|
+
JOIN library_paths lp ON lp.request_id = tr.request_id
|
|
49
|
+
WHERE tr.status = 'imported' OR tr.status = 'downloaded'`)
|
|
50
|
+
.all();
|
|
51
|
+
const txn = this.db.transaction(() => {
|
|
52
|
+
this.db.exec("DELETE FROM library_cache");
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
const item = JSON.parse(row.item);
|
|
55
|
+
this.stmtInsert.run({
|
|
56
|
+
requestId: row.request_id,
|
|
57
|
+
contentTypeId: row.content_type_id,
|
|
58
|
+
contentIdentity: row.content_identity,
|
|
59
|
+
title: item.title ?? "",
|
|
60
|
+
sortTitle: item.sortTitle ?? null,
|
|
61
|
+
summary: item.summary ?? null,
|
|
62
|
+
releaseDate: item.releaseDate ?? null,
|
|
63
|
+
images: item.images ? JSON.stringify(item.images) : "[]",
|
|
64
|
+
externalIds: JSON.stringify(item.externalIds ?? {}),
|
|
65
|
+
fields: item.fields ? JSON.stringify(item.fields) : null,
|
|
66
|
+
libraryPath: row.library_path,
|
|
67
|
+
fileSize: row.file_size ?? null,
|
|
68
|
+
importedAt: row.imported_at,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
txn();
|
|
73
|
+
const count = this.stmtCount.get().count;
|
|
74
|
+
this.logger.info("library cache refreshed", { count });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Return all cached library items, optionally filtered by content type.
|
|
78
|
+
*/
|
|
79
|
+
getItems(contentTypeId) {
|
|
80
|
+
const rows = contentTypeId
|
|
81
|
+
? this.stmtGetByType.all({ type: contentTypeId })
|
|
82
|
+
: this.stmtGetAll.all();
|
|
83
|
+
return rows.map(this.rowToItem);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Return total count of cached items.
|
|
87
|
+
*/
|
|
88
|
+
count() {
|
|
89
|
+
return this.stmtCount.get().count;
|
|
90
|
+
}
|
|
91
|
+
// ── Event handlers ──
|
|
92
|
+
onFileAdded(event) {
|
|
93
|
+
const payload = event.payload;
|
|
94
|
+
// Fetch the full item from tracked_requests to cache it
|
|
95
|
+
const row = this.db
|
|
96
|
+
.prepare(`SELECT tr.request_id, tr.content_type_id, tr.content_identity,
|
|
97
|
+
tr.item, tr.updated_at,
|
|
98
|
+
lp.library_path, lp.file_size, lp.imported_at
|
|
99
|
+
FROM tracked_requests tr
|
|
100
|
+
JOIN library_paths lp ON lp.request_id = tr.request_id
|
|
101
|
+
WHERE tr.request_id = @requestId`)
|
|
102
|
+
.get({ requestId: payload.requestId });
|
|
103
|
+
if (!row) {
|
|
104
|
+
this.logger.warn("library.file_added for unknown request", {
|
|
105
|
+
requestId: payload.requestId,
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const item = JSON.parse(row.item);
|
|
110
|
+
this.stmtInsert.run({
|
|
111
|
+
requestId: row.request_id,
|
|
112
|
+
contentTypeId: row.content_type_id,
|
|
113
|
+
contentIdentity: row.content_identity,
|
|
114
|
+
title: item.title ?? "",
|
|
115
|
+
sortTitle: item.sortTitle ?? null,
|
|
116
|
+
summary: item.summary ?? null,
|
|
117
|
+
releaseDate: item.releaseDate ?? null,
|
|
118
|
+
images: item.images ? JSON.stringify(item.images) : "[]",
|
|
119
|
+
externalIds: JSON.stringify(item.externalIds ?? {}),
|
|
120
|
+
fields: item.fields ? JSON.stringify(item.fields) : null,
|
|
121
|
+
libraryPath: row.library_path,
|
|
122
|
+
fileSize: row.file_size ?? null,
|
|
123
|
+
importedAt: row.imported_at,
|
|
124
|
+
});
|
|
125
|
+
this.logger.debug("library cache updated", {
|
|
126
|
+
requestId: payload.requestId,
|
|
127
|
+
title: item.title,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
onRequestDeleted(event) {
|
|
131
|
+
const payload = event.payload;
|
|
132
|
+
this.stmtDelete.run({ requestId: payload.requestId });
|
|
133
|
+
}
|
|
134
|
+
// ── Helpers ──
|
|
135
|
+
rowToItem(row) {
|
|
136
|
+
return {
|
|
137
|
+
requestId: row.request_id,
|
|
138
|
+
contentTypeId: row.content_type_id,
|
|
139
|
+
title: row.title,
|
|
140
|
+
sortTitle: row.sort_title ?? undefined,
|
|
141
|
+
summary: row.summary ?? undefined,
|
|
142
|
+
releaseDate: row.release_date ?? undefined,
|
|
143
|
+
images: JSON.parse(row.images ?? "[]"),
|
|
144
|
+
externalIds: JSON.parse(row.external_ids ?? "{}"),
|
|
145
|
+
fields: row.fields ? JSON.parse(row.fields) : undefined,
|
|
146
|
+
libraryPath: row.library_path,
|
|
147
|
+
fileSize: row.file_size ?? undefined,
|
|
148
|
+
importedAt: row.imported_at,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=library-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-cache.js","sourceRoot":"","sources":["../../src/library/library-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface LibraryRoots {
|
|
2
|
+
[contentTypeId: string]: string;
|
|
3
|
+
}
|
|
4
|
+
export interface LibraryConfig {
|
|
5
|
+
defaultLibraryRoot: string;
|
|
6
|
+
roots: LibraryRoots;
|
|
7
|
+
linkType: "move" | "hardlink" | "symlink" | "copy";
|
|
8
|
+
importSubtitles: boolean;
|
|
9
|
+
subtitleExtensions: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare const DEFAULT_LIBRARY_CONFIG: LibraryConfig;
|
|
12
|
+
//# sourceMappingURL=library-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-config.d.ts","sourceRoot":"","sources":["../../src/library/library-config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IACnD,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,eAAO,MAAM,sBAAsB,EAAE,aAMpC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
export const DEFAULT_LIBRARY_CONFIG = {
|
|
4
|
+
defaultLibraryRoot: "/media/library",
|
|
5
|
+
roots: {},
|
|
6
|
+
linkType: "hardlink",
|
|
7
|
+
importSubtitles: true,
|
|
8
|
+
subtitleExtensions: ["srt", "ass", "ssa", "sub", "vtt"],
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=library-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-config.js","sourceRoot":"","sources":["../../src/library/library-config.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,4DAA4D;AAc5D,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,kBAAkB,EAAE,gBAAgB;IACpC,KAAK,EAAE,EAAE;IACT,QAAQ,EAAE,UAAU;IACpB,eAAe,EAAE,IAAI;IACrB,kBAAkB,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC;CACxD,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { Logger } from "../types/plugin.js";
|
|
3
|
+
import type { LibraryService } from "./library-service.js";
|
|
4
|
+
export interface DiscoveredGroup {
|
|
5
|
+
/** Absolute path to the subdirectory. */
|
|
6
|
+
directory: string;
|
|
7
|
+
/** Folder name (last path component). */
|
|
8
|
+
folderName: string;
|
|
9
|
+
/** All media files within this directory. */
|
|
10
|
+
files: string[];
|
|
11
|
+
/** All subtitle files within this directory. */
|
|
12
|
+
subtitleFiles: string[];
|
|
13
|
+
/** Content type id inferred from which library root this falls under. */
|
|
14
|
+
contentTypeId: string;
|
|
15
|
+
/** The library this group belongs to, if known (multi-library support). */
|
|
16
|
+
libraryId?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ScanOptions {
|
|
19
|
+
/** Only scan this specific library. If unset, scan all libraries. */
|
|
20
|
+
libraryId?: string;
|
|
21
|
+
/** Only scan these content type ids. If empty, scan all configured roots. */
|
|
22
|
+
contentTypeIds?: string[];
|
|
23
|
+
/** Include items already in the library (for re-scan). */
|
|
24
|
+
includeExisting?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare class LibraryScanner {
|
|
27
|
+
private db;
|
|
28
|
+
private logger;
|
|
29
|
+
private libraryService;
|
|
30
|
+
private stmtCheckPath;
|
|
31
|
+
constructor(db: Database.Database, logger: Logger, libraryService: LibraryService);
|
|
32
|
+
/**
|
|
33
|
+
* Scan all configured library roots and discover media groups.
|
|
34
|
+
* Returns grouped results without metadata enrichment.
|
|
35
|
+
* Supports multi-library: scans roots from ALL libraries, or a specific
|
|
36
|
+
* library if options.libraryId is set, or roots matching specific content
|
|
37
|
+
* types if options.contentTypeIds is set.
|
|
38
|
+
*/
|
|
39
|
+
discoverGroups(options?: ScanOptions): Promise<DiscoveredGroup[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve which libraries to scan based on options.
|
|
42
|
+
*/
|
|
43
|
+
private resolveLibrariesToScan;
|
|
44
|
+
/**
|
|
45
|
+
* Scan a subdirectory for media and subtitle files.
|
|
46
|
+
*/
|
|
47
|
+
private processSubdirectory;
|
|
48
|
+
/**
|
|
49
|
+
* Scan a directory that is expected to be a single content item.
|
|
50
|
+
* Supports nested structures like "Author/Title/file.epub" up to 2 levels deep.
|
|
51
|
+
*/
|
|
52
|
+
private processItemDirectory;
|
|
53
|
+
/**
|
|
54
|
+
* Determine the content type for a given path.
|
|
55
|
+
*/
|
|
56
|
+
private resolveContentType;
|
|
57
|
+
/**
|
|
58
|
+
* Check which directories are already registered in library_paths.
|
|
59
|
+
*/
|
|
60
|
+
findExisting(directories: string[]): Set<string>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=library-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-scanner.d.ts","sourceRoot":"","sources":["../../src/library/library-scanner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAW,MAAM,sBAAsB,CAAC;AAuBpE,MAAM,WAAW,eAAe;IAC9B,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gDAAgD;IAChD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,0DAA0D;IAC1D,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAYD,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,aAAa,CAAqB;gBAGxC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,cAAc;IAWhC;;;;;;OAMG;IACG,cAAc,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAwHvE;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA2B9B;;OAEG;YACW,mBAAmB;IA4DjC;;;OAGG;YACW,oBAAoB;IA2DlC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;OAEG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;CAajD"}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const MEDIA_EXTENSIONS = new Set([
|
|
6
|
+
// Video
|
|
7
|
+
"mkv", "mp4", "avi", "mov", "wmv", "flv", "webm",
|
|
8
|
+
"m4v", "mpg", "mpeg", "ts", "m2ts",
|
|
9
|
+
// Audio / Audiobooks
|
|
10
|
+
"mp3", "m4a", "m4b", "aac", "flac", "ogg", "wav",
|
|
11
|
+
// eBooks
|
|
12
|
+
"epub", "mobi", "azw", "azw3", "pdf", "cbr", "cbz",
|
|
13
|
+
"djvu",
|
|
14
|
+
]);
|
|
15
|
+
const SUBTITLE_EXTENSIONS = new Set([
|
|
16
|
+
"srt", "ass", "ssa", "sub", "vtt",
|
|
17
|
+
]);
|
|
18
|
+
/** Directories to skip when scanning. */
|
|
19
|
+
const SKIP_DIRS = new Set([
|
|
20
|
+
".", "..", "Extras", "Sample", "samples", "Behind the Scenes",
|
|
21
|
+
"Featurettes", "Trailers",
|
|
22
|
+
]);
|
|
23
|
+
function isMediaFile(filePath) {
|
|
24
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
25
|
+
return MEDIA_EXTENSIONS.has(ext);
|
|
26
|
+
}
|
|
27
|
+
function isSubtitleFile(filePath) {
|
|
28
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
29
|
+
return SUBTITLE_EXTENSIONS.has(ext);
|
|
30
|
+
}
|
|
31
|
+
export class LibraryScanner {
|
|
32
|
+
db;
|
|
33
|
+
logger;
|
|
34
|
+
libraryService;
|
|
35
|
+
stmtCheckPath;
|
|
36
|
+
constructor(db, logger, libraryService) {
|
|
37
|
+
this.db = db;
|
|
38
|
+
this.logger = logger.child("library-scanner");
|
|
39
|
+
this.libraryService = libraryService;
|
|
40
|
+
this.stmtCheckPath = db.prepare("SELECT 1 FROM library_paths WHERE library_path = @path LIMIT 1");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Scan all configured library roots and discover media groups.
|
|
44
|
+
* Returns grouped results without metadata enrichment.
|
|
45
|
+
* Supports multi-library: scans roots from ALL libraries, or a specific
|
|
46
|
+
* library if options.libraryId is set, or roots matching specific content
|
|
47
|
+
* types if options.contentTypeIds is set.
|
|
48
|
+
*/
|
|
49
|
+
async discoverGroups(options) {
|
|
50
|
+
const allGroups = [];
|
|
51
|
+
// Determine which libraries to scan
|
|
52
|
+
const librariesToScan = this.resolveLibrariesToScan(options);
|
|
53
|
+
// Collect roots to scan: (contentTypeId, rootPath, libraryId)
|
|
54
|
+
const rootsToScan = [];
|
|
55
|
+
for (const lib of librariesToScan) {
|
|
56
|
+
if (options?.contentTypeIds && options.contentTypeIds.length > 0) {
|
|
57
|
+
// Scan only specified content types within this library
|
|
58
|
+
for (const ctId of options.contentTypeIds) {
|
|
59
|
+
const rootPath = lib.roots[ctId];
|
|
60
|
+
if (rootPath) {
|
|
61
|
+
rootsToScan.push({ contentTypeId: ctId, rootPath, libraryId: lib.id });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.logger.warn("content type has no root in library", {
|
|
65
|
+
contentTypeId: ctId,
|
|
66
|
+
library: lib.name,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Do NOT add default root; only scan explicitly requested roots
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Scan all content-type-specific roots for this library
|
|
74
|
+
for (const [ctId, rootPath] of Object.entries(lib.roots)) {
|
|
75
|
+
rootsToScan.push({ contentTypeId: ctId, rootPath, libraryId: lib.id });
|
|
76
|
+
}
|
|
77
|
+
// Also scan this library's default root
|
|
78
|
+
if (lib.defaultLibraryRoot) {
|
|
79
|
+
rootsToScan.push({
|
|
80
|
+
contentTypeId: "unknown",
|
|
81
|
+
rootPath: lib.defaultLibraryRoot,
|
|
82
|
+
libraryId: lib.id,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// De-duplicate root paths (avoid scanning the same dir twice)
|
|
88
|
+
const seenRoots = new Set();
|
|
89
|
+
for (const { contentTypeId, rootPath, libraryId } of rootsToScan) {
|
|
90
|
+
if (seenRoots.has(rootPath))
|
|
91
|
+
continue;
|
|
92
|
+
seenRoots.add(rootPath);
|
|
93
|
+
const lib = this.libraryService.get(libraryId);
|
|
94
|
+
if (!lib) {
|
|
95
|
+
this.logger.warn("library not found for root, skipping", { libraryId, rootPath });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const normalizedRoot = path.resolve(rootPath);
|
|
99
|
+
this.logger.info("scanning library root", {
|
|
100
|
+
library: lib.name,
|
|
101
|
+
contentTypeId,
|
|
102
|
+
rootPath,
|
|
103
|
+
normalizedRoot,
|
|
104
|
+
});
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = await fs.readdir(normalizedRoot, { withFileTypes: false });
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
this.logger.warn("cannot read library root directory", {
|
|
111
|
+
library: lib.name,
|
|
112
|
+
root: normalizedRoot,
|
|
113
|
+
error: err.message,
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
// Skip hidden files/dirs
|
|
119
|
+
if (entry.startsWith("."))
|
|
120
|
+
continue;
|
|
121
|
+
const fullPath = path.join(normalizedRoot, entry);
|
|
122
|
+
let stat;
|
|
123
|
+
try {
|
|
124
|
+
stat = await fs.stat(fullPath);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
this.logger.debug("cannot stat entry (permission or deleted during scan)", {
|
|
128
|
+
entry,
|
|
129
|
+
reason: "stat failed",
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (stat.isDirectory()) {
|
|
134
|
+
if (SKIP_DIRS.has(entry)) {
|
|
135
|
+
this.logger.debug("skipping excluded directory", { entry });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
await this.processSubdirectory(fullPath, entry, contentTypeId, libraryId, lib, allGroups);
|
|
139
|
+
}
|
|
140
|
+
else if (stat.isFile()) {
|
|
141
|
+
// Files directly in the library root → treat as single-item group
|
|
142
|
+
if (isMediaFile(fullPath)) {
|
|
143
|
+
allGroups.push({
|
|
144
|
+
directory: path.dirname(fullPath),
|
|
145
|
+
folderName: entry,
|
|
146
|
+
files: [fullPath],
|
|
147
|
+
subtitleFiles: [],
|
|
148
|
+
contentTypeId: this.resolveContentType(fullPath, contentTypeId, lib),
|
|
149
|
+
libraryId,
|
|
150
|
+
});
|
|
151
|
+
this.logger.debug("discovered media file at root", {
|
|
152
|
+
library: lib.name,
|
|
153
|
+
file: entry,
|
|
154
|
+
contentTypeId: this.resolveContentType(fullPath, contentTypeId, lib),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.logger.debug("skipping non-media file at root", { entry });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return allGroups;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve which libraries to scan based on options.
|
|
167
|
+
*/
|
|
168
|
+
resolveLibrariesToScan(options) {
|
|
169
|
+
if (options?.libraryId) {
|
|
170
|
+
const lib = this.libraryService.get(options.libraryId);
|
|
171
|
+
return lib ? [lib] : [];
|
|
172
|
+
}
|
|
173
|
+
if (options?.contentTypeIds && options.contentTypeIds.length > 0) {
|
|
174
|
+
// Find libraries that have roots for any of the requested types
|
|
175
|
+
const allLibs = this.libraryService.list();
|
|
176
|
+
const matchingLibs = [];
|
|
177
|
+
for (const lib of allLibs) {
|
|
178
|
+
for (const ctId of options.contentTypeIds) {
|
|
179
|
+
if (lib.roots[ctId]) {
|
|
180
|
+
matchingLibs.push(lib);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// If no library has a matching root, fall back to all libraries so the
|
|
186
|
+
// default roots are scanned (the contentTypeIds filter is handled above).
|
|
187
|
+
return matchingLibs.length > 0 ? matchingLibs : allLibs;
|
|
188
|
+
}
|
|
189
|
+
// No filter — scan all libraries
|
|
190
|
+
return this.libraryService.list();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Scan a subdirectory for media and subtitle files.
|
|
194
|
+
*/
|
|
195
|
+
async processSubdirectory(dirPath, dirName, rootContentTypeId, libraryId, library, allGroups) {
|
|
196
|
+
this.logger.debug("scanning subdirectory", { entry: dirName, fullPath: dirPath });
|
|
197
|
+
// Files directly in this directory (e.g. file.mkv inside item dir)
|
|
198
|
+
const directFiles = [];
|
|
199
|
+
const directSubs = [];
|
|
200
|
+
try {
|
|
201
|
+
const subEntries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
202
|
+
for (const sub of subEntries) {
|
|
203
|
+
if (sub.isDirectory()) {
|
|
204
|
+
if (sub.name.startsWith("."))
|
|
205
|
+
continue;
|
|
206
|
+
if (SKIP_DIRS.has(sub.name))
|
|
207
|
+
continue;
|
|
208
|
+
// This subdirectory IS a content item (e.g. Movies/Dune (2021)/Dune.mkv)
|
|
209
|
+
// Create a separate group for it rather than merging into parent
|
|
210
|
+
const nestedPath = path.join(dirPath, sub.name);
|
|
211
|
+
await this.processItemDirectory(nestedPath, sub.name, rootContentTypeId, libraryId, library, allGroups);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const subFullPath = path.join(dirPath, sub.name);
|
|
215
|
+
if (isMediaFile(subFullPath)) {
|
|
216
|
+
directFiles.push(subFullPath);
|
|
217
|
+
}
|
|
218
|
+
else if (isSubtitleFile(subFullPath)) {
|
|
219
|
+
directSubs.push(subFullPath);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Files directly in this dir → treat this dir as the content item
|
|
227
|
+
if (directFiles.length > 0) {
|
|
228
|
+
const resolvedCT = this.resolveContentType(dirPath, rootContentTypeId, library);
|
|
229
|
+
allGroups.push({
|
|
230
|
+
directory: dirPath,
|
|
231
|
+
folderName: dirName,
|
|
232
|
+
files: directFiles,
|
|
233
|
+
subtitleFiles: directSubs,
|
|
234
|
+
contentTypeId: resolvedCT,
|
|
235
|
+
libraryId,
|
|
236
|
+
});
|
|
237
|
+
this.logger.debug("discovered media group in directory", {
|
|
238
|
+
folderName: dirName,
|
|
239
|
+
files: directFiles.length,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (directFiles.length === 0 && !allGroups.some((g) => g.directory.startsWith(dirPath))) {
|
|
243
|
+
this.logger.debug("skipping directory (no media files found)", { entry: dirName, fullPath: dirPath });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Scan a directory that is expected to be a single content item.
|
|
248
|
+
* Supports nested structures like "Author/Title/file.epub" up to 2 levels deep.
|
|
249
|
+
*/
|
|
250
|
+
async processItemDirectory(dirPath, dirName, rootContentTypeId, libraryId, library, allGroups) {
|
|
251
|
+
const files = [];
|
|
252
|
+
const subtitleFiles = [];
|
|
253
|
+
try {
|
|
254
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
255
|
+
for (const entry of entries) {
|
|
256
|
+
if (entry.name.startsWith("."))
|
|
257
|
+
continue;
|
|
258
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
259
|
+
if (entry.isDirectory()) {
|
|
260
|
+
if (SKIP_DIRS.has(entry.name))
|
|
261
|
+
continue;
|
|
262
|
+
// One more level for deeply nested structures
|
|
263
|
+
try {
|
|
264
|
+
const deeper = await fs.readdir(fullPath, { withFileTypes: true });
|
|
265
|
+
for (const d of deeper) {
|
|
266
|
+
const dFull = path.join(fullPath, d.name);
|
|
267
|
+
if (d.isFile()) {
|
|
268
|
+
if (isMediaFile(dFull))
|
|
269
|
+
files.push(dFull);
|
|
270
|
+
else if (isSubtitleFile(dFull))
|
|
271
|
+
subtitleFiles.push(dFull);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// Ignore — subdirectory may be inaccessible
|
|
277
|
+
}
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (isMediaFile(fullPath))
|
|
281
|
+
files.push(fullPath);
|
|
282
|
+
else if (isSubtitleFile(fullPath))
|
|
283
|
+
subtitleFiles.push(fullPath);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (files.length > 0) {
|
|
290
|
+
const resolvedCT = this.resolveContentType(dirPath, rootContentTypeId, library);
|
|
291
|
+
allGroups.push({
|
|
292
|
+
directory: dirPath,
|
|
293
|
+
folderName: dirName,
|
|
294
|
+
files,
|
|
295
|
+
subtitleFiles,
|
|
296
|
+
contentTypeId: resolvedCT,
|
|
297
|
+
libraryId,
|
|
298
|
+
});
|
|
299
|
+
this.logger.debug("discovered media group in item directory", {
|
|
300
|
+
folderName: dirName,
|
|
301
|
+
files: files.length,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Determine the content type for a given path.
|
|
307
|
+
*/
|
|
308
|
+
resolveContentType(itemPath, rootContentTypeId, library) {
|
|
309
|
+
if (rootContentTypeId !== "unknown")
|
|
310
|
+
return rootContentTypeId;
|
|
311
|
+
for (const [ctId, root] of Object.entries(library.roots)) {
|
|
312
|
+
const normalizedRootPath = path.resolve(root);
|
|
313
|
+
if (itemPath.startsWith(normalizedRootPath)) {
|
|
314
|
+
return ctId;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return "unknown";
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Check which directories are already registered in library_paths.
|
|
321
|
+
*/
|
|
322
|
+
findExisting(directories) {
|
|
323
|
+
const existing = new Set();
|
|
324
|
+
for (const dir of directories) {
|
|
325
|
+
// Check if any library_path starts with this directory
|
|
326
|
+
const row = this.db
|
|
327
|
+
.prepare("SELECT library_path FROM library_paths WHERE library_path LIKE @pattern LIMIT 1")
|
|
328
|
+
.get({ pattern: `${dir}%` });
|
|
329
|
+
if (row) {
|
|
330
|
+
existing.add(dir);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return existing;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=library-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-scanner.js","sourceRoot":"","sources":["../../src/library/library-scanner.ts"],"names":[],"mappings":"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"}
|