__init__.py 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. # coding: utf8
  2. import transaction
  3. from wsgidav import util
  4. from wsgidav import compat
  5. from tracim.lib.content import ContentApi
  6. from tracim.lib.utils import SameValueError
  7. from tracim.lib.base import logger
  8. from tracim.model import new_revision
  9. from tracim.model.data import ActionDescription
  10. from tracim.model.data import ContentType
  11. from tracim.model.data import Content
  12. from tracim.model.data import Workspace
  13. from wsgidav.dav_error import DAVError, HTTP_FORBIDDEN, HTTP_NOT_MODIFIED
  14. class HistoryType(object):
  15. Deleted = 'deleted'
  16. Archived = 'archived'
  17. Standard = 'standard'
  18. All = 'all'
  19. class SpecialFolderExtension(object):
  20. Deleted = '/.deleted'
  21. Archived = '/.archived'
  22. History = '/.history'
  23. class FakeFileStream(object):
  24. """
  25. Fake a FileStream that we're giving to wsgidav to receive data and create files / new revisions
  26. There's two scenarios :
  27. - when a new file is created, wsgidav will call the method createEmptyResource and except to get a _DAVResource
  28. which should have both 'beginWrite' and 'endWrite' method implemented
  29. - when a file which already exists is updated, he's going to call the 'beginWrite' function of the _DAVResource
  30. to get a filestream and write content in it
  31. In the first case scenario, the transfer takes two part : it first create the resource (createEmptyResource)
  32. then add its content (beginWrite, write, close..). If we went without this class, we would create two revision
  33. of the file upon creating a new file, which is not what we want.
  34. """
  35. def __init__(self, content_api: ContentApi, workspace: Workspace, path: str,
  36. file_name: str='', content: Content=None, parent: Content=None):
  37. """
  38. :param content_api:
  39. :param workspace:
  40. :param path:
  41. :param file_name:
  42. :param content:
  43. :param parent:
  44. """
  45. self._file_stream = compat.BytesIO()
  46. self._file_name = file_name if file_name != '' else self._content.file_name
  47. self._content = content
  48. self._api = content_api
  49. self._workspace = workspace
  50. self._parent = parent
  51. self._path = path
  52. def getRefUrl(self) -> str:
  53. """
  54. As wsgidav expect to receive a _DAVResource upon creating a new resource, this method's result is used
  55. by Windows client to establish both file's path and file's name
  56. """
  57. return self._path
  58. def beginWrite(self, contentType) -> 'FakeFileStream':
  59. """
  60. Called by wsgidav, it expect a filestream which possess both 'write' and 'close' operation to write
  61. the file content.
  62. """
  63. return self
  64. def endWrite(self, withErrors: bool):
  65. """
  66. Called by request_server when finished writing everything.
  67. As we call operation to create new content or revision in the close operation, called before endWrite, there
  68. is nothing to do here.
  69. """
  70. pass
  71. def write(self, s: str):
  72. """
  73. Called by request_server when writing content to files, we put it inside a filestream
  74. """
  75. self._file_stream.write(s)
  76. def close(self):
  77. """
  78. Called by request_server when the file content has been written. We either add a new content or create
  79. a new revision
  80. """
  81. self._file_stream.seek(0)
  82. try:
  83. if self._content is None:
  84. self.create_file()
  85. else:
  86. self.update_file()
  87. transaction.commit()
  88. except SameValueError:
  89. # INFO - G.M - 21-03-2018 - Do nothing, file as not change.
  90. msg = 'File {filename} modified through webdav did not change, transaction aborted.' # nopep8
  91. logger.debug(
  92. self,
  93. msg.format(filename=self._file_name)
  94. )
  95. transaction.abort()
  96. transaction.begin()
  97. except ValueError as e:
  98. msg = 'File {filename} modified through webdav can\'t be updated: {exception}' # nopep8
  99. logger.debug(
  100. self,
  101. msg.format(
  102. filename=self._file_name,
  103. e=e.__str__,
  104. )
  105. )
  106. transaction.abort()
  107. transaction.begin()
  108. raise DAVError(HTTP_FORBIDDEN)
  109. def create_file(self):
  110. """
  111. Called when this is a new file; will create a new Content initialized with the correct content
  112. """
  113. is_temporary = self._file_name.startswith('.~') or self._file_name.startswith('~')
  114. file = self._api.create(
  115. content_type=ContentType.File,
  116. workspace=self._workspace,
  117. parent=self._parent,
  118. is_temporary=is_temporary
  119. )
  120. self._api.update_file_data(
  121. file,
  122. self._file_name,
  123. util.guessMimeType(self._file_name),
  124. self._file_stream.read()
  125. )
  126. self._api.save(file, ActionDescription.CREATION)
  127. def update_file(self):
  128. """
  129. Called when we're updating an existing content; we create a new revision and update the file content
  130. """
  131. with new_revision(self._content):
  132. self._api.update_file_data(
  133. self._content,
  134. self._file_name,
  135. util.guessMimeType(self._content.file_name),
  136. self._file_stream.read()
  137. )
  138. self._api.save(self._content, ActionDescription.REVISION)