mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Copyright headers, more docstrings
This commit is contained in:
parent
220f921ab6
commit
ca92d9a428
14 changed files with 183 additions and 41 deletions
19
LICENSE
Normal file
19
LICENSE
Normal file
|
|
@ -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.
|
||||||
2
setup.cfg
Normal file
2
setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[wheel]
|
||||||
|
universal = 1
|
||||||
29
setup.py
Normal file
29
setup.py
Normal file
|
|
@ -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']
|
||||||
|
)
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
vdirsyncer.cli
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: (c) 2014 Markus Unterwaditzer
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
from vdirsyncer.cli_utils import path
|
|
||||||
from vdirsyncer.sync import sync_classes
|
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):
|
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 = ConfigParser.SafeConfigParser()
|
||||||
c.read(fname)
|
c.read(fname)
|
||||||
return dict((c, c.items(c)) for c in c.sections())
|
return dict((c, c.items(c)) for c in c.sections())
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,11 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
vdirsyncer.exceptions
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: (c) 2014 Markus Unterwaditzer
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
'''
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
|
'''Baseclass for all errors.'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class NotFoundError(Error):
|
class NotFoundError(Error):
|
||||||
|
'''The item does not exist (anymore).'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class AlreadyExistingError(Error):
|
class AlreadyExistingError(Error):
|
||||||
|
'''The item exists although it shouldn't, possible race condition.'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class WrongEtagError(Error):
|
class WrongEtagError(Error):
|
||||||
|
'''The given etag doesn't match the etag from the storage, possible race
|
||||||
|
condition.'''
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
'''
|
||||||
|
|
@ -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):
|
class Item(object):
|
||||||
'''should-be-immutable wrapper class for VCALENDAR and VCARD'''
|
'''should-be-immutable wrapper class for VCALENDAR and VCARD'''
|
||||||
def __init__(self, raw):
|
def __init__(self, raw):
|
||||||
|
|
@ -14,6 +23,8 @@ class Item(object):
|
||||||
|
|
||||||
|
|
||||||
class Storage(object):
|
class Storage(object):
|
||||||
|
'''Superclass of all storages, mainly useful to summarize the interface to
|
||||||
|
implement.'''
|
||||||
def __init__(self, fileext='', item_class=Item):
|
def __init__(self, fileext='', item_class=Item):
|
||||||
self.fileext = fileext
|
self.fileext = fileext
|
||||||
self.item_class = item_class
|
self.item_class = item_class
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
vdirsyncer.storage.filesystem
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: (c) 2014 Markus Unterwaditzer
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from vdirsyncer.storage.base import Storage, Item
|
from vdirsyncer.storage.base import Storage, Item
|
||||||
import vdirsyncer.exceptions as exceptions
|
import vdirsyncer.exceptions as exceptions
|
||||||
|
|
||||||
class FilesystemStorage(Storage):
|
class FilesystemStorage(Storage):
|
||||||
|
'''Saves data in vdir collection, mtime is etag.'''
|
||||||
def __init__(self, path, **kwargs):
|
def __init__(self, path, **kwargs):
|
||||||
|
'''
|
||||||
|
:param path: Absolute path to a *collection* inside a vdir.
|
||||||
|
'''
|
||||||
self.path = path
|
self.path = path
|
||||||
super(FilesystemStorage, self).__init__(**kwargs)
|
super(FilesystemStorage, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
vdirsyncer.storage.memory
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:copyright: (c) 2014 Markus Unterwaditzer
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
'''
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from vdirsyncer.storage.base import Item, Storage
|
from vdirsyncer.storage.base import Item, Storage
|
||||||
import vdirsyncer.exceptions as exceptions
|
import vdirsyncer.exceptions as exceptions
|
||||||
|
|
||||||
class MemoryStorage(Storage):
|
class MemoryStorage(Storage):
|
||||||
|
'''
|
||||||
|
Saves data in RAM, only useful for testing.
|
||||||
|
'''
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.items = {} # uid => (etag, object)
|
self.items = {} # uid => (etag, object)
|
||||||
super(MemoryStorage, self).__init__(**kwargs)
|
super(MemoryStorage, self).__init__(**kwargs)
|
||||||
|
|
|
||||||
|
|
@ -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):
|
def sync(storage_a, storage_b, status):
|
||||||
'''Syncronizes two storages.
|
'''Syncronizes two storages.
|
||||||
|
|
||||||
:param storage_a: The first storage
|
:param storage_a: The first storage
|
||||||
|
:type storage_a: :class:`vdirsyncer.storage.base.Storage`
|
||||||
:param storage_b: The second 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_a = dict(storage_a.list_items())
|
||||||
list_b = dict(storage_b.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)):
|
for uid in set(list_a).union(set(list_b)):
|
||||||
if uid not in status:
|
if uid not in status:
|
||||||
if uid in list_a and uid in list_b: # missing 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
|
elif uid in list_a and uid not in list_b: # new item in a
|
||||||
prefetch_items_from_a.append(uid)
|
prefetch_items_from_a.append(uid)
|
||||||
actions.append(('upload', uid, 'a', 'b'))
|
actions.append(('upload', uid, 'a', 'b'))
|
||||||
|
|
|
||||||
|
|
@ -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
|
from unittest import TestCase
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
|
||||||
|
|
@ -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 unittest import TestCase
|
||||||
from vdirsyncer.storage.base import Item
|
from vdirsyncer.storage.base import Item
|
||||||
from vdirsyncer.storage.memory import MemoryStorage
|
from vdirsyncer.storage.memory import MemoryStorage
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue