DarkCat09
4892430f19
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to. Readme: centered title + logo, added badges, features list, updated changelog. Improved Files API, added automatical session saving and restoring to Client. Some changes in makefile and gitignore. License Notice now refers to all contributors.
351 lines
8.5 KiB
Python
351 lines
8.5 KiB
Python
"""File info object used by `python_aternos.atfm`"""
|
|
|
|
import enum
|
|
|
|
from typing import Union
|
|
from typing import TYPE_CHECKING
|
|
|
|
import lxml.html
|
|
|
|
from .aterrors import FileError
|
|
|
|
if TYPE_CHECKING:
|
|
from .atserver import AternosServer
|
|
|
|
|
|
class FileType(enum.IntEnum):
|
|
|
|
"""File or dierctory"""
|
|
|
|
file = 0
|
|
directory = 1
|
|
dir = 1
|
|
|
|
|
|
class AternosFile:
|
|
|
|
"""File class which contains info
|
|
about its path, type and size"""
|
|
|
|
def __init__(
|
|
self,
|
|
atserv: 'AternosServer',
|
|
path: str, rmable: bool,
|
|
dlable: bool, editable: bool,
|
|
ftype: FileType = FileType.file,
|
|
size: Union[int, float] = 0) -> None:
|
|
|
|
"""File class which contains info
|
|
about its path, type and size
|
|
|
|
Args:
|
|
atserv (python_aternos.atserver.AternosServer):
|
|
atserver.AternosServer instance
|
|
path (str): Absolute path to the file
|
|
rmable (bool): Is the file deleteable (removeable)
|
|
dlable (bool): Is the file downloadable
|
|
ftype (python_aternos.atfile.FileType): File or directory
|
|
size (Union[int,float], optional): File size
|
|
"""
|
|
|
|
path = path.lstrip('/')
|
|
path = '/' + path
|
|
|
|
self.atserv = atserv
|
|
|
|
self._path = path
|
|
self._name = path[path.rfind('/') + 1:]
|
|
self._dirname = path[:path.rfind('/')]
|
|
|
|
self._deleteable = rmable
|
|
self._downloadable = dlable
|
|
self._editable = editable
|
|
|
|
self._ftype = ftype
|
|
self._size = float(size)
|
|
|
|
def create(
|
|
self,
|
|
name: str,
|
|
ftype: FileType = FileType.file) -> None:
|
|
|
|
"""Creates a file or a directory inside this one
|
|
|
|
Args:
|
|
name (str): Filename
|
|
ftype (FileType, optional): File type
|
|
|
|
Raises:
|
|
RuntimeWarning: Messages about probabilty of FileError
|
|
(if `self` file object is not a directory)
|
|
FileError: If Aternos denied file creation
|
|
"""
|
|
|
|
if self.is_file:
|
|
raise RuntimeWarning(
|
|
'Creating files only available '
|
|
'inside directories'
|
|
)
|
|
|
|
name = name.strip().replace('/', '_')
|
|
req = self.atserv.atserver_request(
|
|
'https://aternos.org/panel/ajax/files/create.php',
|
|
'POST', data={
|
|
'file': f'{self._path}/{name}',
|
|
'type': 'file'
|
|
if ftype == FileType.file
|
|
else 'directory'
|
|
}
|
|
)
|
|
|
|
if req.content == b'{"success":false}':
|
|
raise FileError('Unable to create a file')
|
|
|
|
def delete(self) -> None:
|
|
|
|
"""Deletes the file
|
|
|
|
Raises:
|
|
RuntimeWarning: Message about probability of FileError
|
|
FileError: If deleting this file is disallowed by Aternos
|
|
"""
|
|
|
|
if not self._deleteable:
|
|
raise RuntimeWarning(
|
|
'The file seems to be protected (undeleteable). '
|
|
'Always check it before calling delete()'
|
|
)
|
|
|
|
req = self.atserv.atserver_request(
|
|
'https://aternos.org/panel/ajax/delete.php',
|
|
'POST', data={'file': self._path},
|
|
sendtoken=True
|
|
)
|
|
|
|
if req.content == b'{"success":false}':
|
|
raise FileError('Unable to delete the file')
|
|
|
|
def get_content(self) -> bytes:
|
|
|
|
"""Requests file content in bytes (downloads it)
|
|
|
|
Raises:
|
|
RuntimeWarning: Message about probability of FileError
|
|
FileError: If downloading this file is disallowed by Aternos
|
|
|
|
Returns:
|
|
File content
|
|
"""
|
|
|
|
if not self._downloadable:
|
|
raise RuntimeWarning(
|
|
'The file seems to be undownloadable. '
|
|
'Always check it before calling get_content()'
|
|
)
|
|
|
|
file = self.atserv.atserver_request(
|
|
'https://aternos.org/panel/ajax/files/download.php',
|
|
'GET', params={
|
|
'file': self._path
|
|
}
|
|
)
|
|
|
|
if file.content == b'{"success":false}':
|
|
raise FileError(
|
|
'Unable to download the file. '
|
|
'Try to get text'
|
|
)
|
|
|
|
return file.content
|
|
|
|
def set_content(self, value: bytes) -> None:
|
|
|
|
"""Modifies file content
|
|
|
|
Args:
|
|
value (bytes): New content
|
|
|
|
Raises:
|
|
FileError: If Aternos denied file saving
|
|
"""
|
|
|
|
req = self.atserv.atserver_request(
|
|
'https://aternos.org/panel/ajax/save.php',
|
|
'POST', data={
|
|
'file': self._path,
|
|
'content': value
|
|
}, sendtoken=True
|
|
)
|
|
|
|
if req.content == b'{"success":false}':
|
|
raise FileError('Unable to save the file')
|
|
|
|
def get_text(self) -> str:
|
|
|
|
"""Requests editing the file as a text
|
|
|
|
Raises:
|
|
RuntimeWarning: Message about probability of FileError
|
|
FileError: If unable to parse text from response
|
|
|
|
Returns:
|
|
File text content
|
|
"""
|
|
|
|
if not self._editable:
|
|
raise RuntimeWarning(
|
|
'The file seems to be uneditable. '
|
|
'Always check it before calling get_text()'
|
|
)
|
|
|
|
if self.is_dir:
|
|
raise RuntimeWarning(
|
|
'Use get_content() to download '
|
|
'a directory as a ZIP file!'
|
|
)
|
|
|
|
filepath = self._path.lstrip("/")
|
|
editor = self.atserv.atserver_request(
|
|
f'https://aternos.org/files/{filepath}', 'GET'
|
|
)
|
|
edittree = lxml.html.fromstring(editor.content)
|
|
editblock = edittree.xpath('//div[@id="editor"]')
|
|
|
|
if len(editblock) < 1:
|
|
raise FileError(
|
|
'Unable to open editor. '
|
|
'Try to get file content'
|
|
)
|
|
|
|
return editblock[0].text_content()
|
|
|
|
def set_text(self, value: str) -> None:
|
|
|
|
"""Modifies the file content,
|
|
but unlike `set_content` takes
|
|
a string as an argument
|
|
|
|
Args:
|
|
value (str): New content
|
|
"""
|
|
|
|
self.set_content(value.encode('utf-8'))
|
|
|
|
@property
|
|
def path(self) -> str:
|
|
|
|
"""Abslute path to the file
|
|
without leading slash
|
|
including filename
|
|
|
|
Returns:
|
|
Full path to the file
|
|
"""
|
|
|
|
return self._path
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
|
|
"""Filename with extension
|
|
|
|
Returns:
|
|
Filename
|
|
"""
|
|
|
|
return self._name
|
|
|
|
@property
|
|
def dirname(self) -> str:
|
|
|
|
"""Full path to the directory
|
|
which contains the file
|
|
without leading slash.
|
|
Empty path means root (`/`)
|
|
|
|
Returns:
|
|
Path to the directory
|
|
"""
|
|
|
|
return self._dirname
|
|
|
|
@property
|
|
def deleteable(self) -> bool:
|
|
|
|
"""True if the file can be deleted,
|
|
otherwise False
|
|
|
|
Returns:
|
|
Can the file be deleted
|
|
"""
|
|
|
|
return self._deleteable
|
|
|
|
@property
|
|
def downloadable(self) -> bool:
|
|
|
|
"""True if the file can be downloaded,
|
|
otherwise False
|
|
|
|
Returns:
|
|
Can the file be downloaded
|
|
"""
|
|
|
|
return self._downloadable
|
|
|
|
@property
|
|
def editable(self) -> bool:
|
|
|
|
"""True if the file can be
|
|
opened in Aternos editor,
|
|
otherwise False
|
|
|
|
Returns:
|
|
Can the file be edited
|
|
"""
|
|
|
|
return self._editable
|
|
|
|
@property
|
|
def ftype(self) -> FileType:
|
|
|
|
"""File object type: file or directory
|
|
|
|
Returns:
|
|
File type
|
|
"""
|
|
|
|
return self._ftype
|
|
|
|
@property
|
|
def is_dir(self) -> bool:
|
|
|
|
"""Check if the file object is a directory
|
|
|
|
Returns:
|
|
True if it is a directory, otherwise False
|
|
"""
|
|
|
|
return self._ftype == FileType.dir
|
|
|
|
@property
|
|
def is_file(self) -> bool:
|
|
|
|
"""Check if the file object is not a directory
|
|
|
|
Returns:
|
|
True if it is a file, otherwise False
|
|
"""
|
|
|
|
return self._ftype == FileType.file
|
|
|
|
@property
|
|
def size(self) -> float:
|
|
|
|
"""File size in bytes
|
|
|
|
Returns:
|
|
File size
|
|
"""
|
|
|
|
return self._size
|