| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- # This file is part of Radicale - CalDAV and CardDAV server
- # Copyright © 2014 Jean-Marc Martins
- # Copyright © 2012-2017 Guillaume Ayoub
- # Copyright © 2017-2018 Unrud <unrud@outlook.com>
- #
- # This library is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This library is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
- import errno
- import os
- import pickle
- import sys
- from typing import Iterable, Iterator, TextIO, cast
- import radicale.item as radicale_item
- from radicale import pathutils
- from radicale.storage.multifilesystem.base import CollectionBase
- from radicale.storage.multifilesystem.cache import CollectionPartCache
- from radicale.storage.multifilesystem.get import CollectionPartGet
- from radicale.storage.multifilesystem.history import CollectionPartHistory
- class CollectionPartUpload(CollectionPartGet, CollectionPartCache,
- CollectionPartHistory, CollectionBase):
- def upload(self, href: str, item: radicale_item.Item
- ) -> radicale_item.Item:
- if not pathutils.is_safe_filesystem_path_component(href):
- raise pathutils.UnsafePathError(href)
- try:
- self._store_item_cache(href, item)
- except Exception as e:
- raise ValueError("Failed to store item %r in collection %r: %s" %
- (href, self.path, e)) from e
- path = pathutils.path_to_filesystem(self._filesystem_path, href)
- # TODO: better fix for "mypy"
- with self._atomic_write(path, newline="") as fo: # type: ignore
- f = cast(TextIO, fo)
- f.write(item.serialize())
- # Clean the cache after the actual item is stored, or the cache entry
- # will be removed again.
- self._clean_item_cache()
- # Track the change
- self._update_history_etag(href, item)
- self._clean_history()
- uploaded_item = self._get(href, verify_href=False)
- if uploaded_item is None:
- raise RuntimeError("Storage modified externally")
- return uploaded_item
- def _upload_all_nonatomic(self, items: Iterable[radicale_item.Item],
- suffix: str = "") -> None:
- """Upload a new set of items non-atomic"""
- def is_safe_free_href(href: str) -> bool:
- return (pathutils.is_safe_filesystem_path_component(href) and
- not os.path.lexists(
- os.path.join(self._filesystem_path, href)))
- def get_safe_free_hrefs(uid: str) -> Iterator[str]:
- for href in [uid if uid.lower().endswith(suffix.lower())
- else uid + suffix,
- radicale_item.get_etag(uid).strip('"') + suffix]:
- if is_safe_free_href(href):
- yield href
- yield radicale_item.find_available_uid(
- lambda href: not is_safe_free_href(href), suffix)
- cache_folder = os.path.join(self._filesystem_path,
- ".Radicale.cache", "item")
- self._storage._makedirs_synced(cache_folder)
- for item in items:
- uid = item.uid
- try:
- cache_content = self._item_cache_content(item)
- except Exception as e:
- raise ValueError(
- "Failed to store item %r in temporary collection %r: %s" %
- (uid, self.path, e)) from e
- for href in get_safe_free_hrefs(uid):
- try:
- f = open(os.path.join(self._filesystem_path, href),
- "w", newline="", encoding=self._encoding)
- except OSError as e:
- if (sys.platform != "win32" and e.errno == errno.EINVAL or
- sys.platform == "win32" and e.errno == 123):
- # not a valid filename
- continue
- raise
- break
- else:
- raise RuntimeError("No href found for item %r in temporary "
- "collection %r" % (uid, self.path))
- with f:
- f.write(item.serialize())
- f.flush()
- self._storage._fsync(f)
- with open(os.path.join(cache_folder, href), "wb") as fb:
- pickle.dump(cache_content, fb)
- fb.flush()
- self._storage._fsync(fb)
- self._storage._sync_directory(cache_folder)
- self._storage._sync_directory(self._filesystem_path)
|