1#!/usr/local/bin/python3.8 2 3## Printing troubleshooter 4 5## Copyright (C) 2008, 2009, 2010, 2012 Red Hat, Inc. 6## Author: Tim Waugh <twaugh@redhat.com> 7 8## This program is free software; you can redistribute it and/or modify 9## it under the terms of the GNU General Public License as published by 10## the Free Software Foundation; either version 2 of the License, or 11## (at your option) any later version. 12 13## This program is distributed in the hope that it will be useful, 14## but WITHOUT ANY WARRANTY; without even the implied warranty of 15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16## GNU General Public License for more details. 17 18## You should have received a copy of the GNU General Public License 19## along with this program; if not, write to the Free Software 20## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 22from gi.repository import Gdk 23from gi.repository import Gtk 24import pprint 25import sys 26import datetime 27import time 28import traceback 29 30if __name__ == "__main__": 31 import os.path 32 import gettext 33 gettext.textdomain ('system-config-printer') 34 35 if sys.argv[0][0] != '/': 36 cwd = os.getcwd () 37 path = cwd + os.path.sep + sys.argv[0] 38 else: 39 path = sys.argv[0] 40 sub = os.path.dirname (path) 41 root = os.path.dirname (sub) 42 sys.path.append (root) 43 44from . import base 45from .base import * 46 47class Troubleshooter: 48 def __init__ (self, quitfn=None, parent=None): 49 self._in_module_call = False 50 51 main = Gtk.Window () 52 if parent: 53 main.set_transient_for (parent) 54 main.set_position (Gtk.WindowPosition.CENTER_ON_PARENT) 55 main.set_modal (True) 56 57 main.set_title (_("Printing troubleshooter")) 58 main.set_property ("default-width", 400) 59 main.set_property ("default-height", 350) 60 main.connect ("delete_event", self.quit) 61 self.main = main 62 self.quitfn = quitfn 63 64 vbox = Gtk.VBox () 65 main.add (vbox) 66 ntbk = Gtk.Notebook () 67 ntbk.set_border_width (6) 68 vbox.pack_start (ntbk, True, True, 0) 69 vbox.pack_start (Gtk.HSeparator (), False, False, 0) 70 box = Gtk.HButtonBox () 71 box.set_border_width (6) 72 box.set_spacing (3) 73 box.set_layout (Gtk.ButtonBoxStyle.END) 74 75 back = Gtk.Button.new_from_stock (Gtk.STOCK_GO_BACK) 76 back.connect ('clicked', self._on_back_clicked) 77 back.set_sensitive (False) 78 self.back = back 79 80 close = Gtk.Button.new_from_stock (Gtk.STOCK_CLOSE) 81 close.connect ('clicked', self.quit) 82 self.close = close 83 84 cancel = Gtk.Button.new_from_stock (Gtk.STOCK_CANCEL) 85 cancel.connect ('clicked', self.quit) 86 self.cancel = cancel 87 88 forward = Gtk.Button.new_from_stock (Gtk.STOCK_GO_FORWARD) 89 forward.connect ('clicked', self._on_forward_clicked) 90 self.forward = forward 91 92 box.pack_start (back, False, False, 0) 93 box.pack_start (cancel, False, False, 0) 94 box.pack_start (close, False, False, 0) 95 box.pack_start (forward, False, False, 0) 96 vbox.pack_start (box, False, False, 0) 97 forward.set_property('can-default', True) 98 forward.set_property('has-default', True) 99 100 ntbk.set_current_page (0) 101 ntbk.set_show_tabs (False) 102 self.ntbk = ntbk 103 self.current_page = 0 104 105 self.questions = [] 106 self.question_answers = [] 107 # timestamp should be accessible through whole troubleshoot 108 now = datetime.datetime.fromtimestamp (time.time ()) 109 self.answers = {'error_log_timestamp': now.strftime ("%F %T")} 110 self.moving_backwards = False 111 112 main.show_all () 113 114 def quit (self, *args): 115 if self._in_module_call: 116 try: 117 self.questions[self.current_page].cancel_operation () 118 except: 119 self._report_traceback () 120 121 return 122 123 try: 124 self.questions[self.current_page].disconnect_signals () 125 except: 126 self._report_traceback () 127 128 # Delete the questions so that their __del__ hooks can run. 129 # Do this in reverse order of creation. 130 for i in range (len (self.questions)): 131 self.questions.pop () 132 133 self.main.hide () 134 if self.quitfn: 135 self.quitfn (self) 136 137 def get_window (self): 138 # Any error dialogs etc from the modules need to be able 139 # to set themselves transient for this window. 140 return self.main 141 142 def no_more_questions (self, question): 143 page = self.questions.index (question) 144 debugprint ("Page %d: No more questions." % page) 145 self.questions = self.questions[:page + 1] 146 self.question_answers = self.question_answers[:page + 1] 147 for p in range (self.ntbk.get_n_pages () - 1, page, -1): 148 self.ntbk.remove_page (p) 149 self._set_back_forward_buttons () 150 151 def new_page (self, widget, question): 152 page = len (self.questions) 153 debugprint ("Page %d: new: %s" % (page, str (question))) 154 self.questions.append (question) 155 self.question_answers.append ([]) 156 self.ntbk.insert_page (widget, None, page) 157 widget.show_all () 158 if page == 0: 159 try: 160 question.connect_signals (self._set_back_forward_buttons) 161 except: 162 self._report_traceback () 163 164 self.ntbk.set_current_page (page) 165 self.current_page = page 166 self._set_back_forward_buttons () 167 return page 168 169 def is_moving_backwards (self): 170 return self.moving_backwards 171 172 def answers_as_text (self): 173 text = "" 174 n = 1 175 for i in range (self.current_page): 176 answers = self.question_answers[i].copy () 177 for hidden in [x for x in answers.keys() if x.startswith ("_")]: 178 del answers[hidden] 179 if len (list(answers.keys ())) == 0: 180 continue 181 text += "Page %d (%s):" % (n, self.questions[i]) + '\n' 182 text += pprint.pformat (answers) + '\n' 183 n += 1 184 return text.rstrip () + '\n' 185 186 def busy (self): 187 self._in_module_call = True 188 self.forward.set_sensitive (False) 189 self.back.set_sensitive (False) 190 gdkwin = self.get_window ().get_window() 191 if gdkwin: 192 gdkwin.set_cursor (Gdk.Cursor.new(Gdk.CursorType.WATCH)) 193 while Gtk.events_pending (): 194 Gtk.main_iteration () 195 196 def ready (self): 197 self._in_module_call = False 198 gdkwin = self.get_window ().get_window() 199 if gdkwin: 200 gdkwin.set_cursor (Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)) 201 202 self._set_back_forward_buttons () 203 204 def _set_back_forward_buttons (self, *args): 205 page = self.current_page 206 self.back.set_sensitive (page != 0) 207 if len (self.questions) == page + 1: 208 # Out of questions. 209 debugprint ("Out of questions") 210 self.forward.set_sensitive (False) 211 self.close.show () 212 self.cancel.hide () 213 else: 214 can = self._can_click_forward (self.questions[page]) 215 debugprint ("Page %d: can click forward? %s" % (page, can)) 216 self.forward.set_sensitive (can) 217 self.close.hide () 218 self.cancel.show () 219 220 def _on_back_clicked (self, widget): 221 self.busy () 222 self.moving_backwards = True 223 try: 224 self.questions[self.current_page].disconnect_signals () 225 except: 226 self._report_traceback () 227 228 self.current_page -= 1 229 question = self.questions[self.current_page] 230 while not self._display (question): 231 # Skip this one. 232 debugprint ("Page %d: skip" % (self.current_page)) 233 self.current_page -= 1 234 question = self.questions[self.current_page] 235 236 self.ntbk.set_current_page (self.current_page) 237 answers = {} 238 for i in range (self.current_page): 239 answers.update (self.question_answers[i]) 240 self.answers = answers 241 242 try: 243 self.questions[self.current_page].\ 244 connect_signals (self._set_back_forward_buttons) 245 except: 246 self._report_traceback () 247 248 self.moving_backwards = False 249 self.ready () 250 251 def _on_forward_clicked (self, widget): 252 self.busy () 253 answer_dict = self._collect_answer (self.questions[self.current_page]) 254 self.question_answers[self.current_page] = answer_dict 255 self.answers.update (answer_dict) 256 257 try: 258 self.questions[self.current_page].disconnect_signals () 259 except: 260 self._report_traceback () 261 262 self.current_page += 1 263 question = self.questions[self.current_page] 264 while not self._display (question): 265 # Skip this one, but collect its answers. 266 answer_dict = self._collect_answer (question) 267 self.question_answers[self.current_page] = answer_dict 268 self.answers.update (answer_dict) 269 debugprint ("Page %d: skip" % (self.current_page)) 270 self.current_page += 1 271 question = self.questions[self.current_page] 272 273 self.ntbk.set_current_page (self.current_page) 274 try: 275 question.connect_signals (self._set_back_forward_buttons) 276 except: 277 self._report_traceback () 278 279 self.ready () 280 if get_debugging (): 281 self._dump_answers () 282 283 def _dump_answers (self): 284 debugprint (self.answers_as_text ()) 285 286 def _report_traceback (self): 287 try: 288 print("Traceback:") 289 (type, value, tb) = sys.exc_info () 290 tblast = traceback.extract_tb (tb, limit=None) 291 if len (tblast): 292 tblast = tblast[:len (tblast) - 1] 293 extxt = traceback.format_exception_only (type, value) 294 for line in traceback.format_tb(tb): 295 print(line.strip ()) 296 print(extxt[0].strip ()) 297 except: 298 pass 299 300 def _display (self, question): 301 result = False 302 try: 303 result = question.display () 304 except: 305 self._report_traceback () 306 307 question.displayed = result 308 return result 309 310 def _can_click_forward (self, question): 311 try: 312 return question.can_click_forward () 313 except: 314 self._report_traceback () 315 return True 316 317 def _collect_answer (self, question): 318 answer = {} 319 try: 320 answer = question.collect_answer () 321 except: 322 self._report_traceback () 323 324 return answer 325 326QUESTIONS = ["Welcome", 327 "SchedulerNotRunning", 328 "CheckLocalServerPublishing", 329 "ChoosePrinter", 330 "CheckPrinterSanity", 331 "CheckPPDSanity", 332 333 "LocalOrRemote", 334 "DeviceListed", 335 "CheckUSBPermissions", 336 337 "RemoteAddress", 338 "CheckNetworkServerSanity", 339 "ChooseNetworkPrinter", 340 341 "NetworkCUPSPrinterShared", 342 343 "QueueNotEnabled", 344 "QueueRejectingJobs", 345 "PrinterStateReasons", 346 347 "VerifyPackages", 348 "CheckSELinux", 349 "ServerFirewalled", 350 "ErrorLogCheckpoint", 351 "PrintTestPage", 352 "ErrorLogFetch", 353 "PrinterStateReasons", 354 "ErrorLogParse", 355 "Locale", 356 "Shrug"] 357 358def run (quitfn=None, parent=None): 359 troubleshooter = Troubleshooter (quitfn, parent=parent) 360 modules_imported = [] 361 for module in QUESTIONS: 362 try: 363 if not module in modules_imported: 364 exec ("from .%s import %s" % (module, module)) 365 modules_imported.append (module) 366 367 exec ("%s (troubleshooter)" % module) 368 except: 369 troubleshooter._report_traceback () 370 return troubleshooter 371 372if __name__ == "__main__": 373 import getopt 374 try: 375 opts, args = getopt.gnu_getopt (sys.argv[1:], '', 376 ['debug']) 377 for opt, optarg in opts: 378 if opt == '--debug': 379 set_debugging (True) 380 except getopt.GetoptError: 381 pass 382 Gdk.threads_init() 383 run (Gtk.main_quit) 384 Gdk.threads_enter () 385 Gtk.main () 386 Gdk.threads_leave () 387