Source

vinstall / vinstall / core / wizard.py

Full commit
# coding: utf8

"""VASM application toolkit prototype"""


import Queue
import sys, os
from inspect import isgeneratorfunction
from concurrent import futures
from .command import ProcessingFacade


class WizardApplication(object):
    "Wizard App"


    def __init__(self, first_controller, config=None):
        self.config = config or {}
        self.controller = first_controller
        self.protocol = WizardProtocol()
        self.protocol.factory = self

    def set_view(self, view):
        "Set the view for this app"
        mod_name = "vinstall.ui"

        try:
            module = __import__(mod_name, globals(), locals(), [view],
                    level=1)
        except ImportError:
            #2.7 behaves differently
            module = __import__(mod_name, globals(), locals(), [view],
                    level=0)
        sys.modules[mod_name] = module

    def make_controller_instance(self, klass):
        """Create an instance of the controller class

        """
        obj = klass()
        obj.config = self.config
        init_method = getattr(obj, "init", None)
        if init_method:
            init_method()
        return obj

    def run(self):
        controller = self.make_controller_instance(self.controller)
        self.protocol.start(controller)

    def stop(self):
        self.protocol.stop()
        sys.exit(0)


class WizardProtocol(object):
    """Controller protocol for wizard-like applications
    This object is responsible for processing each controller object thats part
    of the wizard.

    """
    def __init__(self):
        self.factory = None
        self.current_controller = None
        self.current_render = None
        self.commands = []

    def process_state(self, controller):
        "Process the current state of the wizard"
        self.current_controller = controller
        self.show()

    def show(self):
        """Present the current controller to the user and setup callbacks for
        running next. If optional render method is not present, call next()
        ourselves.

        """
        if hasattr(self.current_controller, "render"):
            self.current_render = render = self.current_controller.render()
            render.main_window.clear_callbacks()
            render.update_main_window()
            render.main_window.add_next_callback(self.next)
            render.main_window.add_previous_callback(self.previous)
        else:
            self.current_render = None
            self.next()

    def run_controller(self):
        "Execute tasks specified by the controller"
        process_method = getattr(self.current_controller, "process", None)
        if self.current_render is None:
            args = tuple()
        else:
            args = self.current_render.get_user_input()
        if process_method:
            self.execute(process_method, args, {})
        command_method = getattr(self.current_controller, "command", None)
        if command_method:
            self.commands.append((command_method, args))

    def next(self, *_):
        "Jump to the next state of the application"
        self.run_controller()
        next_controller = self.current_controller.next()
        if next_controller:
            instance = self.factory.make_controller_instance(next_controller)
            self.process_state(instance)
        else:
            self.process_task_queue()

    def previous(self, *_):
        "Jump back to previous controller"
        controller = self.current_controller.previous()
        if controller:
            command = getattr(controller, "command", None)
            if command:
                self.commands.pop()
            instance = self.factory.make_controller_instance(controller)
            self.process_state(instance)
        else:
            self.stop()

    def execute(self, a_callable, args, kwargs):
        "Execute callable"
        a_callable(*args, **kwargs)

    def process_task_queue(self):
        """Execute tasks scheduled in the command list.
        We will inject a last controller for showing progress and reporting
        task completion, and set callbacks for quiting.

        """
        self.current_render.main_window.disable_buttons()
        queue = Queue.Queue()
        processing = ProcessingFacade(queue)
        for func, args in self.commands:
            if isgeneratorfunction(func):
                for f, a, desc in func(*args):
                    processing.add_command(f, args=a, description=desc)
            else:
                processing.add_command(func, args=args,
                        description=func.__doc__)

        def tasks_finished_callback():
            self.current_render.main_window.set_next_button_label("Reboot")
            self.current_render.main_window.set_previous_button_label("Quit")
            self.current_render.main_window.enable_buttons()
            self.current_render.main_window.clear_callbacks()
            self.current_render.main_window.add_next_callback(self.reboot)
            self.current_render.main_window.add_previous_callback(self.stop)
            #self.current_render.main_window.alert("Installation finished")

        processing.add_command(tasks_finished_callback, args=tuple(),
                description="All tasks done")

        Render = type(self.current_render)

        class ProcessingController(object):

            def render(self):
                return Render("Processing tasks", ("Please wait until we "
                        "process the requested operations"), processing)

            def next(self):
                return None

            def previous(self):
                return None

        controller = self.factory.make_controller_instance(ProcessingController)
        self.process_state(controller)
        processing.execute_all()

    def start(self, first_controller):
        "Start the main loop"
        self.process_state(first_controller)
        self.current_render.main_window.run()

    def stop(self, *_):
        "Exit the wizard"
        self.current_render.main_window.stop()

    def reboot(self, *_):
        "Exit the wizard and reboot"
        self.stop()
        os.kill(1, 2)