kete: have a common worker implementation

For the various commands, it is easier if we have a common interface
than just a simple function call.

Each current command runs something before the mainloop is created, and
then something after the mainloop is terminated. Having such a worker
allows us to have only one place where we start the mainloop, meaning
that the interactive prompt will not try to start it more than once,
and most above, will not kill it in the middle of a command while other
commands are still running.
This commit is contained in:
Benjamin Tissoires 2018-01-25 15:55:56 +01:00 committed by Peter Hutterer
parent 8e1bba496f
commit 403eb51ea7
1 changed files with 78 additions and 55 deletions

View File

@ -265,11 +265,47 @@ class TuhiKeteManager(_DBusObject):
pass pass
class Searcher(GObject.Object): class Worker(GObject.Object):
def __init__(self, manager, address=None): """Implements a command to be executed.
Subclasses need to overwrite run() that will be executed
to setup the command (before the mainloop).
Subclass can also implement the stop() method which
will be executed to terminate the command, once the
mainloop has finished.
The variable need_mainloop needs to be set from the
subclass if the command requires the mainloop to be
run from an undetermined amount of time."""
need_mainloop = False
def __init__(self, manager, args=None):
GObject.GObject.__init__(self) GObject.GObject.__init__(self)
self.manager = manager self.manager = manager
self.address = address self._run = self.run
self._stop = self.stop
def run(self):
pass
def stop(self):
pass
def start(self):
self._run()
if self.need_mainloop:
self.manager.run()
self._stop()
class Searcher(Worker):
need_mainloop = True
def __init__(self, manager, args):
super(Searcher, self).__init__(manager)
self.address = args.address
self.is_pairing = False self.is_pairing = False
def run(self): def run(self):
@ -277,27 +313,26 @@ class Searcher(GObject.Object):
logger.error('Another client is already searching') logger.error('Another client is already searching')
return return
s1 = self.manager.connect('notify::searching', self._on_notify_search) self.s1 = self.manager.connect('notify::searching', self._on_notify_search)
s2 = self.manager.connect('pairable-device', self._on_pairable_device) self.s2 = self.manager.connect('pairable-device', self._on_pairable_device)
self.manager.start_search() self.manager.start_search()
logger.debug('Started searching') logger.debug('Started searching')
for d in self.manager.devices: for d in self.manager.devices:
self._on_pairable_device(self.manager, d) self._on_pairable_device(self.manager, d)
self.manager.run() def stop(self):
if self.manager.searching: if self.manager.searching:
logger.debug('Stopping search') logger.debug('Stopping search')
self.manager.stop_search() self.manager.stop_search()
self.manager.disconnect(s1) self.manager.disconnect(self.s1)
self.manager.disconnect(s2) self.manager.disconnect(self.s2)
def _on_notify_search(self, manager, pspec): def _on_notify_search(self, manager, pspec):
if not manager.searching: if not manager.searching:
logger.info('Search cancelled') logger.info('Search cancelled')
if not self.is_pairing: if not self.is_pairing:
self.manager.quit() self.stop()
def _on_pairable_device(self, manager, device): def _on_pairable_device(self, manager, device):
print('Pairable device: {}'.format(device)) print('Pairable device: {}'.format(device))
@ -318,18 +353,20 @@ class Searcher(GObject.Object):
device.pair() device.pair()
class Listener(GObject.Object): class Listener(Worker):
def __init__(self, manager, address): need_mainloop = True
GObject.GObject.__init__(self)
def __init__(self, manager, args):
super(Listener, self).__init__(manager)
self.manager = manager
self.device = None self.device = None
for d in manager.devices: for d in manager.devices:
if d.address == address: if d.address == args.address:
self.device = d self.device = d
break break
else: else:
logger.error("{}: device not found".format(address)) logger.error("{}: device not found".format(args.address))
# FIXME: this should be an exception
return return
def run(self): def run(self):
@ -344,16 +381,16 @@ class Listener(GObject.Object):
return return
logger.debug("{}: starting listening".format(self.device)) logger.debug("{}: starting listening".format(self.device))
s1 = self.device.connect('notify::listening', self._on_device_listening) self.s1 = self.device.connect('notify::listening', self._on_device_listening)
s2 = self.device.connect('notify::drawings-available', self._on_drawings_available) self.s2 = self.device.connect('notify::drawings-available', self._on_drawings_available)
self.device.start_listening() self.device.start_listening()
self.manager.run() def stop(self):
logger.debug("{}: stopping listening".format(self.device)) logger.debug("{}: stopping listening".format(self.device))
try: try:
self.device.stop_listening() self.device.stop_listening()
self.device.disconnect(s1) self.device.disconnect(self.s1)
self.device.disconnect(s2) self.device.disconnect(self.s2)
except GLib.Error as e: except GLib.Error as e:
if (e.domain != 'g-dbus-error-quark' or if (e.domain != 'g-dbus-error-quark' or
e.code != Gio.IOErrorEnum.EXISTS or e.code != Gio.IOErrorEnum.EXISTS or
@ -364,8 +401,7 @@ class Listener(GObject.Object):
if self.device.listening: if self.device.listening:
return return
logger.info('{}: Listening stopped, exiting'.format(device)) logger.info('{}: Listening stopped'.format(device))
self.manager.quit()
def _on_drawings_available(self, device, pspec): def _on_drawings_available(self, device, pspec):
self._log_drawings_available(device) self._log_drawings_available(device)
@ -375,12 +411,13 @@ class Listener(GObject.Object):
logger.info('{}: drawings available: {}'.format(device, s)) logger.info('{}: drawings available: {}'.format(device, s))
class Fetcher(GObject.Object): class Fetcher(Worker):
def __init__(self, manager, address, index): def __init__(self, manager, args):
GObject.GObject.__init__(self) super(Fetcher, self).__init__(manager)
self.manager = manager
self.device = None self.device = None
self.indices = None self.indices = None
address = args.address
index = args.index
for d in manager.devices: for d in manager.devices:
if d.address == address: if d.address == address:
@ -436,31 +473,16 @@ class Fetcher(GObject.Object):
svg.save() svg.save()
def print_device(d): class Printer(Worker):
print('{}: {}'.format(d.address, d.name)) def run(self):
logger.debug('Listing available devices:')
for d in self.manager.devices:
def cmd_list(manager, args): print(d)
logger.debug('Listing available devices:')
for d in manager.devices:
print_device(d)
def cmd_pair(manager, args):
Searcher(manager, args.address).run()
def cmd_listen(manager, args):
Listener(manager, args.address).run()
def cmd_fetch(manager, args):
Fetcher(manager, args.address, args.index).run()
def parse_list(parser): def parse_list(parser):
sub = parser.add_parser('list', help='list known devices') sub = parser.add_parser('list', help='list known devices')
sub.set_defaults(func=cmd_list) sub.set_defaults(worker=Printer)
def parse_pair(parser): def parse_pair(parser):
@ -469,7 +491,7 @@ def parse_pair(parser):
type=TuhiKeteDevice.is_device_address, type=TuhiKeteDevice.is_device_address,
nargs='?', default=None, nargs='?', default=None,
help='the address of the device to pair') help='the address of the device to pair')
sub.set_defaults(func=cmd_pair) sub.set_defaults(worker=Searcher)
def parse_listen(parser): def parse_listen(parser):
@ -478,7 +500,7 @@ def parse_listen(parser):
type=TuhiKeteDevice.is_device_address, type=TuhiKeteDevice.is_device_address,
default=None, default=None,
help='the address of the device to listen to') help='the address of the device to listen to')
sub.set_defaults(func=cmd_listen) sub.set_defaults(worker=Listener)
def parse_fetch(parser): def parse_fetch(parser):
@ -490,7 +512,7 @@ def parse_fetch(parser):
sub.add_argument('index', metavar='[<index>|all]', type=str, sub.add_argument('index', metavar='[<index>|all]', type=str,
default=None, default=None,
help='the index of the drawing to fetch or a literal "all"') help='the index of the drawing to fetch or a literal "all"')
sub.set_defaults(func=cmd_fetch) sub.set_defaults(worker=Fetcher)
def parse(args): def parse(args):
@ -515,12 +537,13 @@ def main(args):
if args.verbose: if args.verbose:
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
if not hasattr(args, 'worker'):
args.worker = Printer
try: try:
with TuhiKeteManager() as mgr: with TuhiKeteManager() as mgr:
if not hasattr(args, 'func'): worker = args.worker(mgr, args)
args.func = cmd_list worker.start()
args.func(mgr, args)
except DBusError as e: except DBusError as e:
logger.error(e.message) logger.error(e.message)