1# Copyright 2006 Joe Wreschnig, Alexandre Passos
2#           2014 Christoph Reiter
3#           2016 Nick Boultbee
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9
10"""Manage a pool of routines using Python iterators."""
11
12from gi.repository import GLib
13
14
15class _Routine(object):
16
17    def __init__(self, pool, func, funcid, priority, timeout, args, kwargs):
18        self.priority = priority
19        self.timeout = timeout
20        self._source_id = None
21
22        def wrap(func, funcid, args, kwargs):
23            for value in func(*args, **kwargs):
24                yield True
25            pool.remove(funcid)
26            yield False
27
28        f = wrap(func, funcid, args, kwargs)
29        self.source_func = f.__next__
30
31    @property
32    def paused(self):
33        """If the routine is currently running"""
34
35        return self._source_id is None
36
37    def step(self):
38        """Raises StopIteration if the routine has nothing more to do"""
39
40        return self.source_func()
41
42    def resume(self):
43        """Resume, if already running do nothing"""
44
45        if not self.paused:
46            return
47
48        if self.timeout:
49            self._source_id = GLib.timeout_add(
50                self.timeout, self.source_func, priority=self.priority)
51        else:
52            self._source_id = GLib.idle_add(
53                self.source_func, priority=self.priority)
54
55    def pause(self):
56        """Pause, if already paused, do nothing"""
57
58        if self.paused:
59            return
60
61        GLib.source_remove(self._source_id)
62        self._source_id = None
63
64
65class CoPool(object):
66
67    def __init__(self):
68        self.__routines = {}
69
70    def add(self, func, *args, **kwargs):
71        """Register a routine to run in GLib main loop.
72
73        func should be a function that returns a Python iterator (e.g.
74        generator) that provides values until it should stop being called.
75
76        Optional Keyword Arguments:
77        priority -- priority to run at (default GLib.PRIORITY_LOW)
78        funcid -- mutex/removal identifier for this function
79        timeout -- use timeout_add (with given timeout) instead of idle_add
80                   (in milliseconds)
81
82        Only one function with the same funcid can be running at once.
83        Starting a new function with the same ID will stop the old one. If
84        no funcid is given, the function itself is used. The funcid must
85        be usable as a hash key.
86        """
87
88        funcid = kwargs.pop("funcid", func)
89        if funcid in self.__routines:
90            remove(funcid)
91
92        priority = kwargs.pop("priority", GLib.PRIORITY_LOW)
93        timeout = kwargs.pop("timeout", None)
94
95        routine = _Routine(self, func, funcid, priority, timeout, args, kwargs)
96        self.__routines[funcid] = routine
97        routine.resume()
98
99    def _get(self, funcid):
100        if funcid in self.__routines:
101            return self.__routines[funcid]
102        raise ValueError("no pooled routine %r" % funcid)
103
104    def remove(self, funcid):
105        """Stop a registered routine."""
106
107        routine = self._get(funcid)
108        routine.pause()
109        del self.__routines[funcid]
110
111    def remove_all(self):
112        """Stop all running routines."""
113
114        for funcid in list(self.__routines.keys()):
115            self.remove(funcid)
116
117    def pause(self, funcid):
118        """Temporarily pause a registered routine."""
119
120        routine = self._get(funcid)
121        routine.pause()
122
123    def pause_all(self):
124        """Temporarily pause all registered routines."""
125
126        for funcid in self.__routines.keys():
127            self.pause(funcid)
128
129    def resume(self, funcid):
130        """Resume a paused routine."""
131
132        routine = self._get(funcid)
133        routine.resume()
134
135    def step(self, funcid):
136        """Force this function to iterate once."""
137
138        routine = self._get(funcid)
139        return routine.step()
140
141
142# global instance
143
144_copool = CoPool()
145
146add = _copool.add
147pause = _copool.pause
148pause_all = _copool.pause_all
149remove = _copool.remove
150remove_all = _copool.remove_all
151resume = _copool.resume
152step = _copool.step
153