__init__.py 4.9KB

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