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