def testFailAndStop(self): def foo(x): sc.stop() raise TestException(x) sc = ScheduledCall(foo, "bar") return self.assertFailure(sc.start(SimpleSchedule(0.1)), TestException)
def set_schedule(self, schedule): "Set schedule and start playing" self.schedule = schedule self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self) self.service.on_set_schedule(schedule) if not self.playing_program: self.resume_playback()
def testStopAtOnceLater(self): # Ensure that even when LoopingCall.stop() is called from a # reactor callback, it still prevents any subsequent calls. d = defer.Deferred() def foo(): d.errback( failure.DefaultException( "This task also should never get called.")) self._sc = ScheduledCall(foo) self._sc.start(SimpleSchedule(1)) reactor.callLater(0, self._callback_for_testStopAtOnceLater, d) return d
def testStopAtOnceLater(self): # Ensure that even when LoopingCall.stop() is called from a # reactor callback, it still prevents any subsequent calls. d = defer.Deferred() def foo(): d.errback(failure.DefaultException( "This task also should never get called.")) self._sc = ScheduledCall(foo) self._sc.start(SimpleSchedule(1)) reactor.callLater(0, self._callback_for_testStopAtOnceLater, d) return d
class ReactorLoopTestCase(unittest.TestCase): # Slightly inferior tests which exercise interactions with an actual # reactor. def testFailure(self): def foo(x): raise TestException(x) sc = ScheduledCall(foo, "bar") return self.assertFailure(sc.start(SimpleSchedule(0.1)), TestException) def testFailAndStop(self): def foo(x): sc.stop() raise TestException(x) sc = ScheduledCall(foo, "bar") return self.assertFailure(sc.start(SimpleSchedule(0.1)), TestException) def testStopAtOnceLater(self): # Ensure that even when LoopingCall.stop() is called from a # reactor callback, it still prevents any subsequent calls. d = defer.Deferred() def foo(): d.errback(failure.DefaultException( "This task also should never get called.")) self._sc = ScheduledCall(foo) self._sc.start(SimpleSchedule(1)) reactor.callLater(0, self._callback_for_testStopAtOnceLater, d) return d def _callback_for_testStopAtOnceLater(self, d): self._sc.stop() reactor.callLater(0, d.callback, "success") def testWaitDeferred(self): # Tests if the callable isn't scheduled again before the returned # deferred has fired. timings = [0.2, 0.8] clock = task.Clock() def foo(): d = defer.Deferred() d.addCallback(lambda _: sc.stop()) clock.callLater(1, d.callback, None) return d sc = TestableScheduledCall(clock, foo) d = sc.start(SimpleSchedule(0.2)) clock.advance(0.2) clock.pump(timings) self.failIf(clock.calls) def testFailurePropagation(self): # Tests if the failure of the errback of the deferred returned by the # callable is propagated to the sc errback. # # To make sure this test does not hang trial when LoopingCall does not # wait for the callable's deferred, it also checks there are no # calls in the clock's callLater queue. timings = [3] clock = task.Clock() def foo(): d = defer.Deferred() clock.callLater(0.3, d.errback, TestException()) return d sc = TestableScheduledCall(clock, foo) d = sc.start(SimpleSchedule(1)) clock.pump(timings) self.assertFailure(d, TestException) clock.pump(timings) self.failIf(clock.calls) return d
class Playout(object): def __init__(self, service, player_class=None): if player_class is None: player_class = configuration.player_class self.player = _get_class(player_class)(LOOP_FILENAME) self.random_provider = jukebox.RandomProvider() self.service = service self.service.playout = self self.schedule = None self.next_program = None self.playing_program = None # A reference to the timed callback which aborts programs self.duration_call = None self.delayed_start_call = None # Still filename, if being displayed self.still = None # Temporary stack for sequence of videos before going to on_idle self.on_end_call_stack=[] def set_schedule(self, schedule): "Set schedule and start playing" self.schedule = schedule self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self) self.service.on_set_schedule(schedule) if not self.playing_program: self.resume_playback() def resume_current_program(self): current_program = self.playing_program if current_program: self.cue_program(current_program, current_program.seconds_since_playback()) def resume_playback(self): # TODO: Rename to resume_schedule current_program = self.schedule.get_current_program() if current_program: self.cue_program(current_program, current_program.seconds_since_playback()) else: self.on_idle() def cue_next_program(self): """Starts the next program Set the next program with Playout.set_next_program""" if self.next_program: self.cue_program(self.next_program) self.next_program = None def _cancel_pending_calls(self): """Stops any pending calls from starting in the future (and disrupt playback) This is used whenever a program is started and new calls will be registered """ if self.duration_call and not self.duration_call.called: self.duration_call.cancel() self.duration_call = None if self.delayed_start_call and not self.delayed_start_call.called: self.delayed_start_call.cancel() self.delayed_start_call = None def cue_program(self, program, resume_offset=0): """Starts the given program""" self._cancel_pending_calls() duration_text = "Unknown" if program.playback_duration == float("inf"): duration_text = "Infinite" elif program.playback_duration: duration_text = "%i:%02i" % (program.playback_duration / 60, program.playback_duration % 60) # Schedule next call delta = program.playback_duration-resume_offset if delta <= 0.0: self.player.pause_screen() else: self.duration_call = reactor.callLater(delta, self.on_program_ended) logging.info("Playback video_id=%i, offset=%s+%ss name='%s' duration=%s" % ( program.media_id, str(program.playback_offset), str(resume_offset), program.title, duration_text)) # Start playback self.player.play_program(program, resume_offset=resume_offset) self.service.on_playback_started(program) self.playing_program = program def set_next_program(self, program): self.next_program = program self.service.on_set_next_program(program) if program: logging.info("Next scheduled video_id=%i @ %s" % ( program.media_id, program.program_start)) else: logging.warning("Scheduled nothing") # ISchedule.getDelayForNext def getDelayForNext(self): # Queue next program = self.schedule.get_next_program() if program == None: self.scheduler_task.stop() self.set_next_program(None) # This will actually call cue_next_program once more. logging.warning("Program schedule empty") return 0.0 self.set_next_program(program) return program.seconds_until_playback() def stop_schedule(self): if self.scheduler_task and self.scheduler_task.running: self.scheduler_task.stop() #self.set_next_program(None) def start_schedule(self): self.stop_schedule() self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self) def show_still(self, filename="stills/tekniskeprover.png"): self._cancel_pending_calls() self.stop_schedule() self.player.show_still(filename) self.service.on_still(filename) logging.info("Show still: %s", filename) def cancel_still(self): logging.info("Cancel still") self.service.on_still("") self.start_schedule() # TODO: resume_current_program? self.resume_playback() def on_program_ended(self): """ try: logging.debug("Video '%s' #%i ended with %.1fs left. " % ( self.playing_program.title, self.playing_program.media_id, self.player.seconds_until_end_of_playing_video()) ) pass # TODO: Add proper exception/exceptionlogging except: logging.warning("Excepted while trying to log on_program_ended") """ if self.on_end_call_stack: func = self.on_end_call_stack.pop(0) func() else: self.on_idle() def play_jukebox(self): logging.info("Jukebox playback start") program = self.schedule.new_program() limit = 90*60 # 90 minutes long programs max if self.next_program: limit = min(limit, self.next_program.seconds_until_playback()) video = self.random_provider.get_random_video(limit) program.set_program( media_id=video["media_id"], program_start=clock.now(), playback_duration=video["duration"], title=video["name"]) self.cue_program(program) def play_ident(self): logging.info("Ident playback start") program = self.schedule.new_program() program.set_program( media_id=-1, program_start=clock.now(), playback_duration=IDENT_LENGTH, title="Frikanalen Vignett", filename=IDENT_FILENAME) self.cue_program(program) def on_idle(self): time_until_next = float("inf") if self.next_program: time_until_next = self.next_program.seconds_until_playback() # The rules. use_jukebox = configuration.jukebox use_jukebox &= time_until_next > (120+IDENT_LENGTH) use_jukebox &= self.random_provider.enough_room(time_until_next) if use_jukebox: loop_length = 12.0 PAUSE_LENGTH = IDENT_LENGTH+loop_length logging.info("Pause before jukebox: %.1fs" % PAUSE_LENGTH) program = self.schedule.new_program() program.set_program(-1, program_start=clock.now(), playback_duration=loop_length, title="Jukebox pause screen", filename=LOOP_FILENAME, loop=True) self.cue_program(program) self.on_end_call_stack.append(self.play_ident) self.on_end_call_stack.append(self.play_jukebox) elif time_until_next >= 12+IDENT_LENGTH: logging.info("Pause idle: %.1fs" % time_until_next) PAUSE_LENGTH = time_until_next program = self.schedule.new_program() program.set_program(-1, program_start=clock.now(), playback_duration=time_until_next-IDENT_LENGTH, title="Pause screen", filename=LOOP_FILENAME, loop=True) self.cue_program(program) self.on_end_call_stack.append(self.play_ident) else: logging.info("Short idle: %.1fs" % time_until_next) # Show pausescreen program = self.schedule.new_program() t = None if self.next_program: t = self.next_program.seconds_until_playback() program.set_program(-1, program_start=clock.now(), playback_duration=t, title="Pause screen", filename=LOOP_FILENAME, loop=True) #self.cue_program(program) # TODO: Doesn't handle looping self.player.pause_screen() self.playing_program = program self.service.on_playback_started(program)
def start_schedule(self): self.stop_schedule() self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self)
def testDefaultClock(self): """ L{LoopingCall}'s default clock should be the reactor. """ call = ScheduledCall(lambda: None) self.assertEqual(call.clock, reactor)
class Playout(object): zope.interface.implements(ISchedule) def __init__(self, service, player_class=None): if player_class is None: player_class = configuration.player_class self.player = _get_class(player_class)(LOOP_FILENAME) self.random_provider = jukebox.RandomProvider() self.service = service self.service.playout = self self.schedule = None self.next_program = None self.playing_program = None # A reference to the timed callback which aborts programs self.duration_call = None self.delayed_start_call = None # Still filename, if being displayed self.still = None # Temporary stack for sequence of videos before going to on_idle self.on_end_call_stack=[] def set_schedule(self, schedule): "Set schedule and start playing" self.schedule = schedule self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self) self.service.on_set_schedule(schedule) if not self.playing_program: self.resume_playback() def resume_current_program(self): current_program = self.playing_program if current_program: self.cue_program(current_program, current_program.seconds_since_playback()) def resume_playback(self): # TODO: Rename to resume_schedule current_program = self.schedule.get_current_program() if current_program: self.cue_program(current_program, current_program.seconds_since_playback()) else: self.on_idle() def cue_next_program(self): """Starts the next program Set the next program with Playout.set_next_program""" if self.next_program: self.cue_program(self.next_program) self.next_program = None def _cancel_pending_calls(self): """Stops any pending calls from starting in the future (and disrupt playback) This is used whenever a program is started and new calls will be registered """ if self.duration_call and not self.duration_call.called: self.duration_call.cancel() self.duration_call = None if self.delayed_start_call and not self.delayed_start_call.called: self.delayed_start_call.cancel() self.delayed_start_call = None def cue_program(self, program, resume_offset=0): """Starts the given program""" self._cancel_pending_calls() duration_text = "Unknown" if program.playback_duration == float("inf"): duration_text = "Infinite" elif program.playback_duration: duration_text = "%i:%02i" % (program.playback_duration / 60, program.playback_duration % 60) # Schedule next call delta = program.playback_duration-resume_offset if delta <= 0.0: self.player.pause_screen() else: self.duration_call = reactor.callLater(delta, self.on_program_ended) logging.info("Playback video_id=%i, offset=%s+%ss name='%s' duration=%s" % ( program.media_id, str(program.playback_offset), str(resume_offset), program.title, duration_text)) # Start playback self.player.play_program(program, resume_offset=resume_offset) self.service.on_playback_started(program) self.playing_program = program def set_next_program(self, program): self.next_program = program self.service.on_set_next_program(program) if program: logging.info("Next scheduled video_id=%i @ %s" % ( program.media_id, program.program_start)) else: logging.warning("Scheduled nothing") # ISchedule.getDelayForNext def getDelayForNext(self): # Queue next program = self.schedule.get_next_program() if program == None: self.scheduler_task.stop() self.set_next_program(None) # This will actually call cue_next_program once more. logging.warning("Program schedule empty") return 0.0 self.set_next_program(program) return program.seconds_until_playback() def stop_schedule(self): if self.scheduler_task and self.scheduler_task.running: self.scheduler_task.stop() #self.set_next_program(None) def start_schedule(self): self.stop_schedule() self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self) def show_still(self, filename="stills/tekniskeprover.png"): self._cancel_pending_calls() self.stop_schedule() self.player.show_still(filename) self.service.on_still(filename) logging.info("Show still: %s", filename) def cancel_still(self): logging.info("Cancel still") self.service.on_still("") self.start_schedule() # TODO: resume_current_program? self.resume_playback() def on_program_ended(self): """ try: logging.debug("Video '%s' #%i ended with %.1fs left. " % ( self.playing_program.title, self.playing_program.media_id, self.player.seconds_until_end_of_playing_video()) ) pass # TODO: Add proper exception/exceptionlogging except: logging.warning("Excepted while trying to log on_program_ended") """ if self.on_end_call_stack: func = self.on_end_call_stack.pop(0) func() else: self.on_idle() def play_jukebox(self): logging.info("Jukebox playback start") program = self.schedule.new_program() limit = 90*60 # 90 minutes long programs max if self.next_program: limit = min(limit, self.next_program.seconds_until_playback()) video = self.random_provider.get_random_video(limit) program.set_program( media_id=video["media_id"], program_start=clock.now(), playback_duration=video["duration"], title=video["name"]) self.cue_program(program) def play_ident(self): logging.info("Ident playback start") program = self.schedule.new_program() program.set_program( media_id=-1, program_start=clock.now(), playback_duration=IDENT_LENGTH, title="Frikanalen Vignett", filename=IDENT_FILENAME) self.cue_program(program) def on_idle(self): time_until_next = float("inf") if self.next_program: time_until_next = self.next_program.seconds_until_playback() # The rules. use_jukebox = configuration.jukebox use_jukebox &= time_until_next > (120+IDENT_LENGTH) use_jukebox &= self.random_provider.enough_room(time_until_next) if use_jukebox: loop_length = 12.0 PAUSE_LENGTH = IDENT_LENGTH+loop_length logging.info("Pause before jukebox: %.1fs" % PAUSE_LENGTH) program = self.schedule.new_program() program.set_program(-1, program_start=clock.now(), playback_duration=loop_length, title="Jukebox pause screen", filename=LOOP_FILENAME, loop=True) self.cue_program(program) self.on_end_call_stack.append(self.play_ident) self.on_end_call_stack.append(self.play_jukebox) elif time_until_next >= 12+IDENT_LENGTH: logging.info("Pause idle: %.1fs" % time_until_next) PAUSE_LENGTH = time_until_next program = self.schedule.new_program() program.set_program(-1, program_start=clock.now(), playback_duration=time_until_next-IDENT_LENGTH, title="Pause screen", filename=LOOP_FILENAME, loop=True) self.cue_program(program) self.on_end_call_stack.append(self.play_ident) else: logging.info("Short idle: %.1fs" % time_until_next) # Show pausescreen program = self.schedule.new_program() t = None if self.next_program: t = self.next_program.seconds_until_playback() program.set_program(-1, program_start=clock.now(), playback_duration=t, title="Pause screen", filename=LOOP_FILENAME, loop=True) #self.cue_program(program) # TODO: Doesn't handle looping self.player.pause_screen() self.playing_program = program self.service.on_playback_started(program)
class ReactorLoopTestCase(unittest.TestCase): # Slightly inferior tests which exercise interactions with an actual # reactor. def testFailure(self): def foo(x): raise TestException(x) sc = ScheduledCall(foo, "bar") return self.assertFailure(sc.start(SimpleSchedule(0.1)), TestException) def testFailAndStop(self): def foo(x): sc.stop() raise TestException(x) sc = ScheduledCall(foo, "bar") return self.assertFailure(sc.start(SimpleSchedule(0.1)), TestException) def testStopAtOnceLater(self): # Ensure that even when LoopingCall.stop() is called from a # reactor callback, it still prevents any subsequent calls. d = defer.Deferred() def foo(): d.errback( failure.DefaultException( "This task also should never get called.")) self._sc = ScheduledCall(foo) self._sc.start(SimpleSchedule(1)) reactor.callLater(0, self._callback_for_testStopAtOnceLater, d) return d def _callback_for_testStopAtOnceLater(self, d): self._sc.stop() reactor.callLater(0, d.callback, "success") def testWaitDeferred(self): # Tests if the callable isn't scheduled again before the returned # deferred has fired. timings = [0.2, 0.8] clock = task.Clock() def foo(): d = defer.Deferred() d.addCallback(lambda _: sc.stop()) clock.callLater(1, d.callback, None) return d sc = TestableScheduledCall(clock, foo) d = sc.start(SimpleSchedule(0.2)) clock.advance(0.2) clock.pump(timings) self.failIf(clock.calls) def testFailurePropagation(self): # Tests if the failure of the errback of the deferred returned by the # callable is propagated to the sc errback. # # To make sure this test does not hang trial when LoopingCall does not # wait for the callable's deferred, it also checks there are no # calls in the clock's callLater queue. timings = [3] clock = task.Clock() def foo(): d = defer.Deferred() clock.callLater(0.3, d.errback, TestException()) return d sc = TestableScheduledCall(clock, foo) d = sc.start(SimpleSchedule(1)) clock.pump(timings) self.assertFailure(d, TestException) clock.pump(timings) self.failIf(clock.calls) return d
def testBadDelay(self): sc = ScheduledCall(lambda: None) self.assertRaises(TypeError, sc.start, -1)
class Playout(object): # ISchedule.getDelayForNext # This is called by ScheduledCall to determine the number # of seconds until the next time self.cue_next_program is # invoked def getDelayForNext(self): # Queue next next_program = self.schedule.get_next_program() if next_program == None: self.scheduler_task.stop() self.set_next_program(None) # This will actually call cue_next_program once more. logging.warning("Program schedule empty") return 0.0 self.set_next_program(next_program) return next_program.seconds_until_playback() def __init__(self, player_class=None): if player_class is None: player_class = configuration.playerClass self.player = _get_class(player_class)(LOOP_FILENAME) self.random_provider = jukebox.RandomProvider() self.schedule = None self.next_program = None self.playing_program = None # The video_end_timeout is invoked at a video's end time because # the current architecture does not receive media end messages # from CasparCG self.video_end_timeout = None # Temporary stack for sequence of videos before going to on_idle self.programEndCallbackStack = [] def load_schedule(self): "Set schedule and start playing" logging.debug("Attaching schedule") self.schedule = schedulestore.load(clock.now().date(), numDays=1) self.start_schedule() self.self_pending_refresh = reactor.callLater(60 * 60, self.load_schedule) if not self.playing_program: self.resume_schedule() def resume_current_program(self): current_program = self.schedule.get_current_program() logging.debug("in resume_current_program; current_program=%s", current_program) if current_program: logging.debug( "resume_current_program invoking cue_program({})".format( current_program)) self.cue_program(current_program, current_program.seconds_since_scheduled_start()) def resume_schedule(self): """Resumes the currently scheduled program if there is one, otherwise it goes into idle mode""" if self.schedule.get_current_program(): self.resume_current_program() else: self.on_idle() def cue_next_program(self): """Starts the next program Set the next program with Playout.set_next_program""" logging.debug("In cue_next_program - self.next_program=%s", self.next_program) if self.next_program: self.cue_program(self.next_program) self.next_program = None def _clear_timeouts(self): """Clears any previously set timeouts""" logging.debug("in _clear_timeouts") if self.video_end_timeout and not self.video_end_timeout.called: logging.debug("clearing timeouts") self.video_end_timeout.cancel() self.video_end_timeout = None else: logging.debug("not clearing timeout") def _fmt_duration(self, duration): if duration == float("inf"): return "Infinite" return "%i:%02i" % (duration / 60, duration % 60) def cue_program(self, program, seekSeconds=0): logging.debug("In cue_program, program={}, offset={}".format( program, seekSeconds)) logging.debug("schedule.programs = %s", self.schedule.programs) # Clear any timeouts set for the previous program self._clear_timeouts() if program.playback_duration != float("inf"): secondsUntilEnd = program.playback_duration - seekSeconds if secondsUntilEnd <= 0.0: errormsg = """secondsUntilEnd == {} for program {}""" logging.error(errormsg.format(secondsUntilEnd, program)) else: errormsg = """secondsUntilEnd == {} for program {}""" logging.debug(errormsg.format(secondsUntilEnd, program)) self.video_end_timeout = reactor.callLater( secondsUntilEnd, self.on_program_ended) if seekSeconds > 0: logging.info("Playing program {}, seeking {}s".format( program, seekSeconds)) else: logging.info("Playing program {}".format(program)) # Start playback self.player.play_program(program, resume_offset=seekSeconds) self.playing_program = program def set_next_program(self, program): self.next_program = program if program: logging.info("Next scheduled video_id=%i @ %s" % (program.media_id, program.program_start)) else: logging.warning("Scheduled nothing") def stop_schedule(self): logging.info("Stopping schedule") try: if self.scheduler_task.running: self.scheduler_task.stop() except AttributeError: pass def start_schedule(self): logging.info("Starting schedule (will stop the old one if it exists)") self.stop_schedule() self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self) def on_program_ended(self): logging.debug("on_program_ended invoked") if self.programEndCallbackStack: self.programEndCallbackStack.pop(0)() else: self.on_idle() def play_jukebox(self): logging.info("Jukebox playback start") limit = 90 * 60 # 90 minutes long programs max if self.next_program: limit = min(limit, self.next_program.seconds_until_playback()) video = self.random_provider.get_random_video(limit) program = Program(media_id=video["media_id"], program_start=clock.now(), playback_duration=video["duration"], title=video["name"]) logging.debug("jukebox invoking cue_program({})".format(program)) self.cue_program(program) def play_ident(self): logging.info("Ident playback start [skipped]") program = Program( media_id=-1, program_start=clock.now(), playback_duration=2, #IDENT_LENGTH, title="Frikanalen Vignett", filename=IDENT_FILENAME) logging.debug("play_ident invoking cue_program({})".format(program)) self.cue_program(program) def on_idle(self): time_until_next = float("inf") if self.next_program: time_until_next = self.next_program.seconds_until_playback() logging.info("on_idle is invoked, with %.1fs left until next item" %\ (time_until_next,)) # The rules. use_jukebox = configuration.useJukebox use_jukebox &= time_until_next > (120 + IDENT_LENGTH) use_jukebox &= self.random_provider.enough_room(time_until_next) if use_jukebox: loop_length = 12.0 #PAUSE_LENGTH = IDENT_LENGTH+loop_length PAUSE_LENGTH = 2 #loop_length logging.info("Pause before jukebox: %.1fs" % PAUSE_LENGTH) program = Program( media_id=-1, program_start=clock.now(), playback_duration=1, #loop_length, title="Jukebox pause screen", filename=LOOP_FILENAME, loop=True) logging.debug("on_idle invoking cue_program({})".format(program)) self.cue_program(program) self.programEndCallbackStack.append(self.play_ident) self.programEndCallbackStack.append(self.play_jukebox) elif time_until_next >= 12 + IDENT_LENGTH: logging.info("Pause idle: %.1fs" % time_until_next) PAUSE_LENGTH = time_until_next program = Program( media_id=-1, program_start=clock.now(), playback_duration=time_until_next - IDENT_LENGTH, title="Pause screen", filename='[HTML]" "https://frikanalen.no/graphics', loop=True) logging.debug("on_idle invoking cue_program({})".format(program)) self.cue_program(program) self.programEndCallbackStack.append(self.play_ident) else: logging.info("Short idle: %.1fs" % time_until_next) # Show pausescreen t = None if self.next_program: t = self.next_program.seconds_until_playback() program = Program(media_id=-1, program_start=clock.now(), playback_duration=t, title="Pause screen", filename=LOOP_FILENAME, loop=True) #self.cue_program(program) # TODO: Doesn't handle looping self.player.pause_screen() self.playing_program = program
def start_schedule(self): logging.info("Starting schedule (will stop the old one if it exists)") self.stop_schedule() self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self)
def __init__(self, clock, *a, **kw): ScheduledCall.__init__(self, *a, **kw) self.clock = clock
def __init__(self, clock, *a, **kw): ScheduledCall.__init__(self,*a, **kw) self.clock = clock