@crowsgear/escl-protocol-scanner 1.1.1 → 1.2.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/LICENSE +21 -21
- package/README.ko.md +177 -0
- package/README.md +177 -173
- package/dist/main.d.ts +5 -5
- package/dist/main.js +1 -1
- package/dist/services/client.js +22 -22
- package/dist/services/discovery.d.ts +1 -1
- package/dist/services/discovery.d.ts.map +1 -1
- package/dist/services/discovery.js +45 -35
- package/dist/services/discovery.js.map +1 -1
- package/package.json +70 -70
- package/python/__main__.py +23 -0
- package/python/app.py +91 -0
- package/python/base/__init__.py +2 -0
- package/python/base/service.py +23 -0
- package/python/common/__init__.py +4 -0
- package/python/common/exceptions.py +28 -0
- package/python/common/logger.py +25 -0
- package/python/common/response.py +26 -0
- package/python/decorators/__init__.py +2 -0
- package/python/decorators/scanner.py +30 -0
- package/python/escl-scanner.spec +98 -84
- package/python/services/__init__.py +2 -0
- package/python/services/escl/__init__.py +3 -0
- package/python/services/escl/discovery.py +53 -0
- package/python/services/escl/protocol.py +358 -0
- package/python/services/scanner.py +31 -0
- package/scripts/check-python-deps.js +269 -206
- package/dist/client.d.ts +0 -78
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -384
- package/dist/client.js.map +0 -1
- package/dist/discovery.d.ts +0 -80
- package/dist/discovery.d.ts.map +0 -1
- package/dist/discovery.js +0 -304
- package/dist/discovery.js.map +0 -1
- package/dist/types.d.ts +0 -172
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -7
- package/dist/types.js.map +0 -1
- package/python/__pycache__/base.cpython-312.pyc +0 -0
- package/python/__pycache__/escl_backend.cpython-312.pyc +0 -0
- package/python/base.py +0 -57
- package/python/escl_backend.py +0 -559
- package/python/escl_main.py +0 -119
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/services/discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/services/discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,YAAY,EAA+B,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGhH,YAAY,EAAE,qBAAqB,EAAE,CAAC;AAEtC;;;GAGG;AACH,qBAAa,aAAa;IACtB,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,SAAS,CAAsD;IACvE,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,UAAU,CAAuB;gBAE7B,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB;IAS7D;;;OAGG;IACH,OAAO,CAAC,aAAa;IAYrB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IA0C1B;;;;OAIG;IACG,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAoDnE;;OAEG;IACH,aAAa,IAAI,IAAI;IAIrB;;OAEG;IACH,WAAW,IAAI,YAAY,EAAE;IAI7B;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,IAAI,GAAG,IAAI;IAIvE;;OAEG;IACH,oBAAoB,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,IAAI,GAAG,IAAI;IAIxE;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsE1B;;OAEG;IACH,OAAO,CAAC,OAAO;IA2Bf;;OAEG;IACH,OAAO,CAAC,eAAe;CAM1B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAClC,OAAO,GAAE,MAAa,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAG7B"}
|
|
@@ -54,16 +54,14 @@ class ESCLDiscovery {
|
|
|
54
54
|
this.listeners = new Set();
|
|
55
55
|
this.timeout = 5000;
|
|
56
56
|
this.processReady = false;
|
|
57
|
-
this.pythonPath =
|
|
57
|
+
this.pythonPath = "python3";
|
|
58
58
|
this.binaryPath = null;
|
|
59
59
|
if (timeout) {
|
|
60
60
|
this.timeout = timeout;
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Check for pre-built binary
|
|
66
|
-
this.binaryPath = this.getBinaryPath();
|
|
62
|
+
this.pythonPath = options?.pythonPath || "python3";
|
|
63
|
+
// pythonPath가 명시적으로 전달된 경우 바이너리보다 우선 사용
|
|
64
|
+
this.binaryPath = options?.pythonPath ? null : this.getBinaryPath();
|
|
67
65
|
}
|
|
68
66
|
/**
|
|
69
67
|
* Get path to pre-built binary if available
|
|
@@ -71,8 +69,8 @@ class ESCLDiscovery {
|
|
|
71
69
|
*/
|
|
72
70
|
getBinaryPath() {
|
|
73
71
|
const platform = process.platform; // 'win32', 'darwin', 'linux'
|
|
74
|
-
const binDir = path.join(__dirname,
|
|
75
|
-
const binaryName = platform ===
|
|
72
|
+
const binDir = path.join(__dirname, "..", "..", "bin", platform);
|
|
73
|
+
const binaryName = platform === "win32" ? "escl-scanner.exe" : "escl-scanner";
|
|
76
74
|
const binaryPath = path.join(binDir, binaryName);
|
|
77
75
|
if (fs.existsSync(binaryPath)) {
|
|
78
76
|
return binaryPath;
|
|
@@ -89,7 +87,7 @@ class ESCLDiscovery {
|
|
|
89
87
|
if (this.binaryPath) {
|
|
90
88
|
return;
|
|
91
89
|
}
|
|
92
|
-
//
|
|
90
|
+
// Absolute path → check file exists
|
|
93
91
|
if (path.isAbsolute(this.pythonPath)) {
|
|
94
92
|
if (!fs.existsSync(this.pythonPath)) {
|
|
95
93
|
throw new Error(`Python executable not found at: ${this.pythonPath}\n` +
|
|
@@ -97,12 +95,24 @@ class ESCLDiscovery {
|
|
|
97
95
|
}
|
|
98
96
|
return;
|
|
99
97
|
}
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
`
|
|
105
|
-
|
|
98
|
+
// Relative path (contains separator) → resolve from cwd
|
|
99
|
+
if (this.pythonPath.includes(path.sep) || this.pythonPath.includes("/")) {
|
|
100
|
+
const resolvedPath = path.resolve(process.cwd(), this.pythonPath);
|
|
101
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
102
|
+
throw new Error(`Python executable not found at: ${resolvedPath}\n` +
|
|
103
|
+
`Current working directory: ${process.cwd()}\n` +
|
|
104
|
+
`Please ensure the Python virtual environment exists or provide a valid python3 path.`);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Bare command name (e.g. 'python3') → resolve from PATH
|
|
109
|
+
try {
|
|
110
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
111
|
+
(0, child_process_1.execSync)(`${cmd} ${this.pythonPath}`, { stdio: "ignore" });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new Error(`Python command '${this.pythonPath}' not found in PATH.\n` +
|
|
115
|
+
`Set ESCL_PYTHON_PATH environment variable or provide pythonPath option.`);
|
|
106
116
|
}
|
|
107
117
|
}
|
|
108
118
|
/**
|
|
@@ -126,7 +136,7 @@ class ESCLDiscovery {
|
|
|
126
136
|
const scanners = Array.from(this.discovered.values());
|
|
127
137
|
resolve({
|
|
128
138
|
success: true,
|
|
129
|
-
data: scanners
|
|
139
|
+
data: scanners,
|
|
130
140
|
});
|
|
131
141
|
}
|
|
132
142
|
}, discoveryTimeout);
|
|
@@ -139,7 +149,7 @@ class ESCLDiscovery {
|
|
|
139
149
|
this.stopDiscovery();
|
|
140
150
|
resolve({
|
|
141
151
|
success: true,
|
|
142
|
-
data: scanners
|
|
152
|
+
data: scanners,
|
|
143
153
|
});
|
|
144
154
|
}
|
|
145
155
|
};
|
|
@@ -147,8 +157,8 @@ class ESCLDiscovery {
|
|
|
147
157
|
this.startPythonService();
|
|
148
158
|
// Send list command to Python subprocess
|
|
149
159
|
if (this.pythonProcess && this.pythonProcess.stdin) {
|
|
150
|
-
const command = { action:
|
|
151
|
-
this.pythonProcess.stdin.write(JSON.stringify(command) +
|
|
160
|
+
const command = { action: "list" };
|
|
161
|
+
this.pythonProcess.stdin.write(JSON.stringify(command) + "\n");
|
|
152
162
|
}
|
|
153
163
|
}
|
|
154
164
|
catch (error) {
|
|
@@ -192,30 +202,30 @@ class ESCLDiscovery {
|
|
|
192
202
|
// Use pre-built binary (production/package environment)
|
|
193
203
|
console.log(`[eSCL] Using binary: ${this.binaryPath}`);
|
|
194
204
|
this.pythonProcess = (0, child_process_1.spawn)(this.binaryPath, [], {
|
|
195
|
-
stdio: [
|
|
196
|
-
windowsHide: true
|
|
205
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
206
|
+
windowsHide: true,
|
|
197
207
|
});
|
|
198
208
|
}
|
|
199
209
|
else {
|
|
200
210
|
// Fall back to Python script (development environment)
|
|
201
|
-
const pythonScriptPath = path.join(__dirname,
|
|
211
|
+
const pythonScriptPath = path.join(__dirname, "..", "..", "python", "__main__.py");
|
|
202
212
|
console.log(`[eSCL] Using Python script: ${pythonScriptPath}`);
|
|
203
213
|
this.pythonProcess = (0, child_process_1.spawn)(this.pythonPath, [pythonScriptPath], {
|
|
204
|
-
stdio: [
|
|
205
|
-
windowsHide: true
|
|
214
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
215
|
+
windowsHide: true,
|
|
206
216
|
});
|
|
207
217
|
}
|
|
208
218
|
// Handle stdout - parse JSON responses
|
|
209
219
|
if (this.pythonProcess.stdout) {
|
|
210
|
-
this.pythonProcess.stdout.on(
|
|
220
|
+
this.pythonProcess.stdout.on("data", (data) => {
|
|
211
221
|
try {
|
|
212
|
-
const lines = data.toString().split(
|
|
222
|
+
const lines = data.toString().split("\n");
|
|
213
223
|
for (const line of lines) {
|
|
214
224
|
if (line.trim()) {
|
|
215
225
|
const response = JSON.parse(line);
|
|
216
226
|
if (!response.success && response.error) {
|
|
217
227
|
// Handle error response from Python
|
|
218
|
-
console.error(
|
|
228
|
+
console.error("[eSCL Error]", response.error);
|
|
219
229
|
}
|
|
220
230
|
else if (response.success && response.scanners) {
|
|
221
231
|
this.discovered.clear();
|
|
@@ -228,18 +238,18 @@ class ESCLDiscovery {
|
|
|
228
238
|
}
|
|
229
239
|
}
|
|
230
240
|
catch (error) {
|
|
231
|
-
console.error(
|
|
241
|
+
console.error("Error parsing Python response:", error);
|
|
232
242
|
}
|
|
233
243
|
});
|
|
234
244
|
}
|
|
235
245
|
// Handle stderr - logging
|
|
236
246
|
if (this.pythonProcess.stderr) {
|
|
237
|
-
this.pythonProcess.stderr.on(
|
|
238
|
-
console.error(
|
|
247
|
+
this.pythonProcess.stderr.on("data", (data) => {
|
|
248
|
+
console.error("[eSCL Python]", data.toString());
|
|
239
249
|
});
|
|
240
250
|
}
|
|
241
251
|
// Handle process exit
|
|
242
|
-
this.pythonProcess.on(
|
|
252
|
+
this.pythonProcess.on("exit", (code) => {
|
|
243
253
|
console.log(`[eSCL] Python subprocess exited with code ${code}`);
|
|
244
254
|
this.pythonProcess = null;
|
|
245
255
|
this.processReady = false;
|
|
@@ -247,7 +257,7 @@ class ESCLDiscovery {
|
|
|
247
257
|
this.processReady = true;
|
|
248
258
|
}
|
|
249
259
|
catch (error) {
|
|
250
|
-
console.error(
|
|
260
|
+
console.error("Failed to start Python service:", error);
|
|
251
261
|
this.pythonProcess = null;
|
|
252
262
|
}
|
|
253
263
|
}
|
|
@@ -259,8 +269,8 @@ class ESCLDiscovery {
|
|
|
259
269
|
try {
|
|
260
270
|
// Send exit command
|
|
261
271
|
if (this.pythonProcess.stdin) {
|
|
262
|
-
const command = { action:
|
|
263
|
-
this.pythonProcess.stdin.write(JSON.stringify(command) +
|
|
272
|
+
const command = { action: "exit" };
|
|
273
|
+
this.pythonProcess.stdin.write(JSON.stringify(command) + "\n");
|
|
264
274
|
}
|
|
265
275
|
// Force kill after timeout
|
|
266
276
|
setTimeout(() => {
|
|
@@ -271,7 +281,7 @@ class ESCLDiscovery {
|
|
|
271
281
|
}, 1000);
|
|
272
282
|
}
|
|
273
283
|
catch (error) {
|
|
274
|
-
console.error(
|
|
284
|
+
console.error("Error during cleanup:", error);
|
|
275
285
|
if (this.pythonProcess) {
|
|
276
286
|
this.pythonProcess.kill();
|
|
277
287
|
this.pythonProcess = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/services/discovery.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/services/discovery.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4SH,4CAMC;AAhTD,iDAA8D;AAC9D,2CAA6B;AAC7B,uCAAyB;AAMzB;;;GAGG;AACH,MAAa,aAAa;IAStB,YAAY,OAAgB,EAAE,OAA+B;QARrD,kBAAa,GAAwB,IAAI,CAAC;QAC1C,eAAU,GAA8B,IAAI,GAAG,EAAE,CAAC;QAClD,cAAS,GAA4C,IAAI,GAAG,EAAE,CAAC;QAC/D,YAAO,GAAW,IAAI,CAAC;QACvB,iBAAY,GAAY,KAAK,CAAC;QAC9B,eAAU,GAAW,SAAS,CAAC;QAC/B,eAAU,GAAkB,IAAI,CAAC;QAGrC,IAAI,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,SAAS,CAAC;QACnD,wCAAwC;QACxC,IAAI,CAAC,UAAU,GAAG,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;IACxE,CAAC;IAED;;;OAGG;IACK,aAAa;QACjB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,6BAA6B;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACjE,MAAM,UAAU,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC;QAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAEjD,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,OAAO,UAAU,CAAC;QACtB,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACK,kBAAkB;QACtB,yCAAyC;QACzC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO;QACX,CAAC;QAED,oCAAoC;QACpC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CACX,mCAAmC,IAAI,CAAC,UAAU,IAAI;oBAClD,sFAAsF,CAC7F,CAAC;YACN,CAAC;YACD,OAAO;QACX,CAAC;QAED,wDAAwD;QACxD,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACtE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAClE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CACX,mCAAmC,YAAY,IAAI;oBAC/C,8BAA8B,OAAO,CAAC,GAAG,EAAE,IAAI;oBAC/C,sFAAsF,CAC7F,CAAC;YACN,CAAC;YACD,OAAO;QACX,CAAC;QAED,yDAAyD;QACzD,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7D,IAAA,wBAAQ,EAAC,GAAG,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,IAAI,KAAK,CACX,mBAAmB,IAAI,CAAC,UAAU,wBAAwB;gBACtD,yEAAyE,CAChF,CAAC;QACN,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,cAAc,CAAC,OAAgB;QACjC,MAAM,gBAAgB,GAAG,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC;QAEjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,IAAI,CAAC;gBACD,yCAAyC;gBACzC,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAE1B,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;gBACxB,IAAI,QAAQ,GAAG,KAAK,CAAC;gBAErB,oDAAoD;gBACpD,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;oBAClC,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACZ,QAAQ,GAAG,IAAI,CAAC;wBAChB,IAAI,CAAC,aAAa,EAAE,CAAC;wBACrB,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;wBACtD,OAAO,CAAC;4BACJ,OAAO,EAAE,IAAI;4BACb,IAAI,EAAE,QAAQ;yBACjB,CAAC,CAAC;oBACP,CAAC;gBACL,CAAC,EAAE,gBAAgB,CAAC,CAAC;gBAErB,sCAAsC;gBACtC,MAAM,eAAe,GAAG,CAAC,QAAwB,EAAE,EAAE;oBACjD,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACZ,QAAQ,GAAG,IAAI,CAAC;wBAChB,YAAY,CAAC,aAAa,CAAC,CAAC;wBAC5B,IAAI,CAAC,oBAAoB,CAAC,eAAe,CAAC,CAAC;wBAC3C,IAAI,CAAC,aAAa,EAAE,CAAC;wBACrB,OAAO,CAAC;4BACJ,OAAO,EAAE,IAAI;4BACb,IAAI,EAAE,QAAQ;yBACjB,CAAC,CAAC;oBACP,CAAC;gBACL,CAAC,CAAC;gBAEF,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC;gBAC1C,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAE1B,yCAAyC;gBACzC,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;oBACjD,MAAM,OAAO,GAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;oBACjD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;gBACnE,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;OAEG;IACH,aAAa;QACT,IAAI,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,WAAW;QACP,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,mBAAmB,CAAC,QAA4C;QAC5D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,oBAAoB,CAAC,QAA4C;QAC7D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACK,kBAAkB;QACtB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,OAAO,CAAC,kBAAkB;QAC9B,CAAC;QAED,IAAI,CAAC;YACD,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,wDAAwD;gBACxD,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;gBACvD,IAAI,CAAC,aAAa,GAAG,IAAA,qBAAK,EAAC,IAAI,CAAC,UAAU,EAAE,EAAE,EAAE;oBAC5C,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;oBAC/B,WAAW,EAAE,IAAI;iBACpB,CAAC,CAAC;YACP,CAAC;iBAAM,CAAC;gBACJ,uDAAuD;gBACvD,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;gBACnF,OAAO,CAAC,GAAG,CAAC,+BAA+B,gBAAgB,EAAE,CAAC,CAAC;gBAC/D,IAAI,CAAC,aAAa,GAAG,IAAA,qBAAK,EAAC,IAAI,CAAC,UAAU,EAAE,CAAC,gBAAgB,CAAC,EAAE;oBAC5D,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;oBAC/B,WAAW,EAAE,IAAI;iBACpB,CAAC,CAAC;YACP,CAAC;YAED,uCAAuC;YACvC,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;gBAC5B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;oBAClD,IAAI,CAAC;wBACD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;wBAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;4BACvB,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gCACd,MAAM,QAAQ,GAAkB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gCACjD,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;oCACtC,oCAAoC;oCACpC,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;gCAClD,CAAC;qCAAM,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;oCAC/C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;oCACxB,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;wCACtC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;oCAC/C,CAAC;oCACD,IAAI,CAAC,eAAe,EAAE,CAAC;gCAC3B,CAAC;4BACL,CAAC;wBACL,CAAC;oBACL,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACb,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;oBAC3D,CAAC;gBACL,CAAC,CAAC,CAAC;YACP,CAAC;YAED,0BAA0B;YAC1B,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;gBAC5B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;oBAClD,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACpD,CAAC,CAAC,CAAC;YACP,CAAC;YAED,sBAAsB;YACtB,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACnC,OAAO,CAAC,GAAG,CAAC,6CAA6C,IAAI,EAAE,CAAC,CAAC;gBACjE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC1B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAC9B,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;YACxD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC9B,CAAC;IACL,CAAC;IAED;;OAEG;IACK,OAAO;QACX,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,IAAI,CAAC;gBACD,oBAAoB;gBACpB,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;oBAC3B,MAAM,OAAO,GAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;oBACjD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;gBACnE,CAAC;gBAED,2BAA2B;gBAC3B,UAAU,CAAC,GAAG,EAAE;oBACZ,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;wBACrB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;wBAC1B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;oBAC9B,CAAC;gBACL,CAAC,EAAE,IAAI,CAAC,CAAC;YACb,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;gBAC9C,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;oBACrB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;oBAC1B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC9B,CAAC;YACL,CAAC;QACL,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAC9B,CAAC;IAED;;OAEG;IACK,eAAe;QACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAChC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAtRD,sCAsRC;AAED;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CAClC,UAAkB,IAAI,EACtB,OAA+B;IAE/B,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACtD,OAAO,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@crowsgear/escl-protocol-scanner",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "eSCL/AirPrint Protocol Scanner Library - HTTP-based network scanner support with mDNS discovery",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "CrowsGear",
|
|
7
|
-
"homepage": "https://github.com/byeong1/escl-protocol-scanner",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git@github.com:byeong1/escl-protocol-scanner.git"
|
|
11
|
-
},
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/byeong1/escl-protocol-scanner/issues"
|
|
14
|
-
},
|
|
15
|
-
"main": "dist/index.js",
|
|
16
|
-
"types": "dist/index.d.ts",
|
|
17
|
-
"exports": {
|
|
18
|
-
".": {
|
|
19
|
-
"import": "./dist/index.js",
|
|
20
|
-
"require": "./dist/index.js",
|
|
21
|
-
"types": "./dist/index.d.ts"
|
|
22
|
-
},
|
|
23
|
-
"./types": {
|
|
24
|
-
"import": "./dist/types.js",
|
|
25
|
-
"types": "./dist/types.d.ts"
|
|
26
|
-
},
|
|
27
|
-
"./client": {
|
|
28
|
-
"import": "./dist/client.js",
|
|
29
|
-
"types": "./dist/client.d.ts"
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist",
|
|
34
|
-
"python",
|
|
35
|
-
"bin",
|
|
36
|
-
"scripts",
|
|
37
|
-
"README.md",
|
|
38
|
-
"LICENSE"
|
|
39
|
-
],
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "tsc",
|
|
42
|
-
"build:watch": "tsc --watch",
|
|
43
|
-
"clean": "rm -rf dist",
|
|
44
|
-
"prepublishOnly": "yarn run build",
|
|
45
|
-
"prepack": "yarn run build",
|
|
46
|
-
"postinstall": "node scripts/check-python-deps.js",
|
|
47
|
-
"publish:npm": "npm publish --registry https://registry.npmjs.org/ --access public",
|
|
48
|
-
"publish:github": "npm publish --registry https://npm.pkg.github.com --access public",
|
|
49
|
-
"publish:all": "npm version patch && yarn publish:npm && yarn publish:github"
|
|
50
|
-
},
|
|
51
|
-
"keywords": [
|
|
52
|
-
"escl",
|
|
53
|
-
"scanner",
|
|
54
|
-
"airprint",
|
|
55
|
-
"network",
|
|
56
|
-
"mfp",
|
|
57
|
-
"printer",
|
|
58
|
-
"connection",
|
|
59
|
-
"auto connection",
|
|
60
|
-
"scanner auto connection"
|
|
61
|
-
],
|
|
62
|
-
"engines": {
|
|
63
|
-
"node": ">=14.0.0"
|
|
64
|
-
},
|
|
65
|
-
"dependencies": {},
|
|
66
|
-
"devDependencies": {
|
|
67
|
-
"@types/node": "^24.7.2",
|
|
68
|
-
"typescript": "^5.9.3"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@crowsgear/escl-protocol-scanner",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "eSCL/AirPrint Protocol Scanner Library - HTTP-based network scanner support with mDNS discovery",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "CrowsGear",
|
|
7
|
+
"homepage": "https://github.com/byeong1/escl-protocol-scanner",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git@github.com:byeong1/escl-protocol-scanner.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/byeong1/escl-protocol-scanner/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./types": {
|
|
24
|
+
"import": "./dist/types.js",
|
|
25
|
+
"types": "./dist/types.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./client": {
|
|
28
|
+
"import": "./dist/client.js",
|
|
29
|
+
"types": "./dist/client.d.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"python",
|
|
35
|
+
"bin",
|
|
36
|
+
"scripts",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc",
|
|
42
|
+
"build:watch": "tsc --watch",
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
|
+
"prepublishOnly": "yarn run build",
|
|
45
|
+
"prepack": "yarn run build",
|
|
46
|
+
"postinstall": "node scripts/check-python-deps.js",
|
|
47
|
+
"publish:npm": "npm publish --registry https://registry.npmjs.org/ --access public",
|
|
48
|
+
"publish:github": "npm publish --registry https://npm.pkg.github.com --access public",
|
|
49
|
+
"publish:all": "npm version patch && yarn publish:npm && yarn publish:github"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"escl",
|
|
53
|
+
"scanner",
|
|
54
|
+
"airprint",
|
|
55
|
+
"network",
|
|
56
|
+
"mfp",
|
|
57
|
+
"printer",
|
|
58
|
+
"connection",
|
|
59
|
+
"auto connection",
|
|
60
|
+
"scanner auto connection"
|
|
61
|
+
],
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=14.0.0"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/node": "^24.7.2",
|
|
68
|
+
"typescript": "^5.9.3"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
eSCL Scanner Service Entry Point
|
|
5
|
+
Dedicated subprocess for eSCL (AirPrint) network scanners
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from common.response import send_response, error_response
|
|
10
|
+
from app import ESCLApplication
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
try:
|
|
15
|
+
app = ESCLApplication()
|
|
16
|
+
app.run()
|
|
17
|
+
except Exception as e:
|
|
18
|
+
send_response(error_response(f'eSCL service error: {e}'))
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == '__main__':
|
|
23
|
+
main()
|
package/python/app.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
eSCL Scanner Service Application
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from common.logger import setup_logging
|
|
11
|
+
from common.response import send_response, success_response, error_response
|
|
12
|
+
|
|
13
|
+
log = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class ESCLApplication:
|
|
16
|
+
"""eSCL Scanner Service Application"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
setup_logging()
|
|
20
|
+
|
|
21
|
+
self._check_dependencies()
|
|
22
|
+
|
|
23
|
+
from services import ScannerService
|
|
24
|
+
|
|
25
|
+
self.service = ScannerService()
|
|
26
|
+
self._handlers = {
|
|
27
|
+
'list': lambda cmd: self.service.list_scanners(),
|
|
28
|
+
'scan': lambda cmd: self.service.scan(cmd.get('params', {})),
|
|
29
|
+
'capabilities': lambda cmd: self.service.get_capabilities(cmd.get('params', {})),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _check_dependencies():
|
|
34
|
+
"""필수 라이브러리 존재 여부를 확인하고, 없으면 에러 응답 후 종료한다."""
|
|
35
|
+
missing = []
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import zeroconf # noqa: F401
|
|
39
|
+
except ImportError:
|
|
40
|
+
missing.append('zeroconf')
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
import PIL # noqa: F401
|
|
44
|
+
except ImportError:
|
|
45
|
+
missing.append('pillow')
|
|
46
|
+
|
|
47
|
+
if missing:
|
|
48
|
+
send_response(error_response(
|
|
49
|
+
f'Required Python packages are not installed. '
|
|
50
|
+
f'Install with: pip install {" ".join(missing)}'
|
|
51
|
+
))
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
def dispatch(self, command: dict) -> bool:
|
|
55
|
+
"""커맨드를 적절한 백엔드 메서드로 라우팅한다."""
|
|
56
|
+
action = command.get('action')
|
|
57
|
+
log.info('Command received: %s', action)
|
|
58
|
+
|
|
59
|
+
if action == 'exit':
|
|
60
|
+
send_response(success_response(message='eSCL service stopped'))
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
handler = self._handlers.get(action)
|
|
64
|
+
if not handler:
|
|
65
|
+
send_response(error_response(f'Unknown command: {action}'))
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
result = handler(command)
|
|
69
|
+
send_response(result)
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def run(self) -> None:
|
|
73
|
+
"""stdin에서 JSON 커맨드를 읽어 처리하는 메인 루프."""
|
|
74
|
+
log.info('Service started')
|
|
75
|
+
|
|
76
|
+
for line in sys.stdin:
|
|
77
|
+
line = line.strip()
|
|
78
|
+
if not line:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
command = json.loads(line)
|
|
83
|
+
except json.JSONDecodeError as e:
|
|
84
|
+
send_response(error_response(f'JSON parsing error: {e}'))
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
if not self.dispatch(command):
|
|
89
|
+
break
|
|
90
|
+
except Exception as e:
|
|
91
|
+
send_response(error_response(f'Command processing error: {e}'))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Base Service Abstract Class
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Service(ABC):
|
|
11
|
+
"""Base class for all services."""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def list_scanners(self) -> dict:
|
|
15
|
+
"""Discover scanners on the network."""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def scan(self, params: dict) -> dict:
|
|
19
|
+
"""Execute scan with given parameters."""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def get_capabilities(self, params: dict) -> dict:
|
|
23
|
+
"""Retrieve scanner capabilities."""
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Scanner exception classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ScannerError(Exception):
|
|
9
|
+
"""Raised when a scanner operation fails.
|
|
10
|
+
Caught by handle_scanner_errors decorator to produce {'success': False, 'error': ...}.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message: str, *, context: str | None = None):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.context = context
|
|
16
|
+
|
|
17
|
+
def __str__(self) -> str:
|
|
18
|
+
if self.context:
|
|
19
|
+
return f'{super().__str__()} [{self.context}]'
|
|
20
|
+
return super().__str__()
|
|
21
|
+
|
|
22
|
+
def __repr__(self) -> str:
|
|
23
|
+
return f'{self.__class__.__name__}({super().__str__()!r}, context={self.context!r})'
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> dict:
|
|
26
|
+
"""Convert to error response dict."""
|
|
27
|
+
from common.response import error_response
|
|
28
|
+
return error_response(str(self))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Logging configuration for eSCL Scanner Backend.
|
|
5
|
+
Uses Python's standard logging module with stderr output.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def setup_logging() -> None:
|
|
13
|
+
"""Configure root logger for the application.
|
|
14
|
+
|
|
15
|
+
All log output goes to stderr (stdout is reserved for JSON IPC).
|
|
16
|
+
Format: [module.name] message
|
|
17
|
+
Call once at application startup.
|
|
18
|
+
"""
|
|
19
|
+
root = logging.getLogger()
|
|
20
|
+
|
|
21
|
+
if not root.handlers:
|
|
22
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
23
|
+
handler.setFormatter(logging.Formatter('[%(name)s] %(message)s'))
|
|
24
|
+
root.addHandler(handler)
|
|
25
|
+
root.setLevel(logging.DEBUG)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Common response builders for JSON IPC.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def success_response(**kwargs: Any) -> dict:
|
|
13
|
+
"""Build a success response dict."""
|
|
14
|
+
return {'success': True, **kwargs}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def error_response(error: str | Exception) -> dict:
|
|
18
|
+
"""Build an error response dict."""
|
|
19
|
+
return {'success': False, 'error': str(error)}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def send_response(data: dict) -> None:
|
|
23
|
+
"""Send JSON response via stdout."""
|
|
24
|
+
response = json.dumps(data, ensure_ascii=False)
|
|
25
|
+
sys.stdout.write(response + '\n')
|
|
26
|
+
sys.stdout.flush()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Scanner error handling decorator.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import Callable
|
|
9
|
+
from common.exceptions import ScannerError
|
|
10
|
+
from common.response import error_response
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
log = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def handle_scanner_errors(func: Callable) -> Callable:
|
|
17
|
+
"""Decorator that catches ScannerError and unexpected exceptions,
|
|
18
|
+
converting them into a consistent error response dict.
|
|
19
|
+
"""
|
|
20
|
+
@wraps(func)
|
|
21
|
+
def wrapper(*args, **kwargs) -> dict:
|
|
22
|
+
try:
|
|
23
|
+
return func(*args, **kwargs)
|
|
24
|
+
except ScannerError as e:
|
|
25
|
+
log.error('%s error: %s', func.__name__, e)
|
|
26
|
+
return e.to_dict()
|
|
27
|
+
except Exception as e:
|
|
28
|
+
log.error('%s unexpected error: %s', func.__name__, e)
|
|
29
|
+
return error_response(f'{func.__name__} failed: {e}')
|
|
30
|
+
return wrapper
|