1
0

__init__.py 5.4 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-2021 Unrud <unrud@outlook.com>
  5. # Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
  6. #
  7. # This library is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This library is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  19. """
  20. Storage backend that stores data in the file system.
  21. Uses one folder per collection and one file per collection entry.
  22. """
  23. import os
  24. import sys
  25. import time
  26. from typing import ClassVar, Iterator, Optional, Type
  27. from radicale import config
  28. from radicale.log import logger
  29. from radicale.storage.multifilesystem.base import CollectionBase, StorageBase
  30. from radicale.storage.multifilesystem.cache import CollectionPartCache
  31. from radicale.storage.multifilesystem.create_collection import \
  32. StoragePartCreateCollection
  33. from radicale.storage.multifilesystem.delete import CollectionPartDelete
  34. from radicale.storage.multifilesystem.discover import StoragePartDiscover
  35. from radicale.storage.multifilesystem.get import CollectionPartGet
  36. from radicale.storage.multifilesystem.history import CollectionPartHistory
  37. from radicale.storage.multifilesystem.lock import (CollectionPartLock,
  38. StoragePartLock)
  39. from radicale.storage.multifilesystem.meta import CollectionPartMeta
  40. from radicale.storage.multifilesystem.move import StoragePartMove
  41. from radicale.storage.multifilesystem.sync import CollectionPartSync
  42. from radicale.storage.multifilesystem.upload import CollectionPartUpload
  43. from radicale.storage.multifilesystem.verify import StoragePartVerify
  44. class Collection(
  45. CollectionPartDelete, CollectionPartMeta, CollectionPartSync,
  46. CollectionPartUpload, CollectionPartGet, CollectionPartCache,
  47. CollectionPartLock, CollectionPartHistory, CollectionBase):
  48. _etag_cache: Optional[str]
  49. def __init__(self, storage_: "Storage", path: str,
  50. filesystem_path: Optional[str] = None) -> None:
  51. super().__init__(storage_, path, filesystem_path)
  52. self._etag_cache = None
  53. @property
  54. def path(self) -> str:
  55. return self._path
  56. @property
  57. def last_modified(self) -> str:
  58. def relevant_files_iter() -> Iterator[str]:
  59. yield self._filesystem_path
  60. if os.path.exists(self._props_path):
  61. yield self._props_path
  62. for href in self._list():
  63. yield os.path.join(self._filesystem_path, href)
  64. last = max(map(os.path.getmtime, relevant_files_iter()))
  65. return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(last))
  66. @property
  67. def etag(self) -> str:
  68. # reuse cached value if the storage is read-only
  69. if self._storage._lock.locked == "w" or self._etag_cache is None:
  70. self._etag_cache = super().etag
  71. return self._etag_cache
  72. class Storage(
  73. StoragePartCreateCollection, StoragePartLock, StoragePartMove,
  74. StoragePartVerify, StoragePartDiscover, StorageBase):
  75. _collection_class: ClassVar[Type[Collection]] = Collection
  76. def __init__(self, configuration: config.Configuration) -> None:
  77. super().__init__(configuration)
  78. logger.info("storage location: %r", self._filesystem_folder)
  79. self._makedirs_synced(self._filesystem_folder)
  80. logger.info("storage location subfolder: %r", self._get_collection_root_folder())
  81. logger.info("storage cache subfolder usage for 'item': %s", self._use_cache_subfolder_for_item)
  82. logger.info("storage cache subfolder usage for 'history': %s", self._use_cache_subfolder_for_history)
  83. logger.info("storage cache subfolder usage for 'sync-token': %s", self._use_cache_subfolder_for_synctoken)
  84. logger.info("storage cache use mtime and size for 'item': %s", self._use_mtime_and_size_for_item_cache)
  85. logger.debug("storage cache action logging: %s", self._debug_cache_actions)
  86. if self._use_cache_subfolder_for_item is True or self._use_cache_subfolder_for_history is True or self._use_cache_subfolder_for_synctoken is True:
  87. logger.info("storage cache subfolder: %r", self._get_collection_cache_folder())
  88. self._makedirs_synced(self._get_collection_cache_folder())
  89. if sys.platform != "win32":
  90. if not self._folder_umask:
  91. # retrieve current umask by setting a dummy umask
  92. current_umask = os.umask(0o0022)
  93. logger.info("storage folder umask (from system): '%04o'", current_umask)
  94. # reset to original
  95. os.umask(current_umask)
  96. else:
  97. try:
  98. config_umask = int(self._folder_umask, 8)
  99. except Exception:
  100. logger.critical("storage folder umask defined but invalid: '%s'", self._folder_umask)
  101. raise
  102. logger.info("storage folder umask defined: '%04o'", config_umask)
  103. self._config_umask = config_umask