From 154faaf5d58f562698217ba0dddbdef1ac0ea6a8 Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Thu, 1 Jun 2017 20:25:25 -0300 Subject: [PATCH 1/8] When adding a printer, consider also the "driverless" and "bjnp" CUPS backends as network backends When starting the wizard for adding a new printer the available printers and CUPS backends are enumerated by calling all CUPS backends in discovery mode, but in contrary to CUPS' "lpinfo -v" the bunch is devided up into two groups, first the faster local printer backends are called and the appropriate printers listed and after that the slower network printer backends. This way the local printers appear quickly in the wizard and the user can already proceed when the desired local printer shows up. The "driverless" and "bjnp" backends were missing in the list of network backends and therefore were considered local, ending up with the local backend round taking too long. --- newprinter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newprinter.py b/newprinter.py index c8d973a10..1d411f25d 100644 --- a/newprinter.py +++ b/newprinter.py @@ -1986,7 +1986,7 @@ def fetchDevices(self, network=False, current_uri=None): # Search for Bluetooth printers together with the network printers # as the Bluetooth search takes rather long time - network_schemes = ["dnssd", "snmp", "bluetooth"] + network_schemes = ["dnssd", "snmp", "driverless", "bjnp", "bluetooth"] error_handler = self.error_getting_devices if network == False: reply_handler = (lambda x, y: From d907e1681fd7144805767b3d520fc091773ce159 Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Thu, 1 Jun 2017 20:34:17 -0300 Subject: [PATCH 2/8] Suppress backend-only entry for the "bjnp" backend in the list of discovered printers The list of discovered printers and CUPS backends in the wizard for adding a new printer has an entry for the "bjnp" CUPS backend (backend for proprietary network protocol of some Canon printers). This entry appears when no appropriate Canon printer gets found and the entry in the list is useless. Therefore we suppress it. --- newprinter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newprinter.py b/newprinter.py index 1d411f25d..4b0db9273 100644 --- a/newprinter.py +++ b/newprinter.py @@ -2369,7 +2369,7 @@ def replace_generic (device): device2.uri = "delete" devices = [x for x in devices if x.uri not in ("hp", "hpfax", "hal", "beh", "smb", - "scsi", "http", + "scsi", "http", "bjnp", "delete")] newdevices = [] for device in devices: From a225a1c4936f5f40e709b8d87ff2467ef78b0c6a Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Thu, 1 Jun 2017 20:42:26 -0300 Subject: [PATCH 3/8] Improved determining whether two discovered network printer entries are from the same physical printer Various improvements in the PhysicalDevice class: - In each physical device record for a network device determine the IP address of the device and drop it into the _network_host field, instead of a host name. Try to determine the IP from both host names found in the URI and DNS-SD host names. With this it is much more reliable to find out whether two entries of discovered network devices come from the same physical device. - Add the URI of the first device added to a physical device record and do not change it when more devices get added. This way the record can be easily followed when debugging. - When dumping the content of a physical record with the repr() function for debugging, do not only show make, model, and serial number, but also IP, DNS-SD host name, and URI of the first device added. - Improve implementation of the "<" operator, checking for equal and, taking into account the DNS-SD host name, and making the function for separating make and model the same as in the "=" operator. - Added debug output. --- PhysicalDevice.py | 55 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/PhysicalDevice.py b/PhysicalDevice.py index 5393b55ab..8ec3b89d3 100644 --- a/PhysicalDevice.py +++ b/PhysicalDevice.py @@ -23,8 +23,9 @@ gettext.install(domain=config.PACKAGE, localedir=config.localedir) import cupshelpers import urllib.parse - import ppdippstr +import socket +from debug import * class PhysicalDevice: def __init__(self, device): @@ -32,6 +33,7 @@ def __init__(self, device): self._network_host = None self.dnssd_hostname = None self._cupsserver = False + self.firsturi = None self.add_device (device) self._user_data = {} self._ppdippstr = ppdippstr.backends @@ -92,6 +94,20 @@ def _get_host_from_uri (self, uri): if hostport: (host, port) = urllib.parse.splitport (hostport) + if (host): + ip = None + try: + ip = socket.gethostbyname(host) + if ip: + host = ip + except: + pass + elif (dnssdhost): + try: + host = socket.gethostbyname(dnssdhost) + except: + host = None + return self._add_dot_local_if_needed(host), \ self._add_dot_local_if_needed(dnssdhost) @@ -160,6 +176,11 @@ def count_lower (s): if d.uri == device.uri: return + # Use the URI of the very first device added as a kind of identifier + # for this physical device record, to make debugging easier + if not self.firsturi: + self.firsturi = device.uri; + self.devices.append (device) self.devices.sort () @@ -175,11 +196,21 @@ def count_lower (s): address = device.address if address: self._network_host = self._add_dot_local_if_needed(address) + if (hasattr (device, 'hostname') and self.dnssd_hostname is None): hostname = device.hostname if hostname: self.dnssd_hostname = self._add_dot_local_if_needed(hostname) + if (self.dnssd_hostname and self._network_host is None): + try: + self._network_host = socket.gethostbyname(hostname); + except: + self._network_host = None + + debugprint("Device %s added to physical device: %s" % + (device.uri, repr(self))) + def get_devices (self): return self.devices @@ -236,9 +267,9 @@ def __str__ (self): return "(description: %s)" % self.__repr__ () def __repr__ (self): - return "" % (self.mfg, - self.mdl, - self.sn) + return ("" % + (self.mfg, self.mdl, self.sn, self._network_host, + self.dnssd_hostname, self.firsturi)) def __eq__(self, other): if type (other) != type (self): @@ -304,6 +335,9 @@ def __lt__(self, other): if type (other) != type (self): return False + if self == other: + return False; + if self._network_host != other._network_host: if self._network_host is None: return True @@ -313,6 +347,15 @@ def __lt__(self, other): return self._network_host < other._network_host + if self.dnssd_hostname != other.dnssd_hostname: + if self.dnssd_hostname is None: + return True + + if other.dnssd_hostname is None: + return False + + return self.dnssd_hostname < other.dnssd_hostname + devs = other.get_devices() if devs: uris = [x.uri for x in self.devices] @@ -335,7 +378,9 @@ def split_make_and_model (dev): make_and_model = dev.mdl else: make_and_model = "%s %s" % (dev.mfg, dev.mdl) - return cupshelpers.ppds.ppdMakeModelSplit (make_and_model) + (mfg, mdl) = cupshelpers.ppds.ppdMakeModelSplit (make_and_model) + return (cupshelpers.ppds.normalize (mfg), + cupshelpers.ppds.normalize (mdl)) (our_mfg, our_mdl) = split_make_and_model (self) (other_mfg, other_mdl) = split_make_and_model (other) From 196028d672fe1938b8246d9a7490819ff9058881 Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Thu, 1 Jun 2017 21:05:39 -0300 Subject: [PATCH 4/8] When adding a device to the list of discovered devices, use the original physical device entry In the new-printer wizard the list of discovered printers in the first wizard step is built up in several stages. To avoid duplicates in each stage the devices are not only added to the already existing physical device record but also to extra physical device record to mark them as new entries which still need to get added to the list. Before, these new physical device records were used for generating the list entry. Now we look up the original physical device entries as they can contain more information (especially IP address and host name) for the list entry. In addition, som extra debug information is logged. --- newprinter.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/newprinter.py b/newprinter.py index 4b0db9273..f8f740355 100644 --- a/newprinter.py +++ b/newprinter.py @@ -2373,13 +2373,24 @@ def replace_generic (device): "delete")] newdevices = [] for device in devices: + debugprint("Adding device with URI %s" % device.uri) + if (hasattr (device, 'address')): + debugprint(" Device address %s" % device.address) + if (hasattr (device, 'hostname')): + debugprint(" Device host name %s" % device.hostname) physicaldevice = PhysicalDevice (device) + debugprint (" Created physical device %s" % repr(physicaldevice)) try: i = self.devices.index (physicaldevice) + debugprint (" Physical device %d is the same printer" % i) self.devices[i].add_device (device) + debugprint (" New physical device %s is same as physical device %d: %s" % + (repr(physicaldevice), i, repr(self.devices[i]))) + debugprint (" Joining devices") except ValueError: self.devices.append (physicaldevice) newdevices.append (physicaldevice) + debugprint (" Physical device %s is a completely new device" % repr(physicaldevice)) self.devices.sort() if current_uri: @@ -2398,7 +2409,15 @@ def replace_generic (device): network_iter = self.devices_network_iter find_nw_iter = self.devices_find_nw_iter - for device in newdevices: + for newdevice in newdevices: + device = None + try: + i = self.devices.index (newdevice) + device = self.devices[i] + except ValueError: + debugprint("ERROR: Cannot identify new physical device with its entry in the device list (%s)" % + repr(newdevice)) + continue devs = device.get_devices () network = devs[0].device_class == 'network' info = device.get_info () From b21dc00c2bb459775885bc92efa2ee2dddfd53ed Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Thu, 1 Jun 2017 21:12:03 -0300 Subject: [PATCH 5/8] Consider entries for HP printers as the same physical device if the serial number matches If an HP multi-function device on USB has also fax functionality, there are also discovery entries for the "hpfax" backend of HPLIP and an additional entry for the "usb" CUPS backend. These entries have model names which differ from the printer's model name, leading to extra entries in the list of discovered printers. To join these entries we consider two HP printers as equal if they have the same serial number, regardless of the model name. --- PhysicalDevice.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/PhysicalDevice.py b/PhysicalDevice.py index 8ec3b89d3..814bc7eba 100644 --- a/PhysicalDevice.py +++ b/PhysicalDevice.py @@ -323,7 +323,13 @@ def split_make_and_model (dev): (our_mfg, our_mdl) = split_make_and_model (self) (other_mfg, other_mdl) = split_make_and_model (other) - if our_mfg != other_mfg or our_mdl != other_mdl: + if our_mfg != other_mfg: + return False + + if our_mfg == "hp" and self.sn != '' and self.sn == other.sn: + return True + + if our_mdl != other_mdl: return False if self.sn == '' or other.sn == '': From 17507831c143f61a44f5f92b92aa0f8567369975 Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Thu, 1 Jun 2017 21:18:44 -0300 Subject: [PATCH 6/8] When listing discovered USB devices, distinguish between printer and fax Some USB printers appear as two USB devices, one for printing and one for fax. This would lead to 2 connection types in the connection type list which are names "USB" and undistinguishable. Now we check on USB printer entries whether they are for fax and mark them as "Fax - USB". --- newprinter.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/newprinter.py b/newprinter.py index f8f740355..d0d4491b0 100644 --- a/newprinter.py +++ b/newprinter.py @@ -3037,7 +3037,11 @@ def on_tvNPDevices_cursor_changed(self, widget): elif device.type == "serial": device.menuentry = _("Serial Port") elif device.type == "usb": - device.menuentry = _("USB") + if (hasattr(device, "uri") and + device.uri.lower().find("fax") > -1): + device.menuentry = _("Fax") + " - " + _("USB") + else: + device.menuentry = _("USB") elif device.type == "bluetooth": device.menuentry = _("Bluetooth") elif device.type == "hp": @@ -3225,6 +3229,7 @@ def on_tvNPDeviceURIs_cursor_changed(self, widget): page = self.new_printer_device_tabs.get (device.type, self.PAGE_SELECT_DEVICE) self.ntbkNPType.set_current_page(page) + debugprint("Selected connection type. URI: %s" % device.uri) location = '' type = device.type url = device.uri.split(":", 1)[-1] @@ -3234,7 +3239,14 @@ def on_tvNPDeviceURIs_cursor_changed(self, widget): if device.type == "parallel": text = _("A printer connected to the parallel port.") elif device.type == "usb": - text = _("A printer connected to a USB port.") + if (hasattr(device, "uri") and + device.uri.lower().find("fax") > -1): + device.menuentry = _("Fax") + " - " + _("USB") + text = _("A fax machine or the fax function " + "of a multi-function device connected " + "to a USB port.") + else: + text = _("A printer connected to a USB port.") elif device.type == "bluetooth": text = _("A printer connected via Bluetooth.") elif device.type == "hp": From dc33ec009bad6daecb9a097cf3d2c2640bdabeab Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Thu, 1 Jun 2017 21:21:47 -0300 Subject: [PATCH 7/8] Let entries for USB printing appear before USB fax entries in the connection type list On a USB multi-function device the printing functionality is much more important than the fax functionality. Most users only use the printing part, and the fax part is also often not supported by an appropriate driver. So let the entry for printing appear first in the connection type list. --- cupshelpers/cupshelpers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cupshelpers/cupshelpers.py b/cupshelpers/cupshelpers.py index 76ae9df38..978ab8019 100755 --- a/cupshelpers/cupshelpers.py +++ b/cupshelpers/cupshelpers.py @@ -562,6 +562,9 @@ def __lt__(self, other): stype = "dnssds" elif self.uri.find("._printer") != -1: stype = "dnssdl" + if stype == "usb": + if self.uri.lower().find("fax") != -1: + stype = "usbfax" otype = other.type if otype == "dnssd": if other.uri.find("._ipp") != -1: @@ -570,6 +573,9 @@ def __lt__(self, other): otype = "dnssds" elif other.uri.find("._printer") != -1: otype = "dnssdl" + if otype == "usb": + if other.uri.lower().find("fax") != -1: + otype = "usbfax" if not self.is_class and (stype != otype): # "hp"/"hpfax" before "usb" before * before "parallel" before @@ -622,6 +628,10 @@ def __lt__(self, other): return False if stype == "usb": return True + if otype == "usbfax": + return False + if stype == "usbfax": + return True result = bool(self.id) < bool(other.id) if not result: result = self.info < other.info From 2a76c8393101b158b9cc8f05a2470251061e65b5 Mon Sep 17 00:00:00 2001 From: Till Kamppeter Date: Fri, 16 Jun 2017 17:25:44 -0300 Subject: [PATCH 8/8] Use getaddrinfo() to determine IP addresses for physical device matching getaddrinfo() is more universal than gethostbyname(), especially it supports also IPv6. So now we also get IP addresses if the device is only available in an IPv6 network and therefore has only an IPv6 IP address. --- PhysicalDevice.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/PhysicalDevice.py b/PhysicalDevice.py index 814bc7eba..00043222e 100644 --- a/PhysicalDevice.py +++ b/PhysicalDevice.py @@ -61,6 +61,18 @@ def _add_dot_local_if_needed(self, hostname): else: return hostname + def _get_address (self, hostname): + try: + address = socket.getaddrinfo(hostname, 0, + family=socket.AF_INET)[0][4][0] + except: + try: + address = socket.getaddrinfo(hostname, 0, + family=socket.AF_INET6)[0][4][0] + except: + address = None + return address + def _get_host_from_uri (self, uri): hostport = None host = None @@ -97,14 +109,14 @@ def _get_host_from_uri (self, uri): if (host): ip = None try: - ip = socket.gethostbyname(host) + ip = self._get_address(host) if ip: host = ip except: pass elif (dnssdhost): try: - host = socket.gethostbyname(dnssdhost) + host = self._get_address(dnssdhost) except: host = None @@ -204,7 +216,7 @@ def count_lower (s): if (self.dnssd_hostname and self._network_host is None): try: - self._network_host = socket.gethostbyname(hostname); + self._network_host = self._get_address(hostname); except: self._network_host = None