LeoKlaus / SwiftESCL

A Swift implementation of the eSCL protocol (aka AirScan) licensed under MIT.
https://wehrfritz.me/openairscan/
MIT License
1 stars 0 forks source link

SwiftESCL in NSViewController #5

Closed pol2095 closed 7 months ago

pol2095 commented 7 months ago

Hello,,

I need to use SwiftESCL in a NSViewController but it's impossible to use @State :

import Cocoa
import Foundation
import SwiftESCL
import SwiftUI
import AppKit

class ViewController: NSViewController {

    @State var discoveredDevices: [String:ScannerRepresentation] = [:]

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = NSButton(frame: NSRect(x: 150, y: 200, width: 80, height: 55))
        button.title =  "A button in code"
        self.view.addSubview(button)
        button.target = self
        button.action = #selector(buttonTest)

        let browser = Browser(usePlainText: true)
        // The string contains the hostname of the device, which should be sufficient to uniquely identify it.
        //@State var discoveredDevices: [String:SwiftESCL.ScannerRepresentation] = [:]
        // As the discovery runs asynchronously, it's easiest to just pass the dictionary as binding
        browser.setDevices(scanners: $discoveredDevices)
        // Now you just have to start the browser to discover devices:
        browser.start()

        // Do any additional setup after loading the view.
    }

    @objc func buttonTest() {
        print("Test button")
        print(discoveredDevices.count)
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
}

Thanks

LeoKlaus commented 7 months ago

I'm not very familiar with UIKit/AppKit, the given example is meant for SwiftUI.

I don't think the @State and @Binding wrappers exists in AppKit.

The browser is hardcoded to use a @Binding variable, so you'll either need to use NSHostingView() to present a SwiftUI view for the scanner items

override func viewDidLoad() {
        super.viewDidLoad()
        ...
        let deviceView = NSHostingView(rootView: DiscoverDevices())
        self.view.addSubview(deviceView)
        ...

or fork and modify SwiftESCL (if you do that, I'd happily merge a PR).

If you need inspiration for how to use SwiftESCL in SwiftUI, you can check out OpenAirScan. Feel free to use any of the views I've written there (though I recommend writing your own, these are some of the first SwiftUI views I've written).

Device discovery is handled here: https://github.com/LeoKlaus/OpenAirScan/blob/main/OpenAirScan/Views/DiscoverDevices.swift

pol2095 commented 7 months ago

I added a callback method instead of @State for Storyboard app, for SwifUI app is better to use Binding,

pol2095 commented 7 months ago

I have an other question : (imageData, self.responseCode) = scanner.scanDocument(resolution: 300, format: "image/jpeg", source: "Platen", width: 2480, height: 3508) I have a responseCode 503 and after 200, what does it correspond to ? Thanks

LeoKlaus commented 7 months ago

I suggest looking at the ESCL spec. 503 should indicate that the scanner is not ready to accept the job.

pol2095 commented 7 months ago

Thanks a lot, how work scanDocument() using a feeder (multiple pages) ?

LeoKlaus commented 7 months ago

scanDocument() using a feeder (multiple pages) ?

My printer handles the feeder as one document: One scanjob will lead to a multi-page PDF.

LeoKlaus commented 7 months ago

One thing I've learned is that not all printers strictly adhere to the spec. Especially Brother devices often don't work with my implementation.

pol2095 commented 7 months ago

For "image/jpeg", what it return for multiple pages ?

LeoKlaus commented 7 months ago

I haven't tried that, but I'd suspect the scanner either scans just a single page or creates multiple jobs.

pol2095 commented 7 months ago

I tried, the scanner return only one page and the task isn't finished, the scanner wait a tranfer order..., but all pages are scanned physically, I need to click on the cancel button of the scanner. If I retry to scan, I have this message error : 503 Service Unavailable – Server busy. Retry later and

let status = scanner.getStatus()
print(status)

return Processing instead of Idle

pol2095 commented 7 months ago

In the ESCL spec.

For all document formats that support multiple pages (PDF, TIFF etc.,), the scanner SHOULD upload a single file with all the scan pages merged together. For the document formats that do not support multiple pages (like JPEG), the clients SHOULD retrieve the individual JPEG scan images and can do any post-processing operation.
After initiating the scan job, the client is responsible to pace the retrieval of the scan pages. In this document, this transactional model is labeled: Pull Scan.
Once the job is created, the scanner SHOULD be in Pending or Processing state. The client keeps checking the scanner status and the client uses the job URL to upload the next scan page after the JobState moves to Pending or Processing. The client keeps invoking this URL until the NextDocument request returns 404 Not Found. When all pages are scanned, the JobState MUST indicate ‘Completed’ denoting there are no more pages to be uploaded. The JobState SHOULD indicate ‘Canceled’ or ‘Aborted’ if the job was either canceled by the user or due to internal abort
If the NextDocument request times out, the client SHOULD check the JobState and retry pulling the scan pages if the JobState is either Pending or Processing.
After the job is created, if the client fails to retrieve the scan pages for some reason – could be even a network failure, the scanner MAY abort the scan job after a certain time period determined by the scanner. The scanner SHOULD NOT cancel the job if it can retain the scan pages for a longer period without resource constraints.
{JobUri}/NextDocument
GET Description
Upload the next scan document (text or photo), using the scan job settings. Payload
IN
HTTP Header: “TE: chunked” - the client MUST support chunked encoding
OUT
HTTP Header: “Content-Location: {DocumentURI}” – this URI uniquely identifies the uploaded document. It is opaque to the client.
HTTP Header: “Accept-Ranges: {bytes|none}” – this header indicates whether the client can attempt to use the “Range” header on the Document URI provided in the Content-Location header. If the value is set to “none” or the entire header is missing, the client cannot use “Range”.
This header is optional: low end scanner MAY not support this feature. However, high end scanners SHOULD support it.
Payload: raw binary data. Data format according to the job settings: e.g. image/jpeg or application/pdf Status Code
200 OK – Success: complete payload transmitted
301 Moved - The client is redirected to a secure connection.
401 Unauthorized - The client is challenged for access credentials.
404 Not Found – no more page. The last page has already been transmitted.
410 Gone – the scan job doesn’t exist anymore. May want to check the status.
500 Internal Server Error - Unknown internal error.
503 Service Unavailable – the job is active, but the scanner can’t return the payload at the moment. Retry later.
pol2095 commented 7 months ago

I understanded how it work, I used responseURL to download the jpeg images let (responseURL, postResponseCode) = scanner.sendPostRequest(resolution: 300, format: "image/jpeg", source: "Adf", width: 2480, height: 3508) to get 4 pages, download a file using responseURL 4 times.

pol2095 commented 7 months ago

Thanks for your help.