@roster-lock/match-agent 0.1.0

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.
Files changed (174) hide show
  1. package/LICENSE.md +11 -0
  2. package/dist/authentication.js +53 -0
  3. package/dist/authentication.js.map +1 -0
  4. package/dist/config/constants.js +12 -0
  5. package/dist/config/constants.js.map +1 -0
  6. package/dist/config/findFile.js +32 -0
  7. package/dist/config/findFile.js.map +1 -0
  8. package/dist/config/index.js +29 -0
  9. package/dist/config/index.js.map +1 -0
  10. package/dist/config/manage.js +41 -0
  11. package/dist/config/manage.js.map +1 -0
  12. package/dist/cors.js +20 -0
  13. package/dist/cors.js.map +1 -0
  14. package/dist/editor-support/index.js +247 -0
  15. package/dist/editor-support/index.js.map +1 -0
  16. package/dist/handle-room/version-1/file-routes.js +53 -0
  17. package/dist/handle-room/version-1/file-routes.js.map +1 -0
  18. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/MultiAbort.js +76 -0
  19. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/MultiAbort.js.map +1 -0
  20. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/ensurePieceExists.js +218 -0
  21. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/ensurePieceExists.js.map +1 -0
  22. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/getVersions.js +18 -0
  23. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/getVersions.js.map +1 -0
  24. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/index.js +16 -0
  25. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/index.js.map +1 -0
  26. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/schema.js +370 -0
  27. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/schema.js.map +1 -0
  28. package/dist/handle-room/version-1/globals/FolderDB/index.js +18 -0
  29. package/dist/handle-room/version-1/globals/FolderDB/index.js.map +1 -0
  30. package/dist/handle-room/version-1/globals/FolderDB/types.js +3 -0
  31. package/dist/handle-room/version-1/globals/FolderDB/types.js.map +1 -0
  32. package/dist/handle-room/version-1/globals/Room/WebsocketRoom.js +95 -0
  33. package/dist/handle-room/version-1/globals/Room/WebsocketRoom.js.map +1 -0
  34. package/dist/handle-room/version-1/globals/Room/index.js +18 -0
  35. package/dist/handle-room/version-1/globals/Room/index.js.map +1 -0
  36. package/dist/handle-room/version-1/globals/Room/room.js +3 -0
  37. package/dist/handle-room/version-1/globals/Room/room.js.map +1 -0
  38. package/dist/handle-room/version-1/globals/index.js +20 -0
  39. package/dist/handle-room/version-1/globals/index.js.map +1 -0
  40. package/dist/handle-room/version-1/globals/types.js +3 -0
  41. package/dist/handle-room/version-1/globals/types.js.map +1 -0
  42. package/dist/handle-room/version-1/handleDownloads/DownloadTracker.js +83 -0
  43. package/dist/handle-room/version-1/handleDownloads/DownloadTracker.js.map +1 -0
  44. package/dist/handle-room/version-1/handleDownloads/index.js +35 -0
  45. package/dist/handle-room/version-1/handleDownloads/index.js.map +1 -0
  46. package/dist/handle-room/version-1/handleDownloads/types.js +3 -0
  47. package/dist/handle-room/version-1/handleDownloads/types.js.map +1 -0
  48. package/dist/handle-room/version-1/handleRoomSelections/encryption.js +21 -0
  49. package/dist/handle-room/version-1/handleRoomSelections/encryption.js.map +1 -0
  50. package/dist/handle-room/version-1/handleRoomSelections/random.js +12 -0
  51. package/dist/handle-room/version-1/handleRoomSelections/random.js.map +1 -0
  52. package/dist/handle-room/version-1/index.js +28 -0
  53. package/dist/handle-room/version-1/index.js.map +1 -0
  54. package/dist/handle-room/version-1/piece-sort.js +71 -0
  55. package/dist/handle-room/version-1/piece-sort.js.map +1 -0
  56. package/dist/handle-room/version-1/room-handler-bridge/exchange-and-download-selections.js +89 -0
  57. package/dist/handle-room/version-1/room-handler-bridge/exchange-and-download-selections.js.map +1 -0
  58. package/dist/handle-room/version-1/room-handler-bridge/http-handler.js +19 -0
  59. package/dist/handle-room/version-1/room-handler-bridge/http-handler.js.map +1 -0
  60. package/dist/handle-room/version-1/room-handler-bridge/steps.js +170 -0
  61. package/dist/handle-room/version-1/room-handler-bridge/steps.js.map +1 -0
  62. package/dist/handle-room/version-1/room-handler-bridge/ws-handler.js +28 -0
  63. package/dist/handle-room/version-1/room-handler-bridge/ws-handler.js.map +1 -0
  64. package/dist/handle-room/version-1/schema/count.js +16 -0
  65. package/dist/handle-room/version-1/schema/count.js.map +1 -0
  66. package/dist/handle-room/version-1/schema/lock.js +46 -0
  67. package/dist/handle-room/version-1/schema/lock.js.map +1 -0
  68. package/dist/handle-room/version-1/schema/selected.js +13 -0
  69. package/dist/handle-room/version-1/schema/selected.js.map +1 -0
  70. package/dist/handle-room/version-1/select.js +94 -0
  71. package/dist/handle-room/version-1/select.js.map +1 -0
  72. package/dist/index.js +180 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/server.js +88 -0
  75. package/dist/server.js.map +1 -0
  76. package/dist/utils/fs.js +32 -0
  77. package/dist/utils/fs.js.map +1 -0
  78. package/dist/utils/http-router.js +49 -0
  79. package/dist/utils/http-router.js.map +1 -0
  80. package/dist/utils/pagination.js +23 -0
  81. package/dist/utils/pagination.js.map +1 -0
  82. package/dist/utils/websocket-router.js +15 -0
  83. package/dist/utils/websocket-router.js.map +1 -0
  84. package/package.json +66 -0
  85. package/src/authentication.ts +48 -0
  86. package/src/config/constants.ts +7 -0
  87. package/src/config/findFile.ts +34 -0
  88. package/src/config/index.ts +17 -0
  89. package/src/config/manage.ts +48 -0
  90. package/src/cors.ts +23 -0
  91. package/src/editor-support/index.ts +289 -0
  92. package/src/handle-room/version-0/piece-download/availableGamePiece/index.ts +109 -0
  93. package/src/handle-room/version-0/piece-download/availableGamePiece/isUpToDate.ts +78 -0
  94. package/src/handle-room/version-0/piece-download/downloadPieceAssets/downloadSingleAsset.ts +129 -0
  95. package/src/handle-room/version-0/piece-download/downloadPieceAssets/example.ts +116 -0
  96. package/src/handle-room/version-0/piece-download/downloadPieceAssets/index.ts +144 -0
  97. package/src/handle-room/version-0/piece-download/downloadPieceAssets/types.ts +32 -0
  98. package/src/handle-room/version-0/piece-download/downloadPieceAssets/validateAssetFile.ts +54 -0
  99. package/src/handle-room/version-0/piece-download/index.ts +5 -0
  100. package/src/handle-room/version-0/piece-selection/constants.ts +39 -0
  101. package/src/handle-room/version-0/piece-selection/handleRoomSelections.ts +202 -0
  102. package/src/handle-room/version-0/piece-selection/index.ts +31 -0
  103. package/src/handle-room/version-0/piece-selection/roomErrors.ts +25 -0
  104. package/src/handle-room/version-0/piece-selection/steps/encrypton.ts +70 -0
  105. package/src/handle-room/version-0/piece-selection/steps/final-selection.ts +72 -0
  106. package/src/handle-room/version-0/piece-selection/steps/player-selection.ts +100 -0
  107. package/src/handle-room/version-1/constants.ts +44 -0
  108. package/src/handle-room/version-1/file-routes.ts +68 -0
  109. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/MultiAbort.ts +83 -0
  110. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/ensurePieceExists.ts +317 -0
  111. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/getVersions.ts +31 -0
  112. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/index.ts +13 -0
  113. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/schema.ts +547 -0
  114. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/types.ts +6 -0
  115. package/src/handle-room/version-1/globals/FolderDB/index.ts +3 -0
  116. package/src/handle-room/version-1/globals/FolderDB/types.ts +72 -0
  117. package/src/handle-room/version-1/globals/Room/WebsocketRoom.ts +118 -0
  118. package/src/handle-room/version-1/globals/Room/index.ts +3 -0
  119. package/src/handle-room/version-1/globals/Room/room.ts +8 -0
  120. package/src/handle-room/version-1/globals/index.ts +4 -0
  121. package/src/handle-room/version-1/globals/types.ts +7 -0
  122. package/src/handle-room/version-1/handleDownloads/DownloadTracker.ts +98 -0
  123. package/src/handle-room/version-1/handleDownloads/index.ts +41 -0
  124. package/src/handle-room/version-1/handleDownloads/types.ts +9 -0
  125. package/src/handle-room/version-1/handleRoomSelections/encryption.ts +29 -0
  126. package/src/handle-room/version-1/handleRoomSelections/random.ts +9 -0
  127. package/src/handle-room/version-1/index.ts +33 -0
  128. package/src/handle-room/version-1/piece-sort.ts +96 -0
  129. package/src/handle-room/version-1/room-handler-bridge/exchange-and-download-selections.ts +125 -0
  130. package/src/handle-room/version-1/room-handler-bridge/http-handler.ts +16 -0
  131. package/src/handle-room/version-1/room-handler-bridge/server.pseudo.ts +85 -0
  132. package/src/handle-room/version-1/room-handler-bridge/steps.ts +255 -0
  133. package/src/handle-room/version-1/room-handler-bridge/ws-handler.ts +30 -0
  134. package/src/handle-room/version-1/room-handler-state/RoomState.ts +132 -0
  135. package/src/handle-room/version-1/room-handler-state/bind-to-websocket.ts +93 -0
  136. package/src/handle-room/version-1/room-handler-state/handleRoomSelections.ts +71 -0
  137. package/src/handle-room/version-1/room-handler-state/handleRoomWithErrors.ts +55 -0
  138. package/src/handle-room/version-1/room-handler-state/roomErrors.ts +35 -0
  139. package/src/handle-room/version-1/room-handler-state/user-selection.ts +98 -0
  140. package/src/handle-room/version-1/schema/count.ts +13 -0
  141. package/src/handle-room/version-1/schema/lock.ts +47 -0
  142. package/src/handle-room/version-1/schema/selected.ts +14 -0
  143. package/src/handle-room/version-1/select.ts +130 -0
  144. package/src/handle-room/version-1/validateUnknown.ts +11 -0
  145. package/src/index.old.ts +71 -0
  146. package/src/index.ts +231 -0
  147. package/src/server.ts +85 -0
  148. package/src/utils/fs.ts +43 -0
  149. package/src/utils/http-router.ts +55 -0
  150. package/src/utils/pagination.ts +17 -0
  151. package/src/utils/websocket-router.ts +23 -0
  152. package/tests/integration/editor-pieces-search.test.ts +147 -0
  153. package/tests/integration/game-complete.test.ts +72 -0
  154. package/tests/integration/handle-full-selection.test.ts +143 -0
  155. package/tests/integration/helpers/bridge.ts +22 -0
  156. package/tests/integration/helpers/fakeFolderDB.ts +37 -0
  157. package/tests/integration/helpers/fakePluginRuntime.ts +46 -0
  158. package/tests/integration/helpers/lockConfig.ts +71 -0
  159. package/tests/integration/helpers/make-tar-gz.ts +41 -0
  160. package/tests/integration/helpers/piece.ts +98 -0
  161. package/tests/integration/helpers/plugin-dir.ts +26 -0
  162. package/tests/integration/helpers/room.ts +42 -0
  163. package/tests/integration/helpers/server.ts +51 -0
  164. package/tests/integration/helpers/validLockConfig.ts +80 -0
  165. package/tests/integration/piece-download-and-list.test.ts +194 -0
  166. package/tests/integration/piece-ensure-ws.test.ts +256 -0
  167. package/tests/integration/piece-files.test.ts +139 -0
  168. package/tests/integration/piece-sort-list.test.ts +113 -0
  169. package/tests/integration/protocol.test.ts +129 -0
  170. package/tests/integration/root-and-auth.test.ts +100 -0
  171. package/tests/integration/sync-dl.test.ts +62 -0
  172. package/tests/tsconfig.json +14 -0
  173. package/tsconfig.json +22 -0
  174. package/vitest.config.ts +10 -0
@@ -0,0 +1,317 @@
1
+ import { IFolderDB, StoredPieceListing } from "../types";
2
+ import { RosterLockV1Config, ROSTERLOCK_DOWNLOAD_STATE } from "@roster-lock/types";
3
+
4
+ import { existsSync as fsExists, createReadStream } from "node:fs";
5
+ import { rm as fsRm, mkdir } from "node:fs/promises";
6
+ import { join as pathJoin, isAbsolute as isAbsolutePath, relative as pathRelative } from "node:path";
7
+ import { ProgressHandlers } from "../../../handleDownloads/types";
8
+
9
+ import { PluginManager } from "@roster-lock/plugin-runtime";
10
+ import { getDownloadSourceVersion } from "./getVersions";
11
+ import { prepareDatabase } from "./schema";
12
+ import { MultiAbortSignal, raceWithAbort } from "./MultiAbort";
13
+ import { ulid } from 'ulid';
14
+ import { HTTPError } from "../../../../../utils/http-router";
15
+ import { getMatchingAssetsForFile } from "@roster-lock/shared";
16
+ import { getFilesFromFolder } from "../../../../../utils/fs";
17
+ import { Readable } from "node:stream";
18
+
19
+
20
+ type RosterLockPiece = RosterLockV1Config["rosters"][string][number];
21
+
22
+ type PieceIndex = {
23
+ engineName: string,
24
+ pieceType: string,
25
+ logic: string,
26
+ media: string,
27
+ }
28
+
29
+ function pieceIndexOf(
30
+ engine: RosterLockV1Config["engine"],
31
+ pieceType: string,
32
+ piece: Pick<RosterLockPiece, "version">,
33
+ ): PieceIndex {
34
+ return {
35
+ engineName: engine.name,
36
+ pieceType,
37
+ logic: piece.version.logic,
38
+ media: piece.version.media,
39
+ };
40
+ }
41
+
42
+ export class SQLite3FolderDB implements IFolderDB {
43
+ private db: ReturnType<typeof prepareDatabase>;
44
+ private activeDownloads = new Map<string, {
45
+ multiSignal: MultiAbortSignal,
46
+ result: Promise<string>
47
+ }>();
48
+
49
+ constructor(public folder: string, private pluginRuntime: PluginManager){
50
+ if(!isAbsolutePath(folder)){
51
+ throw new Error("Folder must be an absolute path");
52
+ }
53
+ if(!fsExists(folder)){
54
+ throw new Error("Folder does not exist");
55
+ }
56
+
57
+ this.db = prepareDatabase(pathJoin(folder, "rosterlock.sqlite3.db"));
58
+ }
59
+
60
+ public close(){
61
+ this.db.close();
62
+ }
63
+
64
+ private pieceFolder(
65
+ engine: RosterLockV1Config["engine"], pieceType: string, folderName: string
66
+ ){
67
+ return pathJoin(this.folder, engine.name, pieceType, folderName);
68
+ }
69
+
70
+ async listPieces(
71
+ enginename: string,
72
+ pieceType: string,
73
+ logicIds: Array<string>,
74
+ pagination: { page: number, limit: number }
75
+ ){
76
+ const pieces = this.db.listPieces(
77
+ enginename,
78
+ pieceType,
79
+ logicIds,
80
+ pagination,
81
+ )
82
+ return pieces
83
+ }
84
+
85
+ async searchPieces(
86
+ query: {
87
+ engineName: string,
88
+ pieceType?: string,
89
+ search?: string,
90
+ page: number,
91
+ limit: number,
92
+ }
93
+ ): Promise<StoredPieceListing> {
94
+ const { total, items } = this.db.searchPieces(query);
95
+ const pieceKeys = items.map((item)=>({
96
+ pieceType: item.pieceType, logic: item.version.logic, media: item.version.media,
97
+ }));
98
+ const sourcesByPiece = this.db.getDownloadSourcesFor(query.engineName, pieceKeys);
99
+ const statesByPiece = this.db.getPieceStatesFor(query.engineName, pieceKeys);
100
+ return {
101
+ total,
102
+ items: items.map((item)=>{
103
+ const key = `${item.pieceType}\x00${item.version.logic}\x00${item.version.media}`;
104
+ const state = statesByPiece.get(key);
105
+ return {
106
+ piece: {
107
+ ...item,
108
+ downloadSources: (sourcesByPiece.get(key) ?? []).map((source)=>({
109
+ source: source.source,
110
+ lastTest: source.last_test === null ? null : source.last_test * 1000,
111
+ success: source.success === null ? null : source.success === 1,
112
+ ...(source.error === null ? {} : { error: source.error }),
113
+ })),
114
+ },
115
+ completedAt: state?.completedAt ? state.completedAt * 1000 : null,
116
+ };
117
+ }),
118
+ };
119
+ }
120
+
121
+ async* getFilesofAsset(
122
+ engineConfig: RosterLockV1Config["engine"],
123
+ pieceType: string,
124
+ piece: Pick<RosterLockPiece, "version" | "pathVariables">,
125
+ assetName: string
126
+ ): AsyncIterable<string>{
127
+ const state = this.db.getPieceState(pieceIndexOf(engineConfig, pieceType, piece));
128
+ if(!state) throw new HTTPError(404, "Piece Doesn't exist");
129
+ if(state.status !== "complete")
130
+ throw new HTTPError(409, "Piece not finished");
131
+ const folder = this.pieceFolder(
132
+ engineConfig, pieceType, state.folderName
133
+ );
134
+ for await (const path of getFilesFromFolder(folder)){
135
+ const assets = getMatchingAssetsForFile(
136
+ engineConfig.pieceDefinitions[pieceType],
137
+ piece.pathVariables,
138
+ path
139
+ );
140
+ if(assets[0]?.name === assetName)
141
+ yield path;
142
+ }
143
+ }
144
+
145
+ async getPieceFileContents(
146
+ engineConfig: RosterLockV1Config["engine"],
147
+ pieceType: string,
148
+ piece: Pick<RosterLockPiece, "version" | "pathVariables">,
149
+ filePath: string
150
+ ): Promise<Readable> {
151
+ const state = this.db.getPieceState(pieceIndexOf(engineConfig, pieceType, piece));
152
+ if(!state) throw new HTTPError(404, "Piece doesn't exist");
153
+ if(state.status !== "complete")
154
+ throw new HTTPError(409, "Piece not finished");
155
+ const folder = this.pieceFolder(
156
+ engineConfig, pieceType, state.folderName
157
+ );
158
+ const fullPath = pathJoin(folder, filePath);
159
+ const rel = pathRelative(folder, fullPath)
160
+ if(rel.startsWith("..") || isAbsolutePath(rel))
161
+ throw new HTTPError(400, "Can't back out of folder");
162
+ if(!fsExists(fullPath))
163
+ throw new HTTPError(404, "File doesn't exist");
164
+ return createReadStream(fullPath)
165
+ }
166
+
167
+
168
+ async ensurePieceExists(
169
+ lockConfigEngine: RosterLockV1Config["engine"],
170
+ pieceType: string,
171
+ selectedPiece: RosterLockPiece,
172
+ progressHandlers: ProgressHandlers,
173
+ ){
174
+ const pieceIndex = pieceIndexOf(lockConfigEngine, pieceType, selectedPiece);
175
+ // Check if already completed
176
+ const state = this.db.getPieceState(pieceIndex);
177
+ if(state && state.status === "complete") return this.pieceFolder(
178
+ lockConfigEngine, pieceType, state.folderName
179
+ );
180
+
181
+ // Check if currently downloading
182
+ const key = pieceToKey(pieceIndex);
183
+ const activePromise = this.activeDownloads.get(key);
184
+ if(activePromise){
185
+ activePromise.multiSignal.addSignal(progressHandlers);
186
+ try {
187
+ return await raceWithAbort(activePromise.result, progressHandlers.abortSignal);
188
+ }catch(e){
189
+ activePromise.multiSignal.removeSignal(progressHandlers);
190
+ throw e;
191
+ }
192
+ }
193
+
194
+ // Check if already exists but failed
195
+ if(state && state.status === "pending"){
196
+ this.db.resetPieceStatus(pieceIndex);
197
+ }
198
+
199
+ // Start a new download
200
+ const multiSignal = new MultiAbortSignal([progressHandlers]);
201
+ const promise = this.addNewPiece(lockConfigEngine, pieceType, selectedPiece, multiSignal.abortSignal);
202
+ this.activeDownloads.set(key, {
203
+ multiSignal,
204
+ result: promise,
205
+ });
206
+ // promise is already awaited below via raceWithAbort; catch here too so
207
+ // this second attached continuation doesn't count as an unhandled
208
+ // rejection (each .then/.finally chain is tracked independently by Node).
209
+ promise.finally(()=>{
210
+ multiSignal.clear();
211
+ this.activeDownloads.delete(key);
212
+ }).catch(()=>{});
213
+ try {
214
+ return await raceWithAbort(promise, progressHandlers.abortSignal);
215
+ }catch(e){
216
+ multiSignal.removeSignal(progressHandlers);
217
+ throw e;
218
+ }
219
+ }
220
+
221
+ private async addNewPiece(
222
+ lockConfigEngine: RosterLockV1Config["engine"],
223
+ pieceType: string,
224
+ newPiece: RosterLockPiece,
225
+ abortSignal: AbortSignal,
226
+ ){
227
+ const pieceIndex = pieceIndexOf(lockConfigEngine, pieceType, newPiece);
228
+ const { folderName }: { folderName: string } = await (async ()=>{
229
+ const existingState = this.db.getPieceState(pieceIndex);
230
+ if(existingState){
231
+ await fsRm(
232
+ this.pieceFolder(lockConfigEngine, pieceType, existingState.folderName),
233
+ { recursive: true, force: true }
234
+ );
235
+ return { folderName: existingState.folderName };
236
+ }
237
+ const pieceFolder = ulid().toLowerCase();
238
+ this.db.insertNewPiece(
239
+ {
240
+ engineName: lockConfigEngine.name,
241
+ pieceType,
242
+ version: newPiece.version,
243
+ humanInfo: newPiece.humanInfo,
244
+ pathVariables: newPiece.pathVariables,
245
+ downloadSources: newPiece.downloadSources,
246
+ },
247
+ pieceFolder
248
+ );
249
+ return { folderName: pieceFolder };
250
+ })();
251
+
252
+
253
+ const fullPath = this.pieceFolder(lockConfigEngine, pieceType, folderName);
254
+ for(const downloadLocation of newPiece.downloadSources){
255
+ try {
256
+ await mkdir(fullPath, { recursive: true });
257
+ const { finishPromise } = await this.pluginRuntime.downloadToFolder(
258
+ {
259
+ url: downloadLocation,
260
+ destinationFolder: fullPath,
261
+ processHandlers: {
262
+ onProgress: (progress) => {
263
+ this.emitProgress(pieceIndex, {
264
+ type: ROSTERLOCK_DOWNLOAD_STATE.downloadProgress,
265
+ pieceType: pieceType,
266
+ pieceVersions: { logic: pieceIndex.logic, media: pieceIndex.media },
267
+ progress,
268
+ });
269
+ },
270
+ abortSignal,
271
+ }
272
+ }
273
+ );
274
+ await finishPromise;
275
+ this.emitProgress(pieceIndex, {
276
+ type: ROSTERLOCK_DOWNLOAD_STATE.downloadValidation,
277
+ pieceType: pieceIndex.pieceType,
278
+ pieceVersions: { logic: pieceIndex.logic, media: pieceIndex.media },
279
+ });
280
+ const downloadedVersions = await getDownloadSourceVersion(
281
+ fullPath, newPiece.pathVariables, lockConfigEngine.pieceDefinitions[pieceType]
282
+ );
283
+ if(downloadedVersions.logic !== pieceIndex.logic || downloadedVersions.media !== pieceIndex.media){
284
+ throw new Error("Version Mismatch");
285
+ }
286
+ this.db.pieceSuccessfullyDownloaded(pieceIndex, downloadLocation);
287
+ return fullPath;
288
+ }catch(e){
289
+ this.db.pieceFailedToDownload(pieceIndex, downloadLocation, (e as Error).message);
290
+ await fsRm(fullPath, { recursive: true, force: true });
291
+ this.emitProgress(pieceIndex, {
292
+ type: ROSTERLOCK_DOWNLOAD_STATE.downloadFailure,
293
+ pieceType,
294
+ pieceVersions: { logic: pieceIndex.logic, media: pieceIndex.media },
295
+ error: (e as Error).message,
296
+ });
297
+ }
298
+ }
299
+ throw new Error("Failed To Download");
300
+ }
301
+
302
+
303
+ private emitProgress(
304
+ pieceIndex: PieceIndex,
305
+ event: Parameters<ProgressHandlers["onProgress"]>[0]
306
+ ){
307
+ const key = pieceToKey(pieceIndex);
308
+ const multiSignal = this.activeDownloads.get(key)?.multiSignal;
309
+ if(!multiSignal) return;
310
+ multiSignal.emitEvent(event);
311
+ }
312
+ }
313
+
314
+
315
+ function pieceToKey(piece: PieceIndex){
316
+ return `${piece.engineName}-${piece.pieceType}-${piece.logic}-${piece.media}`;
317
+ }
@@ -0,0 +1,31 @@
1
+
2
+ import { RosterLockV1Config } from "@roster-lock/types";
3
+ import { calculatePieceVersion, getAssetsOfFiles } from "@roster-lock/shared";
4
+ import { getFilesFromFolder } from "../../../../../utils/fs";
5
+
6
+ type PieceDefinition = RosterLockV1Config["engine"]["pieceDefinitions"][string];
7
+
8
+ export async function getDownloadSourceVersion(
9
+ folder: string,
10
+ pathVariables: Record<string, string>,
11
+ pieceDefinition: PieceDefinition,
12
+ ){
13
+ const { filesWithAssets } = await getAssetsOfFiles(
14
+ getFilesFromFolder(folder), pathVariables, pieceDefinition
15
+ );
16
+ return calculatePieceVersion(
17
+ filesWithAssets, (relativePath) => getFileFromRoot(folder, relativePath)
18
+ );
19
+ }
20
+
21
+ import { join as pathJoin, relative as pathRelative, sep as pathSep } from "node:path";
22
+
23
+ import { stat as fsStat } from "node:fs/promises";
24
+ import { createReadStream } from "node:fs";
25
+ async function getFileFromRoot(
26
+ root: string, relativePath: string
27
+ ): Promise<{ byteSize: number, stream: AsyncIterable<Uint8Array> }> {
28
+ const filePath = pathJoin(root, relativePath);
29
+ const stats = await fsStat(filePath);
30
+ return { byteSize: stats.size, stream: createReadStream(filePath) } ;
31
+ }
@@ -0,0 +1,13 @@
1
+
2
+ import { PluginManager } from "@roster-lock/plugin-runtime";
3
+ import { SQLite3FolderDB } from "./ensurePieceExists";
4
+ export { SQLite3FolderDB };
5
+
6
+ const dbCache = new Map<string, SQLite3FolderDB>();
7
+ export function getSQLite3FolderDB(folder: string, pluginRuntime: PluginManager){
8
+ const existing = dbCache.get(folder);
9
+ if(existing) return existing;
10
+ const newDB = new SQLite3FolderDB(folder, pluginRuntime);
11
+ dbCache.set(folder, newDB);
12
+ return newDB;
13
+ }