Synced with origin
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# This file is part of Radicale Server - Calendar Server
|
||||
# This file is part of Radicale - CalDAV and CardDAV server
|
||||
# Copyright © 2014 Jean-Marc Martins
|
||||
# Copyright © 2012-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2019 Unrud <unrud@outlook.com>
|
||||
@@ -23,75 +23,57 @@ Uses one folder per collection and one file per collection entry.
|
||||
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import time
|
||||
from itertools import chain
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import ClassVar, Iterator, Optional, Type
|
||||
|
||||
from radicale import pathutils, storage
|
||||
from radicale.storage.multifilesystem.cache import CollectionCacheMixin
|
||||
from radicale import config
|
||||
from radicale.storage.multifilesystem.base import CollectionBase, StorageBase
|
||||
from radicale.storage.multifilesystem.cache import CollectionPartCache
|
||||
from radicale.storage.multifilesystem.create_collection import \
|
||||
StorageCreateCollectionMixin
|
||||
from radicale.storage.multifilesystem.delete import CollectionDeleteMixin
|
||||
from radicale.storage.multifilesystem.discover import StorageDiscoverMixin
|
||||
from radicale.storage.multifilesystem.get import CollectionGetMixin
|
||||
from radicale.storage.multifilesystem.history import CollectionHistoryMixin
|
||||
from radicale.storage.multifilesystem.lock import (CollectionLockMixin,
|
||||
StorageLockMixin)
|
||||
from radicale.storage.multifilesystem.meta import CollectionMetaMixin
|
||||
from radicale.storage.multifilesystem.move import StorageMoveMixin
|
||||
from radicale.storage.multifilesystem.sync import CollectionSyncMixin
|
||||
from radicale.storage.multifilesystem.upload import CollectionUploadMixin
|
||||
from radicale.storage.multifilesystem.verify import StorageVerifyMixin
|
||||
StoragePartCreateCollection
|
||||
from radicale.storage.multifilesystem.delete import CollectionPartDelete
|
||||
from radicale.storage.multifilesystem.discover import StoragePartDiscover
|
||||
from radicale.storage.multifilesystem.get import CollectionPartGet
|
||||
from radicale.storage.multifilesystem.history import CollectionPartHistory
|
||||
from radicale.storage.multifilesystem.lock import (CollectionPartLock,
|
||||
StoragePartLock)
|
||||
from radicale.storage.multifilesystem.meta import CollectionPartMeta
|
||||
from radicale.storage.multifilesystem.move import StoragePartMove
|
||||
from radicale.storage.multifilesystem.sync import CollectionPartSync
|
||||
from radicale.storage.multifilesystem.upload import CollectionPartUpload
|
||||
from radicale.storage.multifilesystem.verify import StoragePartVerify
|
||||
|
||||
|
||||
class Collection(
|
||||
CollectionCacheMixin, CollectionDeleteMixin, CollectionGetMixin,
|
||||
CollectionHistoryMixin, CollectionLockMixin, CollectionMetaMixin,
|
||||
CollectionSyncMixin, CollectionUploadMixin, storage.BaseCollection):
|
||||
CollectionPartDelete, CollectionPartMeta, CollectionPartSync,
|
||||
CollectionPartUpload, CollectionPartGet, CollectionPartCache,
|
||||
CollectionPartLock, CollectionPartHistory, CollectionBase):
|
||||
|
||||
def __init__(self, storage_, path, filesystem_path=None):
|
||||
self._storage = storage_
|
||||
folder = self._storage._get_collection_root_folder()
|
||||
# Path should already be sanitized
|
||||
self._path = pathutils.strip_path(path)
|
||||
self._encoding = self._storage.configuration.get("encoding", "stock")
|
||||
if filesystem_path is None:
|
||||
filesystem_path = pathutils.path_to_filesystem(folder, self.path)
|
||||
self._filesystem_path = filesystem_path
|
||||
_etag_cache: Optional[str]
|
||||
|
||||
def __init__(self, storage_: "Storage", path: str,
|
||||
filesystem_path: Optional[str] = None) -> None:
|
||||
super().__init__(storage_, path, filesystem_path)
|
||||
self._etag_cache = None
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
def path(self) -> str:
|
||||
return self._path
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _atomic_write(self, path, mode="w", newline=None):
|
||||
parent_dir, name = os.path.split(path)
|
||||
# Do not use mkstemp because it creates with permissions 0o600
|
||||
with TemporaryDirectory(
|
||||
prefix=".Radicale.tmp-", dir=parent_dir) as tmp_dir:
|
||||
with open(os.path.join(tmp_dir, name), mode, newline=newline,
|
||||
encoding=None if "b" in mode else self._encoding) as tmp:
|
||||
yield tmp
|
||||
tmp.flush()
|
||||
self._storage._fsync(tmp)
|
||||
os.replace(os.path.join(tmp_dir, name), path)
|
||||
self._storage._sync_directory(parent_dir)
|
||||
|
||||
@property
|
||||
def last_modified(self):
|
||||
relevant_files = chain(
|
||||
(self._filesystem_path,),
|
||||
(self._props_path,) if os.path.exists(self._props_path) else (),
|
||||
(os.path.join(self._filesystem_path, h) for h in self._list()))
|
||||
last = max(map(os.path.getmtime, relevant_files))
|
||||
def last_modified(self) -> str:
|
||||
def relevant_files_iter() -> Iterator[str]:
|
||||
yield self._filesystem_path
|
||||
if os.path.exists(self._props_path):
|
||||
yield self._props_path
|
||||
for href in self._list():
|
||||
yield os.path.join(self._filesystem_path, href)
|
||||
last = max(map(os.path.getmtime, relevant_files_iter()))
|
||||
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(last))
|
||||
|
||||
@property
|
||||
def etag(self):
|
||||
def etag(self) -> str:
|
||||
# reuse cached value if the storage is read-only
|
||||
if self._storage._lock.locked == "w" or self._etag_cache is None:
|
||||
self._etag_cache = super().etag
|
||||
@@ -99,61 +81,11 @@ class Collection(
|
||||
|
||||
|
||||
class Storage(
|
||||
StorageCreateCollectionMixin, StorageDiscoverMixin, StorageLockMixin,
|
||||
StorageMoveMixin, StorageVerifyMixin, storage.BaseStorage):
|
||||
StoragePartCreateCollection, StoragePartLock, StoragePartMove,
|
||||
StoragePartVerify, StoragePartDiscover, StorageBase):
|
||||
|
||||
_collection_class = Collection
|
||||
_collection_class: ClassVar[Type[Collection]] = Collection
|
||||
|
||||
def __init__(self, configuration):
|
||||
def __init__(self, configuration: config.Configuration) -> None:
|
||||
super().__init__(configuration)
|
||||
folder = configuration.get("storage", "filesystem_folder")
|
||||
self._makedirs_synced(folder)
|
||||
|
||||
def _get_collection_root_folder(self):
|
||||
filesystem_folder = self.configuration.get(
|
||||
"storage", "filesystem_folder")
|
||||
return os.path.join(filesystem_folder, "collection-root")
|
||||
|
||||
def _fsync(self, f):
|
||||
if self.configuration.get("storage", "_filesystem_fsync"):
|
||||
try:
|
||||
pathutils.fsync(f.fileno())
|
||||
except OSError as e:
|
||||
raise RuntimeError("Fsync'ing file %r failed: %s" %
|
||||
(f.name, e)) from e
|
||||
|
||||
def _sync_directory(self, path):
|
||||
"""Sync directory to disk.
|
||||
|
||||
This only works on POSIX and does nothing on other systems.
|
||||
|
||||
"""
|
||||
if not self.configuration.get("storage", "_filesystem_fsync"):
|
||||
return
|
||||
if os.name == "posix":
|
||||
try:
|
||||
fd = os.open(path, 0)
|
||||
try:
|
||||
pathutils.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
except OSError as e:
|
||||
raise RuntimeError("Fsync'ing directory %r failed: %s" %
|
||||
(path, e)) from e
|
||||
|
||||
def _makedirs_synced(self, filesystem_path):
|
||||
"""Recursively create a directory and its parents in a sync'ed way.
|
||||
|
||||
This method acts silently when the folder already exists.
|
||||
|
||||
"""
|
||||
if os.path.isdir(filesystem_path):
|
||||
return
|
||||
parent_filesystem_path = os.path.dirname(filesystem_path)
|
||||
# Prevent infinite loop
|
||||
if filesystem_path != parent_filesystem_path:
|
||||
# Create parent dirs recursively
|
||||
self._makedirs_synced(parent_filesystem_path)
|
||||
# Possible race!
|
||||
os.makedirs(filesystem_path, exist_ok=True)
|
||||
self._sync_directory(parent_filesystem_path)
|
||||
self._makedirs_synced(self._filesystem_folder)
|
||||
|
||||
Reference in New Issue
Block a user