@social-mail/social-mail-web-server 1.7.6 → 1.7.8
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/common/spawnPromise.js +3 -3
- package/dist/common/spawnPromise.js.map +1 -1
- package/dist/isolated/FileInfo.d.ts +9 -0
- package/dist/isolated/FileInfo.d.ts.map +1 -0
- package/dist/isolated/FileInfo.js +11 -0
- package/dist/isolated/FileInfo.js.map +1 -0
- package/dist/isolated/IsolatedProcess.d.ts +11 -0
- package/dist/isolated/IsolatedProcess.d.ts.map +1 -0
- package/dist/isolated/IsolatedProcess.js +26 -0
- package/dist/isolated/IsolatedProcess.js.map +1 -0
- package/dist/isolated/extract/IExtract.d.ts +9 -0
- package/dist/isolated/extract/IExtract.d.ts.map +1 -0
- package/dist/isolated/extract/IExtract.js +2 -0
- package/dist/isolated/extract/IExtract.js.map +1 -0
- package/dist/isolated/extract/index.d.ts +7 -0
- package/dist/isolated/extract/index.d.ts.map +1 -0
- package/dist/isolated/extract/index.js +51 -0
- package/dist/isolated/extract/index.js.map +1 -0
- package/dist/isolated/image-converter/IImageConverter.d.ts +11 -0
- package/dist/isolated/image-converter/IImageConverter.d.ts.map +1 -0
- package/dist/isolated/image-converter/IImageConverter.js +2 -0
- package/dist/isolated/image-converter/IImageConverter.js.map +1 -0
- package/dist/isolated/image-converter/formats/size.d.ts +5 -0
- package/dist/isolated/image-converter/formats/size.d.ts.map +1 -0
- package/dist/{server/services → isolated}/image-converter/formats/size.js +1 -0
- package/dist/isolated/image-converter/formats/size.js.map +1 -0
- package/dist/isolated/image-converter/index.d.ts +2 -0
- package/dist/isolated/image-converter/index.d.ts.map +1 -0
- package/dist/isolated/image-converter/index.js +59 -0
- package/dist/isolated/image-converter/index.js.map +1 -0
- package/dist/server/services/convert/FileConversionService.d.ts.map +1 -1
- package/dist/server/services/convert/FileConversionService.js +1 -1
- package/dist/server/services/convert/FileConversionService.js.map +1 -1
- package/dist/server/services/extract/Extract.d.ts.map +1 -1
- package/dist/server/services/extract/Extract.js +3 -53
- package/dist/server/services/extract/Extract.js.map +1 -1
- package/dist/server/services/image-converter/ImageConverterService.d.ts.map +1 -1
- package/dist/server/services/image-converter/ImageConverterService.js +10 -22
- package/dist/server/services/image-converter/ImageConverterService.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/common/spawnPromise.ts +3 -3
- package/src/isolated/FileInfo.ts +13 -0
- package/src/isolated/IsolatedProcess.ts +33 -0
- package/src/isolated/extract/IExtract.ts +4 -0
- package/src/isolated/extract/index.ts +59 -0
- package/src/isolated/image-converter/IImageConverter.ts +6 -0
- package/src/{server/services → isolated}/image-converter/formats/size.ts +1 -2
- package/src/isolated/image-converter/index.ts +73 -0
- package/src/server/services/convert/FileConversionService.ts +1 -1
- package/src/server/services/extract/Extract.ts +3 -48
- package/src/server/services/image-converter/ImageConverterService.ts +13 -24
- package/dist/server/services/image-converter/formats/size.d.ts +0 -4
- package/dist/server/services/image-converter/formats/size.d.ts.map +0 -1
- package/dist/server/services/image-converter/formats/size.js.map +0 -1
package/package.json
CHANGED
|
@@ -31,10 +31,10 @@ export const spawnPromise = (path, args?: string[], options?: SpawnOptionsWithou
|
|
|
31
31
|
});
|
|
32
32
|
cd.on("close", (status) => {
|
|
33
33
|
if (status>0) {
|
|
34
|
+
if (logError) {
|
|
35
|
+
console.error(all);
|
|
36
|
+
}
|
|
34
37
|
if (throwOnFail) {
|
|
35
|
-
if (logError) {
|
|
36
|
-
console.error(all);
|
|
37
|
-
}
|
|
38
38
|
reject(new Error(all.join("\n")));
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
2
|
+
|
|
3
|
+
export default class FileInfo {
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
public static from(file: LocalFile) {
|
|
7
|
+
return new FileInfo(file.path, file.contentType, file.fileName);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
constructor(public readonly path: string, public readonly contentType: string, public readonly fileName: string) {
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
import { spawnPromise } from "../common/spawnPromise.js";
|
|
3
|
+
import FileInfo from "./FileInfo.js";
|
|
4
|
+
import IImageConverter from "./image-converter/IImageConverter.js";
|
|
5
|
+
import IExtract from "./extract/IExtract.js";
|
|
6
|
+
|
|
7
|
+
export default class IsolatedProcess {
|
|
8
|
+
|
|
9
|
+
static async convertImage(p: IImageConverter) {
|
|
10
|
+
return spawnPromise(process.execPath, [
|
|
11
|
+
"--enable-source-maps",
|
|
12
|
+
fileURLToPath(import.meta.resolve("./image-converter/index.js")),
|
|
13
|
+
JSON.stringify(p),
|
|
14
|
+
], {
|
|
15
|
+
logCommand: false,
|
|
16
|
+
logError: true
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async extractText(p: IExtract) {
|
|
21
|
+
const result = await spawnPromise(process.execPath, [
|
|
22
|
+
"--enable-source-maps",
|
|
23
|
+
fileURLToPath(import.meta.resolve("./extract/index.js")),
|
|
24
|
+
JSON.stringify(p),
|
|
25
|
+
], {
|
|
26
|
+
logCommand: false,
|
|
27
|
+
logError: true
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return result.all;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import tesseract from "tesseract.js";
|
|
4
|
+
import FileInfo from "../FileInfo.js";
|
|
5
|
+
import IExtract from "./IExtract.js";
|
|
6
|
+
const { recognize } = tesseract;
|
|
7
|
+
|
|
8
|
+
export class ExtractWorker {
|
|
9
|
+
static async text({ input: file, useOcr }: IExtract) {
|
|
10
|
+
try {
|
|
11
|
+
if (/^image\//.test(file.contentType)) {
|
|
12
|
+
if (useOcr) {
|
|
13
|
+
return this.ocr(file);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return await readFile(file.path, "utf8");
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(error);
|
|
20
|
+
}
|
|
21
|
+
return "";
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async ocr(file: FileInfo) {
|
|
26
|
+
try {
|
|
27
|
+
const buffer = await readFile(file.path);
|
|
28
|
+
const result = await recognize(buffer);
|
|
29
|
+
return result.data.text;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(error);
|
|
32
|
+
}
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const [execPath, self, p] = process.argv;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
ExtractWorker.text(JSON.parse(p))
|
|
42
|
+
.then((r) => console.log(r))
|
|
43
|
+
.catch(console.error);
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
// parentPort.on("message", async ({ file, useOcr }) => {
|
|
47
|
+
// try {
|
|
48
|
+
// const result = await ExtractWorker.text(file, useOcr);
|
|
49
|
+
// parentPort.postMessage({ result });
|
|
50
|
+
// process.exit(0);
|
|
51
|
+
// } catch (error) {
|
|
52
|
+
// try {
|
|
53
|
+
// parentPort.postMessage({ error: error.stack ?? error.toString() });
|
|
54
|
+
// } catch (er1) {
|
|
55
|
+
// // we will ignore this.. we need to close process successfully.
|
|
56
|
+
// }
|
|
57
|
+
// process.exit(0);
|
|
58
|
+
// }
|
|
59
|
+
// });
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-bitwise */
|
|
2
|
-
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
3
2
|
import sharp from "sharp";
|
|
4
3
|
|
|
5
|
-
export default async function (file:
|
|
4
|
+
export default async function (file: { path: string }, ... args: any[] ): Promise<sharp.Sharp> {
|
|
6
5
|
|
|
7
6
|
const size = {
|
|
8
7
|
failOnError: false,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import sharp from "sharp";
|
|
2
|
+
import { parse } from "path";
|
|
3
|
+
import IImageConverter from "./IImageConverter.js";
|
|
4
|
+
|
|
5
|
+
async function transform({
|
|
6
|
+
type,
|
|
7
|
+
input,
|
|
8
|
+
output,
|
|
9
|
+
outputFileName = "file.jpg"
|
|
10
|
+
}: IImageConverter
|
|
11
|
+
) {
|
|
12
|
+
|
|
13
|
+
let { ext } = parse(outputFileName);
|
|
14
|
+
let fileName = type;
|
|
15
|
+
const matches = type.match(/(\w+)\(([^\)]+)\)/);
|
|
16
|
+
let args = [];
|
|
17
|
+
if (matches) {
|
|
18
|
+
fileName = matches[1];
|
|
19
|
+
args = matches[2].split(',').map( (x) => parseFloat(x) );
|
|
20
|
+
switch(fileName) {
|
|
21
|
+
case "jpg":
|
|
22
|
+
ext = ".jpg";
|
|
23
|
+
fileName = "size";
|
|
24
|
+
break;
|
|
25
|
+
case "png":
|
|
26
|
+
ext = ".png";
|
|
27
|
+
fileName = "size";
|
|
28
|
+
break;
|
|
29
|
+
case "gif":
|
|
30
|
+
ext = ".gif";
|
|
31
|
+
fileName = "size";
|
|
32
|
+
break;
|
|
33
|
+
case "webp":
|
|
34
|
+
ext = ".webp";
|
|
35
|
+
fileName = "size";
|
|
36
|
+
break;
|
|
37
|
+
case "ico":
|
|
38
|
+
ext = ".ico";
|
|
39
|
+
fileName = "size";
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { default: fx } = await import("./formats/" + fileName + ".js");
|
|
45
|
+
let r: sharp.Sharp = await fx(input, ... args);
|
|
46
|
+
|
|
47
|
+
switch(ext) {
|
|
48
|
+
case ".jpg":
|
|
49
|
+
r = r.jpeg({ quality: 95});
|
|
50
|
+
break;
|
|
51
|
+
case ".png":
|
|
52
|
+
r = r.png({
|
|
53
|
+
compressionLevel: 9
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
case ".webp":
|
|
57
|
+
r = r.webp({
|
|
58
|
+
nearLossless: true,
|
|
59
|
+
quality: 95
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
62
|
+
case ".gif":
|
|
63
|
+
r = r.gif();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await r.toFile(output.path);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const [execPath, self, p] = process.argv;
|
|
71
|
+
|
|
72
|
+
transform(JSON.parse(p))
|
|
73
|
+
.catch(console.error);
|
|
@@ -2,7 +2,6 @@ import Inject, { RegisterScoped, ServiceProvider } from "@entity-access/entity-a
|
|
|
2
2
|
import TempFileService from "../../storage/TempFileService.js";
|
|
3
3
|
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
4
4
|
import { Convert } from "./Convert.js";
|
|
5
|
-
import PdfDoc from "../extract/pdf/PdfDoc.js";
|
|
6
5
|
import FFCommand from "./FFCommand.js";
|
|
7
6
|
import AppFile from "../../model/entities/AppFile.js";
|
|
8
7
|
import TimeSpan from "@entity-access/entity-access/dist/types/TimeSpan.js";
|
|
@@ -13,6 +12,7 @@ import Page from "@entity-access/server-pages/dist/Page.js";
|
|
|
13
12
|
import StringFormat from "../../../common/StringFormat.js";
|
|
14
13
|
import RateLimiterService from "../rate-limiter/RateLimiterService.js";
|
|
15
14
|
import { globalEnv } from "../../../common/globalEnv.js";
|
|
15
|
+
import PdfDoc from "../extract/pdf/PdfDoc.js";
|
|
16
16
|
|
|
17
17
|
interface IDownloadConvertedFile {
|
|
18
18
|
appFile: AppFile;
|
|
@@ -6,10 +6,11 @@ import FileInfo from "./FileInfo.js";
|
|
|
6
6
|
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
7
7
|
import TempFolder from "@entity-access/server-pages/dist/core/TempFolder.js";
|
|
8
8
|
import { FileType } from "../../../common/FileType.js";
|
|
9
|
-
import PdfDoc from "./pdf/PdfDoc.js";
|
|
10
9
|
import { readFile } from "fs/promises";
|
|
11
10
|
import { Convert } from "../convert/Convert.js";
|
|
12
11
|
import LockFile from "../../storage/LockFile.js";
|
|
12
|
+
import PdfDoc from "./pdf/PdfDoc.js";
|
|
13
|
+
import IsolatedProcess from "../../../isolated/IsolatedProcess.js";
|
|
13
14
|
|
|
14
15
|
export class Extract {
|
|
15
16
|
static async text(src: LocalFile, useOcr = false) {
|
|
@@ -44,52 +45,6 @@ export class Extract {
|
|
|
44
45
|
|
|
45
46
|
using lock = await LockFile.lock("extract-worker-lock");
|
|
46
47
|
|
|
47
|
-
return await
|
|
48
|
-
let resolved = false;
|
|
49
|
-
console.log(`Extracting text from ${file.path}`);
|
|
50
|
-
const execArgv = ["--enable-source-maps"];
|
|
51
|
-
if (process.argv.includes("--trace-deprecation")) {
|
|
52
|
-
execArgv.push("--trace-deprecation");
|
|
53
|
-
}
|
|
54
|
-
const w = new Worker( dirname(fileURLToPath(import.meta.url)) + "/ExtractWorker.js",{
|
|
55
|
-
execArgv,
|
|
56
|
-
resourceLimits: {
|
|
57
|
-
maxOldGenerationSizeMb: 1024
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
w.on("message", ({ result, error }) => {
|
|
61
|
-
if (resolved) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (result) {
|
|
65
|
-
resolved = true;
|
|
66
|
-
resolve(result);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (error) {
|
|
70
|
-
reject(error);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
w.on("error", (error) => {
|
|
74
|
-
if(resolved) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
console.error(error);
|
|
78
|
-
resolved = true;
|
|
79
|
-
// reject(error);
|
|
80
|
-
console.error(`Failed to extract text for ${src.fileName} ${error.stack ?? error}`);
|
|
81
|
-
resolve("");
|
|
82
|
-
});
|
|
83
|
-
w.on("exit", (n) => {
|
|
84
|
-
if (n>0) {
|
|
85
|
-
if (resolved) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
resolved = true;
|
|
89
|
-
reject(new Error(`Worker exited with ${n}`));
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
w.postMessage({ file: { ... FileInfo.from(file)}, useOcr });
|
|
93
|
-
});
|
|
48
|
+
return await IsolatedProcess.extractText({ input: { fileName: file.fileName, contentType: file.contentType, path: file.path }, useOcr });
|
|
94
49
|
}
|
|
95
50
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Inject, { RegisterScoped } from "@entity-access/entity-access/dist/di/di.js";
|
|
2
2
|
import TempFileService from "../../storage/TempFileService.js";
|
|
3
|
-
import sharp from "sharp";
|
|
4
3
|
import { join, parse } from "path";
|
|
5
4
|
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
6
5
|
import { Convert } from "../convert/Convert.js";
|
|
@@ -9,6 +8,7 @@ import { spawnPromise } from "../../../common/spawnPromise.js";
|
|
|
9
8
|
import { copyFile, readdir } from "fs/promises";
|
|
10
9
|
import FFCommand from "../convert/FFCommand.js";
|
|
11
10
|
import { ImagesFolder } from "../../../common/ImagesFolder.js";
|
|
11
|
+
import IsolatedProcess from "../../../isolated/IsolatedProcess.js";
|
|
12
12
|
|
|
13
13
|
@RegisterScoped
|
|
14
14
|
export default class ImageConverterService {
|
|
@@ -92,6 +92,7 @@ export default class ImageConverterService {
|
|
|
92
92
|
async transform(type, file: LocalFile, outputFileName = "file.jpg") {
|
|
93
93
|
|
|
94
94
|
let { ext } = parse(outputFileName);
|
|
95
|
+
|
|
95
96
|
let fileName = type;
|
|
96
97
|
const matches = type.match(/(\w+)\(([^\)]+)\)/);
|
|
97
98
|
let args = [];
|
|
@@ -127,31 +128,19 @@ export default class ImageConverterService {
|
|
|
127
128
|
file = ImagesFolder.localFile("png", "unavailable.png");
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
break;
|
|
142
|
-
case ".webp":
|
|
143
|
-
r = r.webp({
|
|
144
|
-
nearLossless: true,
|
|
145
|
-
quality: 95
|
|
146
|
-
});
|
|
147
|
-
break;
|
|
148
|
-
case ".gif":
|
|
149
|
-
r = r.gif();
|
|
150
|
-
break;
|
|
131
|
+
const tf = await this.tempFileService.createTempFile(ext, outputFileName);
|
|
132
|
+
|
|
133
|
+
const result = await IsolatedProcess.convertImage({
|
|
134
|
+
type,
|
|
135
|
+
input: { path: file.path },
|
|
136
|
+
output: { path: tf.path },
|
|
137
|
+
outputFileName
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (tf.isEmpty || result.status !== 0) {
|
|
141
|
+
throw new Error(`Image conversion failed from ${file.path} to ${tf.path} ${result.all}`);
|
|
151
142
|
}
|
|
152
143
|
|
|
153
|
-
const tf = await this.tempFileService.createTempFile(ext, outputFileName);
|
|
154
|
-
await r.toFile(tf.path);
|
|
155
144
|
return tf;
|
|
156
145
|
}
|
|
157
146
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"size.d.ts","sourceRoot":"","sources":["../../../../../src/server/services/image-converter/formats/size.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oDAAoD,CAAC;AAC/E,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,yBAA+B,IAAI,EAAE,SAAS,EAAE,GAAI,IAAI,EAAE,GAAG,EAAE,GAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAmCtF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"size.js","sourceRoot":"","sources":["../../../../../src/server/services/image-converter/formats/size.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,OAAO,CAAC,KAAK,WAAW,IAAe,EAAE,GAAI,IAAW;IAE3D,MAAM,IAAI,GAAG;QACT,WAAW,EAAE,KAAK;KACd,CAAC;IAET,IAAI,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;QAExB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;YACf,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,uCAAuC;YACvC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACJ,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;YAC7B,uCAAuC;YACvC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;YACrB,uCAAuC;YACvC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;YACvB,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC;QACvB,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;YACxC,OAAO,MAAO,KAAK,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAC,CAAS,CAAC;QACrE,CAAC;QACD,OAAO,MAAO,KAAK,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAC,CAAS;aAC/D,MAAM,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;QAC1B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;AACL,CAAC"}
|