Remove remoteStorage (#648)

Fix #647
This commit is contained in:
Markus Unterwaditzer 2017-07-02 18:42:55 +02:00 committed by GitHub
parent bf79ac1748
commit 37a1eb2fdb
8 changed files with 10 additions and 337 deletions

View file

@ -9,6 +9,11 @@ Package maintainers and users who have to manually update their installation
may want to subscribe to `GitHub's tag feed
<https://github.com/pimutils/vdirsyncer/tags.atom>`_.
Version 0.16.1
==============
- Removed remoteStorage support, see :gh:`647`.
Version 0.16.0
==============

View file

@ -1,7 +1,6 @@
# See the documentation on how to run the tests.
export DAV_SERVER := skip
export REMOTESTORAGE_SERVER := skip
export REQUIREMENTS := release
export TESTSERVER_BASE := ./tests/storage/servers/
export CI := false
@ -42,7 +41,7 @@ all:
install-servers:
set -ex; \
for server in $(DAV_SERVER) $(REMOTESTORAGE_SERVER); do \
for server in $(DAV_SERVER); do \
if [ ! "$$(ls $(TESTSERVER_BASE)$$server/)" ]; then \
git submodule update --init -- "$(TESTSERVER_BASE)$$server"; \
fi; \
@ -90,7 +89,6 @@ release:
install-dev:
pip install -e .
[ "$(REMOTESTORAGE_SERVER)" = "skip" ] || pip install -e .[remotestorage]
[ "$(ETESYNC_TESTS)" = "false" ] || pip install -e .[etesync]
set -xe && if [ "$(REQUIREMENTS)" = "devel" ]; then \
pip install -U --force-reinstall \

View file

@ -208,24 +208,6 @@ or write anything to it.
.. autostorage:: vdirsyncer.storage.google.GoogleContactsStorage
remoteStorage
+++++++++++++
`remoteStorage <https://remotestorage.io/>`_ is an open per-user data storage
protocol. Vdirsyncer contains **highly experimental support** for it.
.. note::
Do not use this storage if you're not prepared for data-loss and breakage.
To use them, you need to install some optional dependencies with::
pip install vdirsyncer[remotestorage]
.. autostorage:: vdirsyncer.storage.remotestorage.RemoteStorageContacts
.. autostorage:: vdirsyncer.storage.remotestorage.RemoteStorageCalendars
EteSync
+++++++

View file

@ -51,25 +51,19 @@ for python, requirements in itertools.product(python_versions,
else:
dav_servers = ("radicale", "xandikos")
rs_servers = ()
if python == latest_python and requirements == "release":
dav_servers += ("owncloud", "nextcloud", "baikal", "davical",
"fastmail")
for server_type, server in itertools.chain(
(("REMOTESTORAGE", x) for x in rs_servers),
(("DAV", x) for x in dav_servers)
):
build_prs = server not in ("fastmail", "davical", "icloud")
for dav_server in dav_servers:
build_prs = dav_server not in ("fastmail", "davical", "icloud")
matrix.append({
'python': python,
'env': ("BUILD=test "
"{server_type}_SERVER={server} "
"DAV_SERVER={dav_server} "
"REQUIREMENTS={requirements} "
"BUILD_PRS={build_prs} "
.format(server_type=server_type,
server=server,
.format(dav_server=dav_server,
requirements=requirements,
build_prs=build_prs and "true" or "false"))
})

View file

@ -70,7 +70,6 @@ setup(
# Optional dependencies
extras_require={
'remotestorage': ['requests-oauthlib'],
'google': ['requests-oauthlib'],
'etesync': ['etesync']
},

View file

@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
import os
import pytest
from vdirsyncer.storage.remotestorage import \
RemoteStorageCalendars, RemoteStorageContacts
from . import StorageTests, get_server_mixin
remotestorage_server = os.environ['REMOTESTORAGE_SERVER']
ServerMixin = get_server_mixin(remotestorage_server)
class RemoteStorageTests(ServerMixin, StorageTests):
remotestorage_server = remotestorage_server
class TestCalendars(RemoteStorageTests):
storage_class = RemoteStorageCalendars
@pytest.fixture(params=['VTODO', 'VEVENT'])
def item_type(self, request):
return request.param
class TestContacts(RemoteStorageTests):
storage_class = RemoteStorageContacts
supports_collections = False
@pytest.fixture(params=['VCARD'])
def item_type(self, request):
return request.param

View file

@ -34,10 +34,6 @@ class _StorageIndex(object):
filesystem='vdirsyncer.storage.filesystem.FilesystemStorage',
http='vdirsyncer.storage.http.HttpStorage',
singlefile='vdirsyncer.storage.singlefile.SingleFileStorage',
remotestorage_contacts=(
'vdirsyncer.storage.remotestorage.RemoteStorageContacts'),
remotestorage_calendars=(
'vdirsyncer.storage.remotestorage.RemoteStorageCalendars'),
google_calendar='vdirsyncer.storage.google.GoogleCalendarStorage',
google_contacts='vdirsyncer.storage.google.GoogleContactsStorage',
etesync_calendars='vdirsyncer.storage.etesync.EtesyncCalendars',

View file

@ -1,266 +0,0 @@
'''
A storage type for accessing contact and calendar data from `remoteStorage
<https://remotestorage.io>`_. It is highly experimental.
A few things are hardcoded for now so the user doesn't have to specify those
things, and plugging in an account "just works".
'''
import logging
from urllib.parse import quote as urlquote, urljoin
import click
from .base import Storage, normalize_meta_value
from .http import HTTP_STORAGE_PARAMETERS, prepare_client_cert, \
prepare_verify
from .. import exceptions, utils, http
from ..vobject import Item
REDIRECT_URI = 'https://vdirsyncer.5apps.com/'
CLIENT_ID = 'https://vdirsyncer.5apps.com'
DRAFT_VERSION = '05'
logger = logging.getLogger(__name__)
def _ensure_slash(dir):
return dir.rstrip('/') + '/'
def _iter_listing(json):
new_listing = '@context' in json # draft-02 and beyond
if new_listing:
json = json['items']
for name, info in json.items():
if not new_listing:
info = {'ETag': info}
yield name, info
class Session(object):
def __init__(self, account, scope, verify=True, verify_fingerprint=None,
auth_cert=None, access_token=None, collection=None):
from oauthlib.oauth2 import MobileApplicationClient
from requests_oauthlib import OAuth2Session
self.user, self.host = account.split('@')
self._settings = {
'cert': prepare_client_cert(auth_cert)
}
self._settings.update(prepare_verify(verify, verify_fingerprint))
self.scope = scope + ':rw'
self._session = OAuth2Session(
CLIENT_ID, client=MobileApplicationClient(CLIENT_ID),
scope=self.scope,
redirect_uri=REDIRECT_URI,
token={'access_token': access_token},
)
subpath = scope
if collection:
subpath = urljoin(_ensure_slash(scope),
_ensure_slash(urlquote(collection)))
self._discover_endpoints(subpath)
if not access_token:
self._get_access_token()
def request(self, method, path, **kwargs):
url = self.endpoints['storage']
if path:
url = urljoin(url, path)
settings = dict(self._settings)
settings.update(kwargs)
return http.request(method, url, session=self._session, **settings)
def _get_access_token(self):
authorization_url, state = \
self._session.authorization_url(self.endpoints['oauth'])
click.echo('Opening {} ...'.format(authorization_url))
try:
utils.open_graphical_browser(authorization_url)
except Exception as e:
logger.warning(str(e))
click.echo('Follow the instructions on the page.')
raise exceptions.UserError('Aborted!')
def _discover_endpoints(self, subpath):
r = http.request(
'GET', 'https://{host}/.well-known/webfinger?resource=acct:{user}'
.format(host=self.host, user=self.user),
**self._settings
)
j = r.json()
for link in j['links']:
if 'remotestorage' in link['rel']:
break
storage = urljoin(_ensure_slash(link['href']),
_ensure_slash(subpath))
props = link['properties']
oauth = props['http://tools.ietf.org/html/rfc6749#section-4.2']
self.endpoints = dict(storage=storage, oauth=oauth)
class RemoteStorage(Storage):
__doc__ = '''
:param account: remoteStorage account, ``"user@example.com"``.
''' + HTTP_STORAGE_PARAMETERS + '''
'''
storage_name = None
item_mimetype = None
fileext = None
def __init__(self, account, verify=True, verify_fingerprint=None,
auth_cert=None, access_token=None, **kwargs):
super(RemoteStorage, self).__init__(**kwargs)
self.session = Session(
account=account,
verify=verify,
verify_fingerprint=verify_fingerprint,
auth_cert=auth_cert,
access_token=access_token,
collection=self.collection,
scope=self.scope)
@classmethod
def discover(cls, **base_args):
if base_args.pop('collection', None) is not None:
raise TypeError('collection argument must not be given.')
session_args, _ = utils.split_dict(base_args, lambda key: key in (
'account', 'verify', 'auth', 'verify_fingerprint', 'auth_cert',
'access_token'
))
session = Session(scope=cls.scope, **session_args)
try:
r = session.request('GET', '')
except exceptions.NotFoundError:
return
for name, _info in _iter_listing(r.json()):
if not name.endswith('/'):
continue # not a folder
newargs = dict(base_args)
newargs['collection'] = name.rstrip('/')
yield newargs
@classmethod
def create_collection(cls, collection, **kwargs):
# remoteStorage folders are autocreated.
assert collection
assert '/' not in collection
kwargs['collection'] = collection
return kwargs
def list(self):
try:
r = self.session.request('GET', '')
except exceptions.NotFoundError:
return
for name, info in _iter_listing(r.json()):
if not name.endswith(self.fileext):
continue
etag = info['ETag']
etag = '"' + etag + '"'
yield name, etag
def _put(self, href, item, etag):
headers = {'Content-Type': self.item_mimetype + '; charset=UTF-8'}
if etag is None:
headers['If-None-Match'] = '*'
else:
headers['If-Match'] = etag
response = self.session.request(
'PUT',
href,
data=item.raw.encode('utf-8'),
headers=headers
)
return href, response.headers['etag']
def update(self, href, item, etag):
assert etag
href, etag = self._put(href, item, etag)
return etag
def upload(self, item):
href = utils.generate_href(item.ident) + self.fileext
return self._put(href, item, None)
def delete(self, href, etag):
headers = {'If-Match': etag}
self.session.request('DELETE', href, headers=headers)
def get(self, href):
response = self.session.request('GET', href)
return Item(response.text), response.headers['etag']
def get_meta(self, key):
try:
return normalize_meta_value(self.session.request('GET', key).text)
except exceptions.NotFoundError:
return u''
def set_meta(self, key, value):
self.session.request(
'PUT',
key,
data=normalize_meta_value(value).encode('utf-8'),
headers={'Content-Type': 'text/plain; charset=utf-8'}
)
class RemoteStorageContacts(RemoteStorage):
__doc__ = '''
remoteStorage contacts. Uses the `vdir_contacts` scope.
''' + RemoteStorage.__doc__
storage_name = 'remotestorage_contacts'
fileext = '.vcf'
item_mimetype = 'text/vcard'
scope = 'vdir_contacts'
def __init__(self, **kwargs):
if kwargs.get('collection'):
raise ValueError(
'No collections allowed for contacts, '
'there is only one addressbook. '
'Use the vcard groups construct to categorize your contacts '
'into groups.'
)
super(RemoteStorageContacts, self).__init__(**kwargs)
class RemoteStorageCalendars(RemoteStorage):
__doc__ = '''
remoteStorage calendars. Uses the `vdir_calendars` scope.
''' + RemoteStorage.__doc__
storage_name = 'remotestorage_calendars'
fileext = '.ics'
item_mimetype = 'text/calendar'
scope = 'vdir_calendars'
def __init__(self, **kwargs):
if not kwargs.get('collection'):
raise exceptions.CollectionRequired()
super(RemoteStorageCalendars, self).__init__(**kwargs)