cista-storage/cista/protocol.py

164 lines
3.4 KiB
Python
Raw Normal View History

2023-10-14 23:29:50 +01:00
from __future__ import annotations
2023-10-21 17:17:09 +01:00
import shutil
from typing import Any
2023-10-14 23:29:50 +01:00
2023-10-21 17:17:09 +01:00
import msgspec
from sanic import BadRequest
2023-10-21 17:17:09 +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
## Control commands
class ControlBase(msgspec.Struct, tag_field="op", tag=str.lower):
def __call__(self):
raise NotImplementedError
class MkDir(ControlBase):
path: str
def __call__(self):
2023-10-21 20:30:47 +01:00
path = config.config.path / filename.sanitize(self.path)
path.mkdir(parents=True, exist_ok=False)
class Rename(ControlBase):
path: str
to: str
def __call__(self):
2023-10-21 20:30:47 +01:00
to = filename.sanitize(self.to)
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)
path.rename(path.with_name(to))
class Rm(ControlBase):
sel: list[str]
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]
for p in sel:
if p.is_dir():
shutil.rmtree(p)
else:
p.unlink()
class Mv(ControlBase):
sel: list[str]
dst: str
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)
if not dst.is_dir():
raise BadRequest("The destination must be a directory")
for p in sel:
shutil.move(p, dst)
class Cp(ControlBase):
sel: list[str]
dst: str
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)
if not dst.is_dir():
raise BadRequest("The destination must be a directory")
for p in sel:
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-14 23:29:50 +01:00
## File uploads and downloads
2023-10-14 23:29:50 +01:00
class FileRange(msgspec.Struct):
name: str
size: int
start: int
end: int
2023-10-14 23:29:50 +01:00
class StatusMsg(msgspec.Struct):
status: str
req: FileRange
class ErrorMsg(msgspec.Struct):
error: dict[str, Any]
2023-10-14 23:29:50 +01:00
2023-10-14 23:29:50 +01:00
## Directory listings
class FileEntry(msgspec.Struct, array_like=True):
level: int
name: str
key: str
2023-10-14 23:29:50 +01:00
mtime: int
size: int
isfile: int
2023-10-14 23:29:50 +01:00
def __repr__(self):
return self.key or "FileEntry()"
class Update(msgspec.Struct, array_like=True):
...
class UpdKeep(Update, tag="k"):
count: int
class UpdDel(Update, tag="d"):
count: int
class UpdIns(Update, tag="i"):
items: list[FileEntry]
class UpdateMessage(msgspec.Struct):
update: list[UpdKeep | UpdDel | UpdIns]
class Space(msgspec.Struct):
disk: int
free: int
usage: int
storage: int
2023-10-14 23:29:50 +01:00
2023-10-14 23:29:50 +01:00
def make_dir_data(root):
if len(root) == 3:
2023-10-14 23:29:50 +01:00
return FileEntry(*root)
id_, size, mtime, listing = root
2023-10-14 23:29:50 +01:00
converted = {}
for name, data in listing.items():
converted[name] = make_dir_data(data)
sz = sum(x.size for x in converted.values())
mt = max(x.mtime for x in converted.values())
return DirEntry(id_, sz, max(mt, mtime), converted)