diff --git a/README.md b/README.md index 89d7ead..c44bc93 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,158 @@ # Document Scanner SDK for Node.js -The package provides Node.js APIs for invoking **Dynamic Web TWAIN Service REST API**. It helps developers to create **desktop** or **server-side** document scanning applications with ease. - -## Key Features -- πŸ–¨οΈ ​Multi-driver Support - - TWAIN (32-bit & 64-bit) - - WIA (Windows Image Acquisition) - - SANE (Scanner Access Now Easy) - - ICA (Image Capture Architecture) - - eSCL (AirScan/Mopria) -- 🌐 ​Cross-platform Compatibility - - Windows 7+ - - Linux (x64/ARM64/MIPS64) - - macOS 10.15+ - -## Prerequisites -- Install Dynamic Web TWAIN Service. - - Windows: [Dynamsoft-Service-Setup.msi](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.msi) - - macOS: [Dynamsoft-Service-Setup.pkg](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.pkg) - - Linux: - - [Dynamsoft-Service-Setup.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.deb) - - [Dynamsoft-Service-Setup-arm64.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-arm64.deb) - - [Dynamsoft-Service-Setup-mips64el.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-mips64el.deb) - - [Dynamsoft-Service-Setup.rpm](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.rpm) - -- Request a [free trial license](https://www.dynamsoft.com/customer/license/trialLicense?product=dwt) for Dynamic Web TWAIN Service. - -## Dynamic Web TWAIN Service Configuration -After installing the Dynamic Web TWAIN Service, navigate to `http://127.0.0.1:18625/` in a web browser to configure the host and port settings. The default host IP address is set to `127.0.0.1`. If you wish to make the service accessible over the local network in your office or company, you can update the host setting to a LAN IP address, such as **192.168.8.72**. + +The package provides Node.js APIs for invoking the **Dynamic Web TWAIN Service REST API**. It helps developers create **desktop** or **server-side** document scanning and processing applications with ease. + +--- + +## πŸš€ Key Features + +- πŸ–¨οΈ **Multi-Driver Support** + - TWAIN (32-bit & 64-bit) + - WIA (Windows Image Acquisition) + - SANE (Scanner Access Now Easy) + - ICA (Image Capture Architecture) + - eSCL (AirScan/Mopria) + - Wi-Fi Direct + +- 🌐 **Cross-Platform Compatibility** + - Windows 7+ + - macOS 10.15+ + - Linux (x64 / ARM64 / MIPS64) + +--- + +## βš™οΈ Prerequisites + +### βœ… Install Dynamic Web TWAIN Service: + +- **Windows**: [Dynamsoft-Service-Setup.msi](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.msi) +- **macOS**: [Dynamsoft-Service-Setup.pkg](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.pkg) +- **Linux**: + - [Dynamsoft-Service-Setup.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.deb) + - [Dynamsoft-Service-Setup-arm64.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-arm64.deb) + - [Dynamsoft-Service-Setup-mips64el.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-mips64el.deb) + - [Dynamsoft-Service-Setup.rpm](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.rpm) + +### πŸ”‘ Get a License + +Request a [free trial license](https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform) for the Dynamic Web TWAIN Service. + +--- + +## 🧩 Configuration + +After installation, open `http://127.0.0.1:18625/` in your browser to configure the **host** and **port** settings. + +> By default, the service is bound to `127.0.0.1`. To access it across the LAN, change the host to your local IP (e.g., `192.168.8.72`). ![dynamsoft-service-config](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/e2b1292e-dfbd-4821-bf41-70e2847dd51e) -## REST API Reference -By default, the REST API's host address is set to `http://127.0.0.1:18622`. +--- + +## πŸ“‘ REST API Endpoints + +[https://www.dynamsoft.com/web-twain/docs/info/api/restful.html](https://www.dynamsoft.com/web-twain/docs/info/api/restful.html) + +## πŸ“¦ Node.js APIs + +### πŸ” Scanner APIs + +- `getDevices(host, scannerType)` + Get available scanners. Returns an array of devices. + +- `createJob(host, parameters)` + Create a new scan job. Returns a job object. -| Method | Endpoint | Description | Parameters | Response | -|--------|-----------------|-------------------------------|------------------------------------|-------------------------------| -| GET | `/DWTAPI/Scanners` | Get a list of scanners | None | `200 OK` with scanner list | -| POST | `/DWTAPI/ScanJobs` | Creates a scan job | `license`, `device`, `config` | `201 Created` with job ID | -| GET | `/DWTAPI/ScanJobs/:id/NextDocument`| Retrieves a document image | `id`: Job ID | `200 OK` with image stream | -| DELETE | `/DWTAPI/ScanJobs/:id`| Deletes a scan job | `id`: Job ID | `200 OK` | +- `checkJob(host, jobId)` + Check job status (e.g., running, canceled, etc.) -## Node.js API -- `getDevices(host, scannerType)` - Get all available scanners. It returns an array of scanner objects. -- `scanDocument(host, parameters, timeout)` - Create a scanner job by feeding one or multiple physical documents. It returns the job id. -- `getImageFile(host, jobId, directory)` - Get one document image by job id. The directory specifies the physical location to save the images. It returns the image path. -- `getImageFiles(host, jobId, directory)` - Get document images by job id. The directory specifies the physical location to save the images. It returns an array of image paths. -- `deleteJob(host, jobId)` - Delete a scan job by job id. It can interrupt the scan process. -- `getImageStreams(host, jobId)` - Get document images by job id. It returns an array of image streams. +- `deleteJob(host, jobId)` + Delete a scan job and terminate scanning. -## Parameter Configuration -The parameter configuration is based on [Dynamsoft Web TWAIN documentation](https://www.dynamsoft.com/web-twain/docs/info/api/Interfaces.html#DeviceConfiguration). It controls the behavior of the scanner. +- `updateJob(host, jobId, parameters)` + Update job status (e.g., cancel a running job). -For example, you can set the resolution to 200 DPI and the pixel type to color: +- `getScannerCapabilities(host, jobId)` + Get scanner capabilities like resolution, color modes. + +### πŸ–ΌοΈ Image Retrieval + +- `getImageFile(host, jobId, directory)` + Fetch one image and save to local disk. + +- `getImageFiles(host, jobId, directory)` + Fetch all images for a job and save to local disk. + +- `getImageStream(host, jobId)` + Fetch one image as a readable stream. + +- `getImageStreams(host, jobId)` + Fetch all images as streams. + +- `getImageInfo(host, jobId)` + Retrieve metadata of the next page. + +### πŸ“„ Document APIs + +- `createDocument(host, parameters)` + Create a new empty document (PDF). + +- `getDocumentInfo(host, docId)` + Get document metadata and structure. + +- `deleteDocument(host, docId)` + Delete an existing document. + +- `getDocumentFile(host, docId, directory)` + Download the document and save as a PDF. + +- `getDocumentStream(host, docId)` + Download document as a stream. + +- `insertPage(host, docId, parameters)` + Insert a new page into an existing document. + +- `deletePage(host, docId, pageId)` + Remove a page from an existing document. + +--- + +## βš™οΈ Scan Job Parameters + +The configuration follows [Dynamsoft Web TWAIN DeviceConfiguration](https://www.dynamsoft.com/web-twain/docs/info/api/Interfaces.html#DeviceConfiguration). ```js let parameters = { - license: "LICENSE-KEY", - device: devices[index].device, -}; - -parameters.config = { + license: "LICENSE-KEY", + device: devices[0].device, + config: { IfShowUI: false, - PixelType: 2, // color + PixelType: 2, // Color Resolution: 200, IfFeederEnabled: false, - IfDuplexEnabled: false, + IfDuplexEnabled: false + } }; ``` -## Examples -Set the `LICENSE-KEY` before running the following examples. +--- + +## πŸ§ͺ Examples -- [Command-line](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/command-line) +Set the `LICENSE-KEY` before running the examples. - Get all available scanners +### πŸ–₯️ Command-line App - ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/24fcb45d-1bea-45ba-9569-b9a2ef377b63) +- [Command-line Example](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/command-line) - Acquire a Document - - ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/2688269d-4f05-4734-bf1c-7ba4e2638d66) + - Discover devices + ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/24fcb45d-1bea-45ba-9569-b9a2ef377b63) -- [Web server](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/web) + - Scan and save documents + ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/2688269d-4f05-4734-bf1c-7ba4e2638d66) - ![server-side-document-scan](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/9a161dda-6f9d-473b-a2d4-168ebd5f6b0b) +### 🌐 Web Server App +- [Web Server Example](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/web) + ![server-side-document-scan](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/9a161dda-6f9d-473b-a2d4-168ebd5f6b0b) diff --git a/examples/REST/app.js b/examples/REST/app.js index f9e2dc3..1e61da4 100644 --- a/examples/REST/app.js +++ b/examples/REST/app.js @@ -20,12 +20,12 @@ app.get('/devices', (req, res) => { }); }); -app.post('/scandocument', async (req, res) => { - const json = req.body; +app.post('/createJob', async (req, res) => { + const data = req.body; let parameters = { license: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", - device: json['scan'], + device: data['scan'], }; parameters.config = { @@ -38,7 +38,8 @@ app.post('/scandocument', async (req, res) => { IfDuplexEnabled: false, }; - let jobId = await docscan4nodejs.scanDocument(dynamsoftService, parameters); + let json = await docscan4nodejs.createJob(dynamsoftService, parameters); + let jobId = json.jobuid; let filename = await docscan4nodejs.getImageFile(dynamsoftService, jobId, './public'); res.send(JSON.stringify({ diff --git a/examples/REST/public/index.html b/examples/REST/public/index.html index c904771..c703491 100644 --- a/examples/REST/public/index.html +++ b/examples/REST/public/index.html @@ -62,7 +62,7 @@

Document Scanning Sample

async function acquireImage() { if (devices.length > 0 && selectSources.selectedIndex >= 0) { - const response = await fetch('/scandocument', { + const response = await fetch('/createJob', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/examples/command-line/app.js b/examples/command-line/app.js index 5dd96d8..65f716f 100644 --- a/examples/command-line/app.js +++ b/examples/command-line/app.js @@ -2,9 +2,12 @@ const docscan4nodejs = require("../../index.js") const readline = require('readline'); let devices = []; -// let host = 'http://192.168.8.119:18622'; let host = 'http://127.0.0.1:18622'; +docscan4nodejs.getServerInfo(host).then((info) => { + console.log('Server info:', info); +}); + const questions = ` Please select an operation: 1. Get scanners @@ -51,6 +54,7 @@ function askQuestion() { let parameters = { license: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", device: devices[index].device, + autoRun: true }; parameters.config = { @@ -59,23 +63,59 @@ function askQuestion() { //XferCount: 1, //PageSize: 1, Resolution: 200, - IfFeederEnabled: false, + IfFeederEnabled: true, IfDuplexEnabled: false, }; - docscan4nodejs.scanDocument(host, parameters).then((jobId) => { - if (jobId !== '') { - console.log('job id: ' + jobId); + docscan4nodejs.createJob(host, parameters).then((job) => { + try { + let jobId = job.jobuid; + (async () => { + // let status = await docscan4nodejs.checkJob(host, jobId); + // console.log('Job status:', status); + + // let caps = await docscan4nodejs.getScannerCapabilities(host, jobId); + // console.log('Capabilities:', caps); + + // let updateStatus = await docscan4nodejs.updateJob(host, jobId, { + // status: docscan4nodejs.JobStatus.RUNNING + // }); + // console.log('Update status:', updateStatus); + let doc = await docscan4nodejs.createDocument(host, {}); + // console.log('Document:', doc); + + let images = await docscan4nodejs.getImageFiles(host, jobId, './'); for (let i = 0; i < images.length; i++) { console.log('Image ' + i + ': ' + images[i]); + let info = await docscan4nodejs.getImageInfo(host, jobId); + // console.log('Image info:', info); + + let insertPage = await docscan4nodejs.insertPage(host, doc.uid, { password: '', source: info.url }); + // console.log('Insert page:', insertPage); } - // await docscan4nodejs.deleteJob(host, jobId); + + + let docinfo = await docscan4nodejs.getDocumentInfo(host, doc.uid); + // console.log('Document info:', docinfo); + + // let deletePage = await docscan4nodejs.deletePage(host, doc.uid, insertPage.pages[0].uid); + // console.log('Delete page:', deletePage); + let docfile = await docscan4nodejs.getDocumentFile(host, doc.uid, './'); + console.log('Document file:', docfile); + + let deleteDoc = await docscan4nodejs.deleteDocument(host, doc.uid); + console.log('Delete document:', deleteDoc); + + await docscan4nodejs.deleteJob(host, jobId); askQuestion(); })(); } - + catch (error) { + console.error('Job creation failed:', error.message); + askQuestion(); + } }); } } diff --git a/examples/web/app.js b/examples/web/app.js index 4be02f0..934292c 100644 --- a/examples/web/app.js +++ b/examples/web/app.js @@ -27,12 +27,12 @@ io.on('connection', (socket) => { }); socket.on('message', async (message) => { - let json = JSON.parse(message); - if (json) { - if (json['scan']) { + let data = JSON.parse(message); + if (data) { + if (data['scan']) { let parameters = { license: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", - device: json['scan'], + device: data['scan'], }; parameters.config = { @@ -45,34 +45,35 @@ io.on('connection', (socket) => { IfDuplexEnabled: false, }; - let jobId = await docscan4nodejs.scanDocument(host, parameters); - - if (jobId !== '') { - console.log('job id: ' + jobId); - let streams = await docscan4nodejs.getImageStreams(host, jobId); - for (let i = 0; i < streams.length; i++) { - await new Promise((resolve, reject) => { - try { - const passThrough = new PassThrough(); - const chunks = []; - - streams[i].pipe(passThrough); - - passThrough.on('data', (chunk) => { - chunks.push(chunk); - }); - - passThrough.on('end', () => { - const buffer = Buffer.concat(chunks); - socket.emit('image', buffer); - resolve(); - }); - } - catch (error) { - reject(error); - } - }); - } + let json = await docscan4nodejs.createJob(host, parameters); + let jobId = json.jobuid; + + let status = await docscan4nodejs.checkJob(host, jobId); + console.log('Job status:', status); + + let streams = await docscan4nodejs.getImageStreams(host, jobId); + for (let i = 0; i < streams.length; i++) { + await new Promise((resolve, reject) => { + try { + const passThrough = new PassThrough(); + const chunks = []; + + streams[i].pipe(passThrough); + + passThrough.on('data', (chunk) => { + chunks.push(chunk); + }); + + passThrough.on('end', () => { + const buffer = Buffer.concat(chunks); + socket.emit('image', buffer); + resolve(); + }); + } + catch (error) { + reject(error); + } + }); } } } diff --git a/index.js b/index.js index 90d27ab..0c50d09 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,11 @@ const ScannerType = { WIATWAINSCANNER: 0x800 }; +const JobStatus = { + RUNNING: 'running', + CANCELED: 'canceled', +}; + // Unified HTTP/HTTPS request handler function request(options) { return new Promise((resolve, reject) => { @@ -59,7 +64,16 @@ function request(options) { }); }); - req.on('error', reject); + req.on('error', (err) => { + if (err.code === 'ECONNRESET') { + console.error('ECONNRESET!!!', { + url: options.url, + retryCount: options.retryCount || 0 + }); + } + reject(err); + }); + if (options.body) { req.write(typeof options.body === 'string' ? options.body @@ -69,10 +83,26 @@ function request(options) { }); } +// Get server version information +async function getServerInfo(host) { + try { + const response = await request({ + url: `${host}/api/server/version`, + method: 'GET', + json: true + }); + return response.data; + } catch (error) { + return { + version: error.message, + compatible: false + }; + } +} + // Fetch single image file and save to directory async function getImageFile(host, jobId, directory) { - const url = `${host}/DWTAPI/ScanJobs/${jobId}/NextDocument`; - + const url = `${host}/api/device/scanners/jobs/${jobId}/next-page`; try { const response = await request({ url, @@ -91,7 +121,6 @@ async function getImageFile(host, jobId, directory) { // Handle successful write writer.on('finish', () => { - console.log('Saved image to', imagePath); resolve(filename); }); @@ -120,7 +149,7 @@ async function getImageFile(host, jobId, directory) { // Fetch single image stream async function getImageStream(host, jobId) { - const url = `${host}/DWTAPI/ScanJobs/${jobId}/NextDocument`; + const url = `${host}/api/device/scanners/jobs/${jobId}/next-page`; try { const response = await request({ @@ -138,93 +167,364 @@ async function getImageStream(host, jobId) { return null; } -module.exports = { - // Get available scanning devices - getDevices: async function (host, scannerType) { - let url = `${host}/DWTAPI/Scanners`; - if (scannerType != null) url += `?type=${scannerType}`; - - try { - const response = await request({ - url, - json: true - }); +// Get available scanning devices +async function getDevices(host, scannerType) { + let url = `${host}/api/device/scanners`; + if (scannerType != null) url += `?type=${scannerType}`; - if (response.data.length > 0) { - console.log('Available scanners:', response.data.length); - return response.data; - } - } catch (error) { - console.error('Device discovery failed:', error.message); + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + if (response.data.length > 0) { + return response.data; } - return []; - }, - - // Create new scan job - scanDocument: async function (host, parameters, timeout = 30) { - const url = `${host}/DWTAPI/ScanJobs?timeout=${timeout}`; - - try { - const response = await request({ - url, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) - }, - body: parameters - }); + } catch (error) { + console.error('Device discovery failed:', error.message); + } + return []; +} - return response.status === 201 ? response.data : ''; - } catch (error) { - console.error('Scan job creation failed:', error.message); - return ''; +// Create new scan job +async function createJob(host, parameters) { + const url = `${host}/api/device/scanners/jobs`; + + try { + const response = await request({ + url, + method: 'POST', + headers: { + 'X-DICS-LICENSE-KEY': parameters.license, + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + json: true, + body: parameters + }); + if (response.status !== 201) { + console.log('Job created:', response); } - }, - // Delete existing scan job - deleteJob: async function (host, jobId) { - if (!jobId) return; + return response.data; + } catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} + +// Retrive the job status +async function checkJob(host, jobId) { + const url = `${host}/api/device/scanners/jobs/${jobId}`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} + +// Delete existing scan job +async function deleteJob(host, jobId) { + if (!jobId) return; + + const url = `${host}/api/device/scanners/jobs/${jobId}`; + try { + await request({ + url, + method: 'DELETE' + }); + } catch (error) { + console.error('Job deletion failed:', error.message); + } +} + +// Update existing scan job status (e.g. 'running', canceled) +async function updateJob(host, jobId, parameters) { + const url = `${host}/api/device/scanners/jobs/${jobId}`; + + try { + const response = await request({ + url, + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + json: true, + body: parameters + }); + + return response.status === 200 ? response.data : ''; + } catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} + +// Get scanner capabilities +async function getScannerCapabilities(host, jobId) { + const url = `${host}/api/device/scanners/jobs/${jobId}/scanner/capabilities`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } + catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} + +// Get multiple image files +async function getImageFiles(host, jobId, directory) { + const images = []; + console.log('Starting image download...'); + + while (true) { + const filename = await getImageFile(host, jobId, directory); + if (!filename) break; + images.push(filename); + } + return images; +} + +// Get multiple image streams +async function getImageStreams(host, jobId) { + const streams = []; + + while (true) { + const stream = await getImageStream(host, jobId); + if (!stream) break; + streams.push(stream); + } + return streams; +} + +// Get image information +async function getImageInfo(host, jobId) { + const url = `${host}/api/device/scanners/jobs/${jobId}/next-page-info`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } catch (error) { + console.error('Image info fetch failed:', error.message); + return ''; + } +} + +//////////////////////// +// Document functions + +// Create a new document +async function createDocument(host, parameters) { + const url = `${host}/api/storage/documents`; + + try { + const response = await request({ + url, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + json: true, + body: parameters + }); + console.log('Document created:', response); + return response.status === 201 ? response.data : ''; + } + catch (error) { + console.error('Document creation failed:', error.message); + return ''; + } +} + +// Get document information +async function getDocumentInfo(host, docId) { + const url = `${host}/api/storage/documents/${docId}`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } + catch (error) { + console.error('Document info fetch failed:', error.message); + return ''; + } +} + +// Delete existing document +async function deleteDocument(host, docId) { + const url = `${host}/api/storage/documents/${docId}`; + + try { + const response = await request({ + url, + method: 'DELETE' + }); + + return response.status === 204 ? 'Document deleted' : ''; + } + catch (error) { + console.error('Document deletion failed:', error.message); + } +} + +// Fetch document file and save to directory +async function getDocumentFile(host, docId, directory) { + const url = `${host}/api/storage/documents/${docId}/content`; + + try { + const response = await request({ + url, + method: 'GET', + stream: true + }); + + if (response.status === 200) { + return new Promise((resolve, reject) => { + const filename = `document_${Date.now()}.pdf`; + const imagePath = path.join(directory, filename); + const writer = fs.createWriteStream(imagePath); + + // Pipe response stream to file + response.stream.pipe(writer); + + // Handle successful write + writer.on('finish', () => { + resolve(filename); + }); + + // Handle errors + const handleError = (err) => { + writer.destroy(); + reject(err); + }; + + writer.on('error', handleError); + response.stream.on('error', handleError); + + // Handle timeout (30 seconds) + const timeout = setTimeout(() => { + handleError(new Error('Download timeout')); + }, 30000); - const url = `${host}/DWTAPI/ScanJobs/${jobId}`; - try { - await request({ - url, - method: 'DELETE' + writer.on('close', () => clearTimeout(timeout)); }); - } catch (error) { - console.error('Job deletion failed:', error.message); } - }, + } + catch (error) { + console.error('Document fetch failed:', error.message); + } +} - getImageFile, - getImageStream, +// Fetch document stream +async function getDocumentStream(host, docId) { + const url = `${host}/api/storage/documents/${docId}/content`; - // Get multiple image files - getImageFiles: async function (host, jobId, directory) { - const images = []; - console.log('Starting image download...'); + try { + const response = await request({ + url, + method: 'GET', + stream: true + }); - while (true) { - const filename = await getImageFile(host, jobId, directory); - if (!filename) break; - images.push(filename); - } - return images; - }, - - // Get multiple image streams - getImageStreams: async function (host, jobId) { - const streams = []; - console.log('Starting stream collection...'); - - while (true) { - const stream = await getImageStream(host, jobId); - if (!stream) break; - streams.push(stream); + if (response.status === 200) { + return response.stream; } - return streams; - }, + } catch (error) { + console.error('Stream fetch failed:', error.message); + } + return null; +} + +// Insert a new page into an existing document +async function insertPage(host, docId, parameters) { + const url = `${host}/api/storage/documents/${docId}/pages`; + + try { + const response = await request({ + url, + method: 'POST', + headers: { + 'X-DICS-DOC-PASSWORD': parameters.password, + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + json: true, + body: parameters + }); + return response.status === 200 ? response.data : ''; + } + catch (error) { + console.error('Page insertion failed:', error.message); + return ''; + } +} - ScannerType +// Delete an existing page from a document +async function deletePage(host, docId, pageId) { + const url = `${host}/api/storage/documents/${docId}/pages/${pageId}`; + + try { + const response = await request({ + url, + method: 'DELETE' + }); + + return response.status === 204 ? 'Page deleted' : ''; + } + catch (error) { + console.error('Page deletion failed:', error.message); + } +} + +module.exports = { + ScannerType, + JobStatus, + getServerInfo, + getDevices, + createJob, + deleteJob, + updateJob, + checkJob, + getImageFile, + getImageStream, + getImageFiles, + getImageStreams, + getScannerCapabilities, + getImageInfo, + createDocument, + getDocumentInfo, + deleteDocument, + getDocumentFile, + getDocumentStream, + insertPage, + deletePage }; \ No newline at end of file