summaryrefslogtreecommitdiffstats
path: root/gamechest/stepper.py
blob: b6dd3358f75a722085b03cc6485f2af9580bc698 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!python3

import collections
import copy
from enum import Enum, auto as enum_auto

Progress = collections.namedtuple('Progress', 'step index count percent')
Progress.step.__doc__ = 'name of the current step'
Progress.index.__doc__ = 'current step index out of count steps'
Progress.count.__doc__ = 'total number of steps'
Progress.percent.__doc__ = 'advance in percent of the current step'

class Stepper:
    '''
    Manage a worker processing steps in background and allows polling its
    progress.

    Takes steps as a dict of step_names/step_worker.
    A step_worker is a function generator taking a queue waiting for a stop
    command to abort the step and yielding progress of the step as
    a percentage, and as second argument it takes a GameChestConfig.
    '''

    class Command(Enum):
        STOP = enum_auto()

    def _create_thread(self):
        return threading.Thread(target=self.worker, daemon=True)

    def __init__(self, conf, steps):
        self.conf = conf
        self.thread = None
        self.commandq = queue.Queue(maxsize=10)
        self.progressq = queue.Queue(maxsize=10)
        self.steps = steps
        self.count = len(steps)
        self.steps.__doc__ = 'steps are a key/func-value dict'
        self.thread = self._create_thread()
        self.last_progress = Progress('unknown', 0, self.count, 0)
        self.status = {}

    def worker(self, *args):
        for index, step, step_worker in enumerate(self.steps.items()):
            self.progressq.put(Progress(step, index, self.count, 0))
            for percent in step_worker(self.commandq, self.conf, self.status):
                self.progressq.put(Progress(step, index, self.count, percent))

    def start(self, status):
        if self.is_alive():
            return
        self.status = copy.deepcopy(status)
        self.thread.start()

    def stop(self):
        if not self.is_alive():
            return
        self.commandq.put(self.Command.STOP)
        self.thread.join()
        self.thread = self._create_thread()

    def is_alive(self):
        return self.thread.is_alive()

    def poll(self):
        with contextlib.suppress(queue.Empty):
            progress = self.progress_queue.get_nowait()
            self.last_progress = progress
        return self.last_progress