123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- # -*- coding: utf-8 -*-
-
- import transaction
- from os.path import normpath as base_normpath
-
- from sqlalchemy.orm import Session
- from wsgidav import util
- from wsgidav import compat
-
- from tracim.lib.core.content import ContentApi
- from tracim.models.data import Workspace, Content, ContentType, \
- ActionDescription
- from tracim.models.revision_protection import new_revision
-
-
- def transform_to_display(string: str) -> str:
- """
- As characters that Windows does not support may have been inserted
- through Tracim in names, before displaying information we update path
- so that all these forbidden characters are replaced with similar
- shape character that are allowed so that the user isn't trouble and
- isn't limited in his naming choice
- """
- _TO_DISPLAY = {
- '/': '⧸',
- '\\': '⧹',
- ':': '∶',
- '*': '∗',
- '?': 'ʔ',
- '"': 'ʺ',
- '<': '❮',
- '>': '❯',
- '|': '∣'
- }
-
- for key, value in _TO_DISPLAY.items():
- string = string.replace(key, value)
-
- return string
-
-
- def transform_to_bdd(string: str) -> str:
- """
- Called before sending request to the database to recover the right names
- """
- _TO_BDD = {
- '⧸': '/',
- '⧹': '\\',
- '∶': ':',
- '∗': '*',
- 'ʔ': '?',
- 'ʺ': '"',
- '❮': '<',
- '❯': '>',
- '∣': '|'
- }
-
- for key, value in _TO_BDD.items():
- string = string.replace(key, value)
-
- return string
-
-
- def normpath(path):
- if path == b'':
- path = b'/'
- elif path == '':
- path = '/'
- return base_normpath(path)
-
-
- class HistoryType(object):
- Deleted = 'deleted'
- Archived = 'archived'
- Standard = 'standard'
- All = 'all'
-
-
- class SpecialFolderExtension(object):
- Deleted = '/.deleted'
- Archived = '/.archived'
- History = '/.history'
-
-
- class FakeFileStream(object):
- """
- Fake a FileStream that we're giving to wsgidav to receive data and create files / new revisions
-
- There's two scenarios :
- - when a new file is created, wsgidav will call the method createEmptyResource and except to get a _DAVResource
- which should have both 'beginWrite' and 'endWrite' method implemented
- - when a file which already exists is updated, he's going to call the 'beginWrite' function of the _DAVResource
- to get a filestream and write content in it
-
- In the first case scenario, the transfer takes two part : it first create the resource (createEmptyResource)
- then add its content (beginWrite, write, close..). If we went without this class, we would create two revision
- of the file upon creating a new file, which is not what we want.
- """
-
- def __init__(
- self,
- session: Session,
- content_api: ContentApi,
- workspace: Workspace,
- path: str,
- file_name: str='',
- content: Content=None,
- parent: Content=None
- ):
- """
-
- :param content_api:
- :param workspace:
- :param path:
- :param file_name:
- :param content:
- :param parent:
- """
- self._file_stream = compat.BytesIO()
- self._session = session
- self._file_name = file_name if file_name != '' else self._content.file_name
- self._content = content
- self._api = content_api
- self._workspace = workspace
- self._parent = parent
- self._path = path
-
- def getRefUrl(self) -> str:
- """
- As wsgidav expect to receive a _DAVResource upon creating a new resource, this method's result is used
- by Windows client to establish both file's path and file's name
- """
- return self._path
-
- def beginWrite(self, contentType) -> 'FakeFileStream':
- """
- Called by wsgidav, it expect a filestream which possess both 'write' and 'close' operation to write
- the file content.
- """
- return self
-
- def endWrite(self, withErrors: bool):
- """
- Called by request_server when finished writing everything.
- As we call operation to create new content or revision in the close operation, called before endWrite, there
- is nothing to do here.
- """
- pass
-
- def write(self, s: str):
- """
- Called by request_server when writing content to files, we put it inside a filestream
- """
- self._file_stream.write(s)
-
- def close(self):
- """
- Called by request_server when the file content has been written. We either add a new content or create
- a new revision
- """
-
- self._file_stream.seek(0)
-
- if self._content is None:
- self.create_file()
- else:
- self.update_file()
-
- transaction.commit()
-
- def create_file(self):
- """
- Called when this is a new file; will create a new Content initialized with the correct content
- """
-
- is_temporary = self._file_name.startswith('.~') or self._file_name.startswith('~')
-
- file = self._api.create(
- content_type=ContentType.File,
- workspace=self._workspace,
- parent=self._parent,
- is_temporary=is_temporary
- )
-
- self._api.update_file_data(
- file,
- self._file_name,
- util.guessMimeType(self._file_name),
- self._file_stream.read()
- )
-
- self._api.save(file, ActionDescription.CREATION)
-
- def update_file(self):
- """
- Called when we're updating an existing content; we create a new revision and update the file content
- """
-
- with new_revision(
- session=self._session,
- content=self._content,
- tm=transaction.manager,
- ):
- self._api.update_file_data(
- self._content,
- self._file_name,
- util.guessMimeType(self._content.file_name),
- self._file_stream.read()
- )
-
- self._api.save(self._content, ActionDescription.REVISION)
|