#!/usr/bin/env python # -*- coding: utf-8 -*- # ***************************************************************************** # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: # Markus Zolliker # # ***************************************************************************** """state machine mixin handles status depending on statemachine state """ from secop.core import BUSY, IDLE, ERROR, Parameter, Command, Done from secop.errors import ProgrammingError from secop.lib.newstatemachine import StateMachine, Retry, Finish, Start, Stop class status_code: """decorator for state methods""" def __init__(self, code, text=None): self.code = code self.text = text def __set_name__(self, owner, name): if not issubclass(owner, HasStates): raise ProgrammingError('when using decorator "status_code", %s must inherit HasStates' % owner.__name__) self.cls = owner self.name = name if 'statusMap' not in owner.__dict__: # we need a copy on each inheritance level owner.statusMap = owner.statusMap.copy() owner.statusMap[name] = self.code, name.replace('_', ' ') if self.text is None else self.text setattr(owner, name, self.func) # replace with original method def __call__(self, func): self.func = func return self class HasStates: status = Parameter() # make sure this is a parameter all_status_changes = False # when true, send also updates for status changes within a cycle _state_machine = None _status = IDLE, '' statusMap = {} def init_state_machine(self, **kwds): self._state_machine = StateMachine( logger=self.log, idle_status=(IDLE, ''), transition=self.state_transition, reset_fast_poll=False, status=(IDLE, ''), **kwds) def initModule(self): super().initModule() self.init_state_machine() def state_transition(self, sm, newstate): """handle status updates""" status = self.get_status(newstate) if sm.next_task: if isinstance(sm.next_task, Stop): if newstate and status is not None: status = status[0], 'stopping (%s)' % status[1] elif newstate: # restart case if status is not None: status = sm.status[0], 'restarting (%s)' % status[1] else: # start case status = self.get_status(sm.next_task.newstate, BUSY) if status: sm.status = status if self.all_status_changes: self.read_status() def get_status(self, statefunc, default_code=None): if statefunc is None: status = self._state_machine.idle_status or (ERROR, 'Finish was returned without final status') else: name = statefunc.__name__ status = self.statusMap.get(name) if status is None and default_code is not None: status = default_code, name.replace('_', ' ') return status def read_status(self): sm = self._state_machine if sm.status == self.status: return Done return sm.status def doPoll(self): super().doPoll() sm = self._state_machine sm.cycle() if sm.statefunc is None: if sm.reset_fast_poll: sm.reset_fast_poll = False self.setFastPoll(False) self.read_status() def on_cleanup(self, sm): if isinstance(sm.cleanup_reason, Exception): return self.on_error(sm) if isinstance(sm.cleanup_reason, Start): return self.on_restart(sm) if isinstance(sm.cleanup_reason, Stop): return self.on_stop(sm) self.log.error('bad cleanup reason %r', sm.cleanup_reason) def on_error(self, sm): self.log.error('handle error %r', sm.cleanup_reason) self.final_status(ERROR, repr(sm.cleanup_reason)) return None def on_restart(self, sm): return None def on_stop(self, sm): return None def start_machine(self, statefunc, fast_poll=True, **kwds): sm = self._state_machine sm.status = self.get_status(statefunc, BUSY) if sm.statefunc: sm.status = sm.status[0], 'restarting' sm.start(statefunc, cleanup=kwds.pop('cleanup', self.on_cleanup), **kwds) self.read_status() if fast_poll: sm.reset_fast_poll = True self.setFastPoll(True) self.pollInfo.trigger(True) # trigger poller def stop_machine(self, stopped_status=(IDLE, 'stopped')): sm = self._state_machine if sm.is_active: sm.idle_status = stopped_status sm.stop() sm.status = self.get_status(sm.statefunc, sm.status[0])[0], 'stopping' self.read_status() self.pollInfo.trigger(True) # trigger poller @Command def stop(self): self.stop_machine() def final_status(self, code=IDLE, text=''): """final status Usage: return self.final_status('IDLE', 'machine idle') """ sm = self._state_machine sm.idle_status = code, text sm.cleanup = None return Finish