class FileResource(DAVNonCollection): """ FileResource resource corresponding to tracim's files """ def __init__(self, path: str, environ: dict, content: Content, tracim_context: "WebdavTracimContext") -> None: super(FileResource, self).__init__(path, environ) self.tracim_context = tracim_context self.content = content self.user = tracim_context.current_user self.session = tracim_context.dbsession self.content_api = ContentApi(current_user=self.user, config=tracim_context.app_config, session=self.session) # this is the property that windows client except to check if the file is read-write or read-only, # but i wasn't able to set this property so you'll have to look into it >.> # self.setPropertyValue('Win32FileAttributes', '00000021') def __repr__(self) -> str: return "<DAVNonCollection: FileResource (%d)>" % self.content.revision_id @webdav_check_right(is_reader) def getContentLength(self) -> int: return self.content.depot_file.file.content_length @webdav_check_right(is_reader) def getContentType(self) -> str: return self.content.file_mimetype @webdav_check_right(is_reader) def getCreationDate(self) -> float: return mktime(self.content.created.timetuple()) @webdav_check_right(is_reader) def getDisplayName(self) -> str: return webdav_convert_file_name_to_display(self.content.file_name) @webdav_check_right(is_reader) def getDisplayInfo(self): return {"type": self.content.type.capitalize()} @webdav_check_right(is_reader) def getLastModified(self) -> float: return mktime(self.content.updated.timetuple()) @webdav_check_right(is_reader) def getContent(self) -> typing.BinaryIO: filestream = compat.BytesIO() filestream.write(self.content.depot_file.file.read()) filestream.seek(0) return filestream def beginWrite(self, contentType: str = None) -> FakeFileStream: return FakeFileStream( content=self.content, content_api=self.content_api, file_name=self.content.file_name, workspace=self.content.workspace, path=self.path, session=self.session, ) def moveRecursive(self, destpath): """As we support recursive move, copymovesingle won't be called, though with copy it'll be called but i have to check if the client ever call that function...""" destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) if normpath(dirname(destpath)) == normpath(dirname(self.path)): # INFO - G.M - 2018-12-12 - renaming case checker = is_contributor else: # INFO - G.M - 2018-12-12 - move case checker = can_move_content try: checker.check(self.tracim_context) except TracimException: raise DAVError(HTTP_FORBIDDEN) invalid_path = False # if content is either deleted or archived, we'll check that we try moving it to the parent # if yes, then we'll unarchive / undelete them, else the action's not allowed if self.content.is_deleted or self.content.is_archived: # we remove all archived and deleted from the path and we check to the destpath # has to be equal or else path not valid # ex: /a/b/.deleted/resource, to be valid destpath has to be = /a/b/resource (no other solution) current_path = re.sub(r"/\.(deleted|archived)", "", self.path) if current_path == destpath: ManageActions( action_type=ActionDescription.UNDELETION if self.content.is_deleted else ActionDescription.UNARCHIVING, api=self.content_api, content=self.content, session=self.session, ).action() else: invalid_path = True # if the content is not deleted / archived, check if we're trying to delete / archive it by # moving it to a .deleted / .archived folder elif basename(dirname(destpath)) in [".deleted", ".archived"]: # same test as above ^ dest_path = re.sub(r"/\.(deleted|archived)", "", destpath) if dest_path == self.path: ManageActions( action_type=ActionDescription.DELETION if ".deleted" in destpath else ActionDescription.ARCHIVING, api=self.content_api, content=self.content, session=self.session, ).action() else: invalid_path = True # else we check if the path is good (not at the root path / not in a deleted/archived path) # and we move the content else: invalid_path = any(x in destpath for x in [".deleted", ".archived"]) invalid_path = invalid_path or any( x in self.path for x in [".deleted", ".archived"]) invalid_path = (invalid_path or dirname(destpath) == self.environ["http_authenticator.realm"]) if not invalid_path: self.move_file(destpath) if invalid_path: raise DAVError(HTTP_FORBIDDEN) def move_file(self, destpath: str) -> None: """ Move file mean changing the path to access to a file. This can mean simple renaming(1), moving file from a directory to one another(2) but also renaming + moving file from a directory to one another at the same time (3). (1): move /dir1/file1 -> /dir1/file2 (2): move /dir1/file1 -> /dir2/file1 (3): move /dir1/file1 -> /dir2/file2 :param destpath: destination path of webdav move :return: nothing """ workspace = self.content.workspace parent = self.content.parent destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) if normpath(dirname(destpath)) == normpath(dirname(self.path)): # INFO - G.M - 2018-12-12 - renaming case checker = is_contributor else: # INFO - G.M - 2018-12-12 - move case checker = can_move_content try: checker.check(self.tracim_context) except TracimException: raise DAVError(HTTP_FORBIDDEN) try: with new_revision(content=self.content, tm=transaction.manager, session=self.session): # INFO - G.M - 2018-03-09 - First, renaming file if needed if basename(destpath) != self.getDisplayName(): new_filename = webdav_convert_file_name_to_bdd( basename(destpath)) regex_file_extension = re.compile("(?P<label>.*){}".format( re.escape(self.content.file_extension))) same_extension = regex_file_extension.match(new_filename) if same_extension: new_label = same_extension.group("label") new_file_extension = self.content.file_extension else: new_label, new_file_extension = os.path.splitext( new_filename) self.content_api.update_content(self.content, new_label=new_label) self.content.file_extension = new_file_extension self.content_api.save(self.content) # INFO - G.M - 2018-03-09 - Moving file if needed destination_workspace = self.tracim_context.candidate_workspace try: destination_parent = self.tracim_context.candidate_parent_content except ContentNotFound: destination_parent = None if destination_parent != parent or destination_workspace != workspace: # INFO - G.M - 12-03-2018 - Avoid moving the file "at the same place" # if the request does not result in a real move. self.content_api.move( item=self.content, new_parent=destination_parent, must_stay_in_same_workspace=False, new_workspace=destination_workspace, ) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) from exc transaction.commit() def copyMoveSingle(self, destpath, isMove): if isMove: # INFO - G.M - 12-03-2018 - This case should not happen # As far as moveRecursive method exist, all move should not go # through this method. If such case appear, try replace this to : #### # self.move_file(destpath) # return #### raise NotImplementedError("Feature not available") destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) try: can_move_content.check(self.tracim_context) except TracimException: raise DAVError(HTTP_FORBIDDEN) new_filename = webdav_convert_file_name_to_bdd(basename(destpath)) regex_file_extension = re.compile("(?P<label>.*){}".format( re.escape(self.content.file_extension))) same_extension = regex_file_extension.match(new_filename) if same_extension: new_label = same_extension.group("label") new_file_extension = self.content.file_extension else: new_label, new_file_extension = os.path.splitext(new_filename) self.tracim_context.set_destpath(destpath) destination_workspace = self.tracim_context.candidate_workspace try: destination_parent = self.tracim_context.candidate_parent_content except ContentNotFound: destination_parent = None try: self.content_api.copy( item=self.content, new_label=new_label, new_file_extension=new_file_extension, new_parent=destination_parent, new_workspace=destination_workspace, ) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) from exc transaction.commit() def supportRecursiveMove(self, destpath): return True @webdav_check_right(is_content_manager) def delete(self): ManageActions( action_type=ActionDescription.DELETION, api=self.content_api, content=self.content, session=self.session, ).action()
class FileResource(DAVNonCollection): """ FileResource resource corresponding to tracim's files """ def __init__(self, path: str, environ: dict, content: Content, tracim_context: "WebdavTracimContext") -> None: super(FileResource, self).__init__(path, environ) self.tracim_context = tracim_context self.content = content self.user = tracim_context.current_user self.session = tracim_context.dbsession self.content_api = ContentApi( current_user=self.user, config=tracim_context.app_config, session=self.session, namespaces_filter=[self.content.content_namespace], ) # this is the property that windows client except to check if the file is read-write or read-only, # but i wasn't able to set this property so you'll have to look into it >.> # self.setPropertyValue('Win32FileAttributes', '00000021') def __repr__(self) -> str: return "<DAVNonCollection: FileResource (%d)>" % self.content.cached_revision_id @webdav_check_right(is_reader) def getContentLength(self) -> int: return self.content.depot_file.file.content_length @webdav_check_right(is_reader) def getContentType(self) -> str: return self.content.file_mimetype @webdav_check_right(is_reader) def getCreationDate(self) -> float: return mktime(self.content.created.timetuple()) @webdav_check_right(is_reader) def getDisplayName(self) -> str: return webdav_convert_file_name_to_display(self.content.file_name) @webdav_check_right(is_reader) def getDisplayInfo(self): return {"type": self.content.type.capitalize()} def getLastModified(self) -> float: return mktime(self.content.updated.timetuple()) @webdav_check_right(is_reader) def getContent(self) -> typing.BinaryIO: return self.content.depot_file.file @webdav_check_right(is_contributor) def beginWrite(self, contentType: str = None) -> FakeFileStream: try: self.content_api.check_upload_size( int(self.environ["CONTENT_LENGTH"]), self.content.workspace) except ( FileSizeOverMaxLimitation, FileSizeOverWorkspaceEmptySpace, FileSizeOverOwnerEmptySpace, ) as exc: raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc)) return FakeFileStream( content=self.content, content_api=self.content_api, file_name=self.content.file_name, workspace=self.content.workspace, path=self.path, session=self.session, ) def moveRecursive(self, destpath): """As we support recursive move, copymovesingle won't be called, though with copy it'll be called but i have to check if the client ever call that function...""" destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) if normpath(dirname(destpath)) == normpath(dirname(self.path)): # INFO - G.M - 2018-12-12 - renaming case checker = is_contributor else: # INFO - G.M - 2018-12-12 - move case checker = can_move_content try: checker.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) invalid_path = False invalid_path = dirname( destpath) == self.environ["http_authenticator.realm"] if not invalid_path: self.move_file(destpath) if invalid_path: raise DAVError(HTTP_FORBIDDEN) def move_file(self, destpath: str) -> None: """ Move file mean changing the path to access to a file. This can mean simple renaming(1), moving file from a directory to one another(2) but also renaming + moving file from a directory to one another at the same time (3). (1): move /dir1/file1 -> /dir1/file2 (2): move /dir1/file1 -> /dir2/file1 (3): move /dir1/file1 -> /dir2/file2 :param destpath: destination path of webdav move :return: nothing """ workspace = self.content.workspace parent = self.content.parent destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) if normpath(dirname(destpath)) == normpath(dirname(self.path)): # INFO - G.M - 2018-12-12 - renaming case checker = is_contributor else: # INFO - G.M - 2018-12-12 - move case checker = can_move_content try: checker.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) try: with new_revision(content=self.content, tm=transaction.manager, session=self.session): # INFO - G.M - 2018-03-09 - First, renaming file if needed if basename(destpath) != self.getDisplayName(): new_filename = webdav_convert_file_name_to_bdd( basename(destpath)) regex_file_extension = re.compile("(?P<label>.*){}".format( re.escape(self.content.file_extension))) same_extension = regex_file_extension.match(new_filename) if same_extension: new_label = same_extension.group("label") new_file_extension = self.content.file_extension else: new_label, new_file_extension = os.path.splitext( new_filename) self.content_api.update_content(self.content, new_label=new_label) self.content.file_extension = new_file_extension self.content_api.save(self.content) # INFO - G.M - 2018-03-09 - Moving file if needed destination_workspace = self.tracim_context.candidate_workspace try: destination_parent = self.tracim_context.candidate_parent_content except ContentNotFound: destination_parent = None if destination_parent != parent or destination_workspace != workspace: # INFO - G.M - 12-03-2018 - Avoid moving the file "at the same place" # if the request does not result in a real move. self.content_api.move( item=self.content, new_parent=destination_parent, must_stay_in_same_workspace=False, new_workspace=destination_workspace, ) self.content_api.execute_update_content_actions(self.content) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc transaction.commit() def copyMoveSingle(self, destpath, isMove): if isMove: # INFO - G.M - 12-03-2018 - This case should not happen # As far as moveRecursive method exist, all move should not go # through this method. If such case appear, try replace this to : #### # self.move_file(destpath) # return #### raise NotImplementedError("Feature not available") destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) try: can_move_content.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) content_in_context = self.content_api.get_content_in_context( self.content) try: self.content_api.check_upload_size(content_in_context.size or 0, self.content.workspace) except ( FileSizeOverMaxLimitation, FileSizeOverWorkspaceEmptySpace, FileSizeOverOwnerEmptySpace, ) as exc: raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc)) new_filename = webdav_convert_file_name_to_bdd(basename(destpath)) regex_file_extension = re.compile("(?P<label>.*){}".format( re.escape(self.content.file_extension))) same_extension = regex_file_extension.match(new_filename) if same_extension: new_label = same_extension.group("label") new_file_extension = self.content.file_extension else: new_label, new_file_extension = os.path.splitext(new_filename) self.tracim_context.set_destpath(destpath) destination_workspace = self.tracim_context.candidate_workspace try: destination_parent = self.tracim_context.candidate_parent_content except ContentNotFound: destination_parent = None try: new_content = self.content_api.copy( item=self.content, new_label=new_label, new_file_extension=new_file_extension, new_parent=destination_parent, new_workspace=destination_workspace, ) self.content_api.execute_created_content_actions(new_content) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc transaction.commit() def supportRecursiveMove(self, destpath): return True @webdav_check_right(is_content_manager) def delete(self): try: with new_revision(session=self.session, tm=transaction.manager, content=self.content): self.content_api.delete(self.content) self.content_api.execute_update_content_actions(self.content) self.content_api.save(self.content) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc transaction.commit()
class FileResource(DAVNonCollection): """ FileResource resource corresponding to tracim's files """ def __init__( self, path: str, environ: dict, content: Content, tracim_context: 'WebdavTracimContext' ) -> None: super(FileResource, self).__init__(path, environ) self.tracim_context = tracim_context self.content = content self.user = tracim_context.current_user self.session = tracim_context.dbsession self.content_api = ContentApi( current_user=self.user, config=tracim_context.app_config, session=self.session, ) # this is the property that windows client except to check if the file is read-write or read-only, # but i wasn't able to set this property so you'll have to look into it >.> # self.setPropertyValue('Win32FileAttributes', '00000021') def __repr__(self) -> str: return "<DAVNonCollection: FileResource (%d)>" % self.content.revision_id @webdav_check_right(is_reader) def getContentLength(self) -> int: return self.content.depot_file.file.content_length @webdav_check_right(is_reader) def getContentType(self) -> str: return self.content.file_mimetype @webdav_check_right(is_reader) def getCreationDate(self) -> float: return mktime(self.content.created.timetuple()) @webdav_check_right(is_reader) def getDisplayName(self) -> str: return webdav_convert_file_name_to_display(self.content.file_name) @webdav_check_right(is_reader) def getDisplayInfo(self): return { 'type': self.content.type.capitalize(), } @webdav_check_right(is_reader) def getLastModified(self) -> float: return mktime(self.content.updated.timetuple()) @webdav_check_right(is_reader) def getContent(self) -> typing.BinaryIO: filestream = compat.BytesIO() filestream.write(self.content.depot_file.file.read()) filestream.seek(0) return filestream def beginWrite(self, contentType: str=None) -> FakeFileStream: return FakeFileStream( content=self.content, content_api=self.content_api, file_name=self.content.file_name, workspace=self.content.workspace, path=self.path, session=self.session, ) def moveRecursive(self, destpath): """As we support recursive move, copymovesingle won't be called, though with copy it'll be called but i have to check if the client ever call that function...""" destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) if normpath(dirname(destpath)) == normpath(dirname(self.path)): # INFO - G.M - 2018-12-12 - renaming case checker = is_contributor else: # INFO - G.M - 2018-12-12 - move case checker = can_move_content try: checker.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) invalid_path = False # if content is either deleted or archived, we'll check that we try moving it to the parent # if yes, then we'll unarchive / undelete them, else the action's not allowed if self.content.is_deleted or self.content.is_archived: # we remove all archived and deleted from the path and we check to the destpath # has to be equal or else path not valid # ex: /a/b/.deleted/resource, to be valid destpath has to be = /a/b/resource (no other solution) current_path = re.sub(r'/\.(deleted|archived)', '', self.path) if current_path == destpath: ManageActions( action_type=ActionDescription.UNDELETION if self.content.is_deleted else ActionDescription.UNARCHIVING, api=self.content_api, content=self.content, session=self.session, ).action() else: invalid_path = True # if the content is not deleted / archived, check if we're trying to delete / archive it by # moving it to a .deleted / .archived folder elif basename(dirname(destpath)) in ['.deleted', '.archived']: # same test as above ^ dest_path = re.sub(r'/\.(deleted|archived)', '', destpath) if dest_path == self.path: ManageActions( action_type=ActionDescription.DELETION if '.deleted' in destpath else ActionDescription.ARCHIVING, api=self.content_api, content=self.content, session=self.session, ).action() else: invalid_path = True # else we check if the path is good (not at the root path / not in a deleted/archived path) # and we move the content else: invalid_path = any(x in destpath for x in ['.deleted', '.archived']) invalid_path = invalid_path or any(x in self.path for x in ['.deleted', '.archived']) invalid_path = invalid_path or dirname(destpath) == self.environ['http_authenticator.realm'] if not invalid_path: self.move_file(destpath) if invalid_path: raise DAVError(HTTP_FORBIDDEN) def move_file(self, destpath: str) -> None: """ Move file mean changing the path to access to a file. This can mean simple renaming(1), moving file from a directory to one another(2) but also renaming + moving file from a directory to one another at the same time (3). (1): move /dir1/file1 -> /dir1/file2 (2): move /dir1/file1 -> /dir2/file1 (3): move /dir1/file1 -> /dir2/file2 :param destpath: destination path of webdav move :return: nothing """ workspace = self.content.workspace parent = self.content.parent destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) if normpath(dirname(destpath)) == normpath(dirname(self.path)): # INFO - G.M - 2018-12-12 - renaming case checker = is_contributor else: # INFO - G.M - 2018-12-12 - move case checker = can_move_content try: checker.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) try: with new_revision( content=self.content, tm=transaction.manager, session=self.session, ): # INFO - G.M - 2018-03-09 - First, renaming file if needed if basename(destpath) != self.getDisplayName(): new_filename = webdav_convert_file_name_to_bdd(basename(destpath)) regex_file_extension = re.compile( '(?P<label>.*){}'.format( re.escape(self.content.file_extension))) same_extension = regex_file_extension.match(new_filename) if same_extension: new_label = same_extension.group('label') new_file_extension = self.content.file_extension else: new_label, new_file_extension = os.path.splitext( new_filename) self.content_api.update_content( self.content, new_label=new_label, ) self.content.file_extension = new_file_extension self.content_api.save(self.content) # INFO - G.M - 2018-03-09 - Moving file if needed destination_workspace = self.tracim_context.candidate_workspace try: destination_parent = self.tracim_context.candidate_parent_content except ContentNotFound: destination_parent = None if destination_parent != parent or destination_workspace != workspace: # nopep8 # INFO - G.M - 12-03-2018 - Avoid moving the file "at the same place" # nopep8 # if the request does not result in a real move. self.content_api.move( item=self.content, new_parent=destination_parent, must_stay_in_same_workspace=False, new_workspace=destination_workspace ) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) from exc transaction.commit() def copyMoveSingle(self, destpath, isMove): if isMove: # INFO - G.M - 12-03-2018 - This case should not happen # As far as moveRecursive method exist, all move should not go # through this method. If such case appear, try replace this to : #### # self.move_file(destpath) # return #### raise NotImplemented('Feature not available') destpath = normpath(destpath) self.tracim_context.set_destpath(destpath) try: can_move_content.check(self.tracim_context) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) new_filename = webdav_convert_file_name_to_bdd(basename(destpath)) regex_file_extension = re.compile( '(?P<label>.*){}'.format(re.escape(self.content.file_extension))) same_extension = regex_file_extension.match(new_filename) if same_extension: new_label = same_extension.group('label') new_file_extension = self.content.file_extension else: new_label, new_file_extension = os.path.splitext(new_filename) self.tracim_context.set_destpath(destpath) destination_workspace = self.tracim_context.candidate_workspace try: destination_parent = self.tracim_context.candidate_parent_content except ContentNotFound as e: destination_parent = None workspace = self.content.workspace parent = self.content.parent try: new_content = self.content_api.copy( item=self.content, new_label=new_label, new_file_extension=new_file_extension, new_parent=destination_parent, new_workspace=destination_workspace ) self.content_api.copy_children(self.content, new_content) except TracimException as exc: raise DAVError(HTTP_FORBIDDEN) from exc transaction.commit() def supportRecursiveMove(self, destpath): return True @webdav_check_right(is_content_manager) def delete(self): ManageActions( action_type=ActionDescription.DELETION, api=self.content_api, content=self.content, session=self.session, ).action()