@fdm-monster/server 2.0.8 → 2.0.9
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/.yarn/install-state.gz +0 -0
- package/README.md +1 -1
- package/RELEASE_NOTES.MD +12 -0
- package/dist/container.js +0 -6
- package/dist/container.js.map +1 -1
- package/dist/container.tokens.js +0 -2
- package/dist/container.tokens.js.map +1 -1
- package/dist/controllers/printer-files.controller.js +8 -75
- package/dist/controllers/printer-files.controller.js.map +1 -1
- package/dist/controllers/validation/printer-files-controller.validation.js +7 -0
- package/dist/controllers/validation/printer-files-controller.validation.js.map +1 -1
- package/dist/server.constants.js +1 -2
- package/dist/server.constants.js.map +1 -1
- package/dist/services/bambu.api.js +24 -12
- package/dist/services/bambu.api.js.map +1 -1
- package/dist/services/file-storage.service.js.map +1 -1
- package/dist/services/moonraker.api.js +42 -9
- package/dist/services/moonraker.api.js.map +1 -1
- package/dist/services/octoprint/octoprint-api.routes.js +1 -1
- package/dist/services/octoprint/octoprint-api.routes.js.map +1 -1
- package/dist/services/octoprint/octoprint.client.js +30 -7
- package/dist/services/octoprint/octoprint.client.js.map +1 -1
- package/dist/services/octoprint/utils/file.utils.js +42 -5
- package/dist/services/octoprint/utils/file.utils.js.map +1 -1
- package/dist/services/octoprint.api.js +12 -8
- package/dist/services/octoprint.api.js.map +1 -1
- package/dist/services/printer-api.interface.js.map +1 -1
- package/dist/services/prusa-link/prusa-link.api.js +21 -5
- package/dist/services/prusa-link/prusa-link.api.js.map +1 -1
- package/package.json +3 -3
- package/dist/state/file.cache.js +0 -68
- package/dist/state/file.cache.js.map +0 -1
- package/dist/state/printer-files.store.js +0 -92
- package/dist/state/printer-files.store.js.map +0 -1
- package/dist/tasks/printer-files-load.task.js +0 -29
- package/dist/tasks/printer-files-load.task.js.map +0 -1
package/dist/state/file.cache.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "FileCache", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return FileCache;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
const _runtimeexceptions = require("../exceptions/runtime.exceptions");
|
|
12
|
-
class FileCache {
|
|
13
|
-
printerFileStorage = new Map();
|
|
14
|
-
totalFileCount = 0;
|
|
15
|
-
logger;
|
|
16
|
-
constructor(loggerFactory){
|
|
17
|
-
this.logger = loggerFactory(FileCache.name);
|
|
18
|
-
}
|
|
19
|
-
cachePrinterFiles(printerId, files) {
|
|
20
|
-
if (!printerId) {
|
|
21
|
-
throw new Error("File Cache cant get a null/undefined printer id");
|
|
22
|
-
}
|
|
23
|
-
this.printerFileStorage.set(printerId, files);
|
|
24
|
-
this.updateCacheFileRefCount();
|
|
25
|
-
}
|
|
26
|
-
getPrinterFiles(printerId) {
|
|
27
|
-
if (!printerId) {
|
|
28
|
-
throw new Error("File Cache cant get a null/undefined printer id");
|
|
29
|
-
}
|
|
30
|
-
return this.printerFileStorage.get(printerId);
|
|
31
|
-
}
|
|
32
|
-
updateCacheFileRefCount() {
|
|
33
|
-
let totalFiles = 0;
|
|
34
|
-
for (const storage of this.printerFileStorage.values()){
|
|
35
|
-
totalFiles += storage?.length || 0;
|
|
36
|
-
}
|
|
37
|
-
if (totalFiles !== this.totalFileCount) {
|
|
38
|
-
this.totalFileCount = totalFiles;
|
|
39
|
-
this.logger.log(`Cache updated. ${this.totalFileCount} file storage references cached.`);
|
|
40
|
-
}
|
|
41
|
-
return totalFiles;
|
|
42
|
-
}
|
|
43
|
-
purgePrinterId(printerId) {
|
|
44
|
-
if (!printerId) {
|
|
45
|
-
throw new _runtimeexceptions.ValidationException("Parameter printerId was not provided.");
|
|
46
|
-
}
|
|
47
|
-
const fileStorage = this.printerFileStorage.get(printerId);
|
|
48
|
-
if (!fileStorage) {
|
|
49
|
-
this.logger.warn("Did not remove printer File Storage as it was not found");
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
this.printerFileStorage.delete(printerId);
|
|
53
|
-
this.logger.log(`Purged printer file cache`);
|
|
54
|
-
}
|
|
55
|
-
purgeFile(printerId, filePath) {
|
|
56
|
-
const files = this.getPrinterFiles(printerId);
|
|
57
|
-
if (!files) return;
|
|
58
|
-
const fileIndex = files.findIndex((f)=>f.path === filePath);
|
|
59
|
-
if (fileIndex === -1) {
|
|
60
|
-
this.logger.warn(`A file removal was ordered but this file was not found in files cache for provided printer id`, filePath);
|
|
61
|
-
return this.logger.log("File was not found in cached printer fileList");
|
|
62
|
-
}
|
|
63
|
-
files.splice(fileIndex, 1);
|
|
64
|
-
this.logger.log(`File was removed from cache`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
//# sourceMappingURL=file.cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/state/file.cache.ts"],"names":["FileCache","printerFileStorage","Map","totalFileCount","logger","loggerFactory","name","cachePrinterFiles","printerId","files","Error","set","updateCacheFileRefCount","getPrinterFiles","get","totalFiles","storage","values","length","log","purgePrinterId","ValidationException","fileStorage","warn","delete","purgeFile","filePath","fileIndex","findIndex","f","path","splice"],"mappings":";;;;+BAKaA;;;eAAAA;;;mCALuB;AAK7B,MAAMA;IACMC,qBAAqB,IAAIC,MAAyB;IAC3DC,iBAAiB,EAAE;IAEVC,OAAsB;IAEvC,YAAYC,aAA6B,CAAE;QACzC,IAAI,CAACD,MAAM,GAAGC,cAAcL,UAAUM,IAAI;IAC5C;IAEAC,kBAAkBC,SAAiB,EAAEC,KAAgB,EAAE;QACrD,IAAI,CAACD,WAAW;YACd,MAAM,IAAIE,MAAM;QAClB;QACA,IAAI,CAACT,kBAAkB,CAACU,GAAG,CAACH,WAAWC;QACvC,IAAI,CAACG,uBAAuB;IAC9B;IAEAC,gBAAgBL,SAAiB,EAAE;QACjC,IAAI,CAACA,WAAW;YACd,MAAM,IAAIE,MAAM;QAClB;QACA,OAAO,IAAI,CAACT,kBAAkB,CAACa,GAAG,CAACN;IACrC;IAEAI,0BAA0B;QACxB,IAAIG,aAAa;QACjB,KAAK,MAAMC,WAAW,IAAI,CAACf,kBAAkB,CAACgB,MAAM,GAAI;YACtDF,cAAcC,SAASE,UAAU;QACnC;QAEA,IAAIH,eAAe,IAAI,CAACZ,cAAc,EAAE;YACtC,IAAI,CAACA,cAAc,GAAGY;YACtB,IAAI,CAACX,MAAM,CAACe,GAAG,CAAC,CAAC,eAAe,EAAE,IAAI,CAAChB,cAAc,CAAC,gCAAgC,CAAC;QACzF;QAEA,OAAOY;IACT;IAEAK,eAAeZ,SAAiB,EAAE;QAChC,IAAI,CAACA,WAAW;YACd,MAAM,IAAIa,sCAAmB,CAAC;QAChC;QAEA,MAAMC,cAAc,IAAI,CAACrB,kBAAkB,CAACa,GAAG,CAACN;QAEhD,IAAI,CAACc,aAAa;YAChB,IAAI,CAAClB,MAAM,CAACmB,IAAI,CAAC;YACjB;QACF;QAEA,IAAI,CAACtB,kBAAkB,CAACuB,MAAM,CAAChB;QAE/B,IAAI,CAACJ,MAAM,CAACe,GAAG,CAAC,CAAC,yBAAyB,CAAC;IAC7C;IAEAM,UAAUjB,SAAiB,EAAEkB,QAAgB,EAAE;QAC7C,MAAMjB,QAAQ,IAAI,CAACI,eAAe,CAACL;QACnC,IAAI,CAACC,OAAO;QAEZ,MAAMkB,YAAYlB,MAAMmB,SAAS,CAAC,CAACC,IAAMA,EAAEC,IAAI,KAAKJ;QACpD,IAAIC,cAAc,CAAC,GAAG;YAEpB,IAAI,CAACvB,MAAM,CAACmB,IAAI,CACd,CAAC,6FAA6F,CAAC,EAC/FG;YAGF,OAAO,IAAI,CAACtB,MAAM,CAACe,GAAG,CAAC;QACzB;QAEAV,MAAMsB,MAAM,CAACJ,WAAW;QACxB,IAAI,CAACvB,MAAM,CAACe,GAAG,CAAC,CAAC,2BAA2B,CAAC;IAC/C;AACF"}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "PrinterFilesStore", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return PrinterFilesStore;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
const _runtimeexceptions = require("../exceptions/runtime.exceptions");
|
|
12
|
-
const _node = require("@sentry/node");
|
|
13
|
-
class PrinterFilesStore {
|
|
14
|
-
printerCache;
|
|
15
|
-
fileCache;
|
|
16
|
-
printerApiFactory;
|
|
17
|
-
logger;
|
|
18
|
-
constructor(loggerFactory, printerCache, fileCache, printerApiFactory){
|
|
19
|
-
this.printerCache = printerCache;
|
|
20
|
-
this.fileCache = fileCache;
|
|
21
|
-
this.printerApiFactory = printerApiFactory;
|
|
22
|
-
this.logger = loggerFactory(PrinterFilesStore.name);
|
|
23
|
-
}
|
|
24
|
-
async loadFilesStore() {
|
|
25
|
-
const printers = await this.printerCache.listCachedPrinters(true);
|
|
26
|
-
for (const printer of printers.filter((p)=>p.enabled)){
|
|
27
|
-
try {
|
|
28
|
-
const printerFiles = await this.loadFiles(printer.id);
|
|
29
|
-
this.fileCache.cachePrinterFiles(printer.id, printerFiles);
|
|
30
|
-
} catch (e) {
|
|
31
|
-
(0, _node.captureException)(e);
|
|
32
|
-
this.logger.error(`Files store failed to load file list for printer ${printer.name}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async loadFiles(printerId) {
|
|
37
|
-
const loginDto = await this.printerCache.getLoginDtoAsync(printerId);
|
|
38
|
-
const printerApi = this.printerApiFactory.getScopedPrinter(loginDto);
|
|
39
|
-
const files = await printerApi.getFiles();
|
|
40
|
-
this.fileCache.cachePrinterFiles(printerId, files);
|
|
41
|
-
return files;
|
|
42
|
-
}
|
|
43
|
-
getFiles(printerId) {
|
|
44
|
-
return this.fileCache.getPrinterFiles(printerId);
|
|
45
|
-
}
|
|
46
|
-
getOutdatedFiles(printerId, ageDaysMax) {
|
|
47
|
-
if (!ageDaysMax) throw new _runtimeexceptions.ValidationException("ageDaysMax property is required to get printer's outdated files");
|
|
48
|
-
const printerFiles = this.getFiles(printerId);
|
|
49
|
-
if (!printerFiles?.length) return [];
|
|
50
|
-
const nowTimestampSeconds = Date.now() / 1000;
|
|
51
|
-
return printerFiles.filter((file)=>!!file.date && file.date + ageDaysMax * 86400 < nowTimestampSeconds);
|
|
52
|
-
}
|
|
53
|
-
async deleteOutdatedFiles(printerId, ageDaysMax) {
|
|
54
|
-
const printerApi = this.printerApiFactory.getById(printerId);
|
|
55
|
-
const failedFiles = [];
|
|
56
|
-
const succeededFiles = [];
|
|
57
|
-
const nonRecursiveFiles = this.getOutdatedFiles(printerId, ageDaysMax);
|
|
58
|
-
const name = (await this.printerCache.getCachedPrinterOrThrowAsync(printerId)).name;
|
|
59
|
-
for (let file of nonRecursiveFiles){
|
|
60
|
-
try {
|
|
61
|
-
await printerApi.deleteFile(file.path);
|
|
62
|
-
succeededFiles.push(file);
|
|
63
|
-
} catch (e) {
|
|
64
|
-
failedFiles.push(file);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
this.logger.log(`Deleted ${succeededFiles.length} successfully and ${failedFiles.length} with failure for printer ${name}.`);
|
|
68
|
-
return {
|
|
69
|
-
failedFiles,
|
|
70
|
-
succeededFiles
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
async purgePrinterFiles(printerId) {
|
|
74
|
-
const printerState = await this.printerCache.getCachedPrinterOrThrowAsync(printerId);
|
|
75
|
-
this.logger.log(`Purging file cache from printer`);
|
|
76
|
-
this.fileCache.purgePrinterId(printerState.id);
|
|
77
|
-
this.logger.log(`Clearing printer files successful`);
|
|
78
|
-
}
|
|
79
|
-
async purgeFiles() {
|
|
80
|
-
const allPrinters = await this.printerCache.listCachedPrinters();
|
|
81
|
-
this.logger.log(`Clearing file caches`);
|
|
82
|
-
for (let printer of allPrinters){
|
|
83
|
-
this.fileCache.purgePrinterId(printer.id);
|
|
84
|
-
}
|
|
85
|
-
this.logger.log(`Clearing caches successful.`);
|
|
86
|
-
}
|
|
87
|
-
async deleteFile(printerId, filePath) {
|
|
88
|
-
this.fileCache.purgeFile(printerId, filePath);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
//# sourceMappingURL=printer-files.store.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/state/printer-files.store.ts"],"names":["PrinterFilesStore","logger","loggerFactory","printerCache","fileCache","printerApiFactory","name","loadFilesStore","printers","listCachedPrinters","printer","filter","p","enabled","printerFiles","loadFiles","id","cachePrinterFiles","e","captureException","error","printerId","loginDto","getLoginDtoAsync","printerApi","getScopedPrinter","files","getFiles","getPrinterFiles","getOutdatedFiles","ageDaysMax","ValidationException","length","nowTimestampSeconds","Date","now","file","date","deleteOutdatedFiles","getById","failedFiles","succeededFiles","nonRecursiveFiles","getCachedPrinterOrThrowAsync","deleteFile","path","push","log","purgePrinterFiles","printerState","purgePrinterId","purgeFiles","allPrinters","filePath","purgeFile"],"mappings":";;;;+BAQaA;;;eAAAA;;;mCARuB;sBAKH;AAG1B,MAAMA;;;;IACMC,OAAsB;IAEvC,YACEC,aAA6B,EAC7B,AAAiBC,YAA0B,EAC3C,AAAgBC,SAAoB,EACpC,AAAiBC,iBAAoC,CACrD;aAHiBF,eAAAA;aACDC,YAAAA;aACCC,oBAAAA;QAEjB,IAAI,CAACJ,MAAM,GAAGC,cAAcF,kBAAkBM,IAAI;IACpD;IAEA,MAAMC,iBAAgC;QACpC,MAAMC,WAAW,MAAM,IAAI,CAACL,YAAY,CAACM,kBAAkB,CAAC;QAC5D,KAAK,MAAMC,WAAWF,SAASG,MAAM,CAAC,CAACC,IAAMA,EAAEC,OAAO,EAAG;YACvD,IAAI;gBACF,MAAMC,eAAe,MAAM,IAAI,CAACC,SAAS,CAACL,QAAQM,EAAE;gBACpD,IAAI,CAACZ,SAAS,CAACa,iBAAiB,CAACP,QAAQM,EAAE,EAAEF;YAC/C,EAAE,OAAOI,GAAG;gBACVC,IAAAA,sBAAgB,EAACD;gBACjB,IAAI,CAACjB,MAAM,CAACmB,KAAK,CAAC,CAAC,iDAAiD,EAAEV,QAAQJ,IAAI,EAAE;YACtF;QACF;IACF;IAEA,MAAMS,UAAUM,SAAiB,EAAgB;QAC/C,MAAMC,WAAW,MAAM,IAAI,CAACnB,YAAY,CAACoB,gBAAgB,CAACF;QAC1D,MAAMG,aAAa,IAAI,CAACnB,iBAAiB,CAACoB,gBAAgB,CAACH;QAC3D,MAAMI,QAAQ,MAAMF,WAAWG,QAAQ;QACvC,IAAI,CAACvB,SAAS,CAACa,iBAAiB,CAACI,WAAWK;QAC5C,OAAOA;IACT;IAEAC,SAASN,SAAiB,EAAE;QAC1B,OAAO,IAAI,CAACjB,SAAS,CAACwB,eAAe,CAACP;IACxC;IAEAQ,iBAAiBR,SAAiB,EAAES,UAAkB,EAAE;QACtD,IAAI,CAACA,YAAY,MAAM,IAAIC,sCAAmB,CAAC;QAC/C,MAAMjB,eAAe,IAAI,CAACa,QAAQ,CAACN;QACnC,IAAI,CAACP,cAAckB,QAAQ,OAAO,EAAE;QACpC,MAAMC,sBAAsBC,KAAKC,GAAG,KAAK;QACzC,OAAOrB,aAAaH,MAAM,CAAC,CAACyB,OAAS,CAAC,CAACA,KAAKC,IAAI,IAAID,KAAKC,IAAI,GAAGP,aAAa,QAAQG;IACvF;IAEA,MAAMK,oBAAoBjB,SAAiB,EAAES,UAAkB,EAAE;QAC/D,MAAMN,aAAa,IAAI,CAACnB,iBAAiB,CAACkC,OAAO,CAAClB;QAElD,MAAMmB,cAAc,EAAE;QACtB,MAAMC,iBAAiB,EAAE;QACzB,MAAMC,oBAAoB,IAAI,CAACb,gBAAgB,CAACR,WAAWS;QAE3D,MAAMxB,OAAO,AAAC,CAAA,MAAM,IAAI,CAACH,YAAY,CAACwC,4BAA4B,CAACtB,UAAS,EAAGf,IAAI;QAEnF,KAAK,IAAI8B,QAAQM,kBAAmB;YAClC,IAAI;gBACF,MAAMlB,WAAWoB,UAAU,CAACR,KAAKS,IAAI;gBACrCJ,eAAeK,IAAI,CAACV;YACtB,EAAE,OAAOlB,GAAG;gBACVsB,YAAYM,IAAI,CAACV;YACnB;QACF;QAEA,IAAI,CAACnC,MAAM,CAAC8C,GAAG,CACb,CAAC,QAAQ,EAAEN,eAAeT,MAAM,CAAC,kBAAkB,EAAEQ,YAAYR,MAAM,CAAC,0BAA0B,EAAE1B,KAAK,CAAC,CAAC;QAE7G,OAAO;YACLkC;YACAC;QACF;IACF;IAEA,MAAMO,kBAAkB3B,SAAiB,EAAE;QACzC,MAAM4B,eAAe,MAAM,IAAI,CAAC9C,YAAY,CAACwC,4BAA4B,CAACtB;QAE1E,IAAI,CAACpB,MAAM,CAAC8C,GAAG,CAAC,CAAC,+BAA+B,CAAC;QACjD,IAAI,CAAC3C,SAAS,CAAC8C,cAAc,CAACD,aAAajC,EAAE;QAC7C,IAAI,CAACf,MAAM,CAAC8C,GAAG,CAAC,CAAC,iCAAiC,CAAC;IACrD;IAEA,MAAMI,aAAa;QACjB,MAAMC,cAAc,MAAM,IAAI,CAACjD,YAAY,CAACM,kBAAkB;QAE9D,IAAI,CAACR,MAAM,CAAC8C,GAAG,CAAC,CAAC,oBAAoB,CAAC;QACtC,KAAK,IAAIrC,WAAW0C,YAAa;YAC/B,IAAI,CAAChD,SAAS,CAAC8C,cAAc,CAACxC,QAAQM,EAAE;QAC1C;QACA,IAAI,CAACf,MAAM,CAAC8C,GAAG,CAAC,CAAC,2BAA2B,CAAC;IAC/C;IAEA,MAAMH,WAAWvB,SAAiB,EAAEgC,QAAgB,EAAE;QACpD,IAAI,CAACjD,SAAS,CAACkD,SAAS,CAACjC,WAAWgC;IACtC;AACF"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "PrinterFilesLoadTask", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return PrinterFilesLoadTask;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
class PrinterFilesLoadTask {
|
|
12
|
-
printerFilesStore;
|
|
13
|
-
logger;
|
|
14
|
-
constructor(loggerFactory, printerFilesStore){
|
|
15
|
-
this.printerFilesStore = printerFilesStore;
|
|
16
|
-
this.logger = loggerFactory(PrinterFilesLoadTask.name);
|
|
17
|
-
}
|
|
18
|
-
async run() {
|
|
19
|
-
this.logger.log("Loading files store in background");
|
|
20
|
-
try {
|
|
21
|
-
await this.printerFilesStore.loadFilesStore();
|
|
22
|
-
this.logger.log("Files store loaded successfully in background");
|
|
23
|
-
} catch (error) {
|
|
24
|
-
this.logger.error("Failed to load files store in background", error);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
//# sourceMappingURL=printer-files-load.task.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tasks/printer-files-load.task.ts"],"names":["PrinterFilesLoadTask","logger","loggerFactory","printerFilesStore","name","run","log","loadFilesStore","error"],"mappings":";;;;+BAQaA;;;eAAAA;;;AAAN,MAAMA;;IACXC,OAAsB;IAEtB,YACEC,aAA6B,EAC7B,AAAiBC,iBAAoC,CACrD;aADiBA,oBAAAA;QAEjB,IAAI,CAACF,MAAM,GAAGC,cAAcF,qBAAqBI,IAAI;IACvD;IAEA,MAAMC,MAAM;QACV,IAAI,CAACJ,MAAM,CAACK,GAAG,CAAC;QAChB,IAAI;YACF,MAAM,IAAI,CAACH,iBAAiB,CAACI,cAAc;YAC3C,IAAI,CAACN,MAAM,CAACK,GAAG,CAAC;QAClB,EAAE,OAAOE,OAAO;YACd,IAAI,CAACP,MAAM,CAACO,KAAK,CAAC,4CAA4CA;QAChE;IACF;AACF"}
|