class Welcome(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Welcome") welcome = gtk.HBox () welcome.set_spacing (12) welcome.set_border_width (12) image = gtk.Image () image.set_alignment (0, 0) image.set_from_stock (gtk.STOCK_PRINT, gtk.ICON_SIZE_DIALOG) intro = gtk.Label ('<span weight="bold" size="larger">' + _("Trouble-shooting Printing") + '</span>\n\n' + _("The next few screens will contain some " "questions about your problem with printing. " "Based on your answers a solution may be " "suggested.") + '\n\n' + _("Click 'Forward' to begin.")) intro.set_alignment (0, 0) intro.set_use_markup (True) intro.set_line_wrap (True) welcome.pack_start (image, False, False, 0) welcome.pack_start (intro, True, True, 0) page = troubleshooter.new_page (welcome, self) def collect_answer (self): parent = self.troubleshooter.get_window () # Store the authentication dialog instance in the answers. This # allows the password to be cached. factory = AuthConnFactory (parent) self.op = TimedOperation (factory.get_connection, parent=parent) return {'_authenticated_connection_factory': factory, '_authenticated_connection': self.op.run () } def cancel_operation (self): self.op.cancel ()
class Welcome(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Welcome") welcome = Gtk.HBox () welcome.set_spacing (12) welcome.set_border_width (12) image = Gtk.Image () image.set_alignment (0, 0) image.set_from_stock (Gtk.STOCK_PRINT, Gtk.IconSize.DIALOG) intro = Gtk.Label(label='<span weight="bold" size="larger">' + _("Trouble-shooting Printing") + '</span>\n\n' + _("The next few screens will contain some " "questions about your problem with printing. " "Based on your answers a solution may be " "suggested.") + '\n\n' + _("Click 'Forward' to begin.")) intro.set_alignment (0, 0) intro.set_use_markup (True) intro.set_line_wrap (True) welcome.pack_start (image, False, False, 0) welcome.pack_start (intro, True, True, 0) page = troubleshooter.new_page (welcome, self) def collect_answer (self): parent = self.troubleshooter.get_window () # Store the authentication dialog instance in the answers. This # allows the password to be cached. factory = AuthConnFactory (parent) self.op = TimedOperation (factory.get_connection, parent=parent) return {'_authenticated_connection_factory': factory, '_authenticated_connection': self.op.run () } def cancel_operation (self): self.op.cancel ()
class PrinterFinder: def __init__ (self): self.quit = False def find (self, hostname, callback_fn): self.hostname = hostname self.callback_fn = callback_fn self.op = TimedOperation (self._do_find, callback=lambda x, y: None) def cancel (self): self.op.cancel () self.quit = True def _do_find (self): self._cached_attributes = dict() for fn in [self._probe_jetdirect, self._probe_ipp, self._probe_snmp, self._probe_lpd, self._probe_hplip, self._probe_smb]: if self.quit: return try: fn () except Exception, e: nonfatalException () # Signal that we've finished. if not self.quit: self.callback_fn (None)
class NetworkCUPSPrinterShared(Question): def __init__(self, troubleshooter): Question.__init__(self, troubleshooter, "Queue not shared?") page = self.initial_vbox( _("Queue Not Shared"), _("The CUPS printer on the server is not " "shared.")) troubleshooter.new_page(page, self) def display(self): self.answers = {} answers = self.troubleshooter.answers if (answers.has_key('remote_cups_queue_listed') and answers['remote_cups_queue_listed'] == False): return False parent = self.troubleshooter.get_window() if not answers.has_key('remote_cups_queue_attributes'): if not (answers.has_key('remote_server_try_connect') and answers.has_key('remote_cups_queue')): return False try: host = answers['remote_server_try_connect'] self.op = TimedOperation(cups.Connection, kwargs={"host": host}, parent=parent) c = self.op.run() self.op = TimedOperation(c.getPrinterAttributes, args=(answers['remote_cups_queue'], ), parent=parent) attr = self.op.run() except RuntimeError: return False except cups.IPPError: return False self.answers['remote_cups_queue_attributes'] = attr else: attr = answers['remote_cups_queue_attributes'] if attr.has_key('printer-is-shared'): # CUPS >= 1.2 if not attr['printer-is-shared']: return True return False def can_click_forward(self): return False def collect_answer(self): return self.answers def cancel_operation(self): self.op.cancel()
class NetworkCUPSPrinterShared(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Queue not shared?") page = self.initial_vbox (_("Queue Not Shared"), _("The CUPS printer on the server is not " "shared.")) troubleshooter.new_page (page, self) def display (self): self.answers = {} answers = self.troubleshooter.answers if ('remote_cups_queue_listed' in answers and answers['remote_cups_queue_listed'] == False): return False parent = self.troubleshooter.get_window () if 'remote_cups_queue_attributes' not in answers: if not ('remote_server_try_connect' in answers and 'remote_cups_queue' in answers): return False try: host = answers['remote_server_try_connect'] self.op = TimedOperation (cups.Connection, kwargs={"host": host}, parent=parent) c = self.op.run () self.op = TimedOperation (c.getPrinterAttributes, args=(answers['remote_cups_queue'],), parent=parent) attr = self.op.run () except RuntimeError: return False except cups.IPPError: return False self.answers['remote_cups_queue_attributes'] = attr else: attr = answers['remote_cups_queue_attributes'] if 'printer-is-shared' in attr: # CUPS >= 1.2 if not attr['printer-is-shared']: return True return False def can_click_forward (self): return False def collect_answer (self): return self.answers def cancel_operation (self): self.op.cancel ()
class SchedulerNotRunning(Question): def __init__(self, troubleshooter): Question.__init__(self, troubleshooter, "Scheduler not running?") page = self.initial_vbox( _("CUPS Service Stopped"), _("The CUPS print spooler does not appear " "to be running. To correct this, choose " "System->Administration->Services from " "the main menu and look for the 'cups' " "service.")) troubleshooter.new_page(page, self) def display(self): self.answers = {} if self.troubleshooter.answers.get('cups_queue_listed', False): return False parent = self.troubleshooter.get_window() # Find out if CUPS is running. failure = False try: self.op = TimedOperation(cups.Connection, parent=parent) c = self.op.run() except RuntimeError: failure = True self.answers['cups_connection_failure'] = failure return failure def can_click_forward(self): return False def collect_answer(self): return self.answers def cancel_operation(self): self.op.cancel()
class SchedulerNotRunning(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Scheduler not running?") page = self.initial_vbox (_("CUPS Service Stopped"), _("The CUPS print spooler does not appear " "to be running. To correct this, choose " "System->Administration->Services from " "the main menu and look for the 'cups' " "service.")) troubleshooter.new_page (page, self) def display (self): self.answers = {} if self.troubleshooter.answers.get ('cups_queue_listed', False): return False parent = self.troubleshooter.get_window () # Find out if CUPS is running. failure = False try: self.op = TimedOperation (cups.Connection, parent=parent) c = self.op.run () except RuntimeError: failure = True self.answers['cups_connection_failure'] = failure return failure def can_click_forward (self): return False def collect_answer (self): return self.answers def cancel_operation (self): self.op.cancel ()
class ErrorLogFetch(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Error log fetch") troubleshooter.new_page (Gtk.Label (), self) self.persistent_answers = {} def display (self): answers = self.troubleshooter.answers parent = self.troubleshooter.get_window () self.answers = {} checkpoint = answers.get ('error_log_checkpoint') cursor = answers.get ('error_log_cursor') if ('error_log' in self.persistent_answers or 'journal' in self.persistent_answers): checkpoint = None cursor = None def fetch_log (c): prompt = c._get_prompt_allowed () c._set_prompt_allowed (False) c._connect () (tmpfd, tmpfname) = tempfile.mkstemp () os.close (tmpfd) success = False try: c.getFile ('/admin/log/error_log', tmpfname) success = True except cups.HTTPError: try: os.remove (tmpfname) except OSError: pass c._set_prompt_allowed (prompt) if success: return tmpfname return None self.authconn = self.troubleshooter.answers['_authenticated_connection'] if 'error_log_debug_logging_set' in answers: try: self.op = TimedOperation (self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run () except cups.IPPError: return False settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' orig_settings = answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get ('MaxLogSize', '2000000') success = False def set_settings (connection, settings): connection.adminSetServerSettings (settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep (1) connection._connect () break except RuntimeError: # Connection failed attempt += 1 try: self.op = TimedOperation (set_settings, (self.authconn, settings), parent=parent) self.op.run () self.persistent_answers['error_log_debug_logging_unset'] = True except cups.IPPError: pass self.answers = {} if journal and cursor != None: def journal_format (x): try: priority = "XACEWNIDd"[x['PRIORITY']] except (IndexError, TypeError): priority = " " return (priority + " " + x['__REALTIME_TIMESTAMP'].strftime("[%m/%b/%Y:%T]") + " " + x['MESSAGE']) r = journal.Reader () r.seek_cursor (cursor) r.add_match (_COMM="cupsd") self.answers['journal'] = [journal_format (x) for x in r] if checkpoint != None: self.op = TimedOperation (fetch_log, (self.authconn,), parent=parent) tmpfname = self.op.run () if tmpfname != None: f = open (tmpfname) f.seek (checkpoint) lines = f.readlines () os.remove (tmpfname) self.answers = { 'error_log': [x.strip () for x in lines] } return False def collect_answer (self): answers = self.persistent_answers.copy () answers.update (self.answers) return answers def cancel_operation (self): self.op.cancel () # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection () self.answers['_authenticated_connection'] = self.authconn
class PrintTestPage(Question): STATE = { cups.IPP_JOB_PENDING: _("Pending"), cups.IPP_JOB_HELD: _("Held"), cups.IPP_JOB_PROCESSING: _("Processing"), cups.IPP_JOB_STOPPED: _("Stopped"), cups.IPP_JOB_CANCELED: _("Canceled"), cups.IPP_JOB_ABORTED: _("Aborted"), cups.IPP_JOB_COMPLETED: _("Completed") } def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Print test page") page = Gtk.VBox () page.set_spacing (12) page.set_border_width (12) label = Gtk.Label () label.set_alignment (0, 0) label.set_use_markup (True) label.set_line_wrap (True) page.pack_start (label, False, False, 0) self.main_label = label self.main_label_text = ('<span weight="bold" size="larger">' + _("Test Page") + '</span>\n\n' + _("Now print a test page. If you are having " "problems printing a specific document, " "print that document now and mark the print " "job below.")) hbox = Gtk.HButtonBox () hbox.set_border_width (0) hbox.set_spacing (3) hbox.set_layout (Gtk.ButtonBoxStyle.START) self.print_button = Gtk.Button.new_with_label (_("Print Test Page")) hbox.pack_start (self.print_button, False, False, 0) self.cancel_button = Gtk.Button.new_with_label (_("Cancel All Jobs")) hbox.pack_start (self.cancel_button, False, False, 0) page.pack_start (hbox, False, False, 0) tv = Gtk.TreeView () test_cell = Gtk.CellRendererToggle () test = Gtk.TreeViewColumn (_("Test"), test_cell, active=0) job = Gtk.TreeViewColumn (_("Job"), Gtk.CellRendererText (), text=1) printer_cell = Gtk.CellRendererText () printer = Gtk.TreeViewColumn (_("Printer"), printer_cell, text=2) name_cell = Gtk.CellRendererText () name = Gtk.TreeViewColumn (_("Document"), name_cell, text=3) status = Gtk.TreeViewColumn (_("Status"), Gtk.CellRendererText (), text=4) test_cell.set_radio (False) self.test_cell = test_cell printer.set_resizable (True) printer_cell.set_property ("ellipsize", Pango.EllipsizeMode.END) printer_cell.set_property ("width-chars", 20) name.set_resizable (True) name_cell.set_property ("ellipsize", Pango.EllipsizeMode.END) name_cell.set_property ("width-chars", 20) status.set_resizable (True) tv.append_column (test) tv.append_column (job) tv.append_column (printer) tv.append_column (name) tv.append_column (status) tv.set_rules_hint (True) sw = Gtk.ScrolledWindow () sw.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type (Gtk.ShadowType.IN) sw.add (tv) self.treeview = tv page.pack_start (sw, False, False, 0) label = Gtk.Label(label=_("Did the marked print jobs print correctly?")) label.set_line_wrap (True) label.set_alignment (0, 0) page.pack_start (label, False, False, 0) vbox = Gtk.VBox () vbox.set_spacing (6) self.yes = Gtk.RadioButton (label=_("Yes")) no = Gtk.RadioButton.new_with_label_from_widget (self.yes, _("No")) vbox.pack_start (self.yes, False, False, 0) vbox.pack_start (no, False, False, 0) page.pack_start (vbox, False, False, 0) self.persistent_answers = {} troubleshooter.new_page (page, self) def display (self): answers = self.troubleshooter.answers if 'cups_queue' not in answers: return False parent = self.troubleshooter.get_window () self.authconn = answers['_authenticated_connection'] mediatype = None defaults = answers.get ('cups_printer_ppd_defaults', {}) for opts in defaults.values (): for opt, value in opts.items (): if opt == "MediaType": mediatype = value break if mediatype != None: mediatype_string = '\n\n' + (_("Remember to load paper of type " "'%s' into the printer first.") % mediatype) else: mediatype_string = "" label_text = self.main_label_text + mediatype_string self.main_label.set_markup (label_text) model = Gtk.ListStore (bool, int, str, str, str) self.treeview.set_model (model) self.job_to_iter = {} test_jobs = self.persistent_answers.get ('test_page_job_id', []) def get_jobs (): c = self.authconn try: r = ["job-id", "job-name", "job-state", "job-printer-uri", "printer-name"] jobs_dict = c.getJobs (which_jobs='not-completed', my_jobs=False, requested_attributes=r) completed_jobs_dict = c.getJobs (which_jobs='completed', requested_attributes=r) except TypeError: # requested_attributes requires pycups 1.9.50 jobs_dict = c.getJobs (which_jobs='not-completed', my_jobs=False) completed_jobs_dict = c.getJobs (which_jobs='completed') return (jobs_dict, completed_jobs_dict) self.op = TimedOperation (get_jobs, parent=parent) try: (jobs_dict, completed_jobs_dict) = self.op.run () except (OperationCanceled, cups.IPPError): return False # We want to display the jobs in the queue for this printer... try: queue_uri_ending = "/" + self.troubleshooter.answers['cups_queue'] jobs_on_this_printer = [x for x in jobs_dict.keys () if \ jobs_dict[x]['job-printer-uri']. \ endswith (queue_uri_ending)] except: jobs_on_this_printer = [] # ...as well as any other jobs we've previous submitted as test pages. jobs = list (set(test_jobs).union (set (jobs_on_this_printer))) completed_jobs_dict = None for job in jobs: try: j = jobs_dict[job] except KeyError: try: j = completed_jobs_dict[job] except KeyError: continue iter = model.append (None) self.job_to_iter[job] = iter model.set_value (iter, 0, job in test_jobs) model.set_value (iter, 1, job) self.update_job (job, j) return True def connect_signals (self, handler): self.print_sigid = self.print_button.connect ("clicked", self.print_clicked) self.cancel_sigid = self.cancel_button.connect ("clicked", self.cancel_clicked) self.test_sigid = self.test_cell.connect ('toggled', self.test_toggled) def create_subscription (): c = self.authconn sub_id = c.createSubscription ("/", events=["job-created", "job-completed", "job-stopped", "job-progress", "job-state-changed"]) return sub_id parent = self.troubleshooter.get_window () self.op = TimedOperation (create_subscription, parent=parent) try: self.sub_id = self.op.run () except (OperationCanceled, cups.IPPError): pass try: bus = dbus.SystemBus () except: bus = None self.bus = bus if bus: bus.add_signal_receiver (self.handle_dbus_signal, path=DBUS_PATH, dbus_interface=DBUS_IFACE) self.timer = GLib.timeout_add_seconds (1, self.update_jobs_list) def disconnect_signals (self): if self.bus: self.bus.remove_signal_receiver (self.handle_dbus_signal, path=DBUS_PATH, dbus_interface=DBUS_IFACE) self.print_button.disconnect (self.print_sigid) self.cancel_button.disconnect (self.cancel_sigid) self.test_cell.disconnect (self.test_sigid) def cancel_subscription (sub_id): c = self.authconn c.cancelSubscription (sub_id) parent = self.troubleshooter.get_window () self.op = TimedOperation (cancel_subscription, (self.sub_id,), parent=parent) try: self.op.run () except (OperationCanceled, cups.IPPError): pass try: del self.sub_seq except: pass GLib.source_remove (self.timer) def collect_answer (self): if not self.displayed: return {} self.answers = self.persistent_answers.copy () parent = self.troubleshooter.get_window () success = self.yes.get_active () self.answers['test_page_successful'] = success class collect_jobs: def __init__ (self, model): self.jobs = [] model.foreach (self.each, None) def each (self, model, path, iter, user_data): self.jobs.append (model.get (iter, 0, 1, 2, 3, 4)) model = self.treeview.get_model () jobs = collect_jobs (model).jobs def collect_attributes (jobs): job_attrs = None c = self.authconn with_attrs = [] for (test, jobid, printer, doc, status) in jobs: attrs = None if test: try: attrs = c.getJobAttributes (jobid) except AttributeError: # getJobAttributes was introduced in pycups 1.9.35. if job_attrs == None: job_attrs = c.getJobs (which_jobs='all') attrs = self.job_attrs[jobid] with_attrs.append ((test, jobid, printer, doc, status, attrs)) return with_attrs self.op = TimedOperation (collect_attributes, (jobs,), parent=parent) try: with_attrs = self.op.run () self.answers['test_page_job_status'] = with_attrs except (OperationCanceled, cups.IPPError): pass return self.answers def cancel_operation (self): self.op.cancel () # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection () self.answers['_authenticated_connection'] = self.authconn def handle_dbus_signal (self, *args): debugprint ("D-Bus signal caught: updating jobs list soon") GLib.source_remove (self.timer) self.timer = GLib.timeout_add (200, self.update_jobs_list) def update_job (self, jobid, job_dict): iter = self.job_to_iter[jobid] model = self.treeview.get_model () try: printer_name = job_dict['printer-name'] except KeyError: try: uri = job_dict['job-printer-uri'] r = uri.rfind ('/') printer_name = uri[r + 1:] except KeyError: printer_name = None if printer_name != None: model.set_value (iter, 2, printer_name) model.set_value (iter, 3, job_dict['job-name']) model.set_value (iter, 4, self.STATE[job_dict['job-state']]) def print_clicked (self, widget): now = time.time () tt = time.localtime (now) when = time.strftime ("%d/%b/%Y:%T %z", tt) self.persistent_answers['test_page_attempted'] = when answers = self.troubleshooter.answers parent = self.troubleshooter.get_window () def print_test_page (*args, **kwargs): factory = answers['_authenticated_connection_factory'] c = factory.get_connection () return c.printTestPage (*args, **kwargs) tmpfname = None mimetypes = [None, 'text/plain'] for mimetype in mimetypes: try: if mimetype == None: # Default test page. self.op = TimedOperation (print_test_page, (answers['cups_queue'],), parent=parent) jobid = self.op.run () elif mimetype == 'text/plain': (tmpfd, tmpfname) = tempfile.mkstemp () os.write (tmpfd, b"This is a test page.\n") os.close (tmpfd) self.op = TimedOperation (print_test_page, (answers['cups_queue'],), kwargs={'file': tmpfname, 'format': mimetype}, parent=parent) jobid = self.op.run () try: os.unlink (tmpfname) except OSError: pass tmpfname = None jobs = self.persistent_answers.get ('test_page_job_id', []) jobs.append (jobid) self.persistent_answers['test_page_job_id'] = jobs break except OperationCanceled: self.persistent_answers['test_page_submit_failure'] = 'cancel' break except RuntimeError: self.persistent_answers['test_page_submit_failure'] = 'connect' break except cups.IPPError as e: (e, s) = e.args if (e == cups.IPP_DOCUMENT_FORMAT and mimetypes.index (mimetype) < (len (mimetypes) - 1)): # Try next format. if tmpfname != None: os.unlink (tmpfname) tmpfname = None continue self.persistent_answers['test_page_submit_failure'] = (e, s) show_error_dialog (_("Error submitting test page"), _("There was an error during the CUPS " "operation: '%s'.") % s, self.troubleshooter.get_window ()) break def cancel_clicked (self, widget): self.persistent_answers['test_page_jobs_cancelled'] = True jobids = [] for jobid, iter in self.job_to_iter.items (): jobids.append (jobid) def cancel_jobs (jobids): c = self.authconn for jobid in jobids: try: c.cancelJob (jobid) except cups.IPPError as e: (e, s) = e.args if e != cups.IPP_NOT_POSSIBLE: self.persistent_answers['test_page_cancel_failure'] = (e, s) self.op = TimedOperation (cancel_jobs, (jobids,), parent=self.troubleshooter.get_window ()) try: self.op.run () except (OperationCanceled, cups.IPPError): pass def test_toggled (self, cell, path): model = self.treeview.get_model () iter = model.get_iter (path) active = model.get_value (iter, 0) model.set_value (iter, 0, not active) def update_jobs_list (self): def get_notifications (self): c = self.authconn try: notifications = c.getNotifications ([self.sub_id], [self.sub_seq + 1]) except AttributeError: notifications = c.getNotifications ([self.sub_id]) return notifications # Enter the GDK lock. We need to do this because we were # called from a timeout. Gdk.threads_enter () parent = self.troubleshooter.get_window () self.op = TimedOperation (get_notifications, (self,), parent=parent) try: notifications = self.op.run () except (OperationCanceled, cups.IPPError): Gdk.threads_leave () return True answers = self.troubleshooter.answers model = self.treeview.get_model () queue = answers['cups_queue'] test_jobs = self.persistent_answers.get('test_page_job_id', []) for event in notifications['events']: seq = event['notify-sequence-number'] try: if seq <= self.sub_seq: # Work around a bug in pycups < 1.9.34 continue except AttributeError: pass self.sub_seq = seq job = event['notify-job-id'] nse = event['notify-subscribed-event'] if nse == 'job-created': if (job in test_jobs or event['printer-name'] == queue): iter = model.append (None) self.job_to_iter[job] = iter model.set_value (iter, 0, True) model.set_value (iter, 1, job) else: continue elif job not in self.job_to_iter: continue if (job in test_jobs and nse in ["job-stopped", "job-completed"]): comp = self.persistent_answers.get ('test_page_completions', []) comp.append ((job, event['notify-text'])) self.persistent_answers['test_page_completions'] = comp self.update_job (job, event) # Update again when we're told to. (But we might update sooner if # there is a D-Bus signal.) GLib.source_remove (self.timer) self.timer = GLib.timeout_add_seconds ( notifications['notify-get-interval'], self.update_jobs_list) debugprint ("Update again in %ds" % notifications['notify-get-interval']) Gdk.threads_leave () return False
class PrinterFinder: def __init__ (self): self.quit = False def find (self, hostname, callback_fn): self.hostname = hostname self.callback_fn = callback_fn self.op = TimedOperation (self._do_find, callback=lambda x, y: None) def cancel (self): self.op.cancel () self.quit = True def _do_find (self): self._cached_attributes = dict() for fn in [self._probe_hplip, self._probe_jetdirect, self._probe_ipp, self._probe_snmp, self._probe_lpd, self._probe_smb]: if self.quit: return try: fn () except Exception: nonfatalException () # Signal that we've finished. if not self.quit: self.callback_fn (None) def _new_device (self, uri, info, location = None): device_dict = { 'device-class': 'network', 'device-info': "%s" % info } if location: device_dict['device-location']=location device_dict.update (self._cached_attributes) new_device = cupshelpers.Device (uri, **device_dict) debugprint ("Device found: %s" % uri) self.callback_fn (new_device) def _probe_snmp (self): # Run the CUPS SNMP backend, pointing it at the host. try: debugprint ("snmp: trying") p = subprocess.Popen (args=["/usr/lib/cups/backend/snmp", self.hostname], close_fds=True, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) except OSError as e: debugprint ("snmp: no good") if e.errno == errno.ENOENT: return raise (stdout, stderr) = p.communicate () if p.returncode != 0: debugprint ("snmp: no good (return code %d)" % p.returncode) return if self.quit: debugprint ("snmp: no good") return for line in stdout.decode ().split ('\n'): words = wordsep (line) n = len (words) if n == 6: (device_class, uri, make_and_model, info, device_id, device_location) = words elif n == 5: (device_class, uri, make_and_model, info, device_id) = words elif n == 4: (device_class, uri, make_and_model, info) = words else: continue device_dict = { 'device-class': device_class, 'device-make-and-model': make_and_model, 'device-info': info } if n == 5: debugprint ("snmp: Device ID found:\n%s" % device_id) device_dict['device-id'] = device_id if n == 6: device_dict['device-location'] = device_location device = cupshelpers.Device (uri, **device_dict) debugprint ("Device found: %s" % uri) self.callback_fn (device) # Cache the make and model for use by other search methods # that are not able to determine it. self._cached_attributes['device-make-and-model'] = make_and_model self._cached_attributes['device_id'] = device_id debugprint ("snmp: done") def _probe_lpd (self): debugprint ("lpd: trying") lpd = LpdServer (self.hostname) for name in lpd.get_possible_queue_names (): if self.quit: debugprint ("lpd: no good") return found = lpd.probe_queue (name, []) if found is None: # Couldn't even connect. debugprint ("lpd: couldn't connect") break if found: uri = "lpd://%s/%s" % (self.hostname, name) self._new_device(uri, self.hostname) if not found and name.startswith ("pr"): break time.sleep(0.1) # avoid DOS and following counter measures debugprint ("lpd: done") def _probe_hplip (self): if ('device-make-and-model' in self._cached_attributes and \ self._cached_attributes['device-make-and-model'] != "Unknown" and \ not self._cached_attributes['device-make-and-model'].lower ().startswith ("hp") and \ not self._cached_attributes['device-make-and-model'].lower ().startswith ("hewlett")): debugprint ("hplip: no good (Non-HP printer: %s)" % self._cached_attributes['device-make-and-model']) return try: debugprint ("hplip: trying") p = subprocess.Popen (args=["hp-makeuri", "-c", self.hostname], close_fds=True, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) except OSError as e: if e.errno == errno.ENOENT: return raise (stdout, stderr) = p.communicate () if p.returncode != 0: debugprint ("hplip: no good (return code %d)" % p.returncode) return if self.quit: debugprint ("hplip: no good") return uri = stdout.decode ().strip () debugprint ("hplip: uri is %s" % uri) if uri.find (":") != -1: self._new_device(uri, uri) debugprint ("hplip: done") def _probe_smb (self): if not PYSMB_AVAILABLE: return smbc_auth = BackgroundSmbAuthContext () debug = 0 if get_debugging (): debug = 10 ctx = pysmb.smbc.Context (debug=debug, auth_fn=smbc_auth.callback) entries = [] uri = "smb://%s/" % self.hostname debugprint ("smb: trying") try: while smbc_auth.perform_authentication () > 0: if self.quit: debugprint ("smb: no good") return try: entries = ctx.opendir (uri).getdents () except Exception as e: smbc_auth.failed (e) except RuntimeError as e: (e, s) = e.args if e not in [errno.ENOENT, errno.EACCES, errno.EPERM]: debugprint ("Runtime error: %s" % repr ((e, s))) except: nonfatalException () if self.quit: debugprint ("smb: no good") return for entry in entries: if entry.smbc_type == pysmb.smbc.PRINTER_SHARE: uri = "smb://%s/%s" % (smburi.urlquote (self.hostname), smburi.urlquote (entry.name)) info = "SMB (%s)" % self.hostname self._new_device(uri, info) debugprint ("smb: done") def _probe_jetdirect (self): port = 9100 #jetdirect sock_address = (self.hostname, port) debugprint ("jetdirect: trying") s = open_socket(self.hostname, port) if not s: debugprint ("jetdirect: %s:%d CLOSED" % sock_address) else: # port is open so assume its a JetDirect device debugprint ("jetdirect %s:%d OPEN" % sock_address) uri = "socket://%s:%d" % sock_address info = "JetDirect (%s)" % self.hostname self._new_device(uri, info) s.close () debugprint ("jetdirect: done") def _probe_ipp (self): debugprint ("ipp: trying") try: ai = socket.getaddrinfo(self.hostname, 631, socket.AF_UNSPEC, socket.SOCK_STREAM) except socket.gaierror: debugprint ("ipp: can't resolve %s" % self.hostname) debugprint ("ipp: no good") return for res in ai: af, socktype, proto, canonname, sa = res if (af == socket.AF_INET and sa[0] == '127.0.0.1' or af == socket.AF_INET6 and sa[0] == '::1'): debugprint ("ipp: do not probe local cups server") debugprint ("ipp: no good") return try: c = cups.Connection (host = self.hostname) except RuntimeError: debugprint ("ipp: can't connect to server/printer") debugprint ("ipp: no good") return try: printers = c.getPrinters () except cups.IPPError: debugprint ("%s is probably not a cups server but IPP printer" % self.hostname) uri = "ipp://%s:631/ipp" % (self.hostname) info = "IPP (%s)" % self.hostname self._new_device(uri, info) debugprint ("ipp: done") return for name, queue in printers.items (): uri = queue['printer-uri-supported'] info = queue['printer-info'] location = queue['printer-location'] self._new_device(uri, info, location) debugprint ("ipp: done")
class ChooseNetworkPrinter(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Choose network printer") page1 = self.initial_vbox (_("Choose Network Printer"), _("Please select the network printer you " "are trying to use from the list below. " "If it does not appear in the list, " "select 'Not listed'.")) tv = Gtk.TreeView () name = Gtk.TreeViewColumn (_("Name"), Gtk.CellRendererText (), text=0) location = Gtk.TreeViewColumn (_("Location"), Gtk.CellRendererText (), text=1) info = Gtk.TreeViewColumn (_("Information"), Gtk.CellRendererText (), text=2) name.set_property ("resizable", True) location.set_property ("resizable", True) info.set_property ("resizable", True) tv.append_column (name) tv.append_column (location) tv.append_column (info) tv.set_rules_hint (True) sw = Gtk.ScrolledWindow () sw.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type (Gtk.ShadowType.IN) sw.add (tv) page1.pack_start (sw, True, True, 0) self.treeview = tv troubleshooter.new_page (page1, self) def display (self): answers = self.troubleshooter.answers if answers['cups_queue_listed']: return False if not answers.get ('remote_server_cups', False): return False server = answers['remote_server_try_connect'] model = Gtk.ListStore (str, str, str, GObject.TYPE_PYOBJECT) self.model = model self.treeview.set_model (model) iter = model.append (None) model.set (iter, 0, _("Not listed"), 1, '', 2, '', 3, 0) parent = self.troubleshooter.get_window () try: self.op = TimedOperation (cups.Connection, kwargs={"host": server}, parent=parent) c = self.op.run () self.op = TimedOperation (c.getDests, parent=parent) dests = self.op.run () printers = None dests_list = [] for (name, instance), dest in dests.items (): if name is None: continue if instance is not None: queue = "%s/%s" % (name, instance) else: queue = name if printers is None: self.op = TimedOperation (c.getPrinters) printers = self.op.run () if name not in printers: info = _("Unknown") location = _("Unknown") else: printer = printers[name] info = printer.get('printer-info', _("Unknown")) location = printer.get('printer-location', _("Unknown")) dests_list.append ((queue, location, info, dest)) dests_list.sort (key=lambda x: x[0]) for queue, location, info, dest in dests_list: iter = model.append (None) model.set (iter, 0, queue, 1, location, 2, info, 3, dest) except cups.HTTPError: pass except cups.IPPError: pass except RuntimeError: pass return True def connect_signals (self, handler): self.signal_id = self.treeview.connect ("cursor-changed", handler) def disconnect_signals (self): self.treeview.disconnect (self.signal_id) def can_click_forward (self): model, iter = self.treeview.get_selection ().get_selected () if iter is None: return False return True def collect_answer (self): if not self.troubleshooter.answers.get ('remote_server_cups', False): return {} model, iter = self.treeview.get_selection ().get_selected () if not model: return {} dest = model.get_value (iter, 3) if dest == 0: class enum_dests: def __init__ (self, model): self.dests = [] model.foreach (self.each, None) def each (self, model, path, iter, user_data): dest = model.get_value (iter, 3) if dest: self.dests.append ((dest.name, dest.instance)) return { 'remote_cups_queue_listed': False, 'remote_cups_dests_available': enum_dests (model).dests } else: return { 'remote_cups_queue_listed': True, 'remote_cups_dest': dest, 'remote_cups_queue': dest.name, 'remote_cups_instance': dest.instance } def cancel_operation (self): self.op.cancel ()
class ErrorLogFetch(Question): def __init__(self, troubleshooter): Question.__init__(self, troubleshooter, "Error log fetch") troubleshooter.new_page(Gtk.Label(), self) self.persistent_answers = {} def display(self): answers = self.troubleshooter.answers parent = self.troubleshooter.get_window() self.answers = {} checkpoint = answers.get('error_log_checkpoint') cursor = answers.get('error_log_cursor') if ('error_log' in self.persistent_answers or 'journal' in self.persistent_answers): checkpoint = None cursor = None def fetch_log(c): prompt = c._get_prompt_allowed() c._set_prompt_allowed(False) c._connect() (tmpfd, tmpfname) = tempfile.mkstemp() os.close(tmpfd) success = False try: c.getFile('/admin/log/error_log', tmpfname) success = True except cups.HTTPError: try: os.remove(tmpfname) except OSError: pass c._set_prompt_allowed(prompt) if success: return tmpfname return None self.authconn = self.troubleshooter.answers[ '_authenticated_connection'] if 'error_log_debug_logging_set' in answers: try: self.op = TimedOperation(self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run() except cups.IPPError: return False settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' orig_settings = answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get('MaxLogSize', '2000000') success = False def set_settings(connection, settings): connection.adminSetServerSettings(settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep(1) connection._connect() break except RuntimeError: # Connection failed attempt += 1 try: self.op = TimedOperation(set_settings, (self.authconn, settings), parent=parent) self.op.run() self.persistent_answers['error_log_debug_logging_unset'] = True except cups.IPPError: pass self.answers = {} if journal and cursor != None: def journal_format(x): try: priority = "XACEWNIDd"[x['PRIORITY']] except (IndexError, TypeError): priority = " " return (priority + " " + x['__REALTIME_TIMESTAMP'].strftime("[%m/%b/%Y:%T]") + " " + x['MESSAGE']) r = journal.Reader() r.seek_cursor(cursor) r.add_match(_COMM="cupsd") self.answers['journal'] = [journal_format(x) for x in r] if checkpoint != None: self.op = TimedOperation(fetch_log, (self.authconn, ), parent=parent) tmpfname = self.op.run() if tmpfname != None: f = open(tmpfname) f.seek(checkpoint) lines = f.readlines() os.remove(tmpfname) self.answers = {'error_log': [x.strip() for x in lines]} return False def collect_answer(self): answers = self.persistent_answers.copy() answers.update(self.answers) return answers def cancel_operation(self): self.op.cancel() # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection() self.answers['_authenticated_connection'] = self.authconn
class PrinterStateReasons(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Printer state reasons") page = self.initial_vbox (_("Status Messages"), _("There are status messages associated with " "this queue.")) self.label = Gtk.Label () self.label.set_alignment (0, 0) self.label.set_line_wrap (True) page.pack_start (self.label, False, False, 0) troubleshooter.new_page (page, self) def display (self): troubleshooter = self.troubleshooter try: queue = troubleshooter.answers['cups_queue'] except KeyError: return False parent = self.troubleshooter.get_window () cups.setServer ('') self.op = TimedOperation (cups.Connection, parent=parent) c = self.op.run () self.op = TimedOperation (c.getPrinterAttributes, args=(queue,), parent=parent) dict = self.op.run () the_ppdcache = ppdcache.PPDCache () text = '' state_message = dict['printer-state-message'] if state_message: text += _("The printer's state message is: '%s'.") % state_message text += '\n\n' state_reasons_list = dict['printer-state-reasons'] if type (state_reasons_list) == unicode: state_reasons_list = [state_reasons_list] self.state_message = state_message self.state_reasons = state_reasons_list human_readable_errors = [] human_readable_warnings = [] for reason in state_reasons_list: if reason == "none": continue r = statereason.StateReason (queue, reason, the_ppdcache) (title, description) = r.get_description () level = r.get_level () if level == statereason.StateReason.ERROR: human_readable_errors.append (description) elif level == statereason.StateReason.WARNING: human_readable_warnings.append (description) if human_readable_errors: text += _("Errors are listed below:") + '\n' text += reduce (lambda x, y: x + "\n" + y, human_readable_errors) text += '\n\n' if human_readable_warnings: text += _("Warnings are listed below:") + '\n' text += reduce (lambda x, y: x + "\n" + y, human_readable_warnings) self.label.set_text (text) if (state_message == '' and len (human_readable_errors) == 0 and len (human_readable_warnings) == 0): return False # If this screen has been show before, don't show it again if # nothing changed. if troubleshooter.answers.has_key ('printer-state-message'): if (troubleshooter.answers['printer-state-message'] == self.state_message and troubleshooter.answers['printer-state-reasons'] == self.state_reasons): return False return True def collect_answer (self): if not self.displayed: return {} return { 'printer-state-message': self.state_message, 'printer-state-reasons': self.state_reasons } def cancel_operation (self): self.op.cancel ()
class PrinterStateReasons(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Printer state reasons") page = self.initial_vbox (_("Status Messages"), _("There are status messages associated with " "this queue.")) self.label = Gtk.Label () self.label.set_alignment (0, 0) self.label.set_line_wrap (True) page.pack_start (self.label, False, False, 0) troubleshooter.new_page (page, self) def display (self): troubleshooter = self.troubleshooter try: queue = troubleshooter.answers['cups_queue'] except KeyError: return False parent = self.troubleshooter.get_window () cups.setServer ('') self.op = TimedOperation (cups.Connection, parent=parent) c = self.op.run () self.op = TimedOperation (c.getPrinterAttributes, args=(queue,), parent=parent) dict = self.op.run () the_ppdcache = ppdcache.PPDCache () text = '' state_message = dict['printer-state-message'] if state_message: text += _("The printer's state message is: '%s'.") % state_message text += '\n\n' state_reasons_list = dict['printer-state-reasons'] if type (state_reasons_list) == str: state_reasons_list = [state_reasons_list] self.state_message = state_message self.state_reasons = state_reasons_list human_readable_errors = [] human_readable_warnings = [] for reason in state_reasons_list: if reason == "none": continue r = statereason.StateReason (queue, reason, the_ppdcache) (title, description) = r.get_description () level = r.get_level () if level == statereason.StateReason.ERROR: human_readable_errors.append (description) elif level == statereason.StateReason.WARNING: human_readable_warnings.append (description) if human_readable_errors: text += _("Errors are listed below:") + '\n' text += reduce (lambda x, y: x + "\n" + y, human_readable_errors) text += '\n\n' if human_readable_warnings: text += _("Warnings are listed below:") + '\n' text += reduce (lambda x, y: x + "\n" + y, human_readable_warnings) self.label.set_text (text) if (state_message == '' and len (human_readable_errors) == 0 and len (human_readable_warnings) == 0): return False # If this screen has been show before, don't show it again if # nothing changed. if 'printer-state-message' in troubleshooter.answers: if (troubleshooter.answers['printer-state-message'] == self.state_message and troubleshooter.answers['printer-state-reasons'] == self.state_reasons): return False return True def collect_answer (self): if not self.displayed: return {} return { 'printer-state-message': self.state_message, 'printer-state-reasons': self.state_reasons } def cancel_operation (self): self.op.cancel ()
class ErrorLogFetch(Question): def __init__(self, troubleshooter): Question.__init__(self, troubleshooter, "Error log fetch") troubleshooter.new_page(Gtk.Label(), self) self.persistent_answers = {} def display(self): answers = self.troubleshooter.answers parent = self.troubleshooter.get_window() self.answers = {} try: checkpoint = answers['error_log_checkpoint'] except KeyError: checkpoint = None if self.persistent_answers.has_key('error_log'): checkpoint = None def fetch_log(c): prompt = c._get_prompt_allowed() c._set_prompt_allowed(False) c._connect() (tmpfd, tmpfname) = tempfile.mkstemp() os.close(tmpfd) success = False try: c.getFile('/admin/log/error_log', tmpfname) success = True except cups.HTTPError: try: os.remove(tmpfname) except OSError: pass c._set_prompt_allowed(prompt) if success: return tmpfname return None self.authconn = self.troubleshooter.answers[ '_authenticated_connection'] if answers.has_key('error_log_debug_logging_set'): try: self.op = TimedOperation(self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run() except cups.IPPError: return False settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' orig_settings = answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get('MaxLogSize', '2000000') success = False def set_settings(connection, settings): connection.adminSetServerSettings(settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep(1) connection._connect() break except RuntimeError: # Connection failed attempt += 1 try: self.op = TimedOperation(set_settings, (self.authconn, settings), parent=parent) self.op.run() self.persistent_answers['error_log_debug_logging_unset'] = True except cups.IPPError: pass if checkpoint != None: self.op = TimedOperation(fetch_log, (self.authconn, ), parent=parent) tmpfname = self.op.run() if tmpfname != None: f = file(tmpfname) f.seek(checkpoint) lines = f.readlines() os.remove(tmpfname) self.answers = {'error_log': map(lambda x: x.strip(), lines)} return False def collect_answer(self): answers = self.persistent_answers.copy() answers.update(self.answers) return answers def cancel_operation(self): self.op.cancel() # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection() self.answers['_authenticated_connection'] = self.authconn
class PrintTestPage(Question): STATE = { cups.IPP_JOB_PENDING: _("Pending"), cups.IPP_JOB_HELD: _("Held"), cups.IPP_JOB_PROCESSING: _("Processing"), cups.IPP_JOB_STOPPED: _("Stopped"), cups.IPP_JOB_CANCELED: _("Canceled"), cups.IPP_JOB_ABORTED: _("Aborted"), cups.IPP_JOB_COMPLETED: _("Completed") } def __init__(self, troubleshooter): Question.__init__(self, troubleshooter, "Print test page") page = Gtk.VBox() page.set_spacing(12) page.set_border_width(12) label = Gtk.Label() label.set_alignment(0, 0) label.set_use_markup(True) label.set_line_wrap(True) page.pack_start(label, False, False, 0) self.main_label = label self.main_label_text = ('<span weight="bold" size="larger">' + _("Test Page") + '</span>\n\n' + _("Now print a test page. If you are having " "problems printing a specific document, " "print that document now and mark the print " "job below.")) hbox = Gtk.HButtonBox() hbox.set_border_width(0) hbox.set_spacing(3) hbox.set_layout(Gtk.ButtonBoxStyle.START) self.print_button = Gtk.Button.new_with_label(_("Print Test Page")) hbox.pack_start(self.print_button, False, False, 0) self.cancel_button = Gtk.Button.new_with_label(_("Cancel All Jobs")) hbox.pack_start(self.cancel_button, False, False, 0) page.pack_start(hbox, False, False, 0) tv = Gtk.TreeView() test_cell = Gtk.CellRendererToggle() test = Gtk.TreeViewColumn(_("Test"), test_cell, active=0) job = Gtk.TreeViewColumn(_("Job"), Gtk.CellRendererText(), text=1) printer_cell = Gtk.CellRendererText() printer = Gtk.TreeViewColumn(_("Printer"), printer_cell, text=2) name_cell = Gtk.CellRendererText() name = Gtk.TreeViewColumn(_("Document"), name_cell, text=3) status = Gtk.TreeViewColumn(_("Status"), Gtk.CellRendererText(), text=4) test_cell.set_radio(False) self.test_cell = test_cell printer.set_resizable(True) printer_cell.set_property("ellipsize", Pango.EllipsizeMode.END) printer_cell.set_property("width-chars", 20) name.set_resizable(True) name_cell.set_property("ellipsize", Pango.EllipsizeMode.END) name_cell.set_property("width-chars", 20) status.set_resizable(True) tv.append_column(test) tv.append_column(job) tv.append_column(printer) tv.append_column(name) tv.append_column(status) tv.set_rules_hint(True) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) sw.add(tv) self.treeview = tv page.pack_start(sw, False, False, 0) label = Gtk.Label( label=_("Did the marked print jobs print correctly?")) label.set_line_wrap(True) label.set_alignment(0, 0) page.pack_start(label, False, False, 0) vbox = Gtk.VBox() vbox.set_spacing(6) self.yes = Gtk.RadioButton(label=_("Yes")) no = Gtk.RadioButton.new_with_label_from_widget(self.yes, _("No")) vbox.pack_start(self.yes, False, False, 0) vbox.pack_start(no, False, False, 0) page.pack_start(vbox, False, False, 0) self.persistent_answers = {} troubleshooter.new_page(page, self) def display(self): answers = self.troubleshooter.answers if 'cups_queue' not in answers: return False parent = self.troubleshooter.get_window() self.authconn = answers['_authenticated_connection'] mediatype = None defaults = answers.get('cups_printer_ppd_defaults', {}) for opts in defaults.values(): for opt, value in opts.items(): if opt == "MediaType": mediatype = value break if mediatype != None: mediatype_string = '\n\n' + (_("Remember to load paper of type " "'%s' into the printer first.") % mediatype) else: mediatype_string = "" label_text = self.main_label_text + mediatype_string self.main_label.set_markup(label_text) model = Gtk.ListStore(bool, int, str, str, str) self.treeview.set_model(model) self.job_to_iter = {} test_jobs = self.persistent_answers.get('test_page_job_id', []) def get_jobs(): c = self.authconn try: r = [ "job-id", "job-name", "job-state", "job-printer-uri", "printer-name" ] jobs_dict = c.getJobs(which_jobs='not-completed', my_jobs=False, requested_attributes=r) completed_jobs_dict = c.getJobs(which_jobs='completed', requested_attributes=r) except TypeError: # requested_attributes requires pycups 1.9.50 jobs_dict = c.getJobs(which_jobs='not-completed', my_jobs=False) completed_jobs_dict = c.getJobs(which_jobs='completed') return (jobs_dict, completed_jobs_dict) self.op = TimedOperation(get_jobs, parent=parent) try: (jobs_dict, completed_jobs_dict) = self.op.run() except (OperationCanceled, cups.IPPError): return False # We want to display the jobs in the queue for this printer... try: queue_uri_ending = "/" + self.troubleshooter.answers['cups_queue'] jobs_on_this_printer = [x for x in jobs_dict.keys () if \ jobs_dict[x]['job-printer-uri']. \ endswith (queue_uri_ending)] except: jobs_on_this_printer = [] # ...as well as any other jobs we've previous submitted as test pages. jobs = list(set(test_jobs).union(set(jobs_on_this_printer))) completed_jobs_dict = None for job in jobs: try: j = jobs_dict[job] except KeyError: try: j = completed_jobs_dict[job] except KeyError: continue iter = model.append(None) self.job_to_iter[job] = iter model.set_value(iter, 0, job in test_jobs) model.set_value(iter, 1, job) self.update_job(job, j) return True def connect_signals(self, handler): self.print_sigid = self.print_button.connect("clicked", self.print_clicked) self.cancel_sigid = self.cancel_button.connect("clicked", self.cancel_clicked) self.test_sigid = self.test_cell.connect('toggled', self.test_toggled) def create_subscription(): c = self.authconn sub_id = c.createSubscription("/", events=[ "job-created", "job-completed", "job-stopped", "job-progress", "job-state-changed" ]) return sub_id parent = self.troubleshooter.get_window() self.op = TimedOperation(create_subscription, parent=parent) try: self.sub_id = self.op.run() except (OperationCanceled, cups.IPPError): pass try: bus = dbus.SystemBus() except: bus = None self.bus = bus if bus: bus.add_signal_receiver(self.handle_dbus_signal, path=DBUS_PATH, dbus_interface=DBUS_IFACE) self.timer = GLib.timeout_add_seconds(1, self.update_jobs_list) def disconnect_signals(self): if self.bus: self.bus.remove_signal_receiver(self.handle_dbus_signal, path=DBUS_PATH, dbus_interface=DBUS_IFACE) self.print_button.disconnect(self.print_sigid) self.cancel_button.disconnect(self.cancel_sigid) self.test_cell.disconnect(self.test_sigid) def cancel_subscription(sub_id): c = self.authconn c.cancelSubscription(sub_id) parent = self.troubleshooter.get_window() self.op = TimedOperation(cancel_subscription, (self.sub_id, ), parent=parent) try: self.op.run() except (OperationCanceled, cups.IPPError): pass try: del self.sub_seq except: pass GLib.source_remove(self.timer) def collect_answer(self): if not self.displayed: return {} self.answers = self.persistent_answers.copy() parent = self.troubleshooter.get_window() success = self.yes.get_active() self.answers['test_page_successful'] = success class collect_jobs: def __init__(self, model): self.jobs = [] model.foreach(self.each, None) def each(self, model, path, iter, user_data): self.jobs.append(model.get(iter, 0, 1, 2, 3, 4)) model = self.treeview.get_model() jobs = collect_jobs(model).jobs def collect_attributes(jobs): job_attrs = None c = self.authconn with_attrs = [] for (test, jobid, printer, doc, status) in jobs: attrs = None if test: try: attrs = c.getJobAttributes(jobid) except AttributeError: # getJobAttributes was introduced in pycups 1.9.35. if job_attrs == None: job_attrs = c.getJobs(which_jobs='all') attrs = self.job_attrs[jobid] with_attrs.append((test, jobid, printer, doc, status, attrs)) return with_attrs self.op = TimedOperation(collect_attributes, (jobs, ), parent=parent) try: with_attrs = self.op.run() self.answers['test_page_job_status'] = with_attrs except (OperationCanceled, cups.IPPError): pass return self.answers def cancel_operation(self): self.op.cancel() # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection() self.answers['_authenticated_connection'] = self.authconn def handle_dbus_signal(self, *args): debugprint("D-Bus signal caught: updating jobs list soon") GLib.source_remove(self.timer) self.timer = GLib.timeout_add(200, self.update_jobs_list) def update_job(self, jobid, job_dict): iter = self.job_to_iter[jobid] model = self.treeview.get_model() try: printer_name = job_dict['printer-name'] except KeyError: try: uri = job_dict['job-printer-uri'] r = uri.rfind('/') printer_name = uri[r + 1:] except KeyError: printer_name = None if printer_name != None: model.set_value(iter, 2, printer_name) model.set_value(iter, 3, job_dict['job-name']) model.set_value(iter, 4, self.STATE[job_dict['job-state']]) def print_clicked(self, widget): now = time.time() tt = time.localtime(now) when = time.strftime("%d/%b/%Y:%T %z", tt) self.persistent_answers['test_page_attempted'] = when answers = self.troubleshooter.answers parent = self.troubleshooter.get_window() def print_test_page(*args, **kwargs): factory = answers['_authenticated_connection_factory'] c = factory.get_connection() return c.printTestPage(*args, **kwargs) tmpfname = None mimetypes = [None, 'text/plain'] for mimetype in mimetypes: try: if mimetype == None: # Default test page. self.op = TimedOperation(print_test_page, (answers['cups_queue'], ), parent=parent) jobid = self.op.run() elif mimetype == 'text/plain': (tmpfd, tmpfname) = tempfile.mkstemp() os.write(tmpfd, b"This is a test page.\n") os.close(tmpfd) self.op = TimedOperation(print_test_page, (answers['cups_queue'], ), kwargs={ 'file': tmpfname, 'format': mimetype }, parent=parent) jobid = self.op.run() try: os.unlink(tmpfname) except OSError: pass tmpfname = None jobs = self.persistent_answers.get('test_page_job_id', []) jobs.append(jobid) self.persistent_answers['test_page_job_id'] = jobs break except OperationCanceled: self.persistent_answers['test_page_submit_failure'] = 'cancel' break except RuntimeError: self.persistent_answers['test_page_submit_failure'] = 'connect' break except cups.IPPError as e: (e, s) = e.args if (e == cups.IPP_DOCUMENT_FORMAT and mimetypes.index(mimetype) < (len(mimetypes) - 1)): # Try next format. if tmpfname != None: os.unlink(tmpfname) tmpfname = None continue self.persistent_answers['test_page_submit_failure'] = (e, s) show_error_dialog( _("Error submitting test page"), _("There was an error during the CUPS " "operation: '%s'.") % s, self.troubleshooter.get_window()) break def cancel_clicked(self, widget): self.persistent_answers['test_page_jobs_cancelled'] = True jobids = [] for jobid, iter in self.job_to_iter.items(): jobids.append(jobid) def cancel_jobs(jobids): c = self.authconn for jobid in jobids: try: c.cancelJob(jobid) except cups.IPPError as e: (e, s) = e.args if e != cups.IPP_NOT_POSSIBLE: self.persistent_answers['test_page_cancel_failure'] = ( e, s) self.op = TimedOperation(cancel_jobs, (jobids, ), parent=self.troubleshooter.get_window()) try: self.op.run() except (OperationCanceled, cups.IPPError): pass def test_toggled(self, cell, path): model = self.treeview.get_model() iter = model.get_iter(path) active = model.get_value(iter, 0) model.set_value(iter, 0, not active) def update_jobs_list(self): def get_notifications(self): c = self.authconn try: notifications = c.getNotifications([self.sub_id], [self.sub_seq + 1]) except AttributeError: notifications = c.getNotifications([self.sub_id]) return notifications # Enter the GDK lock. We need to do this because we were # called from a timeout. Gdk.threads_enter() parent = self.troubleshooter.get_window() self.op = TimedOperation(get_notifications, (self, ), parent=parent) try: notifications = self.op.run() except (OperationCanceled, cups.IPPError): Gdk.threads_leave() return True answers = self.troubleshooter.answers model = self.treeview.get_model() queue = answers['cups_queue'] test_jobs = self.persistent_answers.get('test_page_job_id', []) for event in notifications['events']: seq = event['notify-sequence-number'] try: if seq <= self.sub_seq: # Work around a bug in pycups < 1.9.34 continue except AttributeError: pass self.sub_seq = seq job = event['notify-job-id'] nse = event['notify-subscribed-event'] if nse == 'job-created': if (job in test_jobs or event['printer-name'] == queue): iter = model.append(None) self.job_to_iter[job] = iter model.set_value(iter, 0, True) model.set_value(iter, 1, job) else: continue elif job not in self.job_to_iter: continue if (job in test_jobs and nse in ["job-stopped", "job-completed"]): comp = self.persistent_answers.get('test_page_completions', []) comp.append((job, event['notify-text'])) self.persistent_answers['test_page_completions'] = comp self.update_job(job, event) # Update again when we're told to. (But we might update sooner if # there is a D-Bus signal.) GLib.source_remove(self.timer) self.timer = GLib.timeout_add_seconds( notifications['notify-get-interval'], self.update_jobs_list) debugprint("Update again in %ds" % notifications['notify-get-interval']) Gdk.threads_leave() return False
class ErrorLogFetch(Question): def __init__(self, troubleshooter): Question.__init__(self, troubleshooter, "Error log fetch") page = self.initial_vbox( _("Retrieve Journal Entries"), _("No system journal entries were found. " "This may be because you are not an " "administrator. To fetch journal entries " "please run this command:")) self.entry = Gtk.Entry() self.entry.set_editable(False) page.pack_start(self.entry, False, False, 0) troubleshooter.new_page(page, self) self.persistent_answers = {} def display(self): answers = self.troubleshooter.answers parent = self.troubleshooter.get_window() self.answers = {} checkpoint = answers.get('error_log_checkpoint') cursor = answers.get('error_log_cursor') timestamp = answers.get('error_log_timestamp') if (self.persistent_answers.has_key('error_log') or self.persistent_answers.has_key('journal')): checkpoint = None cursor = None def fetch_log(c): prompt = c._get_prompt_allowed() c._set_prompt_allowed(False) c._connect() (tmpfd, tmpfname) = tempfile.mkstemp() os.close(tmpfd) success = False try: c.getFile('/admin/log/error_log', tmpfname) success = True except cups.HTTPError: try: os.remove(tmpfname) except OSError: pass c._set_prompt_allowed(prompt) if success: return tmpfname return None now = datetime.datetime.fromtimestamp(time.time()).strftime("%F %T") self.authconn = self.troubleshooter.answers[ '_authenticated_connection'] if answers.has_key('error_log_debug_logging_set'): try: self.op = TimedOperation(self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run() except cups.IPPError: return False settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' orig_settings = answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get('MaxLogSize', '2000000') success = False def set_settings(connection, settings): connection.adminSetServerSettings(settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep(1) connection._connect() break except RuntimeError: # Connection failed attempt += 1 try: self.op = TimedOperation(set_settings, (self.authconn, settings), parent=parent) self.op.run() self.persistent_answers['error_log_debug_logging_unset'] = True except cups.IPPError: pass self.answers = {} if journal and cursor != None: def journal_format(x): try: priority = "XACEWNIDd"[x['PRIORITY']] except (IndexError, TypeError): priority = " " return (priority + " " + x['__REALTIME_TIMESTAMP'].strftime("[%m/%b/%Y:%T]") + " " + x['MESSAGE']) r = journal.Reader() r.seek_cursor(cursor) r.add_match(_COMM="cupsd") self.answers['journal'] = map(journal_format, r) if checkpoint != None: self.op = TimedOperation(fetch_log, (self.authconn, ), parent=parent) tmpfname = self.op.run() if tmpfname != None: f = file(tmpfname) f.seek(checkpoint) lines = f.readlines() os.remove(tmpfname) self.answers['error_log'] = map(lambda x: x.strip(), lines) if (len(self.answers.get('journal', [])) + len(self.answers.get('error_log', []))) == 0: cmd = ("su -c 'journalctl _COMM=cupsd " "--since=\"%s\" --until=\"%s\"' > troubleshoot-logs.txt" % (answers['error_log_timestamp'], now)) self.entry.set_text(cmd) return True return False def collect_answer(self): answers = self.persistent_answers.copy() answers.update(self.answers) return answers def cancel_operation(self): self.op.cancel() # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection() self.answers['_authenticated_connection'] = self.authconn
class ErrorLogFetch(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Error log fetch") page = self.initial_vbox (_("Retrieve Journal Entries"), _("No system journal entries were found. " "This may be because you are not an " "administrator. To fetch journal entries " "please run this command:")) self.entry = Gtk.Entry () self.entry.set_editable (False) page.pack_start (self.entry, False, False, 0) troubleshooter.new_page (page, self) self.persistent_answers = {} def display (self): answers = self.troubleshooter.answers parent = self.troubleshooter.get_window () self.answers = {} checkpoint = answers.get ('error_log_checkpoint') cursor = answers.get ('error_log_cursor') timestamp = answers.get ('error_log_timestamp') if ('error_log' in self.persistent_answers or 'journal' in self.persistent_answers): checkpoint = None cursor = None def fetch_log (c): prompt = c._get_prompt_allowed () c._set_prompt_allowed (False) c._connect () with tempfile.NamedTemporaryFile (delete=False) as tmpf: success = False try: c.getFile ('/admin/log/error_log', tmpf.file) success = True except cups.HTTPError: try: os.remove (tmpf.file) except OSError: pass c._set_prompt_allowed (prompt) if success: return tmpf.file return None now = datetime.datetime.fromtimestamp (time.time ()).strftime ("%F %T") self.authconn = self.troubleshooter.answers['_authenticated_connection'] if 'error_log_debug_logging_set' in answers: try: self.op = TimedOperation (self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run () except cups.IPPError: return False settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' orig_settings = answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get ('MaxLogSize', '2000000') success = False def set_settings (connection, settings): connection.adminSetServerSettings (settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep (1) connection._connect () break except RuntimeError: # Connection failed attempt += 1 try: self.op = TimedOperation (set_settings, (self.authconn, settings), parent=parent) self.op.run () self.persistent_answers['error_log_debug_logging_unset'] = True except cups.IPPError: pass self.answers = {} if journal and cursor != None: def journal_format (x): try: priority = "XACEWNIDd"[x['PRIORITY']] except (IndexError, TypeError): priority = " " return (priority + " " + x['__REALTIME_TIMESTAMP'].strftime("[%m/%b/%Y:%T]") + " " + x['MESSAGE']) r = journal.Reader () r.seek_cursor (cursor) r.add_match (_SYSTEMD_UNIT="cups.service") self.answers['journal'] = [journal_format (x) for x in r] if checkpoint != None: self.op = TimedOperation (fetch_log, (self.authconn,), parent=parent) tmpfname = self.op.run () if tmpfname != None: f = open (tmpfname) f.seek (checkpoint) lines = f.readlines () os.remove (tmpfname) self.answers = { 'error_log': [x.strip () for x in lines] } if (len (self.answers.get ('journal', [])) + len (self.answers.get ('error_log', []))) == 0: cmd = ("su -c 'journalctl -u cups.service " "--since=\"%s\" --until=\"%s\"' > troubleshoot-logs.txt" % (answers['error_log_timestamp'], now)) self.entry.set_text (cmd) return True return False def collect_answer (self): answers = self.persistent_answers.copy () answers.update (self.answers) return answers def cancel_operation (self): self.op.cancel () # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection () self.answers['_authenticated_connection'] = self.authconn
class PrinterFinder: def __init__(self): self.quit = False def find(self, hostname, callback_fn): self.hostname = hostname self.callback_fn = callback_fn self.op = TimedOperation(self._do_find, callback=lambda x, y: None) def cancel(self): self.op.cancel() self.quit = True def _do_find(self): self._cached_attributes = dict() for fn in [ self._probe_jetdirect, self._probe_ipp, self._probe_snmp, self._probe_lpd, self._probe_hplip, self._probe_smb ]: if self.quit: return try: fn() except Exception: nonfatalException() # Signal that we've finished. if not self.quit: self.callback_fn(None) def _new_device(self, uri, info, location=None): device_dict = {'device-class': 'network', 'device-info': "%s" % info} if location: device_dict['device-location'] = location device_dict.update(self._cached_attributes) new_device = cupshelpers.Device(uri, **device_dict) debugprint("Device found: %s" % uri) self.callback_fn(new_device) def _probe_snmp(self): # Run the CUPS SNMP backend, pointing it at the host. null = file("/dev/null", "r+") try: debugprint("snmp: trying") p = subprocess.Popen( args=["/usr/lib/cups/backend/snmp", self.hostname], close_fds=True, stdin=null, stdout=subprocess.PIPE, stderr=null) except OSError as e: debugprint("snmp: no good") if e == errno.ENOENT: return raise (stdout, stderr) = p.communicate() if p.returncode != 0: debugprint("snmp: no good (return code %d)" % p.returncode) return if self.quit: debugprint("snmp: no good") return for line in stdout.split('\n'): words = wordsep(line) n = len(words) if n == 6: (device_class, uri, make_and_model, info, device_id, device_location) = words elif n == 5: (device_class, uri, make_and_model, info, device_id) = words elif n == 4: (device_class, uri, make_and_model, info) = words else: continue device_dict = { 'device-class': device_class, 'device-make-and-model': make_and_model, 'device-info': info } if n == 5: debugprint("snmp: Device ID found:\n%s" % device_id) device_dict['device-id'] = device_id if n == 6: device_dict['device-location'] = device_location device = cupshelpers.Device(uri, **device_dict) debugprint("Device found: %s" % uri) self.callback_fn(device) # Cache the make and model for use by other search methods # that are not able to determine it. self._cached_attributes['device-make-and-model'] = make_and_model self._cached_attributes['device_id'] = device_id debugprint("snmp: done") def _probe_lpd(self): debugprint("lpd: trying") lpd = LpdServer(self.hostname) for name in lpd.get_possible_queue_names(): if self.quit: debugprint("lpd: no good") return found = lpd.probe_queue(name, []) if found == None: # Couldn't even connect. debugprint("lpd: couldn't connect") break if found: uri = "lpd://%s/%s" % (self.hostname, name) self._new_device(uri, self.hostname) if not found and name.startswith("pr"): break time.sleep(0.1) # avoid DOS and following counter measures debugprint("lpd: done") def _probe_hplip(self): if (self._cached_attributes.has_key ('device-make-and-model') and \ self._cached_attributes['device-make-and-model'] != "Unknown" and \ not self._cached_attributes['device-make-and-model'].lower ().startswith ("hp") and \ not self._cached_attributes['device-make-and-model'].lower ().startswith ("hewlett")): debugprint("hplip: no good (Non-HP printer: %s)" % self._cached_attributes['device-make-and-model']) return null = file("/dev/null", "r+") try: debugprint("hplip: trying") p = subprocess.Popen(args=["hp-makeuri", "-c", self.hostname], close_fds=True, stdin=null, stdout=subprocess.PIPE, stderr=null) except OSError as e: if e == errno.ENOENT: return raise (stdout, stderr) = p.communicate() if p.returncode != 0: debugprint("hplip: no good (return code %d)" % p.returncode) return if self.quit: debugprint("hplip: no good") return uri = stdout.strip() debugprint("hplip: uri is %s" % uri) if uri.find(":") != -1: self._new_device(uri, uri) debugprint("hplip: done") def _probe_smb(self): if not PYSMB_AVAILABLE: return smbc_auth = BackgroundSmbAuthContext() debug = 0 if get_debugging(): debug = 10 ctx = pysmb.smbc.Context(debug=debug, auth_fn=smbc_auth.callback) entries = [] uri = "smb://%s/" % self.hostname debugprint("smb: trying") try: while smbc_auth.perform_authentication() > 0: if self.quit: debugprint("smb: no good") return try: entries = ctx.opendir(uri).getdents() except Exception as e: smbc_auth.failed(e) except RuntimeError as e: (e, s) = e.args if e not in [errno.ENOENT, errno.EACCES, errno.EPERM]: debugprint("Runtime error: %s" % repr((e, s))) except: nonfatalException() if self.quit: debugprint("smb: no good") return for entry in entries: if entry.smbc_type == pysmb.smbc.PRINTER_SHARE: uri = "smb://%s/%s" % (smburi.urlquote( self.hostname), smburi.urlquote(entry.name)) info = "SMB (%s)" % self.hostname self._new_device(uri, info) debugprint("smb: done") def _probe_jetdirect(self): port = 9100 #jetdirect sock_address = (self.hostname, port) debugprint("jetdirect: trying") s = open_socket(self.hostname, port) if not s: debugprint("jetdirect: %s:%d CLOSED" % sock_address) else: # port is open so assume its a JetDirect device debugprint("jetdirect %s:%d OPEN" % sock_address) uri = "socket://%s:%d" % sock_address info = "JetDirect (%s)" % self.hostname self._new_device(uri, info) s.close() debugprint("jetdirect: done") def _probe_ipp(self): debugprint("ipp: trying") try: ai = socket.getaddrinfo(self.hostname, 631, socket.AF_UNSPEC, socket.SOCK_STREAM) except socket.gaierror: debugprint("ipp: can't resolve %s" % self.hostname) debugprint("ipp: no good") return for res in ai: af, socktype, proto, canonname, sa = res if (af == socket.AF_INET and sa[0] == '127.0.0.1' or af == socket.AF_INET6 and sa[0] == '::1'): debugprint("ipp: do not probe local cups server") debugprint("ipp: no good") return try: c = cups.Connection(host=self.hostname) except RuntimeError: debugprint("ipp: can't connect to server/printer") debugprint("ipp: no good") return try: printers = c.getPrinters() except cups.IPPError: debugprint("%s is probably not a cups server but IPP printer" % self.hostname) uri = "ipp://%s:631/ipp" % (self.hostname) info = "IPP (%s)" % self.hostname self._new_device(uri, info) debugprint("ipp: done") return for name, queue in printers.iteritems(): uri = queue['printer-uri-supported'] info = queue['printer-info'] location = queue['printer-location'] self._new_device(uri, info, location) debugprint("ipp: done")
class ErrorLogCheckpoint(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Error log checkpoint") page = self.initial_vbox (_("Debugging"), _("This step will enable debugging output " "from the CUPS scheduler. This may " "cause the scheduler to restart. Click " "the button below to enable debugging.")) button = gtk.Button (_("Enable Debugging")) buttonbox = gtk.HButtonBox () buttonbox.set_border_width (0) buttonbox.set_layout (gtk.BUTTONBOX_START) buttonbox.pack_start (button, False, False, 0) self.button = button page.pack_start (buttonbox, False, False, 0) self.label = gtk.Label () self.label.set_alignment (0, 0) self.label.set_line_wrap (True) page.pack_start (self.label, False, False, 0) troubleshooter.new_page (page, self) self.persistent_answers = {} def __del__ (self): if not self.persistent_answers.get ('error_log_debug_logging_set', False): return c = self.troubleshooter.answers['_authenticated_connection'] c._set_lock (False) settings = c.adminGetServerSettings () if len (settings.keys ()) == 0: return settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' answers = self.troubleshooter.answers orig_settings = self.persistent_answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get ('MaxLogSize', '2000000') c.adminSetServerSettings (settings) def display (self): self.answers = {} answers = self.troubleshooter.answers if not answers['cups_queue_listed']: return False self.authconn = answers['_authenticated_connection'] parent = self.troubleshooter.get_window () def getServerSettings (): # Fail if auth required. cups.setPasswordCB (lambda x: '') cups.setServer ('') c = cups.Connection () return c.adminGetServerSettings () try: self.op = TimedOperation (getServerSettings, parent=parent) settings = self.op.run () except RuntimeError: return False except cups.IPPError: settings = {} self.forward_allowed = False self.label.set_text ('') if len (settings.keys ()) == 0: # Requires root return True else: self.persistent_answers['cups_server_settings'] = settings try: if int (settings[cups.CUPS_SERVER_DEBUG_LOGGING]) != 0: # Already enabled return False except KeyError: pass except ValueError: pass return True def connect_signals (self, handler): self.button_sigid = self.button.connect ('clicked', self.enable_clicked, handler) def disconnect_signals (self): self.button.disconnect (self.button_sigid) def collect_answer (self): answers = self.troubleshooter.answers if not answers['cups_queue_listed']: return {} parent = self.troubleshooter.get_window () self.answers.update (self.persistent_answers) if self.answers.has_key ('error_log_checkpoint'): return self.answers (tmpfd, tmpfname) = tempfile.mkstemp () os.close (tmpfd) try: self.op = TimedOperation (self.authconn.getFile, args=('/admin/log/error_log', tmpfname), parent=parent) self.op.run () except RuntimeError: try: os.remove (tmpfname) except OSError: pass return self.answers except cups.IPPError: try: os.remove (tmpfname) except OSError: pass return self.answers statbuf = os.stat (tmpfname) os.remove (tmpfname) self.answers['error_log_checkpoint'] = statbuf[6] self.persistent_answers['error_log_checkpoint'] = statbuf[6] return self.answers def can_click_forward (self): return self.forward_allowed def enable_clicked (self, button, handler): parent = self.troubleshooter.get_window () self.troubleshooter.busy () try: self.op = TimedOperation (self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run () except (cups.IPPError, OperationCanceled): self.troubleshooter.ready () self.forward_allowed = True handler (button) return self.persistent_answers['cups_server_settings'] = settings.copy () MAXLOGSIZE='MaxLogSize' try: prev_debug = int (settings[cups.CUPS_SERVER_DEBUG_LOGGING]) except KeyError: prev_debug = 0 try: prev_logsize = int (settings[MAXLOGSIZE]) except (KeyError, ValueError): prev_logsize = -1 if prev_debug == 0 or prev_logsize != '0': settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '1' settings[MAXLOGSIZE] = '0' success = False def set_settings (connection, settings): connection.adminSetServerSettings (settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep (1) connection._connect () break except RuntimeError: # Connection failed attempt += 1 try: debugprint ("Settings to set: " + repr (settings)) self.op = TimedOperation (set_settings, args=(self.authconn, settings,), parent=parent) self.op.run () success = True except cups.IPPError: pass except RuntimeError: pass if success: self.persistent_answers['error_log_debug_logging_set'] = True self.label.set_text (_("Debug logging enabled.")) else: self.label.set_text (_("Debug logging was already enabled.")) self.forward_allowed = True self.troubleshooter.ready () handler (button) def cancel_operation (self): self.op.cancel () # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection () self.answers['_authenticated_connection'] = self.authconn
class DeviceListed(Question): def __init__ (self, troubleshooter): # Is the device listed? Question.__init__ (self, troubleshooter, "Choose device") page1 = self.initial_vbox (_("Choose Device"), _("Please select the device you want " "to use from the list below. " "If it does not appear in the list, " "select 'Not listed'.")) tv = Gtk.TreeView () name = Gtk.TreeViewColumn (_("Name"), Gtk.CellRendererText (), text=0) info = Gtk.TreeViewColumn (_("Information"), Gtk.CellRendererText (), text=1) uri = Gtk.TreeViewColumn (_("Device URI"), Gtk.CellRendererText (), text=2) name.set_property ("resizable", True) info.set_property ("resizable", True) uri.set_property ("resizable", True) tv.append_column (name) tv.append_column (info) tv.append_column (uri) tv.set_rules_hint (True) sw = Gtk.ScrolledWindow () sw.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type (Gtk.ShadowType.IN) sw.add (tv) page1.pack_start (sw, True, True, 0) self.treeview = tv troubleshooter.new_page (page1, self) def display (self): self.answers = {} answers = self.troubleshooter.answers if (answers['printer_is_remote'] or answers.get ('cups_printer_remote', False)): return False model = Gtk.ListStore (str, str, str, GObject.TYPE_PYOBJECT) self.treeview.set_model (model) iter = model.append (None) model.set (iter, 0, _("Not listed"), 1, '', 2, '', 3, NotListed) devices = {} parent = self.troubleshooter.get_window () # Skip device list if this page is hidden and we're skipping # backwards past it. if not (answers['cups_queue_listed'] and self.troubleshooter.is_moving_backwards ()): # Otherwise, fetch devices. self.authconn = answers['_authenticated_connection'] try: self.op = TimedOperation (self.authconn.getDevices, parent=parent) devices = self.op.run () devices_list = [] for uri, device in devices.items (): if uri.find (':') == -1: continue if device.get('device-class') != 'direct': continue name = device.get('device-info', _("Unknown")) info = device.get('device-make-and-model', _("Unknown")) devices_list.append ((name, info, uri, device)) devices_list.sort (key=lambda x: x[0]) for name, info, uri, device in devices_list: iter = model.append (None) model.set (iter, 0, name, 1, info, 2, uri, 3, device) except cups.HTTPError: pass except cups.IPPError: pass except RuntimeError: pass if answers['cups_queue_listed']: try: printer_dict = answers['cups_printer_dict'] uri = printer_dict['device-uri'] device = devices[uri] self.answers['cups_device_dict'] = device except KeyError: pass return False return True def connect_signals (self, handler): self.signal_id = self.treeview.connect ("cursor-changed", handler) def disconnect_signals (self): self.treeview.disconnect (self.signal_id) def can_click_forward (self): model, iter = self.treeview.get_selection ().get_selected () if iter is None: return False return True def collect_answer (self): if not self.displayed: return self.answers model, iter = self.treeview.get_selection ().get_selected () device = model.get_value (iter, 3) if device == NotListed: class enum_devices: def __init__ (self, model): self.devices = {} model.foreach (self.each, None) def each (self, model, path, iter, user_data): uri = model.get_value (iter, 2) device = model.get_value (iter, 3) if device != NotListed: self.devices[uri] = device self.answers['cups_device_listed'] = False avail = enum_devices (model).devices self.answers['cups_devices_available'] = avail else: uri = model.get_value (iter, 2) self.answers['cups_device_listed'] = True self.answers['cups_device_uri'] = uri self.answers['cups_device_attributes'] = device return self.answers def cancel_operation (self): self.op.cancel () # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection () self.answers['_authenticated_connection'] = self.authconn
class ErrorLogFetch(Question): def __init__ (self, troubleshooter): Question.__init__ (self, troubleshooter, "Error log fetch") troubleshooter.new_page (gtk.Label (), self) self.persistent_answers = {} def display (self): answers = self.troubleshooter.answers parent = self.troubleshooter.get_window () self.answers = {} try: checkpoint = answers['error_log_checkpoint'] except KeyError: checkpoint = None if self.persistent_answers.has_key ('error_log'): checkpoint = None def fetch_log (c): prompt = c._get_prompt_allowed () c._set_prompt_allowed (False) c._connect () (tmpfd, tmpfname) = tempfile.mkstemp () os.close (tmpfd) success = False try: c.getFile ('/admin/log/error_log', tmpfname) success = True except cups.HTTPError: try: os.remove (tmpfname) except OSError: pass c._set_prompt_allowed (prompt) if success: return tmpfname return None self.authconn = self.troubleshooter.answers['_authenticated_connection'] if answers.has_key ('error_log_debug_logging_set'): try: self.op = TimedOperation (self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run () except cups.IPPError: return False settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' orig_settings = answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get ('MaxLogSize', '2000000') success = False def set_settings (connection, settings): connection.adminSetServerSettings (settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep (1) connection._connect () break except RuntimeError: # Connection failed attempt += 1 try: self.op = TimedOperation (set_settings, (self.authconn, settings), parent=parent) self.op.run () self.persistent_answers['error_log_debug_logging_unset'] = True except cups.IPPError: pass if checkpoint != None: self.op = TimedOperation (fetch_log, (self.authconn,), parent=parent) tmpfname = self.op.run () if tmpfname != None: f = file (tmpfname) f.seek (checkpoint) lines = f.readlines () os.remove (tmpfname) self.answers = { 'error_log': map (lambda x: x.strip (), lines) } return False def collect_answer (self): answers = self.persistent_answers.copy () answers.update (self.answers) return answers def cancel_operation (self): self.op.cancel () # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection () self.answers['_authenticated_connection'] = self.authconn
class ErrorLogCheckpoint(Question): def __init__(self, troubleshooter): Question.__init__(self, troubleshooter, "Error log checkpoint") page = self.initial_vbox( _("Debugging"), _("This step will enable debugging output " "from the CUPS scheduler. This may " "cause the scheduler to restart. Click " "the button below to enable debugging.")) button = Gtk.Button.new_with_label(_("Enable Debugging")) buttonbox = Gtk.HButtonBox() buttonbox.set_border_width(0) buttonbox.set_layout(Gtk.ButtonBoxStyle.START) buttonbox.pack_start(button, False, False, 0) self.button = button page.pack_start(buttonbox, False, False, 0) self.label = Gtk.Label() self.label.set_alignment(0, 0) self.label.set_line_wrap(True) page.pack_start(self.label, False, False, 0) troubleshooter.new_page(page, self) self.persistent_answers = {} def __del__(self): if not self.persistent_answers.get('error_log_debug_logging_set', False): return f = self.troubleshooter.answers['_authenticated_connection_factory'] c = f.get_connection() c._set_lock(False) settings = c.adminGetServerSettings() if len(list(settings.keys())) == 0: return settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '0' answers = self.troubleshooter.answers orig_settings = self.persistent_answers['cups_server_settings'] settings['MaxLogSize'] = orig_settings.get('MaxLogSize', '2000000') c.adminSetServerSettings(settings) def display(self): self.answers = {} answers = self.troubleshooter.answers if not answers['cups_queue_listed']: return False self.authconn = answers['_authenticated_connection'] parent = self.troubleshooter.get_window() def getServerSettings(): # Fail if auth required. cups.setPasswordCB(lambda x: '') cups.setServer('') c = cups.Connection() return c.adminGetServerSettings() try: self.op = TimedOperation(getServerSettings, parent=parent) settings = self.op.run() except RuntimeError: return False except cups.IPPError: settings = {} self.forward_allowed = False self.label.set_text('') if len(list(settings.keys())) == 0: # Requires root return True else: self.persistent_answers['cups_server_settings'] = settings try: if int(settings[cups.CUPS_SERVER_DEBUG_LOGGING]) != 0: # Already enabled return False except KeyError: pass except ValueError: pass return True def connect_signals(self, handler): self.button_sigid = self.button.connect('clicked', self.enable_clicked, handler) def disconnect_signals(self): self.button.disconnect(self.button_sigid) def collect_answer(self): answers = self.troubleshooter.answers if not answers['cups_queue_listed']: return {} parent = self.troubleshooter.get_window() self.answers.update(self.persistent_answers) if 'error_log_checkpoint' in self.answers: return self.answers (tmpfd, tmpfname) = tempfile.mkstemp() os.close(tmpfd) try: self.op = TimedOperation(self.authconn.getFile, args=('/admin/log/error_log', tmpfname), parent=parent) self.op.run() except (RuntimeError, cups.IPPError) as e: self.answers['error_log_checkpoint_exc'] = e try: os.remove(tmpfname) except OSError: pass except cups.HTTPError as e: self.answers['error_log_checkpoint_exc'] = e try: os.remove(tmpfname) except OSError: pass # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection() self.answers['_authenticated_connection'] = self.authconn try: statbuf = os.stat(tmpfname) os.remove(tmpfname) except OSError: statbuf = [0, 0, 0, 0, 0, 0, 0] self.answers['error_log_checkpoint'] = statbuf[6] self.persistent_answers['error_log_checkpoint'] = statbuf[6] if journal: j = journal.Reader() j.seek_tail() cursor = j.get_previous()['__CURSOR'] self.answers['error_log_cursor'] = cursor self.persistent_answers['error_log_cursor'] = cursor return self.answers def can_click_forward(self): return self.forward_allowed def enable_clicked(self, button, handler): parent = self.troubleshooter.get_window() self.troubleshooter.busy() try: self.op = TimedOperation(self.authconn.adminGetServerSettings, parent=parent) settings = self.op.run() except (cups.IPPError, OperationCanceled): self.troubleshooter.ready() self.forward_allowed = True handler(button) return self.persistent_answers['cups_server_settings'] = settings.copy() MAXLOGSIZE = 'MaxLogSize' try: prev_debug = int(settings[cups.CUPS_SERVER_DEBUG_LOGGING]) except KeyError: prev_debug = 0 try: prev_logsize = int(settings[MAXLOGSIZE]) except (KeyError, ValueError): prev_logsize = -1 if prev_debug == 0 or prev_logsize != '0': settings[cups.CUPS_SERVER_DEBUG_LOGGING] = '1' settings[MAXLOGSIZE] = '0' success = False def set_settings(connection, settings): connection.adminSetServerSettings(settings) # Now reconnect. attempt = 1 while attempt <= 5: try: time.sleep(1) connection._connect() break except RuntimeError: # Connection failed attempt += 1 try: debugprint("Settings to set: " + repr(settings)) self.op = TimedOperation(set_settings, args=( self.authconn, settings, ), parent=parent) self.op.run() success = True except cups.IPPError: pass except RuntimeError: pass if success: self.persistent_answers['error_log_debug_logging_set'] = True self.label.set_text(_("Debug logging enabled.")) else: self.label.set_text(_("Debug logging was already enabled.")) self.forward_allowed = True self.troubleshooter.ready() handler(button) def cancel_operation(self): self.op.cancel() # Abandon the CUPS connection and make another. answers = self.troubleshooter.answers factory = answers['_authenticated_connection_factory'] self.authconn = factory.get_connection() self.answers['_authenticated_connection'] = self.authconn