diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8195411 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Markus Unterwaditzer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5e40900 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[wheel] +universal = 1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d9d994d --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer + ~~~~~~~~~~ + + vdirsyncer is a syncronization tool for vdir. See the README for more + details. + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + +from setuptools import setup, find_packages + +setup( + name='vdirsyncer', + version='0.1.0', + author='Markus Unterwaditzer', + author_email='markus@unterwaditzer.net', + url='https://github.com/untitaker/vdirsyncer', + description='A syncronization tool for vdir', + long_description=open('README.md').read(), + packages=find_packages(), + include_package_data=True, + entry_points={ + 'console_scripts': ['vdirsyncer = vdirsyncer.cli:main'] + }, + install_requires=['argvard'] +) diff --git a/vdirsyncer/__init__.py b/vdirsyncer/__init__.py index e69de29..71d182f 100644 --- a/vdirsyncer/__init__.py +++ b/vdirsyncer/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer + ~~~~~~~~~~ + + vdirsyncer is a syncronization tool for vdir. See the README for more + details. + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' +__version__ = '0.1.0' diff --git a/vdirsyncer/cli.py b/vdirsyncer/cli.py index beaf17b..277a3b3 100644 --- a/vdirsyncer/cli.py +++ b/vdirsyncer/cli.py @@ -1,10 +1,23 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.cli + ~~~~~~~~~~~~~~ + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + import os import ConfigParser -from vdirsyncer.cli_utils import path from vdirsyncer.sync import sync_classes +def _path(p): + p = os.path.expanduser(p) + p = os.path.abspath(p) + return p + def get_config_parser(env): - fname = env.get('VDIRSYNCER_CONFIG', path('~/.vdirsyncer/config')) + fname = env.get('VDIRSYNCER_CONFIG', _path('~/.vdirsyncer/config')) c = ConfigParser.SafeConfigParser() c.read(fname) return dict((c, c.items(c)) for c in c.sections()) diff --git a/vdirsyncer/cli_utils.py b/vdirsyncer/cli_utils.py deleted file mode 100644 index 4f23b85..0000000 --- a/vdirsyncer/cli_utils.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -''' - watdo.cli_utils - ~~~~~~~~~ - - This module contains helper functions that should be useful for arbitrary - CLI interfaces. - - :copyright: (c) 2013 Markus Unterwaditzer - :license: MIT, see LICENSE for more details. -''' - -import os -import sys - - -def bail_out(msg): - print(msg) - sys.exit(1) - - -def check_directory(path): - if not os.path.exists(path): - os.makedirs(path) - - -def path(p): - p = os.path.expanduser(p) - p = os.path.abspath(p) - return p - - -def confirm(message='Are you sure? (Y/n)'): - inp = raw_input(message).lower().strip() - if not inp or inp == 'y': - return True - return False diff --git a/vdirsyncer/exceptions.py b/vdirsyncer/exceptions.py index fdc5205..29e660a 100644 --- a/vdirsyncer/exceptions.py +++ b/vdirsyncer/exceptions.py @@ -1,11 +1,25 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.exceptions + ~~~~~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + class Error(Exception): + '''Baseclass for all errors.''' pass class NotFoundError(Error): + '''The item does not exist (anymore).''' pass class AlreadyExistingError(Error): + '''The item exists although it shouldn't, possible race condition.''' pass class WrongEtagError(Error): + '''The given etag doesn't match the etag from the storage, possible race + condition.''' pass diff --git a/vdirsyncer/storage/__init__.py b/vdirsyncer/storage/__init__.py index e69de29..ad3a5b4 100644 --- a/vdirsyncer/storage/__init__.py +++ b/vdirsyncer/storage/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.storage + ~~~~~~~~~~~~~~~~~~ + + There are storage classes which control the access to one vdir-collection + and offer basic CRUD-ish methods for modifying those collections. The exact + interface is described in `vdirsyncer.storage.base`, the `Storage` class + should be a superclass of all storage classes. + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' diff --git a/vdirsyncer/storage/base.py b/vdirsyncer/storage/base.py index 0f12ce4..6c1a313 100644 --- a/vdirsyncer/storage/base.py +++ b/vdirsyncer/storage/base.py @@ -1,3 +1,12 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.storage.base + ~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + class Item(object): '''should-be-immutable wrapper class for VCALENDAR and VCARD''' def __init__(self, raw): @@ -14,6 +23,8 @@ class Item(object): class Storage(object): + '''Superclass of all storages, mainly useful to summarize the interface to + implement.''' def __init__(self, fileext='', item_class=Item): self.fileext = fileext self.item_class = item_class diff --git a/vdirsyncer/storage/filesystem.py b/vdirsyncer/storage/filesystem.py index 2e9da1c..9a6948b 100644 --- a/vdirsyncer/storage/filesystem.py +++ b/vdirsyncer/storage/filesystem.py @@ -1,9 +1,22 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.storage.filesystem + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + import os from vdirsyncer.storage.base import Storage, Item import vdirsyncer.exceptions as exceptions class FilesystemStorage(Storage): + '''Saves data in vdir collection, mtime is etag.''' def __init__(self, path, **kwargs): + ''' + :param path: Absolute path to a *collection* inside a vdir. + ''' self.path = path super(FilesystemStorage, self).__init__(**kwargs) diff --git a/vdirsyncer/storage/memory.py b/vdirsyncer/storage/memory.py index f423c67..8485c84 100644 --- a/vdirsyncer/storage/memory.py +++ b/vdirsyncer/storage/memory.py @@ -1,8 +1,20 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.storage.memory + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + import datetime from vdirsyncer.storage.base import Item, Storage import vdirsyncer.exceptions as exceptions class MemoryStorage(Storage): + ''' + Saves data in RAM, only useful for testing. + ''' def __init__(self, **kwargs): self.items = {} # uid => (etag, object) super(MemoryStorage, self).__init__(**kwargs) diff --git a/vdirsyncer/sync.py b/vdirsyncer/sync.py index 0e390d1..7ff1f21 100644 --- a/vdirsyncer/sync.py +++ b/vdirsyncer/sync.py @@ -1,9 +1,31 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.sync + ~~~~~~~~~~~~~~~ + + The function in `vdirsyncer.sync` can be called on two instances of + `Storage` to syncronize them. Due to the abstract API storage classes are + implementing, the two given instances don't have to be of the same exact + type. This allows us not only to syncronize a local vdir with a CalDAV + server, but also syncronize two CalDAV servers or two local vdirs. + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + + def sync(storage_a, storage_b, status): '''Syncronizes two storages. :param storage_a: The first storage + :type storage_a: :class:`vdirsyncer.storage.base.Storage` :param storage_b: The second storage - :param status: {uid: (etag_a, etag_b)} + :type storage_b: :class:`vdirsyncer.storage.base.Storage` + :param status: + {uid: (etag_a, etag_b)}, metadata about the two storages for detection + of changes. Will be modified by the function and should be passed to it + at the next sync. If this is the first sync, an empty dictionary should + be provided. ''' list_a = dict(storage_a.list_items()) list_b = dict(storage_b.list_items()) @@ -15,7 +37,7 @@ def sync(storage_a, storage_b, status): for uid in set(list_a).union(set(list_b)): if uid not in status: if uid in list_a and uid in list_b: # missing status - status[uid] = (list_a[uid], list_b[uid]) # TODO: might need etag diffing too? + status[uid] = (list_a[uid], list_b[uid]) # TODO: might need some kind of diffing too? elif uid in list_a and uid not in list_b: # new item in a prefetch_items_from_a.append(uid) actions.append(('upload', uid, 'a', 'b')) diff --git a/vdirsyncer/tests/test_storage.py b/vdirsyncer/tests/test_storage.py index bf60995..cd4259e 100644 --- a/vdirsyncer/tests/test_storage.py +++ b/vdirsyncer/tests/test_storage.py @@ -1,3 +1,13 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.tests.test_storage + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' +__version__ = '0.1.0' + from unittest import TestCase import os import tempfile diff --git a/vdirsyncer/tests/test_sync.py b/vdirsyncer/tests/test_sync.py index 53aa339..0e0e0e2 100644 --- a/vdirsyncer/tests/test_sync.py +++ b/vdirsyncer/tests/test_sync.py @@ -1,3 +1,12 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.tests.test_sync + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2014 Markus Unterwaditzer + :license: MIT, see LICENSE for more details. +''' + from unittest import TestCase from vdirsyncer.storage.base import Item from vdirsyncer.storage.memory import MemoryStorage