diff options
34 files changed, 1112 insertions, 645 deletions
| @@ -11,8 +11,23 @@ MANIFEST  *.pyc  *.log  *.*~ +scripts/profiling/sync/profiles + +debian/files +debian/soledad-client.postinst.debhelper +debian/soledad-client.prerm.debhelper +debian/soledad-client.substvars +debian/soledad-client/ +debian/soledad-common.postinst.debhelper +debian/soledad-common.prerm.debhelper +debian/soledad-common.substvars +debian/soledad-common/ +debian/soledad-server.postinst.debhelper +debian/soledad-server.postrm.debhelper +debian/soledad-server.prerm.debhelper +debian/soledad-server.substvars +debian/soledad-server/  *.csv  .eggs  _trial_temp  .DS_Store -scripts/profiling/sync/profiles diff --git a/client/changes/bug_4417_default-encoding-to-utf8 b/client/changes/bug_4417_default-encoding-to-utf8 new file mode 100644 index 00000000..6801b766 --- /dev/null +++ b/client/changes/bug_4417_default-encoding-to-utf8 @@ -0,0 +1 @@ +  o Defaults detected encoding to utf-8 to avoid bug if detected encoding is None. Closes: #4417 diff --git a/client/changes/bug_open-db-in-autocommit-mode b/client/changes/bug_open-db-in-autocommit-mode new file mode 100644 index 00000000..26edd5a9 --- /dev/null +++ b/client/changes/bug_open-db-in-autocommit-mode @@ -0,0 +1,2 @@ +  o Open db in autocommit mode, to avoid nested transactions problems. +    Closes: #4400 diff --git a/client/changes/feature-use-chardet-as-fallback b/client/changes/feature-use-chardet-as-fallback new file mode 100644 index 00000000..f96a959c --- /dev/null +++ b/client/changes/feature-use-chardet-as-fallback @@ -0,0 +1 @@ +  o Use chardet as fallback if cchardet not found. diff --git a/client/pkg/requirements.pip b/client/pkg/requirements.pip index f29d5c74..75c1025f 100644 --- a/client/pkg/requirements.pip +++ b/client/pkg/requirements.pip @@ -1,12 +1,11 @@ -pysqlcipher>2.6.3 +#pysqlcipher>2.6.3  u1db  scrypt  pycryptopp -cchardet -zope.proxy -twisted -# XXX -- fix me! -# oauth is not strictly needed by us, but we need it until u1db adds it to its -# release as a dep. +# cchardet not packaged, we need python-chardet from the +# repos instead. +chardet +zope.proxy +#twisted  # we handle specific packages in debian/control  oauth diff --git a/client/src/leap/soledad/client/_version.py b/client/src/leap/soledad/client/_version.py index 588e4eb5..889fba38 100644 --- a/client/src/leap/soledad/client/_version.py +++ b/client/src/leap/soledad/client/_version.py @@ -1,216 +1,13 @@ -# 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 is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) +# 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. -# these strings will be replaced by git during git-archive +version_version = '0.8.0alpha2' +version_full = 'aa6a34bc4ac5962dacaa5908778e444fe5aae3d7' -import subprocess -import sys -import re -import os.path -IN_LONG_VERSION_PY = True - -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - - -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: -        f = open(versionfile_source, "r") -        for line in f.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) -        f.close() -    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(",")]) -    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of -    # just "foo-1.0". If we see a "tag: " prefix, prefer those. -    TAG = "tag: " -    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) -    if not tags: -        # Either we're using git < 1.8.3, or there really are no tags. We use -        # a heuristic: assume all version tags have a digit. The old git %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". -        tags = set([r for r in refs if re.search(r'\d', r)]) -        if verbose: -            print("discarding '%s', no digits" % ",".join(refs - tags)) -    if verbose: -        print("likely tags: %s" % ",".join(sorted(tags))) -    for ref in sorted(tags): -        # 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( -            os.path.join('..', here)) - -    ###################################################### -    # XXX patch for our specific configuration with -    # the three projects leap.soledad.{common, client, server} -    # inside the same repo. -    ###################################################### -    root = os.path.dirname(os.path.join('..', root)) - -    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 = "leap.soledad.client-" -versionfile_source = "src/leap/soledad/client/_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/client/src/taskthread/__init__.py b/client/src/taskthread/__init__.py new file mode 100755 index 00000000..a734a829 --- /dev/null +++ b/client/src/taskthread/__init__.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() diff --git a/client/src/taskthread/tests/__init__.py b/client/src/taskthread/tests/__init__.py new file mode 100755 index 00000000..92bd912f --- /dev/null +++ b/client/src/taskthread/tests/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/client/src/taskthread/tests/unit/__init__.py b/client/src/taskthread/tests/unit/__init__.py new file mode 100755 index 00000000..92bd912f --- /dev/null +++ b/client/src/taskthread/tests/unit/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/client/src/taskthread/tests/unit/test_taskthread.py b/client/src/taskthread/tests/unit/test_taskthread.py new file mode 100755 index 00000000..82565922 --- /dev/null +++ b/client/src/taskthread/tests/unit/test_taskthread.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +# 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.:w + + +import threading +import unittest2 as unittest + +from mock import Mock, patch + +from taskthread import TaskThread, TaskInProcessException, TimerTask + +forever_event = threading.Event() + + +def forever_function(*args, **kwargs): +    forever_event.wait() +    forever_event.clear() + + +class TaskThreadTestCase(unittest.TestCase): +    """ +    Tests for :py:class:`.TaskThread`. +    """ + +    def test___init__(self): +        """ +        Test the __init__ method. It doesn't really do much. +        """ +        task_thread = TaskThread(forever_function) +        self.assertEqual(forever_function, task_thread.task) + +    def test_run_not_running(self): +        """ +        Verifies that thread will shut down when running is false +        """ +        event = Mock() +        event.wait = Mock(side_effect=[True]) +        event.clear = Mock(side_effect=Exception("Should never be called")) +        task_thread = TaskThread(forever_function, +                                 event=event) +        task_thread.running = False +        task_thread.run() +        event.wait.assert_called_once_with() + +    def test_run_executes_task(self): +        event = Mock() +        event.wait = Mock(side_effect=[True, True]) + +        def stop_iteration(*args, **kwargs): +            args[0].running = False + +        task_thread = TaskThread(stop_iteration, +                                 event=event) + +        task_thread.args = [task_thread] +        task_thread.kwargs = {'a': 2} +        task_thread.in_task = True +        task_thread.run() +        self.assertEqual(False, task_thread.in_task) + +    def test_run_task(self): +        event = Mock() +        task_thread = TaskThread(forever_function, +                                 event=event) +        args = [1] +        kwargs = {'a': 1} + +        task_thread.run_task(*args, **kwargs) +        self.assertEqual(tuple(args), task_thread.args) +        self.assertEqual(kwargs, task_thread.kwargs) +        event.set.assert_called_once_with() + +    def test_run_task_task_in_progress(self): +        event = Mock() +        task_thread = TaskThread(forever_function, +                                 event=event) +        task_thread.in_task = True +        self.assertRaises(TaskInProcessException, task_thread.run_task) + +    def test_join_task(self): +        task_thread = TaskThread(forever_function) +        task_thread.in_task = True +        task_thread.task_complete = Mock() +        task_thread.task_complete.wait = Mock(side_effect=[True]) +        success = task_thread.join_task(1) +        self.assertTrue(success) + +    def test_join_task_not_running(self): +        task_thread = TaskThread(forever_function) +        task_thread.task_complete = Mock() +        task_thread.wait =\ +            Mock(side_effect=Exception("Should never be called")) +        task_thread.join_task(1) + +    def test_join(self): +        task_thread = TaskThread(forever_function) +        task_thread.start() +        task_thread.run_task() +        # Set the event so the task completes +        forever_event.set() +        task_thread.join_task(1) +        task_thread.join(1) + +    def test_execute_multiple_tasks(self): +        task_thread = TaskThread(forever_function) +        task_thread.start() +        task_thread.run_task() +        # Set the event so the task completes +        forever_event.set() +        task_thread.join_task(1) +        forever_event.set() +        task_thread.join_task(1) +        task_thread.join(1) + + +def my_func(): +    pass + + +class TimerTaskTestCase(unittest.TestCase): + +    def test___int__(self): + +        task = TimerTask(my_func, +                         delay=100) +        self.assertEqual(my_func, task.execute_fcn) +        self.assertEqual(100, task.delay) +        self.assertIsNone(task.count_fcn) +        self.assertIsNone(task.threshold) + +    def test___int__raises(self): +        self.assertRaises(ValueError, TimerTask.__init__, +                          TimerTask(None), +                          my_func(), +                          count_fcn=Mock()) + +        self.assertRaises(ValueError, TimerTask.__init__, +                          TimerTask(None), +                          my_func(), +                          threshold=Mock()) + +    @patch('taskthread.TaskThread') +    def test_start(self, TaskThreadMock): +        task = TimerTask(my_func) +        thread = TaskThreadMock.return_value + +        task.start() +        self.assertTrue(task.running) +        self.assertEqual(thread, task.thread) +        thread.start.assert_called_once_with() +        thread.run_task.assert_called_once_with() + +    @patch('taskthread.TaskThread') +    def test_start_restarts(self, TaskThreadMock): +        task = TimerTask(my_func, threshold=1, count_fcn=Mock()) +        thread = TaskThreadMock.return_value +        task.last_count = 1 +        task.thread = thread + +        task.start() +        self.assertEqual(0, task.last_count) +        self.assertEqual(0, thread.start.called) +        thread.run_task.assert_called_once_with() + +    @patch('taskthread.TaskThread') +    def test_stop(self, TaskThreadMock): +        running_lock = Mock() +        running_lock.__enter__ = Mock() +        running_lock.__exit__ = Mock() +        task = TimerTask(my_func) +        task.thread = TaskThreadMock.return_value +        task.running = True +        task.event = Mock() +        task.running_lock = running_lock + +        task.stop() + +        self.assertEqual(False, task.running) +        self.assertEqual(1, task.event.set.called) +        running_lock.__enter__.assert_called_once_with() +        running_lock.__exit__.assert_called_once_with(None, None, None) +        task.thread.join_task.assert_called_once_with(2) + +    @patch('taskthread.TaskThread') +    def test_stop_not_running(self, TaskThreadMock): +        task = TimerTask(my_func) +        task.thread = TaskThreadMock.return_value +        task.running = False +        task.event = Mock() + +        task.stop() + +        self.assertEqual(False, task.running) +        self.assertEqual(0, task.event.set.called) +        self.assertEqual(0, task.thread.join_task.called) + +    @patch('taskthread.TaskThread') +    def test_shutdown(self, TaskThreadMock): +        task = TimerTask(my_func) +        task.thread = TaskThreadMock.return_value +        task.running = False +        task.shutdown() +        task.thread.join.assert_called_once_with(2) + +    def test__exec_if_threshold_met(self): +        self.called = False + +        def exec_fcn(): +            self.called = True + +        def count_fcn(): +            return 10 + +        task = TimerTask(exec_fcn, count_fcn=count_fcn, threshold=1) +        task.last_count = 9 +        task._exec_if_threshold_met() +        self.assertTrue(self.called) +        self.assertEqual(10, task.last_count) + +    def test__exec_if_threshold_met_not_met(self): + +        def exec_fcn(): +            raise Exception("This shouldn't happen!!") + +        def count_fcn(): +            return 10 + +        task = TimerTask(exec_fcn, count_fcn=count_fcn, threshold=10) +        task.last_count = 9 +        task._exec_if_threshold_met() +        self.assertEqual(9, task.last_count) + +    def test__exec(self): +        self.called = False + +        def exec_fcn(): +            self.called = True + +        task = TimerTask(exec_fcn) +        task._exec() +        self.assertTrue(self.called) + +    def test__exec_threshold(self): +        self.called = False + +        def exec_fcn(): +            self.called = True + +        def count_fcn(): +            return 1 + +        task = TimerTask(exec_fcn, count_fcn=count_fcn, threshold=1) +        task._exec() +        self.assertTrue(self.called) + +    @patch('threading.Event') +    def test__wait(self, event_mock): +        task = TimerTask(my_func) +        event = event_mock.return_value + +        task._wait() +        event.wait.assert_called_once_with(timeout=task.delay) +        self.assertEqual(1, event.clear.called) + +    @patch('threading.RLock') +    def test__exit_loop(self, mock_rlock): +        task = TimerTask(my_func) +        task.running = False +        lock = mock_rlock.return_value +        lock.__enter__ = Mock() +        lock.__exit__ = Mock() +        self.assertTrue(task._exit_loop()) +        self.assertEqual(1, lock.__enter__.called) +        lock.__exit__.assert_called_once_with(None, None, None) + +    @patch('threading.RLock') +    def test__exit_loop_running(self, mock_rlock): +        lock = mock_rlock.return_value +        lock.__enter__ = Mock() +        lock.__exit__ = Mock() +        task = TimerTask(my_func) +        task.running = True +        self.assertFalse(task._exit_loop()) +        self.assertEqual(1, lock.__enter__.called) +        lock.__exit__.assert_called_once_with(None, None, None) + +    @patch('threading.RLock') +    @patch('threading.Event') +    def test__run_threshold_timer(self, event_mock, rlock_mock): +        self.task = None +        event = event_mock.return_value +        lock = rlock_mock.return_value +        lock.__enter__ = Mock() +        lock.__exit__ = Mock() + +        def exec_fcn(): +            self.task.running = False + +        self.task = TimerTask(exec_fcn) +        self.task._run_threshold_timer() + +        self.assertFalse(self.task.running) +        self.assertEqual(2, event.wait.call_count) diff --git a/common/src/leap/soledad/common/_version.py b/common/src/leap/soledad/common/_version.py index a58af503..889fba38 100644 --- a/common/src/leap/soledad/common/_version.py +++ b/common/src/leap/soledad/common/_version.py @@ -1,215 +1,13 @@ -# 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 is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) +# 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. -# these strings will be replaced by git during git-archive -import subprocess -import sys -import re -import os.path +version_version = '0.8.0alpha2' +version_full = 'aa6a34bc4ac5962dacaa5908778e444fe5aae3d7' -IN_LONG_VERSION_PY = True -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - - -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: -        f = open(versionfile_source, "r") -        for line in f.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) -        f.close() -    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(",")]) -    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of -    # just "foo-1.0". If we see a "tag: " prefix, prefer those. -    TAG = "tag: " -    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) -    if not tags: -        # Either we're using git < 1.8.3, or there really are no tags. We use -        # a heuristic: assume all version tags have a digit. The old git %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". -        tags = set([r for r in refs if re.search(r'\d', r)]) -        if verbose: -            print("discarding '%s', no digits" % ",".join(refs - tags)) -    if verbose: -        print("likely tags: %s" % ",".join(sorted(tags))) -    for ref in sorted(tags): -        # 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( -            os.path.join('..', here)) - -    # -    # XXX patch for our specific configuration with -    # the three projects leap.soledad.{common, client, server} -    # inside the same repo. -    # -    root = os.path.dirname(os.path.join('..', root)) - -    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 = "leap.soledad.common-" -versionfile_source = "src/leap/soledad/common/_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/deb_release.sh b/deb_release.sh new file mode 100755 index 00000000..83995bde --- /dev/null +++ b/deb_release.sh @@ -0,0 +1,13 @@ +#!/bin/zsh + +for pkg in common client server; do +  cd ${pkg} +  echo `pwd` +  rm src/leap/soledad/${pkg}/_version.py +  python setup.py freeze_debianver +  sed -i 's/-dirty//g' src/leap/soledad/${pkg}/_version.py  +  git add src/leap/soledad/${pkg}/_version.py +  cd .. +done + +git commit -m "Freeze debian version." diff --git a/debian/__init__.py b/debian/__init__.py new file mode 100644 index 00000000..f48ad105 --- /dev/null +++ b/debian/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: +    __import__('pkg_resources').declare_namespace(__name__) +except ImportError: +    from pkgutil import extend_path +    __path__ = extend_path(__path__, __name__) diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..d8344b0c --- /dev/null +++ b/debian/changelog @@ -0,0 +1,205 @@ +soledad (0.8.0~alpha2) unstable; urgency=medium + +  * Pre-release of soledad 0.8.0 +  * Cherry-pick commits to create soledad user, add it to sudoers. + + -- Ben Carrillo <ben@futeisha.org>  Tue, 29 Sep 2015 10:43:00 -0400 + +soledad (0.7.3) unstable; urgency=medium + +  * Update to 0.7.3 release + + -- Ben Carrillo <ben@futeisha.org>  Thu, 24 Sep 2015 15:23:44 -0400 + +soledad (0.7.2) unstable; urgency=medium + +  * Update to 0.7.2 release +  * Update short descriptions in debian/control to be unique +  * Update debian/copyright to point to the specific GPL-3 symlink +  * Fix init.d-script-does-not-provide-itself lintian error +   + -- Micah Anderson <micah@debian.org>  Tue, 01 Sep 2015 15:43:11 -0400 + +soledad (0.6.5.1) unstable; urgency=medium + +  * Update to 0.6.5 release +  * Cherry-pick 67f17cd30 to fix illegal header error (closes #7122) + + -- Ben Carrillo <ben@futeisha.org>  Wed, 10 Jun 2015 12:06:56 -0400 + +soledad (0.6.3) unstable; urgency=medium + +  * Update to 0.6.3 release +  * Fix server initscript location (#6557) + + -- db <db@leap.se>  Tue, 16 Dec 2014 11:26:58 -0400 + +soledad (0.6.0) unstable; urgency=low + +  [ Ben Carrillo ] +  * Disable WAL (workaround for #5562) +  * Use python-chardet + +  [ db ] +  * Update to 0.6.0 release. +  * Do not depend on pysqlite (closes #2945) + + -- db <db@leap.se>  Wed, 01 Oct 2014 15:39:38 -0300 + +soledad (0.5.2.1) unstable; urgency=medium + +  * Update soledad-client version dependency on python-pycryptopp +    to make backports to wheezy easier + + -- Micah Anderson <micah@debian.org>  Tue, 01 Jul 2014 14:37:45 -0400 + +soledad (0.5.2) unstable; urgency=low + +  * Update to 0.5.2 release. + + -- db <db@leap.se>  Fri, 06 Jun 2014 16:53:03 -0300 + +soledad (0.5.1) unstable; urgency=low + +  * Update to 0.5.1 release. + + -- db <db@leap.se>  Fri, 06 Jun 2014 16:48:57 -0300 + +soledad (0.5.0) unstable; urgency=low + +  * Update to 0.5.0 release. + + -- Ben Carrillo <ben@futeisha.org>  Wed, 09 Apr 2014 15:47:11 -0500 + +soledad (0.4.4.1) unstable; urgency=medium + +  * Set version dependency of python-pycryptopp for soledad-client + + -- Micah Anderson <micah@debian.org>  Tue, 10 Dec 2013 21:10:27 -0500 + +soledad (0.4.4) unstable; urgency=low + +  * Update to 0.4.4 release.  + + -- Ben Carrillo <ben@futeisha.org>  Tue, 10 Dec 2013 17:06:28 -0400 + +soledad (0.4.3) unstable; urgency=low + +  * Update to 0.4.3 release + + -- Ben Carrillo <ben@futeisha.org>  Fri, 15 Nov 2013 23:37:55 -0200 + +soledad (0.4.2.2) unstable; urgency=low + +  * soledad-client: Cherry-pick change for autocommit mode, which fixes +    a "cannot start a transaction within a transaction" that was +    rendering the client unusable. +  * soledad-client: Cherry-pick for default encoding, which fixes a bug +    that was rendering the client unusable. + + -- Ben Carrillo <ben@futeisha.org>  Thu, 07 Nov 2013 20:09:29 -0200 + +soledad (0.4.2.1) unstable; urgency=low + +  * Change cchardet dependency for chardet explicitly. + + -- Ben Carrillo <ben@futeisha.org>  Wed, 06 Nov 2013 17:46:03 -0200 + +soledad (0.4.2) unstable; urgency=low + +  * Update to 0.4.2 + + -- Ben Carrillo <ben@futeisha.org>  Tue, 05 Nov 2013 11:18:39 -0200 + +soledad (0.4.1) unstable; urgency=low + +  * Update to 0.4.1. +  * Freeze version to 0.4.1 using freeze_debianver command. +  * Use python-chardet as a fallback. + + -- Micah Anderson <micah@debian.org>  Tue, 15 Oct 2013 15:27:19 -0300 + +soledad (0.4.0) unstable; urgency=low + +  * Update to 0.4.0. + + -- Micah Anderson <micah@debian.org>  Tue, 24 Sep 2013 16:50:19 -0400   + +soledad (0.3.2) unstable; urgency=low + +  * Update to 0.3.2 + + -- Micah Anderson <micah@debian.org>  Thu, 12 Sep 2013 14:13:56 +0200 + +soledad (0.3.1.1) unstable; urgency=low + +  [ Ben Carrillo ] +  * Fix installation of __init__ file on namespace module. +  * Add LICENSE. + +  [ Micah Anderson ] +  * Add fix to initscript so it uses the new namespace +	 + -- Micah Anderson <micah@debian.org>  Thu, 29 Aug 2013 14:46:23 -0400 + +soledad (0.3.1) unstable; urgency=low + +  [ Ben Carrillo ] +  * Update to latest release. +  * Fix pydist-overrides. + +  [ Micah Anderson ] +  * Stop setup.py from installing initscript so packaging can + + -- Micah Anderson <micah@debian.org>  Mon, 26 Aug 2013 18:21:09 +0200 + +soledad (0.3.1~pre) unstable; urgency=low + +  * Upgrade to latest code in develop +  * Setup additional binary package +  * Fix pydist-overrides for new package name + + -- Micah Anderson <micah@debian.org>  Thu, 22 Aug 2013 14:57:54 -0400 + +soledad (0.3.0.3) unstable; urgency=low + +  * Fixed missing epoch on python-twisted-conch + + -- Micah Anderson <micah@debian.org>  Thu, 15 Aug 2013 13:16:54 -0400 + +soledad (0.3.0.2) unstable; urgency=low + +  * Pin twisted version in Depends to get 13 or greater + + -- Micah Anderson <micah@debian.org>  Thu, 15 Aug 2013 11:31:57 -0400 + +soledad (0.3.0.1) unstable; urgency=low + +  * Update initscript to require that START=yes is set +    in /etc/default/soledad + + -- Micah Anderson <micah@debian.org>  Wed, 14 Aug 2013 11:18:01 -0400 + +soledad (0.3.0) unstable; urgency=low + +  * Update to 0.3.0 + + -- Micah Anderson <micah@debian.org>  Tue, 13 Aug 2013 14:42:06 -0400 + +soledad (0.2.3) unstable; urgency=low + +  * Upgrade to new release + + -- Micah Anderson <micah@debian.org>  Mon, 29 Jul 2013 20:47:19 -0400 + +soledad (0.2.2.1) unstable; urgency=low + +  * Fix installation of initscript + + -- Micah Anderson <micah@debian.org>  Thu, 18 Jul 2013 15:54:51 -0400 + +soledad (0.2.2) unstable; urgency=low + +  * Initial debian package + + -- Micah Anderson <micah@debian.org>  Thu, 30 May 2013 15:11:11 -0400 diff --git a/debian/clean b/debian/clean new file mode 100644 index 00000000..c9349d36 --- /dev/null +++ b/debian/clean @@ -0,0 +1 @@ +src/leap.soledad.egg-info/* diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..2b379093 --- /dev/null +++ b/debian/control @@ -0,0 +1,43 @@ +Source: soledad +Section: python +Priority: optional +Maintainer: Micah Anderson <micah@debian.org> +Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 9) +Standards-Version: 3.9.6 + +Package: soledad-server +Architecture: all +Depends: ${misc:Depends}, ${python:Depends}, python-configparser, python-couchdb,  + python-simplejson, python-oauth, python-u1db, python-routes, python-openssl,  + soledad-common (>= 0.5.0), python-six, python-twisted-web (>= 13.0.0-1~bpo70+1) +Description: Synchronization of locally encrypted data among devices (server files) + Soledad is the part of LEAP that allows application data to be securely  + shared among devices. It provides, to other parts of the LEAP client, an  + API for data storage and sync. + . + This package contains the server components. + +Package: soledad-common +Architecture: all +Depends: ${misc:Depends}, ${python:Depends}, python-simplejson, python-oauth, python-u1db,  + python-six +Description: Synchronization of locally encrypted data among devices (common files) + Soledad is the part of LEAP that allows application data to be securely  + shared among devices. It provides, to other parts of the LEAP client, an  + API for data storage and sync. + . + This package contains the common soledad libraries. For the server, see the + soledad-server package + +Package: soledad-client +Architecture: all +Depends: ${misc:Depends}, ${python:Depends}, python-sqlcipher (>= 2.6.3.3+b1), + python-simplejson, python-oauth, python-u1db, python-scrypt,  + python-dirspec, python-pycryptopp (>= 0.6.0.20120313-1~), soledad-common, + python-chardet, python-twisted-core (>= 13.0.0) +Description: Synchronization of locally encrypted data among devices (client files) + Soledad is the part of LEAP that allows application data to be securely  + shared among devices. It provides, to other parts of the LEAP client, an  + API for data storage and sync. + . + This package contains the soledad client. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..edd67753 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,16 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: soledad +Upstream-Contact: info@leap.se +Source: <git://code.leap.se/soledad/> + +Files: * +Copyright: Copyright (C) 2013 LEAP +License: GPL-3+ + +Files: debian/* +Copyright: Copyright 2013 Micah Anderson <micah@leap.se> +License: GPL-3+ +  +License: GPL-3+ + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL-3'. diff --git a/debian/pydist-overrides b/debian/pydist-overrides new file mode 100644 index 00000000..a577bca6 --- /dev/null +++ b/debian/pydist-overrides @@ -0,0 +1,11 @@ +pysqlcipher python-sqlcipher +PyOpenSSL python-openssl +u1db python-u1db +scrypt python-scrypt +cchardet python-chardet +zope.proxy python-zope.proxy +configparser python-configparser +leap.soledad.common soledad-common +leap.soledad.server soledad-server +leap.soledad.client soledad-client +leap.common python-leap-common diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..88ef50b5 --- /dev/null +++ b/debian/rules @@ -0,0 +1,29 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +#DH_VERBOSE=1 + +%: +	dh $@ --with python2 --namespace=leap.soledad + +override_dh_auto_clean: +	cd client && python setup.py clean -a  +	cd common && python setup.py clean -a  +	cd server && python setup.py clean -a  + +override_dh_auto_install:  +	cd client && python setup.py install --root=../debian/soledad-client +	cd common && python setup.py install --root=../debian/soledad-common +	cd server && LEAP_SKIP_INIT=1 python setup.py install --root=../debian/soledad-server + +override_dh_install: +	# leap.soledad provides the leap.soledad namespace __init__.py +	for pv in $(shell pyversions -vr debian/control); do \ +	    cp common/src/leap/__init__.py debian/soledad-common/usr/local/lib/python$$pv/*-packages/leap/; \ +	    cp common/src/leap/__init__.py debian/soledad-common/usr/local/lib/python$$pv/*-packages/leap/soledad/; \ +	done +	dh_install + +override_dh_auto_clean: +	dh_auto_clean +	find . -name "*.pyc" -exec rm -rf {} \; diff --git a/debian/soledad-client.pyremove b/debian/soledad-client.pyremove new file mode 100644 index 00000000..936da7d3 --- /dev/null +++ b/debian/soledad-client.pyremove @@ -0,0 +1 @@ +*.pth diff --git a/debian/soledad-common.pyremove b/debian/soledad-common.pyremove new file mode 100644 index 00000000..936da7d3 --- /dev/null +++ b/debian/soledad-common.pyremove @@ -0,0 +1 @@ +*.pth diff --git a/debian/soledad-server.init b/debian/soledad-server.init new file mode 120000 index 00000000..6448df05 --- /dev/null +++ b/debian/soledad-server.init @@ -0,0 +1 @@ +../server/pkg/soledad-server
\ No newline at end of file diff --git a/debian/soledad-server.install b/debian/soledad-server.install new file mode 100644 index 00000000..01342c4b --- /dev/null +++ b/debian/soledad-server.install @@ -0,0 +1,2 @@ +server/pkg/create-user-db usr/bin/ +debian/soledad-sudoers etc/sudoers.d/ diff --git a/debian/soledad-server.postinst b/debian/soledad-server.postinst new file mode 100644 index 00000000..0f46caf6 --- /dev/null +++ b/debian/soledad-server.postinst @@ -0,0 +1,30 @@ +#!/bin/sh + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin + +# create soledad group +getent group soledad > /dev/null +RET=$? + +if [ ! $RET -eq 0 ]; then +    addgroup --system soledad +fi + +# create soledad user +id soledad > /dev/null 2>&1 +RET=$? + +if [ ! $RET -eq 0 ]; then +  adduser --system soledad --no-create-home --home /srv/leap/ --ingroup soledad +fi + +# create soledad-admin user +id soledad-admin > /dev/null 2>&1 +RET=$? + +if [ ! $RET -eq 0 ]; then +  adduser --system soledad-admin --no-create-home --home /srv/leap/ --ingroup soledad +fi + + +invoke-rc.d soledad-server start diff --git a/debian/soledad-server.postrm b/debian/soledad-server.postrm new file mode 100644 index 00000000..b4e5618e --- /dev/null +++ b/debian/soledad-server.postrm @@ -0,0 +1,54 @@ +#!/bin/sh +# postrm script for soledad-server +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +#        * <postrm> `remove' +#        * <postrm> `purge' +#        * <old-postrm> `upgrade' <new-version> +#        * <new-postrm> `failed-upgrade' <old-version> +#        * <new-postrm> `abort-install' +#        * <new-postrm> `abort-install' <old-version> +#        * <new-postrm> `abort-upgrade' <old-version> +#        * <disappearer's-postrm> `disappear' <overwriter> +#          <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in +    purge) + +	# XXX some actions needed here: +	# XXX remove soledad users? +	# XXX remove users from sudoers? + +	#for d in path1 \ +	#	 path2 \  +	#	 path3 ; do +	#	[ -d $d ] && rm -rf $d +	#done + +    ;; + +    remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) +    ;; + +    *) +	echo "postrm called with unknown argument \`$1'" >&2 +	exit 1 +    ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +# XXX kali: disabling debhelper autogenerated code for postrm ---------- +# it was causing the following error: +#  postrm-contains-additional-updaterc.d-calls etc/init.d/soledad-server +# XXX DEBHELPER# +# ---------------------------------------------------------------------- + +exit 0 diff --git a/debian/soledad-server.pyremove b/debian/soledad-server.pyremove new file mode 100644 index 00000000..936da7d3 --- /dev/null +++ b/debian/soledad-server.pyremove @@ -0,0 +1 @@ +*.pth diff --git a/debian/soledad-sudoers b/debian/soledad-sudoers new file mode 100644 index 00000000..642497f8 --- /dev/null +++ b/debian/soledad-sudoers @@ -0,0 +1,2 @@ +Cmnd_Alias SOLEDAD_CREATE_DB = /usr/bin/create-user-db +soledad ALL=(soledad-admin) NOPASSWD: SOLEDAD_CREATE_DB diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..89ae9db8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/server/changes/bug_6557_fix-server-initscript-location b/server/changes/bug_6557_fix-server-initscript-location new file mode 100644 index 00000000..6032b302 --- /dev/null +++ b/server/changes/bug_6557_fix-server-initscript-location @@ -0,0 +1 @@ +  o Fix server initscript location (#6557). diff --git a/server/changes/bug_fix-initscript-uid-and-gid b/server/changes/bug_fix-initscript-uid-and-gid new file mode 100644 index 00000000..d4767984 --- /dev/null +++ b/server/changes/bug_fix-initscript-uid-and-gid @@ -0,0 +1 @@ +  o Fix server daemon uid and gid by passing them to twistd on the initscript. diff --git a/server/pkg/requirements.pip b/server/pkg/requirements.pip index f9cce08e..e7d474f9 100644 --- a/server/pkg/requirements.pip +++ b/server/pkg/requirements.pip @@ -2,7 +2,7 @@ configparser  u1db  routes  PyOpenSSL -twisted>=12.3.0 +#twisted  # we don't want all twisted deps in deb!  #pinned for wheezy compatibility  Beaker==1.6.3 #wheezy  couchdb==0.8 #wheezy diff --git a/server/pkg/soledad-server b/server/pkg/soledad-server index 74ed122e..da00e06b 100644 --- a/server/pkg/soledad-server +++ b/server/pkg/soledad-server @@ -1,6 +1,6 @@  #!/bin/sh  ### BEGIN INIT INFO -# Provides:          soledad +# Provides:          soledad-server  # Required-Start:    $network $named $remote_fs $syslog $time  # Required-Stop:     $network $named $remote_fs $syslog  # Default-Start:     2 3 4 5 diff --git a/server/src/leap/soledad/server/_version.py b/server/src/leap/soledad/server/_version.py index 61bb57d9..889fba38 100644 --- a/server/src/leap/soledad/server/_version.py +++ b/server/src/leap/soledad/server/_version.py @@ -1,216 +1,13 @@ -# 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 is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) +# 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. -# these strings will be replaced by git during git-archive +version_version = '0.8.0alpha2' +version_full = 'aa6a34bc4ac5962dacaa5908778e444fe5aae3d7' -import subprocess -import sys -import re -import os.path -IN_LONG_VERSION_PY = True - -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - - -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: -        f = open(versionfile_source, "r") -        for line in f.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) -        f.close() -    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(",")]) -    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of -    # just "foo-1.0". If we see a "tag: " prefix, prefer those. -    TAG = "tag: " -    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) -    if not tags: -        # Either we're using git < 1.8.3, or there really are no tags. We use -        # a heuristic: assume all version tags have a digit. The old git %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". -        tags = set([r for r in refs if re.search(r'\d', r)]) -        if verbose: -            print("discarding '%s', no digits" % ",".join(refs - tags)) -    if verbose: -        print("likely tags: %s" % ",".join(sorted(tags))) -    for ref in sorted(tags): -        # 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( -            os.path.join('..', here)) - -    ###################################################### -    # XXX patch for our specific configuration with -    # the three projects leap.soledad.{common, client, server} -    # inside the same repo. -    ###################################################### -    root = os.path.dirname(os.path.join('..', root)) - -    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 = "leap.soledad.server-" -versionfile_source = "src/leap/soledad/server/_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} | 
