utils.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # -*- coding: utf-8 -*-
  2. import transaction
  3. from os.path import normpath as base_normpath
  4. from wsgidav import util
  5. from wsgidav import compat
  6. from tracim.lib.core.content import ContentApi
  7. from tracim.models.data import Workspace, Content, ContentType, \
  8. ActionDescription
  9. from tracim.models.revision_protection import new_revision
  10. def transform_to_display(string: str) -> str:
  11. """
  12. As characters that Windows does not support may have been inserted
  13. through Tracim in names, before displaying information we update path
  14. so that all these forbidden characters are replaced with similar
  15. shape character that are allowed so that the user isn't trouble and
  16. isn't limited in his naming choice
  17. """
  18. _TO_DISPLAY = {
  19. '/': '⧸',
  20. '\\': '⧹',
  21. ':': '∶',
  22. '*': '∗',
  23. '?': 'ʔ',
  24. '"': 'ʺ',
  25. '<': '❮',
  26. '>': '❯',
  27. '|': '∣'
  28. }
  29. for key, value in _TO_DISPLAY.items():
  30. string = string.replace(key, value)
  31. return string
  32. def transform_to_bdd(string: str) -> str:
  33. """
  34. Called before sending request to the database to recover the right names
  35. """
  36. _TO_BDD = {
  37. '⧸': '/',
  38. '⧹': '\\',
  39. '∶': ':',
  40. '∗': '*',
  41. 'ʔ': '?',
  42. 'ʺ': '"',
  43. '❮': '<',
  44. '❯': '>',
  45. '∣': '|'
  46. }
  47. for key, value in _TO_BDD.items():
  48. string = string.replace(key, value)
  49. return string
  50. def normpath(path):
  51. if path == b'':
  52. path = b'/'
  53. elif path == '':
  54. path = '/'
  55. return base_normpath(path)
  56. class HistoryType(object):
  57. Deleted = 'deleted'
  58. Archived = 'archived'
  59. Standard = 'standard'
  60. All = 'all'
  61. class SpecialFolderExtension(object):
  62. Deleted = '/.deleted'
  63. Archived = '/.archived'
  64. History = '/.history'
  65. class FakeFileStream(object):
  66. """
  67. Fake a FileStream that we're giving to wsgidav to receive data and create files / new revisions
  68. There's two scenarios :
  69. - when a new file is created, wsgidav will call the method createEmptyResource and except to get a _DAVResource
  70. which should have both 'beginWrite' and 'endWrite' method implemented
  71. - when a file which already exists is updated, he's going to call the 'beginWrite' function of the _DAVResource
  72. to get a filestream and write content in it
  73. In the first case scenario, the transfer takes two part : it first create the resource (createEmptyResource)
  74. then add its content (beginWrite, write, close..). If we went without this class, we would create two revision
  75. of the file upon creating a new file, which is not what we want.
  76. """
  77. def __init__(
  78. self,
  79. content_api: ContentApi,
  80. workspace: Workspace,
  81. path: str,
  82. file_name: str='',
  83. content: Content=None,
  84. parent: Content=None
  85. ):
  86. """
  87. :param content_api:
  88. :param workspace:
  89. :param path:
  90. :param file_name:
  91. :param content:
  92. :param parent:
  93. """
  94. self._file_stream = compat.BytesIO()
  95. self._file_name = file_name if file_name != '' else self._content.file_name
  96. self._content = content
  97. self._api = content_api
  98. self._workspace = workspace
  99. self._parent = parent
  100. self._path = path
  101. def getRefUrl(self) -> str:
  102. """
  103. As wsgidav expect to receive a _DAVResource upon creating a new resource, this method's result is used
  104. by Windows client to establish both file's path and file's name
  105. """
  106. return self._path
  107. def beginWrite(self, contentType) -> 'FakeFileStream':
  108. """
  109. Called by wsgidav, it expect a filestream which possess both 'write' and 'close' operation to write
  110. the file content.
  111. """
  112. return self
  113. def endWrite(self, withErrors: bool):
  114. """
  115. Called by request_server when finished writing everything.
  116. As we call operation to create new content or revision in the close operation, called before endWrite, there
  117. is nothing to do here.
  118. """
  119. pass
  120. def write(self, s: str):
  121. """
  122. Called by request_server when writing content to files, we put it inside a filestream
  123. """
  124. self._file_stream.write(s)
  125. def close(self):
  126. """
  127. Called by request_server when the file content has been written. We either add a new content or create
  128. a new revision
  129. """
  130. self._file_stream.seek(0)
  131. if self._content is None:
  132. self.create_file()
  133. else:
  134. self.update_file()
  135. transaction.commit()
  136. def create_file(self):
  137. """
  138. Called when this is a new file; will create a new Content initialized with the correct content
  139. """
  140. is_temporary = self._file_name.startswith('.~') or self._file_name.startswith('~')
  141. file = self._api.create(
  142. content_type=ContentType.File,
  143. workspace=self._workspace,
  144. parent=self._parent,
  145. is_temporary=is_temporary
  146. )
  147. self._api.update_file_data(
  148. file,
  149. self._file_name,
  150. util.guessMimeType(self._file_name),
  151. self._file_stream.read()
  152. )
  153. self._api.save(file, ActionDescription.CREATION)
  154. def update_file(self):
  155. """
  156. Called when we're updating an existing content; we create a new revision and update the file content
  157. """
  158. with new_revision(self._content):
  159. self._api.update_file_data(
  160. self._content,
  161. self._file_name,
  162. util.guessMimeType(self._content.file_name),
  163. self._file_stream.read()
  164. )
  165. self._api.save(self._content, ActionDescription.REVISION)