def wsd_validate_scan_ticket(hosted_scan_service: wsd_transfer__structures.HostedService, tkt: wsd_scan__structures.ScanTicket) \ -> typing.Tuple[bool, wsd_scan__structures.ScanTicket]: """ Submit a ValidateScanTicket request, and parse the response. Scanner devices can validate scan settings/parameters and fix errors if any. It is recommended to always validate a ticket before submitting the actual scan job. :param hosted_scan_service: the wsd scan service to query :type hosted_scan_service: wsd_transfer__structures.HostedService :param tkt: the ScanTicket to submit for validation purposes :type tkt: wsd_scan__structures.ScanTicket :return: a tuple of the form (boolean, ScanTicket), where the first field is True if no errors were found during\ validation, along with the same ticket submitted, or False if errors were found, along with a corrected ticket. """ fields = {"FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr} x = wsd_common.submit_request({hosted_scan_service.ep_ref_addr}, "ws-scan__validate_scan_ticket.xml", { **fields, **tkt.as_map() }) v = wsd_common.xml_find(x, ".//sca:ValidTicket") if v.text == 'true' or v.text == '1': return True, tkt else: return False, wsd_scan__parsers.parse_scan_ticket( wsd_common.xml_find(x, ".//sca::ValidScanTicket"))
def wsd_get_scanner_elements( hosted_scan_service: wsd_transfer__structures.HostedService): """ Submit a GetScannerElements request, and parse the response. The device should reply with informations about itself, its configuration, its status and the defalt scan ticket :param hosted_scan_service: the wsd scan service to query :type hosted_scan_service: wsd_transfer__structures.HostedService :return: a tuple of the form (ScannerDescription, ScannerConfiguration, ScannerStatus, ScanTicket) """ fields = {"FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr} x = wsd_common.submit_request({hosted_scan_service.ep_ref_addr}, "ws-scan__get_scanner_elements.xml", fields) re = wsd_common.xml_find(x, ".//sca:ScannerElements") sca_status = wsd_common.xml_find(re, ".//sca:ScannerStatus") sca_config = wsd_common.xml_find(re, ".//sca:ScannerConfiguration") sca_descr = wsd_common.xml_find(re, ".//sca:ScannerDescription") std_ticket = wsd_common.xml_find(re, ".//sca:DefaultScanTicket") description = wsd_scan__parsers.parse_scan_description(sca_descr) status = wsd_scan__parsers.parse_scan_status(sca_status) config = wsd_scan__parsers.parse_scan_configuration(sca_config) std_ticket = wsd_scan__parsers.parse_scan_ticket(std_ticket) return description, config, status, std_ticket
def wsd_renew(hosted_service: wsd_transfer__structures.HostedService, subscription_id: str, expiration: typing.Union[datetime, timedelta] = None) \ -> bool: """ Renew an events subscription of a wsd service :param hosted_service: the wsd service that you want to renew the subscription :type hosted_service: wsd_transfer__structures.HostedService :param subscription_id: the ID returned from a previous successful event subscription call :type subscription_id: str :param expiration: Expiration time, as a datetime or timedelta object :type expiration: datetime | timedelta | None :return: False if a fault message is received instead, True otherwise :rtype: bool """ fields_map = { "FROM": wsd_globals.urn, "TO": hosted_service.ep_ref_addr, "SUBSCRIPTION_ID": subscription_id, "EXPIRES": expiration } x = wsd_common.submit_request({hosted_service.ep_ref_addr}, "ws-eventing__renew.xml", fields_map) return False if wsd_common.check_fault(x) else True
def wsd_get_status(hosted_service: wsd_transfer__structures.HostedService, subscription_id: str) \ -> typing.Union[None, bool, datetime]: """ Get the status of an events subscription of a wsd service :param hosted_service: the wsd service from which you want to hear about the subscription status :type hosted_service: wsd_transfer__structures.HostedService :param subscription_id: the ID returned from a previous successful event subscription call :type subscription_id: str :return: False if a fault message is received instead, \ none if the subscription has no expiration set, \ the expiration date otherwise :rtype: None | False | datetime """ fields_map = { "FROM": wsd_globals.urn, "TO": hosted_service.ep_ref_addr, "SUBSCRIPTION_ID": subscription_id } x = wsd_common.submit_request({hosted_service.ep_ref_addr}, "ws-eventing__get_status.xml", fields_map) if wsd_common.check_fault(x): return False e = wsd_common.xml_find(x, ".//wse:Expires") return xml_helpers.parse_xml_datetime(e.text.replace(" ", ""), weak=True) if e is not None else None
def wsd_subscribe(hosted_service: wsd_transfer__structures.HostedService, event_uri: str, notify_addr: str, expiration: typing.Union[datetime, timedelta] = None) \ -> typing.Union[etree.ElementTree, bool]: """ Subscribe to a certain type of events of a wsd service :param hosted_service: the wsd service to receive event notifications from :type hosted_service: wsd_transfer__structures.HostedService :param event_uri: the full URI of the targeted event class. \ Those URIs are taken from ws specifications :type event_uri: str :param notify_addr: The address to send notifications to. :type notify_addr: str :param expiration: Expiration time, as a datetime or timedelta object :type expiration: datetime | timedelta | None :return: the xml SubscribeResponse of the wsd service\ or False if a fault message is received instead :rtype: lxml.etree.ElementTree | False """ if expiration is None: pass elif isinstance(expiration, datetime): expiration = xml_helpers.fmt_as_xml_datetime(expiration) elif isinstance(expiration, timedelta): expiration = xml_helpers.fmt_as_xml_duration(expiration) else: raise TypeError("Type %s not allowed" % expiration.__class__) expiration_tag = "" if expiration is not None: expiration_tag = "<wse:Expires>%s</wse:Expires>" % expiration fields_map = { "FROM": wsd_globals.urn, "TO": hosted_service.ep_ref_addr, "NOTIFY_ADDR": notify_addr, "EXPIRES": expiration, "FILTER_DIALECT": "http://schemas.xmlsoap.org/ws/2006/02/devprof/Action", "EVENT": event_uri, "OPT_EXPIRATION": expiration_tag } x = wsd_common.submit_request({hosted_service.ep_ref_addr}, "ws-eventing__subscribe.xml", fields_map) if wsd_common.check_fault(x): return False return wsd_common.xml_find(x, ".//wse:SubscribeResponse")
def wsd_scan_available_event_subscribe( hosted_scan_service: wsd_transfer__structures.HostedService, display_str: str, context_str: str, notify_addr: str, expiration: typing.Union[datetime, timedelta] = None): """ Subscribe to ScanAvailable events. :param hosted_scan_service: the wsd service to receive event notifications from :param display_str: the string to display on the device control panel :param context_str: a string internally used to identify the selection of this wsd host as target of the scan :param notify_addr: The address to send notifications to. :param expiration: Expiration time, as a datetime or timedelta object :return: a subscription ID and the token needed in CreateScanJob to start a device-initiated scan, \ or False if a fault message is received instead """ if expiration is None: pass elif expiration.__class__ == "datetime.datetime": expiration = xml_helpers.fmt_as_xml_datetime(expiration) elif expiration.__class__ == "datetime.timedelta": expiration = xml_helpers.fmt_as_xml_duration(expiration) else: raise TypeError expiration_tag = "" if expiration is not None: expiration_tag = "<wse:Expires>%s</wse:Expires>" % expiration fields_map = { "FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr, "NOTIFY_ADDR": notify_addr, "OPT_EXPIRATION": expiration_tag, "DISPLAY_STR": display_str, "CONTEXT": context_str } try: x = wsd_common.submit_request( {hosted_scan_service.ep_ref_addr}, "ws-scan__scan_available_event_subscribe.xml", fields_map) dest_token = wsd_common.xml_find(x, ".//sca:DestinationToken").text subscription_id = wsd_common.xml_find(x, ".//wse:Identifier").text return subscription_id, dest_token except TimeoutError: return False
def wsd_get_job_history(hosted_scan_service: wsd_transfer__structures.HostedService) \ -> typing.List[wsd_scan__structures.JobSummary]: """ Submit a GetJobHistory request, and parse the response. The device should reply with a list of recently ended jobs. Note that some device implementations do not keep or share job history through WSD. :param hosted_scan_service: the wsd scan service to query :type hosted_scan_service: wsd_transfer__structures.HostedService :return: a list of JobSummary elements. """ fields = {"FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr} x = wsd_common.submit_request({hosted_scan_service.ep_ref_addr}, "ws-scan__get_job_history.xml", fields) jsl = [] for y in wsd_common.xml_findall(x, ".//sca:JobSummary"): jsl.append(wsd_scan__parsers.parse_job_summary(y)) return jsl
def wsd_get_active_jobs(hosted_scan_service: wsd_transfer__structures.HostedService) \ -> typing.List[wsd_scan__structures.JobSummary]: """ Submit a GetActiveJobs request, and parse the response. The device should reply with a list of all active scan jobs. :param hosted_scan_service: the wsd scan service to query :type hosted_scan_service: wsd_transfer__structures.HostedService :return: a list of JobSummary elements :rtype: list[wsd_scan__structures.JobSummary] """ fields = {"FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr} x = wsd_common.submit_request({hosted_scan_service.ep_ref_addr}, "ws-scan__get_active_jobs.xml", fields) jsl = [] for y in wsd_common.xml_findall(x, ".//sca:JobSummary"): jsl.append(wsd_scan__parsers.parse_job_summary(y)) return jsl
def wsd_unsubscribe(hosted_service: wsd_transfer__structures.HostedService, subscription_id: str) \ -> bool: """ Unsubscribe from events notifications of a wsd service :param hosted_service: the wsd service from which you want to unsubscribe for events :type hosted_service: wsd_transfer__structures.HostedService :param subscription_id: the ID returned from a previous successful event subscription call :type subscription_id: str :return: False if a fault message is received instead, True otherwise :rtype: bool """ fields_map = { "FROM": wsd_globals.urn, "TO": hosted_service.ep_ref_addr, "SUBSCRIPTION_ID": subscription_id } x = wsd_common.submit_request({hosted_service.ep_ref_addr}, "ws-eventing__unsubscribe.xml", fields_map) return False if wsd_common.check_fault(x) else True
def wsd_get_job_elements( hosted_scan_service: wsd_transfer__structures.HostedService, job: wsd_scan__structures.ScanJob): """ Submit a GetJob request, and parse the response. The device should reply with info's about the specified job, such as its status, the ticket submitted for job initiation, the final parameters set effectively used to scan, and a document list. :param hosted_scan_service: the wsd scan service to query :type hosted_scan_service: wsd_transfer__structures.HostedService :param job: the ScanJob instance representing the job to abort :type job: wsd_scan_structures.ScanJob :return: a tuple of the form (JobStatus, ScanTicket, DocumentParams, doclist),\ where doclist is a list of document names """ fields = { "FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr, "JOB_ID": job.id } x = wsd_common.submit_request({hosted_scan_service.ep_ref_addr}, "ws-scan__get_job_elements.xml", fields) q = wsd_common.xml_find(x, ".//sca:JobStatus") jstatus = wsd_scan__parsers.parse_job_status(q) st = wsd_common.xml_find(x, ".//sca:ScanTicket") tkt = wsd_scan__parsers.parse_scan_ticket(st) dfp = wsd_common.xml_find(x, ".//sca:Documents/sca:DocumentFinalParameters") dps = wsd_scan__parsers.parse_document_params(dfp) dlist = [ x.text for x in wsd_common.xml_findall( dfp, "sca:Document/sca:DocumentDescription/sca:DocumentName") ] return jstatus, tkt, dps, dlist
def wsd_create_scan_job(hosted_scan_service: wsd_transfer__structures.HostedService, tkt: wsd_scan__structures.ScanTicket, scan_identifier: str = "", dest_token: str = "") \ -> wsd_scan__structures.ScanJob: """ Submit a CreateScanJob request, and parse the response. This creates a scan job and starts the image(s) acquisition. :param hosted_scan_service: the wsd scan service to query :type hosted_scan_service: wsd_transfer__structures.HostedService :param tkt: the ScanTicket to submit for validation purposes :type tkt: wsd_scan__structures.ScanTicket :param scan_identifier: a string identifying the device-initiated scan to handle, if any :type scan_identifier: str :param dest_token: a token assigned by the scanner to this client, needed for device-initiated scans :type dest_token: str :return: a ScanJob instance :rtype: wsd_scan__structures.ScanJob """ fields = { "FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr, "SCAN_ID": scan_identifier, "DEST_TOKEN": dest_token } x = wsd_common.submit_request({hosted_scan_service.ep_ref_addr}, "ws-scan__create_scan_job.xml", { **fields, **tkt.as_map() }) x = wsd_common.xml_find(x, ".//sca:CreateScanJobResponse") return wsd_scan__parsers.parse_scan_job(x)
def wsd_cancel_job(hosted_scan_service: wsd_transfer__structures.HostedService, job: wsd_scan__structures.ScanJob) \ -> bool: """ Submit a CancelJob request, and parse the response. Stops and aborts the specified scan job. :param hosted_scan_service: the wsd scan service to query :type hosted_scan_service: wsd_transfer__structures.HostedService :param job: the ScanJob instance representing the job to abort :type job: wsd_scan_structures.ScanJob :return: True if the job is found and then aborted, False if the specified job do not exists or already ended. :rtype: bool """ fields = { "FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr, "JOB_ID": job.id } x = wsd_common.submit_request({hosted_scan_service.ep_ref_addr}, "ws-scan__cancel_job.xml", fields) wsd_common.xml_find(x, ".//sca:ClientErrorJobIdNotFound") return x is None
def wsd_get(target_service: wsd_discovery__structures.TargetService): """ Query wsd target for information about model/device and hosted services. :param target_service: A wsd target :type target_service: wsd_discovery__structures.TargetService :return: A tuple containing a TargetInfo and a list of HostedService instances. """ fields = {"FROM": wsd_globals.urn, "TO": target_service.ep_ref_addr} x = wsd_common.submit_request(target_service.xaddrs, "ws-transfer__get.xml", fields) if x is False: return False meta = wsd_common.xml_find(x, ".//mex:Metadata") meta_model = wsd_common.xml_find( meta, ".//mex:MetadataSection[@Dialect=\ 'http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisModel']" ) meta_dev = wsd_common.xml_find( meta, ".//mex:MetadataSection[@Dialect=\ 'http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisDevice']" ) meta_rel = wsd_common.xml_find( meta, ".//mex:MetadataSection[@Dialect=\ 'http://schemas.xmlsoap.org/ws/2006/02/devprof/Relationship']" ) tinfo = wsd_transfer__structures.TargetInfo() # WSD-Profiles section 5.1 (+ PNP-X) tinfo.manufacturer = wsd_common.xml_find(meta_model, ".//wsdp:Manufacturer").text q = wsd_common.xml_find(meta_model, ".//wsdp:ManufacturerUrl") if q is not None: tinfo.manufacturer_url = q.text tinfo.model_name = wsd_common.xml_find(meta_model, ".//wsdp:ModelName").text q = wsd_common.xml_find(meta_model, ".//wsdp:ModelNumber") if q is not None: tinfo.model_number = q.text q = wsd_common.xml_find(meta_model, ".//wsdp:ModelUrl") if q is not None: tinfo.model_url = q.text q = wsd_common.xml_find(meta_model, ".//wsdp:PresentationUrl") if q is not None: tinfo.presentation_url = q.text tinfo.device_cat = wsd_common.xml_find( meta_model, ".//pnpx:DeviceCategory").text.split() tinfo.friendly_name = wsd_common.xml_find(meta_dev, ".//wsdp:FriendlyName").text tinfo.fw_ver = wsd_common.xml_find(meta_dev, ".//wsdp:FirmwareVersion").text tinfo.serial_num = wsd_common.xml_find(meta_dev, ".//wsdp:SerialNumber").text hservices = [] # WSD-Profiles section 5.2 (+ PNP-X) wsd_common.xml_findall( meta_rel, ".//wsdp:Relationship[@Type='http://schemas.xmlsoap.org/ws/2006/02/devprof/host']" ) for r in meta_rel: # UNCLEAR how the host item should differ from the target endpoint, and how to manage multiple host items # TBD - need some real-case examples # host = xml_find(r, ".//wsdp:Host") # if host is not None: #"if omitted, implies the same endpoint reference of the targeted service" # xml_find(host, ".//wsdp:Types").text # xml_find(host, ".//wsdp:ServiceId").text # er = xml_find(host, ".//wsa:EndpointReference") # xml_find(er, ".//wsa:Address").text #Optional endpoint fields not implemented yet hosted = wsd_common.xml_findall(r, ".//wsdp:Hosted") for h in hosted: hs = wsd_transfer__structures.HostedService() hs.types = wsd_common.xml_find(h, ".//wsdp:Types").text.split() hs.service_id = wsd_common.xml_find(h, ".//wsdp:ServiceId").text q = wsd_common.xml_find(h, ".//pnpx:HardwareId") if q is not None: hs.hardware_id = q.text q = wsd_common.xml_find(h, ".//pnpx:CompatibleId") if q is not None: hs.compatible_id = q.text q = wsd_common.xml_find(h, ".//wsdp:ServiceAddress") if q is not None: hs.service_address = q.text er = wsd_common.xml_find(h, ".//wsa:EndpointReference") hs.ep_ref_addr = wsd_common.xml_find(er, ".//wsa:Address").text hservices.append(hs) # WSD-Profiles section 5.3 and 5.4 omitted return tinfo, hservices
def wsd_get_printer_elements(hosted_print_service): fields = {"FROM": wsd_globals.urn, "TO": hosted_print_service.ep_ref_addr} wsd_common.submit_request({hosted_print_service.ep_ref_addr}, "ws-print__get_printer_elements.xml", fields)