upload.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2014 Jean-Marc Martins
  3. # Copyright © 2012-2017 Guillaume Ayoub
  4. # Copyright © 2017-2018 Unrud <unrud@outlook.com>
  5. #
  6. # This library is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  18. import os
  19. import pickle
  20. import sys
  21. from typing import Iterable, Set, TextIO, cast
  22. import radicale.item as radicale_item
  23. from radicale import pathutils
  24. from radicale.storage.multifilesystem.base import CollectionBase
  25. from radicale.storage.multifilesystem.cache import CollectionPartCache
  26. from radicale.storage.multifilesystem.get import CollectionPartGet
  27. from radicale.storage.multifilesystem.history import CollectionPartHistory
  28. class CollectionPartUpload(CollectionPartGet, CollectionPartCache,
  29. CollectionPartHistory, CollectionBase):
  30. def upload(self, href: str, item: radicale_item.Item
  31. ) -> radicale_item.Item:
  32. if not pathutils.is_safe_filesystem_path_component(href):
  33. raise pathutils.UnsafePathError(href)
  34. try:
  35. self._store_item_cache(href, item)
  36. except Exception as e:
  37. raise ValueError("Failed to store item %r in collection %r: %s" %
  38. (href, self.path, e)) from e
  39. path = pathutils.path_to_filesystem(self._filesystem_path, href)
  40. with self._atomic_write(path, newline="") as fo:
  41. f = cast(TextIO, fo)
  42. f.write(item.serialize())
  43. # Clean the cache after the actual item is stored, or the cache entry
  44. # will be removed again.
  45. self._clean_item_cache()
  46. # Track the change
  47. self._update_history_etag(href, item)
  48. self._clean_history()
  49. uploaded_item = self._get(href, verify_href=False)
  50. if uploaded_item is None:
  51. raise RuntimeError("Storage modified externally")
  52. return uploaded_item
  53. def _upload_all_nonatomic(self, items: Iterable[radicale_item.Item],
  54. suffix: str = "") -> None:
  55. """Upload a new set of items.
  56. This takes a list of vobject items and
  57. uploads them nonatomic and without existence checks.
  58. """
  59. cache_folder = os.path.join(self._filesystem_path,
  60. ".Radicale.cache", "item")
  61. self._storage._makedirs_synced(cache_folder)
  62. hrefs: Set[str] = set()
  63. for item in items:
  64. uid = item.uid
  65. try:
  66. cache_content = self._item_cache_content(item)
  67. except Exception as e:
  68. raise ValueError(
  69. "Failed to store item %r in temporary collection %r: %s" %
  70. (uid, self.path, e)) from e
  71. href_candidate_funtions = []
  72. if os.name == "posix" or sys.platform == "win32":
  73. href_candidate_funtions.append(
  74. lambda: uid if uid.lower().endswith(suffix.lower())
  75. else uid + suffix)
  76. href_candidate_funtions.extend((
  77. lambda: radicale_item.get_etag(uid).strip('"') + suffix,
  78. lambda: radicale_item.find_available_uid(hrefs.__contains__,
  79. suffix)))
  80. href = f = None
  81. while href_candidate_funtions:
  82. href = href_candidate_funtions.pop(0)()
  83. if href in hrefs:
  84. continue
  85. if not pathutils.is_safe_filesystem_path_component(href):
  86. if not href_candidate_funtions:
  87. raise pathutils.UnsafePathError(href)
  88. continue
  89. try:
  90. f = open(pathutils.path_to_filesystem(
  91. self._filesystem_path, href),
  92. "w", newline="", encoding=self._encoding)
  93. break
  94. except OSError as e:
  95. if href_candidate_funtions and (
  96. os.name == "posix" and e.errno == 22 or
  97. sys.platform == "win32" and e.errno == 123):
  98. continue
  99. raise
  100. assert href is not None and f is not None
  101. with f:
  102. f.write(item.serialize())
  103. f.flush()
  104. self._storage._fsync(f)
  105. hrefs.add(href)
  106. with open(os.path.join(cache_folder, href), "wb") as fb:
  107. pickle.dump(cache_content, fb)
  108. fb.flush()
  109. self._storage._fsync(fb)
  110. self._storage._sync_directory(cache_folder)
  111. self._storage._sync_directory(self._filesystem_path)