OSDN Git Service

Updating README.md
[rebornos/cnchi-gnome-osdn.git] / Cnchi / slides.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # slides.py
5 #
6 # Copyright © 2013-2018 Reborn
7 #
8 # This file is part of Cnchi.
9 #
10 # Cnchi is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # Cnchi is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # The following additional terms are in effect as per Section 7 of the license:
21 #
22 # The preservation of all legal notices and author attributions in
23 # the material or in the Appropriate Legal Notices displayed
24 # by works containing it is required.
25 #
26 # You should have received a copy of the GNU General Public License
27 # along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
28
29
30 """ Shows slides while installing. Also manages installing messages and progress bars """
31
32 import sys
33 import logging
34 import os
35 import queue
36 import subprocess
37
38
39 import gi
40 gi.require_version('Gtk', '3.0')
41 from gi.repository import Gtk, GLib, GdkPixbuf
42
43 import show_message as show
44 import misc.extra as misc
45
46 from pages.gtkbasebox import GtkBaseBox
47
48 from logging_utils import ContextFilter
49
50 # When testing, no _() is available
51 try:
52     _("")
53 except NameError as err:
54     def _(message):
55         return message
56
57 class Slides(GtkBaseBox):
58     """ Slides page """
59
60     # Check events queue every second
61     MANAGE_EVENTS_TIMER = 1000
62
63     # Change image slide every half minute
64     SLIDESHOW_TIMER = 35000
65
66     def __init__(self, params, prev_page=None, next_page=None):
67         """ Initialize class and its vars """
68         super().__init__(self, params, 'slides', prev_page, next_page)
69
70         self.progress_bar = self.gui.get_object('progress_bar')
71         self.progress_bar.set_show_text(True)
72         self.progress_bar.set_name('i_progressbar')
73
74         self.downloads_progress_bar = self.gui.get_object('downloads_progress_bar')
75         self.downloads_progress_bar.set_show_text(True)
76         self.downloads_progress_bar.set_name('a_progressbar')
77
78         self.info_label = self.gui.get_object('info_label')
79
80         self.fatal_error = False
81         self.should_pulse = False
82
83         self.revealer = self.gui.get_object('revealer1')
84         self.revealer.connect('notify::child-revealed', self.image_revealed)
85         self.slide = 0
86         self.stop_slideshow = False
87
88         GLib.timeout_add(Slides.MANAGE_EVENTS_TIMER, self.manage_events_from_cb_queue)
89
90     def translate_ui(self):
91         """ Translates all ui elements """
92         if not self.info_label.get_label():
93             self.info_label.set_markup(_("Please wait..."))
94
95         self.header.set_subtitle(_("Installing RebornOS..."))
96
97     def prepare(self, direction):
98         """ Prepare slides screen """
99         self.translate_ui()
100         self.show_all()
101
102         # Last screen reached, hide main progress bar (the one at the top).
103         self.main_progressbar.hide()
104
105         # Also hide total downloads progress bar
106         self.downloads_progress_bar.hide()
107
108         # Hide backwards and forwards buttons
109         self.backwards_button.hide()
110         self.forward_button.hide()
111
112         # Hide close button (we've reached the point of no return)
113         self.header.set_show_close_button(False)
114
115         # Set slide image (and show it)
116         self.reveal_next_slide()
117
118     def reveal_next_slide(self):
119         """ Loads slide and reveals it """
120         if not self.stop_slideshow:
121             self.slide = ((self.slide + 1) % 3) + 1
122             if 0 < self.slide <= 3:
123                 try:
124                     data_dir = self.settings.get('data')
125                     path = os.path.join(data_dir, 'images/slides',
126                                         '{}.png'.format(self.slide))
127                     img = self.gui.get_object('slide1')
128                     # img.set_from_file(path)
129                     # 800x334
130                     pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
131                         path, 820, 334, False)
132                     # set the content of the image as the pixbuf
133                     img.set_from_pixbuf(pixbuf)
134                     # Reveal image
135                     self.revealer.set_reveal_child(True)
136                 except FileNotFoundError:
137                     # FIXME: Installation process finishes before we can read these values ?¿
138                     logging.warning("Can't get configuration values.")
139                     self.stop_slideshow = True
140
141     def image_revealed(self, revealer, _revealed):
142         """ Called when a image slide is shown
143             revealer: Gtk.Revealer
144             revealed: GParamBoolean """
145         if not self.stop_slideshow:
146             if revealer.get_child_revealed() and not self.stop_slideshow:
147                 GLib.timeout_add(Slides.SLIDESHOW_TIMER, self.hide_slide)
148             else:
149                 self.reveal_next_slide()
150
151     def hide_slide(self):
152         """ Hide image shown in slideshow, this will trigger image_revealed()
153         so the next slide image will be revealed """
154         self.revealer.set_reveal_child(False)
155         return False
156
157     @staticmethod
158     def store_values():
159         """ Nothing to be done here """
160         return False
161
162     def stop_pulse(self):
163         """ Stop pulsing progressbar """
164         self.should_pulse = False
165         # self.progress_bar.hide()
166         self.info_label.show_all()
167
168     def start_pulse(self):
169         """ Start pulsing progressbar """
170
171         def pbar_pulse():
172             """ Pulse progressbar """
173             if self.should_pulse:
174                 self.progress_bar.pulse()
175             return self.should_pulse
176
177         if not self.should_pulse:
178             # Hide any text that might be in info area
179             self.info_label.set_markup("")
180             self.info_label.hide()
181             # Show progress bar (just in case)
182             self.progress_bar.show_all()
183             self.progress_bar.set_show_text(True)
184             self.should_pulse = True
185             GLib.timeout_add(100, pbar_pulse)
186
187     def manage_events_from_cb_queue(self):
188         """ We should be quick here and do as less as possible """
189
190         if self.fatal_error:
191             return False
192
193         if self.callback_queue is None:
194             return True
195
196         while not self.callback_queue.empty():
197             try:
198                 event = self.callback_queue.get_nowait()
199             except ValueError as queue_error:
200                 # Calling get_nowait so many times can issue a ValueError
201                 # exception with this error: semaphore or lock released too
202                 # many times. Log it anyways to keep an eye on this error
203                 logging.error(queue_error)
204                 return True
205             except queue.Empty:
206                 # Queue is empty, just quit.
207                 return True
208
209             if event[0] == 'percent':
210                 self.progress_bar.set_fraction(float(event[1]))
211             elif event[0] == 'downloads_percent':
212                 self.downloads_progress_bar.set_fraction(float(event[1]))
213             elif event[0] == 'progress_bar_show_text':
214                 if event[1]:
215                     self.progress_bar.set_text(event[1])
216                 else:
217                     self.progress_bar.set_text("")
218             elif event[0] == 'progress_bar':
219                 if event[1] == 'hide':
220                     self.progress_bar.hide()
221                 elif event[1] == 'show':
222                     self.progress_bar.show()
223             elif event[0] == 'downloads_progress_bar':
224                 if event[1] == 'hide':
225                     self.downloads_progress_bar.hide()
226                 elif event[1] == 'show':
227                     self.downloads_progress_bar.show()
228             elif event[0] == 'pulse':
229                 if event[1] == 'stop':
230                     self.stop_pulse()
231                 elif event[1] == 'start':
232                     self.start_pulse()
233             elif event[0] == 'finished':
234                 logging.info(event[1])
235                 self.installation_finished()
236             elif event[0] == 'error':
237                 self.callback_queue.task_done()
238                 self.install_error(event[1])
239             elif event[0] == 'info':
240                 logging.info(event[1])
241                 if self.should_pulse:
242                     self.progress_bar.set_text(event[1])
243                 else:
244                     self.info_label.set_markup(event[1])
245             elif event[0] == 'cache_pkgs_md5_check_failed':
246                 logging.debug(
247                     'Adding %s to cache_pkgs_md5_check_failed list', event[1])
248                 self.settings.set('cache_pkgs_md5_check_failed', event[1])
249             else:
250                 logging.warning("Event %s not recognised. Ignoring.", event[0])
251
252             self.callback_queue.task_done()
253
254         return True
255
256     def empty_queue(self):
257         """ Empties messages queue """
258         while not self.callback_queue.empty():
259             try:
260                 self.callback_queue.get_nowait()
261                 self.callback_queue.task_done()
262             except queue.Empty:
263                 return
264
265     @staticmethod
266     def reboot():
267         """ Reboots the system, used when installation is finished """
268         with misc.raised_privileges():
269             try:
270                 cmd = ["sync"]
271                 subprocess.call(cmd)
272                 cmd = ["/usr/bin/systemctl", "reboot", "--force", "--no-wall"]
273                 subprocess.call(cmd)
274             except subprocess.CalledProcessError as error:
275                 logging.error(error)
276
277     def check_bootloader(self):
278         """ Check that bootloader has been successfuly installed
279             Shows a dialog to the user in case something has failed """
280         bootloader_install = self.settings.get('bootloader_install')
281         bootloader_install_ok = self.settings.get('bootloader_installation_successful')
282
283         if bootloader_install and not bootloader_install_ok:
284             # Warn user about GRUB and ask if we should open wiki page.
285             boot_warn = _(
286                 "IMPORTANT: There may have been a problem with the bootloader installation "
287                 "which could prevent your system from booting properly. Before rebooting, "
288                 "you may want to verify whether or not the bootloader is installed and "
289                 "configured.\n\n"
290                 "The Arch Linux Wiki contains troubleshooting information:\n"
291                 "\thttps://wiki.archlinux.org/index.php/GRUB\n\n"
292                 "Would you like to view the wiki page now?")
293             response = show.question(self.get_main_window(), boot_warn)
294             if response == Gtk.ResponseType.YES:
295                 import webbrowser
296                 misc.drop_privileges()
297                 wiki_url = 'https://wiki.archlinux.org/index.php/GRUB'
298                 webbrowser.open(wiki_url)
299
300     def installation_finished(self):
301         """ Installation finished """
302         log_util = ContextFilter()
303         log_util.send_install_result("True")
304
305         self.stop_slideshow = True
306
307         install_ok = _(
308             "Installation Complete!\n"
309             "Do you want to restart your system now?")
310         response = show.question(self.get_main_window(), install_ok)
311
312         try:
313             misc.remove_temp_files(self.settings.get('temp'))
314         except FileNotFoundError:
315             # FIXME: Installation process finishes before we can read these values ?¿
316             logging.warning("Can't get configuration values.")
317
318         logging.shutdown()
319
320         if response == Gtk.ResponseType.YES:
321             self.reboot()
322         else:
323             sys.exit(0)
324
325     def install_error(self, error):
326         """ A fatal error has been issued """
327
328         self.stop_slideshow = True
329
330         # Empty the events queue
331         self.empty_queue()
332
333         log_util = ContextFilter()
334         log_util.send_install_result("False")
335         if log_util.have_install_id:
336             # Add install id to error message
337             # (we can lookup logs on bugsnag by the install id)
338             tpl = _(
339                 'Please reference the following number when reporting this error: ')
340             error_message = '{0}\n{1}{2}'.format(
341                 error, tpl, log_util.install_id)
342         else:
343             error_message = error
344
345         show.fatal_error(self.get_main_window(), error_message)