2023-10-14 23:29:50 +01:00
|
|
|
from __future__ import annotations
|
2023-10-21 17:17:09 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
import shutil
|
2023-10-23 02:51:39 +01:00
|
|
|
from typing import Any
|
2023-10-14 23:29:50 +01:00
|
|
|
|
2023-10-21 17:17:09 +01:00
|
|
|
import msgspec
|
2023-10-21 02:44:43 +01:00
|
|
|
from sanic import BadRequest
|
2023-10-21 17:17:09 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
from cista import config
|
2023-10-21 20:30:47 +01:00
|
|
|
from cista.util import filename
|
2023-10-14 23:29:50 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
## Control commands
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
class ControlBase(msgspec.Struct, tag_field="op", tag=str.lower):
|
|
|
|
def __call__(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
class MkDir(ControlBase):
|
|
|
|
path: str
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
def __call__(self):
|
2023-10-21 20:30:47 +01:00
|
|
|
path = config.config.path / filename.sanitize(self.path)
|
2023-11-08 20:38:40 +00:00
|
|
|
path.mkdir(parents=True, exist_ok=False)
|
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
|
|
|
|
class Rename(ControlBase):
|
|
|
|
path: str
|
|
|
|
to: str
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
def __call__(self):
|
2023-10-21 20:30:47 +01:00
|
|
|
to = filename.sanitize(self.to)
|
2023-10-21 02:44:43 +01:00
|
|
|
if "/" in to:
|
|
|
|
raise BadRequest("Rename 'to' name should only contain filename, not path")
|
2023-10-21 20:30:47 +01:00
|
|
|
path = config.config.path / filename.sanitize(self.path)
|
2023-10-21 02:44:43 +01:00
|
|
|
path.rename(path.with_name(to))
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
class Rm(ControlBase):
|
|
|
|
sel: list[str]
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
def __call__(self):
|
|
|
|
root = config.config.path
|
2023-10-21 20:30:47 +01:00
|
|
|
sel = [root / filename.sanitize(p) for p in self.sel]
|
2023-10-21 02:44:43 +01:00
|
|
|
for p in sel:
|
2023-11-08 20:38:40 +00:00
|
|
|
if p.is_dir():
|
|
|
|
shutil.rmtree(p)
|
|
|
|
else:
|
|
|
|
p.unlink()
|
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
|
|
|
|
class Mv(ControlBase):
|
|
|
|
sel: list[str]
|
|
|
|
dst: str
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
def __call__(self):
|
|
|
|
root = config.config.path
|
2023-10-21 20:30:47 +01:00
|
|
|
sel = [root / filename.sanitize(p) for p in self.sel]
|
|
|
|
dst = root / filename.sanitize(self.dst)
|
2023-10-21 02:44:43 +01:00
|
|
|
if not dst.is_dir():
|
|
|
|
raise BadRequest("The destination must be a directory")
|
|
|
|
for p in sel:
|
|
|
|
shutil.move(p, dst)
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
class Cp(ControlBase):
|
|
|
|
sel: list[str]
|
|
|
|
dst: str
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
def __call__(self):
|
|
|
|
root = config.config.path
|
2023-10-21 20:30:47 +01:00
|
|
|
sel = [root / filename.sanitize(p) for p in self.sel]
|
|
|
|
dst = root / filename.sanitize(self.dst)
|
2023-10-21 02:44:43 +01:00
|
|
|
if not dst.is_dir():
|
|
|
|
raise BadRequest("The destination must be a directory")
|
|
|
|
for p in sel:
|
2023-11-08 20:38:40 +00:00
|
|
|
if p.is_dir():
|
|
|
|
# Note: copies as dst rather than in dst unless name is appended.
|
|
|
|
shutil.copytree(
|
|
|
|
p,
|
|
|
|
dst / p.name,
|
|
|
|
dirs_exist_ok=True,
|
|
|
|
ignore_dangling_symlinks=True,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
shutil.copy2(p, dst)
|
|
|
|
|
|
|
|
|
|
|
|
ControlTypes = MkDir | Rename | Rm | Mv | Cp
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-21 02:44:43 +01:00
|
|
|
|
2023-10-14 23:29:50 +01:00
|
|
|
## File uploads and downloads
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-14 23:29:50 +01:00
|
|
|
class FileRange(msgspec.Struct):
|
|
|
|
name: str
|
|
|
|
size: int
|
|
|
|
start: int
|
|
|
|
end: int
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-14 23:29:50 +01:00
|
|
|
class StatusMsg(msgspec.Struct):
|
|
|
|
status: str
|
|
|
|
req: FileRange
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-23 02:51:39 +01:00
|
|
|
class ErrorMsg(msgspec.Struct):
|
|
|
|
error: dict[str, Any]
|
2023-10-14 23:29:50 +01:00
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-14 23:29:50 +01:00
|
|
|
## Directory listings
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
class FileEntry(msgspec.Struct, array_like=True):
|
|
|
|
level: int
|
|
|
|
name: str
|
2023-11-08 20:38:40 +00:00
|
|
|
key: str
|
2023-10-14 23:29:50 +01:00
|
|
|
mtime: int
|
2023-11-12 23:20:40 +00:00
|
|
|
size: int
|
|
|
|
isfile: int
|
2023-10-14 23:29:50 +01:00
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
def __repr__(self):
|
|
|
|
return self.key or "FileEntry()"
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-17 19:33:31 +01:00
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
class Update(msgspec.Struct, array_like=True):
|
|
|
|
...
|
2023-10-17 19:33:31 +01:00
|
|
|
|
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
class UpdKeep(Update, tag="k"):
|
|
|
|
count: int
|
2023-10-17 19:33:31 +01:00
|
|
|
|
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
class UpdDel(Update, tag="d"):
|
|
|
|
count: int
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-17 19:33:31 +01:00
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
class UpdIns(Update, tag="i"):
|
|
|
|
items: list[FileEntry]
|
2023-10-17 19:33:31 +01:00
|
|
|
|
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
class UpdateMessage(msgspec.Struct):
|
|
|
|
update: list[UpdKeep | UpdDel | UpdIns]
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-11-12 23:20:40 +00:00
|
|
|
|
|
|
|
class Space(msgspec.Struct):
|
|
|
|
disk: int
|
|
|
|
free: int
|
|
|
|
usage: int
|
|
|
|
storage: int
|