diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/leap/bitmask/_version.py | 206 | ||||
-rw-r--r-- | src/leap/bitmask/backend/taskthread.py | 296 |
2 files changed, 305 insertions, 197 deletions
diff --git a/src/leap/bitmask/_version.py b/src/leap/bitmask/_version.py index 412b0c9e..8e2b1c8c 100644 --- a/src/leap/bitmask/_version.py +++ b/src/leap/bitmask/_version.py @@ -1,201 +1,13 @@ -IN_LONG_VERSION_PY = True -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. +# This file was generated by the `freeze_debianver` command in setup.py +# Using 'versioneer.py' (0.7+) from +# revision-control system data, or from the parent directory name of an +# unpacked source archive. Distribution tarballs contain a pre-generated copy +# of this file. -# This file is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) +version_version = '0.9.0rc2' +version_full = 'f4547479fc050f338845f4f546d8dd7c0e4512eb' -# these strings will be replaced by git during git-archive -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - -import subprocess -import sys -import re -import os.path - - -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError: - e = sys.exc_info()[1] - if verbose: - print("unable to run %s" % args[0]) - print(e) - return None - stdout = p.communicate()[0].strip() - if sys.version >= '3': - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %s (error)" % args[0]) - return None - return stdout - - -def get_expanded_variables(versionfile_source): - # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} - try: - for line in open(versionfile_source, "r").readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["full"] = mo.group(1) - except EnvironmentError: - pass - return variables - - -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - for ref in list(refs): - if not re.search(r'\d', ref): - if verbose: - print("discarding '%s', no digits" % ref) - refs.discard(ref) - # Assume all version tags have a digit. git's %d expansion - # behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us - # distinguish between branches and tags. By ignoring refnames - # without digits, we filter out many common branch names like - # "release" and "stabilization", as well as "HEAD" and "master". - if verbose: - print("remaining refs: %s" % ",".join(sorted(refs))) - for ref in sorted(refs): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %s" % r) - return {"version": r, - "full": variables["full"].strip()} - # no suitable tags, so we use the full revision id - if verbose: - print("no suitable tags, using full revision id") - return {"version": variables["full"].strip(), - "full": variables["full"].strip()} - - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. - - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - return {} - - GIT = "git" - if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" % ( - stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope - # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) - - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' " - "doesn't start with prefix '%s'" % - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "" -parentdir_prefix = "bitmask-" -versionfile_source = "src/leap/bitmask/_version.py" - - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = {"refnames": git_refnames, "full": git_full} - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose) - if not ver: - ver = default - return ver +def get_versions(default={}, verbose=False): + return {'version': version_version, 'full': version_full} diff --git a/src/leap/bitmask/backend/taskthread.py b/src/leap/bitmask/backend/taskthread.py new file mode 100644 index 00000000..a734a829 --- /dev/null +++ b/src/leap/bitmask/backend/taskthread.py @@ -0,0 +1,296 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import logging +import threading + +__version__ = '1.4' + + +logger = logging.getLogger(__name__) + + +class TaskInProcessException(BaseException): + pass + + +class TaskThread(threading.Thread): + """ + A thread object that repeats a task. + + Usage example:: + + from taskthread import TaskThread + + import time + + def my_task(*args, **kwargs): + print args, kwargs + + task_thread = TaskThread(my_task) + task_thread.start() + for i in xrange(10): + task_thread.run_task() + task_thread.join_task() + task_thread.join() + + .. note:: If :py:meth:`~TaskThread.run_task` is + invoked while run_task is in progress, + :py:class:`~.TaskInProcessException` will + be raised. + + :param task: + A ``function``. This param is the task to execute when + run_task is called. + :param event: + A ``threading.Event``. This event will be set when run_task + is called. The default value is a new event, but may be + specified for testing purposes. + """ + + daemon = True + ''' + Threads marked as daemon will be terminated. + ''' + def __init__(self, task, event=threading.Event(), + *args, **kwargs): + super(TaskThread, self).__init__() + self.task = task + self.task_event = event + self.running = True + self.running_lock = threading.Lock() + self.in_task = False + self.task_complete = threading.Event() + self.args = args + self.kwargs = kwargs + + def run(self): + """ + Called by threading.Thread, this runs in the new thread. + """ + while True: + self.task_event.wait() + if not self.running: + logger.debug("TaskThread exiting") + return + logger.debug("TaskThread starting task") + with self.running_lock: + self.task_event.clear() + self.task_complete.clear() + self.task(*self.args, **self.kwargs) + with self.running_lock: + self.in_task = False + self.task_complete.set() + + def run_task(self, *args, **kwargs): + """ + Run an instance of the task. + + :param args: + The arguments to pass to the task. + + :param kwargs: + The keyword arguments to pass to the task. + """ + # Don't allow this call if the thread is currently + # in a task. + with self.running_lock: + if self.in_task: + raise TaskInProcessException() + self.in_task = True + logger.debug("Waking up the thread") + self.args = args + self.kwargs = kwargs + # Wake up the thread to do it's thing + self.task_event.set() + + def join_task(self, time_out): + """ + Wait for the currently running task to complete. + + :param time_out: + An ``int``. The amount of time to wait for the + task to finish. + """ + with self.running_lock: + if not self.in_task: + return + + success = self.task_complete.wait(time_out) + if success: + self.task_complete.clear() + return success + + def join(self, timeout=None): + """ + Wait for the task to finish + """ + self.running = False + self.task_event.set() + super(TaskThread, self).join(timeout=timeout) + + +class TimerTask(object): + """ + An object that executes a commit function at a given interval. + This class is driven by a TaskThread. A new TaskThread will be + created the first time :py:meth:`.~start` is called. All + subsequent calls to start will reuse the same thread. + + Usage example:: + + from taskthread import TimerTask + import time + + count = 0 + def get_count(): + return count + def execute(): + print "Count: %d" % count + + task = TimerTask(execute, + timeout=10, + count_fcn=get_count, + threshold=1) + + task.start() + + for i in xrange(100000): + count += 1 + time.sleep(1) + task.stop() + count = 0 + task.start() + for i in xrange(100000): + count += 1 + time.sleep(1) + task.shutdown() + + :param execute_fcn: + A `function`. This function will be executed on each time interval. + + :param delay: + An `int`. The delay in **seconds** invocations of + `execute_fcn`. Default: `10`. + + :param count_fcn: + A `function`. This function returns a current count. If the count + has not changed more the `threshold` since the last invocation of + `execute_fcn`, `execute_fcn` will not be called again. If not + specified, `execute_fcn` will be called each time the timer fires. + **Optional**. If count_fcn is specified, ``threshold`` is required. + + :param threshold: + An `int`. Specifies the minimum delta in `count_fcn` that must be + met for `execute_fcn` to be invoked. **Optional**. Must be + specified in conjunction with `count_fcn`. + + """ + def __init__(self, execute_fcn, delay=10, count_fcn=None, threshold=None): + self.running = True + self.execute_fcn = execute_fcn + self.last_count = 0 + self.event = threading.Event() + self.delay = delay + self.thread = None + self.running_lock = threading.RLock() + if bool(threshold) != bool(count_fcn): + raise ValueError("Must specify threshold " + "and count_fcn, or neither") + + self.count_fcn = count_fcn + self.threshold = threshold + + def start(self): + """ + Start the task. This starts a :py:class:`.~TaskThread`, and starts + running run_threshold_timer on the thread. + + """ + if not self.thread: + logger.debug('Starting up the taskthread') + self.thread = TaskThread(self._run_threshold_timer) + self.thread.start() + + if self.threshold: + self.last_count = 0 + + logger.debug('Running the task') + self.running = True + self.thread.run_task() + + def stop(self): + """ + Stop the task, leaving the :py:class:`.~TaskThread` running + so start can be called again. + + """ + logger.debug('Stopping the task') + wait = False + with self.running_lock: + if self.running: + wait = True + self.running = False + if wait: + self.event.set() + self.thread.join_task(2) + + def shutdown(self): + """ + Close down the task thread and stop the task if it is running. + + """ + logger.debug('Shutting down the task') + self.stop() + self.thread.join(2) + + def _exec_if_threshold_met(self): + new_count = self.count_fcn() + logger.debug('new_count: %d' % new_count) + if new_count >= self.last_count + self.threshold: + self.execute_fcn() + self.last_count = new_count + + def _exec(self): + if self.count_fcn: + self._exec_if_threshold_met() + else: + self.execute_fcn() + + def _wait(self): + self.event.wait(timeout=self.delay) + self.event.clear() + logger.debug('Task woken up') + + def _exit_loop(self): + """ + If self.running is false, it means the task should shut down. + """ + exit_loop = False + with self.running_lock: + if not self.running: + exit_loop = True + logger.debug('Task shutting down') + return exit_loop + + def _run_threshold_timer(self): + """ + Main loop of the timer task + + """ + logger.debug('In Task') + while True: + self._wait() + if self._exit_loop(): + return + self._exec() |