| Index: third_party/APScheduler/build/lib.linux-x86_64-2.6/apscheduler/scheduler.py
|
| diff --git a/third_party/APScheduler/build/lib.linux-x86_64-2.6/apscheduler/scheduler.py b/third_party/APScheduler/build/lib.linux-x86_64-2.6/apscheduler/scheduler.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cc50f490f72153d60e3b91d7a7d9df30c7195f20
|
| --- /dev/null
|
| +++ b/third_party/APScheduler/build/lib.linux-x86_64-2.6/apscheduler/scheduler.py
|
| @@ -0,0 +1,407 @@
|
| +"""
|
| +This module is the main part of the library, and is the only module that
|
| +regular users should be concerned with.
|
| +"""
|
| +from threading import Thread, Event, Lock
|
| +from datetime import datetime, timedelta
|
| +from logging import getLogger
|
| +import os
|
| +
|
| +from apscheduler.util import time_difference, asbool
|
| +from apscheduler.triggers import DateTrigger, IntervalTrigger, CronTrigger
|
| +
|
| +
|
| +logger = getLogger(__name__)
|
| +
|
| +
|
| +class Job(object):
|
| + """
|
| + Represents a task scheduled in the scheduler.
|
| + """
|
| +
|
| + def __init__(self, trigger, func, args, kwargs):
|
| + self.thread = None
|
| + self.trigger = trigger
|
| + self.func = func
|
| + self.args = args
|
| + self.kwargs = kwargs
|
| + if hasattr(func, '__name__'):
|
| + self.name = func.__name__
|
| + else:
|
| + self.name = str(func)
|
| +
|
| + def run(self):
|
| + """
|
| + Starts the execution of this job in a separate thread.
|
| + """
|
| + if (self.thread and self.thread.isAlive()):
|
| + logger.info('Skipping run of job %s (previously triggered '
|
| + 'instance is still running)', self)
|
| + else:
|
| + self.thread = Thread(target=self.run_in_thread)
|
| + self.thread.setDaemon(False)
|
| + self.thread.start()
|
| +
|
| + def run_in_thread(self):
|
| + """
|
| + Runs the associated callable.
|
| + This method is executed in a dedicated thread.
|
| + """
|
| + try:
|
| + self.func(*self.args, **self.kwargs)
|
| + except:
|
| + logger.exception('Error executing job "%s"', self)
|
| + raise
|
| +
|
| + def __str__(self):
|
| + return '%s: %s' % (self.name, repr(self.trigger))
|
| +
|
| + def __repr__(self):
|
| + return '%s(%s, %s)' % (self.__class__.__name__, self.name,
|
| + repr(self.trigger))
|
| +
|
| +
|
| +class SchedulerShutdownError(Exception):
|
| + """
|
| + Thrown when attempting to use the scheduler after
|
| + it's been shut down.
|
| + """
|
| +
|
| + def __init__(self):
|
| + Exception.__init__(self, 'Scheduler has already been shut down')
|
| +
|
| +
|
| +class SchedulerAlreadyRunningError(Exception):
|
| + """
|
| + Thrown when attempting to start the scheduler, but it's already running.
|
| + """
|
| +
|
| + def __init__(self):
|
| + Exception.__init__(self, 'Scheduler is already running')
|
| +
|
| +
|
| +class Scheduler(object):
|
| + """
|
| + This class is responsible for scheduling jobs and triggering
|
| + their execution.
|
| + """
|
| +
|
| + stopped = False
|
| + thread = None
|
| + misfire_grace_time = 1
|
| + daemonic = True
|
| +
|
| + def __init__(self, **config):
|
| + self.jobs = []
|
| + self.jobs_lock = Lock()
|
| + self.wakeup = Event()
|
| + self.configure(config)
|
| +
|
| + def configure(self, config):
|
| + """
|
| + Updates the configuration with the given options.
|
| + """
|
| + for key, val in config.items():
|
| + if key.startswith('apscheduler.'):
|
| + key = key[12:]
|
| + if key == 'misfire_grace_time':
|
| + self.misfire_grace_time = int(val)
|
| + elif key == 'daemonic':
|
| + self.daemonic = asbool(val)
|
| +
|
| + def start(self):
|
| + """
|
| + Starts the scheduler in a new thread.
|
| + """
|
| + if self.thread and self.thread.isAlive():
|
| + raise SchedulerAlreadyRunningError
|
| +
|
| + self.stopped = False
|
| + self.thread = Thread(target=self.run, name='APScheduler')
|
| + self.thread.setDaemon(self.daemonic)
|
| + self.thread.start()
|
| + logger.info('Scheduler started')
|
| +
|
| + def shutdown(self, timeout=0):
|
| + """
|
| + Shuts down the scheduler and terminates the thread.
|
| + Does not terminate any currently running jobs.
|
| +
|
| + :param timeout: time (in seconds) to wait for the scheduler thread to
|
| + terminate, 0 to wait forever, None to skip waiting
|
| + """
|
| + if self.stopped or not self.thread.isAlive():
|
| + return
|
| +
|
| + logger.info('Scheduler shutting down')
|
| + self.stopped = True
|
| + self.wakeup.set()
|
| + if timeout is not None:
|
| + self.thread.join(timeout)
|
| + self.jobs = []
|
| +
|
| + def cron_schedule(self, year='*', month='*', day='*', week='*',
|
| + day_of_week='*', hour='*', minute='*', second='*',
|
| + args=None, kwargs=None):
|
| + """
|
| + Decorator that causes its host function to be scheduled
|
| + according to the given parameters.
|
| + This decorator does not wrap its host function.
|
| + The scheduled function will be called without any arguments.
|
| + See :meth:`add_cron_job` for more information.
|
| + """
|
| + def inner(func):
|
| + self.add_cron_job(func, year, month, day, week, day_of_week, hour,
|
| + minute, second, args, kwargs)
|
| + return func
|
| + return inner
|
| +
|
| + def interval_schedule(self, weeks=0, days=0, hours=0, minutes=0, seconds=0,
|
| + start_date=None, repeat=0, args=None, kwargs=None):
|
| + """
|
| + Decorator that causes its host function to be scheduled
|
| + for execution on specified intervals.
|
| + This decorator does not wrap its host function.
|
| + The scheduled function will be called without any arguments.
|
| + Note that the default repeat value is 0, which means to repeat forever.
|
| + See :meth:`add_delayed_job` for more information.
|
| + """
|
| + def inner(func):
|
| + self.add_interval_job(func, weeks, days, hours, minutes, seconds,
|
| + start_date, repeat, args, kwargs)
|
| + return func
|
| + return inner
|
| +
|
| + def _add_job(self, trigger, func, args, kwargs):
|
| + """
|
| + Adds a Job to the job list and notifies the scheduler thread.
|
| +
|
| + :param trigger: trigger for the given callable
|
| + :param args: list of positional arguments to call func with
|
| + :param kwargs: dict of keyword arguments to call func with
|
| + :return: the scheduled job
|
| + :rtype: Job
|
| + """
|
| + if self.stopped:
|
| + raise SchedulerShutdownError
|
| + if not hasattr(func, '__call__'):
|
| + raise TypeError('func must be callable')
|
| +
|
| + if args is None:
|
| + args = []
|
| + if kwargs is None:
|
| + kwargs = {}
|
| +
|
| + job = Job(trigger, func, args, kwargs)
|
| + self.jobs_lock.acquire()
|
| + try:
|
| + self.jobs.append(job)
|
| + finally:
|
| + self.jobs_lock.release()
|
| + logger.info('Added job "%s"', job)
|
| +
|
| + # Notify the scheduler about the new job
|
| + self.wakeup.set()
|
| +
|
| + return job
|
| +
|
| + def add_date_job(self, func, date, args=None, kwargs=None):
|
| + """
|
| + Adds a job to be completed on a specific date and time.
|
| +
|
| + :param func: callable to run
|
| + :param args: positional arguments to call func with
|
| + :param kwargs: keyword arguments to call func with
|
| + """
|
| + trigger = DateTrigger(date)
|
| + return self._add_job(trigger, func, args, kwargs)
|
| +
|
| + def add_interval_job(self, func, weeks=0, days=0, hours=0, minutes=0,
|
| + seconds=0, start_date=None, repeat=0, args=None,
|
| + kwargs=None):
|
| + """
|
| + Adds a job to be completed on specified intervals.
|
| +
|
| + :param func: callable to run
|
| + :param weeks: number of weeks to wait
|
| + :param days: number of days to wait
|
| + :param hours: number of hours to wait
|
| + :param minutes: number of minutes to wait
|
| + :param seconds: number of seconds to wait
|
| + :param start_date: when to first execute the job and start the
|
| + counter (default is after the given interval)
|
| + :param repeat: number of times the job will be run (0 = repeat
|
| + indefinitely)
|
| + :param args: list of positional arguments to call func with
|
| + :param kwargs: dict of keyword arguments to call func with
|
| + """
|
| + interval = timedelta(weeks=weeks, days=days, hours=hours,
|
| + minutes=minutes, seconds=seconds)
|
| + trigger = IntervalTrigger(interval, repeat, start_date)
|
| + return self._add_job(trigger, func, args, kwargs)
|
| +
|
| + def add_cron_job(self, func, year='*', month='*', day='*', week='*',
|
| + day_of_week='*', hour='*', minute='*', second='*',
|
| + args=None, kwargs=None):
|
| + """
|
| + Adds a job to be completed on times that match the given expressions.
|
| +
|
| + :param func: callable to run
|
| + :param year: year to run on
|
| + :param month: month to run on (0 = January)
|
| + :param day: day of month to run on
|
| + :param week: week of the year to run on
|
| + :param day_of_week: weekday to run on (0 = Monday)
|
| + :param hour: hour to run on
|
| + :param second: second to run on
|
| + :param args: list of positional arguments to call func with
|
| + :param kwargs: dict of keyword arguments to call func with
|
| + :return: the scheduled job
|
| + :rtype: Job
|
| + """
|
| + trigger = CronTrigger(year=year, month=month, day=day, week=week,
|
| + day_of_week=day_of_week, hour=hour,
|
| + minute=minute, second=second)
|
| + return self._add_job(trigger, func, args, kwargs)
|
| +
|
| + def is_job_active(self, job):
|
| + """
|
| + Determines if the given job is still on the job list.
|
| +
|
| + :return: True if the job is still active, False if not
|
| + """
|
| + self.jobs_lock.acquire()
|
| + try:
|
| + return job in self.jobs
|
| + finally:
|
| + self.jobs_lock.release()
|
| +
|
| + def unschedule_job(self, job):
|
| + """
|
| + Removes a job, preventing it from being fired any more.
|
| + """
|
| + self.jobs_lock.acquire()
|
| + try:
|
| + self.jobs.remove(job)
|
| + finally:
|
| + self.jobs_lock.release()
|
| + logger.info('Removed job "%s"', job)
|
| + self.wakeup.set()
|
| +
|
| + def unschedule_func(self, func):
|
| + """
|
| + Removes all jobs that would execute the given function.
|
| + """
|
| + self.jobs_lock.acquire()
|
| + try:
|
| + remove_list = [job for job in self.jobs if job.func == func]
|
| + for job in remove_list:
|
| + self.jobs.remove(job)
|
| + logger.info('Removed job "%s"', job)
|
| + finally:
|
| + self.jobs_lock.release()
|
| +
|
| + # Have the scheduler calculate a new wakeup time
|
| + self.wakeup.set()
|
| +
|
| + def dump_jobs(self):
|
| + """
|
| + Gives a textual listing of all jobs currently scheduled on this
|
| + scheduler.
|
| +
|
| + :rtype: str
|
| + """
|
| + job_strs = []
|
| + now = datetime.now()
|
| + self.jobs_lock.acquire()
|
| + try:
|
| + for job in self.jobs:
|
| + next_fire_time = job.trigger.get_next_fire_time(now)
|
| + job_str = '%s (next fire time: %s)' % (str(job),
|
| + next_fire_time)
|
| + job_strs.append(job_str)
|
| + finally:
|
| + self.jobs_lock.release()
|
| +
|
| + if job_strs:
|
| + return os.linesep.join(job_strs)
|
| + return 'No jobs currently scheduled.'
|
| +
|
| + def _get_next_wakeup_time(self, now):
|
| + """
|
| + Determines the time of the next job execution, and removes finished
|
| + jobs.
|
| +
|
| + :param now: the result of datetime.now(), generated elsewhere for
|
| + consistency.
|
| + """
|
| + next_wakeup = None
|
| + finished_jobs = []
|
| +
|
| + self.jobs_lock.acquire()
|
| + try:
|
| + for job in self.jobs:
|
| + next_run = job.trigger.get_next_fire_time(now)
|
| + if next_run is None:
|
| + finished_jobs.append(job)
|
| + elif next_run and (next_wakeup is None or \
|
| + next_run < next_wakeup):
|
| + next_wakeup = next_run
|
| +
|
| + # Clear out any finished jobs
|
| + for job in finished_jobs:
|
| + self.jobs.remove(job)
|
| + logger.info('Removed finished job "%s"', job)
|
| + finally:
|
| + self.jobs_lock.release()
|
| +
|
| + return next_wakeup
|
| +
|
| + def _get_current_jobs(self):
|
| + """
|
| + Determines which jobs should be executed right now.
|
| + """
|
| + current_jobs = []
|
| + now = datetime.now()
|
| + start = now - timedelta(seconds=self.misfire_grace_time)
|
| +
|
| + self.jobs_lock.acquire()
|
| + try:
|
| + for job in self.jobs:
|
| + next_run = job.trigger.get_next_fire_time(start)
|
| + if next_run:
|
| + time_diff = time_difference(now, next_run)
|
| + if next_run < now and time_diff <= self.misfire_grace_time:
|
| + current_jobs.append(job)
|
| + finally:
|
| + self.jobs_lock.release()
|
| +
|
| + return current_jobs
|
| +
|
| + def run(self):
|
| + """
|
| + Runs the main loop of the scheduler.
|
| + """
|
| + self.wakeup.clear()
|
| + while not self.stopped:
|
| + # Execute any jobs scheduled to be run right now
|
| + for job in self._get_current_jobs():
|
| + logger.debug('Executing job "%s"', job)
|
| + job.run()
|
| +
|
| + # Figure out when the next job should be run, and
|
| + # adjust the wait time accordingly
|
| + now = datetime.now()
|
| + next_wakeup_time = self._get_next_wakeup_time(now)
|
| +
|
| + # Sleep until the next job is scheduled to be run,
|
| + # or a new job is added, or the scheduler is stopped
|
| + if next_wakeup_time is not None:
|
| + wait_seconds = time_difference(next_wakeup_time, now)
|
| + logger.debug('Next wakeup is due at %s (in %f seconds)',
|
| + next_wakeup_time, wait_seconds)
|
| + self.wakeup.wait(wait_seconds)
|
| + else:
|
| + logger.debug('No jobs; waiting until a job is added')
|
| + self.wakeup.wait()
|
| + self.wakeup.clear()
|
|
|