Compare commits

...

4 commits

View file

@ -52,7 +52,7 @@ class Collection(BaseCollection):
text=row.data.decode(),
)
def get_multi(self, hrefs: Iterable[str]) -> Iterable[Tuple[str, Optional["radicale_item.Item"]]]:
def _get_multi(self, hrefs: Iterable[str], *, connection) -> Iterable[Tuple[str, Optional["radicale_item.Item"]]]:
item_table = self._storage._meta.tables['item']
hrefs_ = list(hrefs)
#hrefs_ = [(x,) for x in hrefs]
@ -69,7 +69,6 @@ class Collection(BaseCollection):
),
)
l = []
with self._storage._engine.begin() as connection:
for row in connection.execute(select_stmt):
l += [(row.name, self._row_to_item(row))]
hrefs_set = set(hrefs_)
@ -77,7 +76,11 @@ class Collection(BaseCollection):
l += [(x, None) for x in (hrefs_set - hrefs_set_have)]
return l
def get_all(self) -> Iterator["radicale_item.Item"]:
def get_multi(self, hrefs: Iterable[str]) -> Iterable[Tuple[str, Optional["radicale_item.Item"]]]:
with self._storage._engine.begin() as c:
return self._get_multi(hrefs=hrefs, connection=c)
def _get_all(self, *, connection) -> Iterator["radicale_item.Item"]:
item_table = self._storage._meta.tables['item']
select_stmt = sa.select(
item_table.c,
@ -90,7 +93,13 @@ class Collection(BaseCollection):
for row in connection.execute(select_stmt):
yield self._row_to_item(row)
def upload(self, href: str, item: "radicale_item.Item") -> "radicale_item.Item":
def get_all(self) -> Iterator["radicale_item.Item"]:
with self._storage._engine.begin() as c:
for i in self._get_all(connection=c):
yield i
def _upload(self, href: str, item: "radicale_item.Item", *, connection) -> "radicale_item.Item":
item_table = self._storage._meta.tables['item']
item_serialized = item.serialize().encode()
@ -121,16 +130,19 @@ class Collection(BaseCollection):
item_table.c.name == href,
),
)
with self._storage._engine.begin() as connection:
if connection.execute(select_stmt).one_or_none() is None:
connection.execute(insert_stmt)
else:
connection.execute(update_stmt)
res = list(self.get_multi([href]))[0][1]
res = list(self._get_multi([href], connection=connection))[0][1]
assert res is not None
return res
def delete(self, href: Optional[str] = None) -> None:
def upload(self, href: str, item: "radicale_item.Item") -> "radicale_item.Item":
with self._storage._engine.begin() as c:
return self._upload(href, item, connection=c)
def _delete(self, *, connection, href: Optional[str] = None) -> None:
collection_table = self._storage._meta.tables['collection']
item_table = self._storage._meta.tables['item']
if href is None:
@ -148,10 +160,13 @@ class Collection(BaseCollection):
item_table.c.name == href,
),
)
with self._storage._engine.begin() as connection:
connection.execute(delete_stmt)
def get_meta(self, key: Optional[str] = None) -> Union[Mapping[str, str], Optional[str]]:
def delete(self, href: Optional[str] = None) -> None:
with self._storage._engine.begin() as c:
return self._delete(connection=c, href=href)
def _get_meta(self, *, connection, key: Optional[str] = None) -> Union[Mapping[str, str], Optional[str]]:
collection_metadata = self._storage._meta.tables['collection_metadata']
select_meta = sa.select(
collection_metadata.c.key,
@ -166,14 +181,17 @@ class Collection(BaseCollection):
collection_metadata.c.key == key,
)
metadata = {}
with self._storage._engine.begin() as connection:
for row in connection.execute(select_meta):
metadata[row.key] = row.value
if key is not None:
return metadata.get(key)
return metadata
def set_meta(self, props: Mapping[str, str]) -> None:
def get_meta(self, key: Optional[str] = None) -> Union[Mapping[str, str], Optional[str]]:
with self._storage._engine.begin() as c:
return self._get_meta(connection=c, key=key)
def _set_meta(self, props: Mapping[str, str], *, connection) -> None:
collection_metadata = self._storage._meta.tables['collection_metadata']
delete_stmt = sa.delete(
collection_metadata,
@ -183,12 +201,14 @@ class Collection(BaseCollection):
insert_stmt = sa.insert(
collection_metadata,
).values([dict(collection_id=self._id, key=k, value=v) for k, v in props.items()])
with self._storage._engine.begin() as connection:
connection.execute(delete_stmt)
connection.execute(insert_stmt)
@property
def last_modified(self) -> str:
def set_meta(self, props: Mapping[str, str]) -> None:
with self._storage._engine.begin() as c:
return self._set_meta(props, connection=c)
def _last_modified(self, *, connection) -> str:
collection = self._storage._meta.tables['collection']
select_stmt = sa.select(
collection.c.modified,
@ -197,11 +217,15 @@ class Collection(BaseCollection):
).where(
collection.c.id == self._id,
)
with self._storage._engine.begin() as connection:
c = connection.execute(select_stmt).one()
return c.modified.strftime('%a, %d %b %Y %H:%M:%S GMT')
def _update_history_etag(self, href: str, item: Optional["radicale_item.Item"]) -> str:
@property
def last_modified(self):
with self._storage._engine.begin() as c:
return self._last_modified(connection=c)
def _update_history_etag(self, href: str, item: Optional["radicale_item.Item"], *, connection) -> str:
item_history_table = self._storage._meta.tables['item_history']
select_etag_stmt = sa.select(
item_history_table.c,
@ -214,7 +238,6 @@ class Collection(BaseCollection):
),
)
exists: bool
with self._storage._engine.begin() as connection:
item_history = connection.execute(select_etag_stmt).one_or_none()
if item_history is not None:
exists = True
@ -250,7 +273,7 @@ class Collection(BaseCollection):
connection.execute(upsert)
return history_etag
def _get_deleted_history_refs(self):
def _get_deleted_history_refs(self, *, connection):
item_table = self._storage._meta.tables['item']
item_history_table = self._storage._meta.tables['item_history']
select_stmt = sa.select(
@ -267,17 +290,16 @@ class Collection(BaseCollection):
).where(
item_table.c.id == None,
)
with self._storage._engine.begin() as connection:
for row in connection.execute(select_stmt):
yield row.href
def _delete_history_refs(self):
def _delete_history_refs(self, *, connection):
item_history_table = self._storage._meta.tables['item_history']
delete_stmt = sa.delete(
item_history_table,
).where(
sa.and_(
item_history_table.c.href.in_(list(self._get_deleted_history_refs())),
item_history_table.c.href.in_(list(self._get_deleted_history_refs(connection=connection))),
item_history_table.c.collection_id == self._id,
item_history_table.c.modified < (datetime.datetime.now() - datetime.timedelta(seconds=self._storage.configuration.get('storage', 'max_sync_token_age')))
),
@ -285,12 +307,10 @@ class Collection(BaseCollection):
with self._storage._engine.begin() as connection:
connection.execute(delete_stmt)
def sync(self, old_token: str = '') -> Tuple[str, Iterable[str]]:
def _sync(self, *, connection, old_token: str = '') -> Tuple[str, Iterable[str]]:
_prefix = 'http://radicale.org/ns/sync/'
collection_state_table = self._storage._meta.tables['collection_state']
def check_token_name(token_name: str) -> bool:
print(token_name)
print(len(token_name))
if len(token_name) != 64:
return False
for c in token_name:
@ -311,10 +331,10 @@ class Collection(BaseCollection):
# compute new state
for href, item in itertools.chain(
((item.href, item) for item in self.get_all()),
((href, None) for href in self._get_deleted_history_refs())
((href, None) for href in self._get_deleted_history_refs(connection=connection))
):
assert isinstance(href, str)
history_etag = self._update_history_etag(href, item)
history_etag = self._update_history_etag(href, item, connection=connection)
state[href] = history_etag
token_name_hash.update((href + '/' + history_etag).encode())
token_name = token_name_hash.hexdigest()
@ -326,7 +346,6 @@ class Collection(BaseCollection):
# load old state
old_state = {}
with self._storage._engine.begin() as connection:
if old_token_name:
select_stmt = sa.select(
collection_state_table.c,
@ -362,6 +381,10 @@ class Collection(BaseCollection):
return token, changes
def sync(self, old_token: str = '') -> Tuple[str, Iterable[str]]:
with self._storage._engine.begin() as c:
return self._sync(connection=c, old_token=old_token)
class Storage(BaseStorage):
def __init__(self, configuration: "radicale.config.Configuration"):
@ -377,8 +400,7 @@ class Storage(BaseStorage):
path_parts = path_parts[:-1]
return path_parts
def discover(self, path: str, depth: str = "0") -> Iterable["radicale.types.CollectionOrItem"]:
logger.info("path = %s, depth = %s", path, depth)
def _discover(self, path: str, *, connection, depth: str = "0") -> Iterable["radicale.types.CollectionOrItem"]:
if path == '/':
return [Collection(self, self._root_collection.id, '')]
path_parts = self._split_path(path)
@ -440,7 +462,6 @@ class Storage(BaseStorage):
)
l = []
with self._engine.begin() as connection:
self_collection = connection.execute(select_stmt).one_or_none()
if self_collection is None:
return []
@ -458,25 +479,57 @@ class Storage(BaseStorage):
l += [self_collection._row_to_item(row)]
return l
def discover(self, path: str, depth: str = "0") -> Iterable["radicale.types.CollectionOrItem"]:
with self._engine.begin() as c:
return self._discover(path, connection=c, depth=depth)
def _move(self, item: "radicale_item.Item", to_collection: "BaseCollection", to_href: str, *, connection) -> None:
assert isinstance(item.collection, Collection)
assert isinstance(to_collection, Collection)
src_collection_id = item.collection._id
dst_collection_id = to_collection._id
item_table = self._meta.tables['item']
delete_stmt = sa.delete(
item_table,
).where(
sa.and_(
item_table.c.collection_id == dst_collection_id,
item_table.c.name == to_href,
)
)
update_stmt = sa.update(
item_table,
).values(
collection_id=dst_collection_id,
name=to_href,
).where(
sa.and_(
item_table.c.collection_id == src_collection_id,
item_table.c.name == item.href,
)
)
connection.execute(delete_stmt)
connection.execute(update_stmt)
def move(self, item: "radicale_item.Item", to_collection: "BaseCollection", to_href: str) -> None:
pass
with self._engine.begin() as c:
return self._move(item, to_collection, to_href, connection=c)
def create_collection(
def _create_collection(
self,
href: str,
*,
connection,
items: Optional[Iterable["radicale_item.Item"]]=None,
props: Optional[Mapping[str, str]]=None,
) -> "BaseCollection":
print('creating collection')
print(f'href={href}, items={items}, props={props}')
path = self._split_path(href)
parent_id = self._root_collection.id
collection_table = self._meta.tables['collection']
collection_metadata_table = self._meta.tables['collection_metadata']
item_table = self._meta.tables['item']
with self._engine.begin() as connection:
for p in path:
select_stmt = sa.select(
collection_table.c,
@ -525,19 +578,32 @@ class Storage(BaseStorage):
collection_metadata_table,
).values([dict(collection_id=parent_id, key=k, value=v) for k, v in props.items()])
connection.execute(insert_stmt)
if props is not None and 'key' in props and items is not None:
print(items)
# TODO insert items
c = Collection(self, parent_id, '/'.join(path))
print(c)
if props is not None and 'key' in props and items is not None:
for i in items:
assert i.href is not None
c._upload(i.href, i, connection=connection)
return c
def create_collection(
self,
href: str,
items: Optional[Iterable["radicale_item.Item"]]=None,
props: Optional[Mapping[str, str]]=None,
) -> "BaseCollection":
with self._engine.begin() as c:
return self._create_collection(href, connection=c, items=items, props=props)
@radicale.types.contextmanager
def acquire_lock(self, mode: str, user: str = "") -> Iterator[None]:
# locking happens on a db level
def acquire_lock(self, mod: str, user: str = "") -> Iterator[None]:
yield
def verify(self) -> bool:
def _verify(self, *, connection) -> bool:
_ = connection
return True
def verify(self):
with self._engine.begin() as c:
return self._verify(connection=c)