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