LocalStorage mapped to real fs

This commit is contained in:
2025-11-06 10:53:33 +08:00
parent 70fc4f0be5
commit 01b0e56d46
3 changed files with 65 additions and 27 deletions

View File

@@ -37,8 +37,13 @@ async def a_main():
print("Config:", config.get_config())
print("Hello World")
from .storage.local_storage import LocalStorage
from .storage.storage import AsyncFileContextManager as FCTX
from pathlib import PurePosixPath
fs = LocalStorage(r"D:\AuberyZhao\BackupBox\backup_box")
cfg = config.get_config()
stm: config.StorageItem = { "type": "LocalStorageItem", "path": "" }
for stm in cfg["storage"].values():
break
fs = LocalStorage.from_config(stm)
async for rt in fs.sync_storage():
print(rt)
print(fs.dump_fs_tree())

View File

@@ -1,8 +1,11 @@
from backup_box.storage.storage import AsyncFileReader, AsyncFileWriter
from pathlib import PurePath, PurePosixPath
from pathlib import PurePosixPath, PurePath
from ..config import StorageItem
from .vnode import VFileSystem, VFile, VDir
from .vnode import VFileSystem, VFile, VDir, new_storage_dir, new_storage_file
from typing import Literal, overload
from stat import S_ISDIR
from aiofiles import open
from aiofiles.os import remove, rmdir, stat, listdir, mkdir
class LocalStorage(VFileSystem):
def __init__(self, base_path: str) -> None:
@@ -15,7 +18,7 @@ class LocalStorage(VFileSystem):
raise ValueError("config type mismatch!")
return LocalStorage(cfg["path"])
def _vnode_path_to_syspath(self, *path: VFile | VDir):
def _vnode_path_to_syspath(self, *path: VFile | VDir) -> PurePath:
filtered = []
# filter abslute path
for n in path:
@@ -25,33 +28,53 @@ class LocalStorage(VFileSystem):
if name in [".", "..", ""]:
continue
filtered.append(name)
return self._base.joinpath(*filtered)
return PurePath(self._base.joinpath(*filtered))
async def _fill_vnode_with_real_fs(self, vparent: VDir, real_path: PurePath):
try:
for name in await listdir(real_path):
p = real_path.joinpath(name)
st = await stat(p)
if S_ISDIR(st.st_mode):
child_node = new_storage_dir(name)
await self._fill_vnode_with_real_fs(child_node, p)
else:
child_node = new_storage_file(name, int(st.st_mtime), st.st_size)
vparent["c"].append(child_node)
except FileNotFoundError:
pass
async def sync_storage(self):
curr = 0
total = 1
parents: list[VDir] = []
self._root["c"].clear()
node = self._root
yield curr, total
while True:
if node["ty"] == "dir":
pass
if len(parents) <= 0:
break
await self._fill_vnode_with_real_fs(self._root, self._base)
yield 1, 1
async def _on_remove_node(self, node_path: list[VFile | VDir]) -> None:
raise NotImplementedError
real_path = self._vnode_path_to_syspath(*node_path)
st = await stat(real_path)
if S_ISDIR(st.st_mode):
await rmdir(real_path)
else:
await remove(real_path)
async def _on_create_node(self, node_path: list[VFile | VDir]) -> None:
raise NotImplementedError
if len(node_path) > 0 and node_path[-1]["ty"] == "dir":
real_path = self._vnode_path_to_syspath(*node_path)
await mkdir(real_path)
@overload
async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal['r'] = "r") -> AsyncFileReader: ...
async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal["r"] = "r") -> AsyncFileReader: ...
@overload
async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal['w']) -> AsyncFileWriter: ...
async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal['r'] | Literal['w'] = "r") -> AsyncFileWriter | AsyncFileReader:
raise NotImplementedError
async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal["w"]) -> AsyncFileWriter: ...
async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal["r"] | Literal["w"] = "r") -> AsyncFileWriter | AsyncFileReader:
real_path = self._vnode_path_to_syspath(*node_path)
if mode == "r":
return await open(real_path, "rb")
elif mode == "w":
return await open(real_path, "wb")
else:
raise ValueError()

View File

@@ -2,24 +2,34 @@ from ..config import StorageItem
from abc import ABC, abstractmethod
from pathlib import PurePosixPath
from collections.abc import Awaitable, AsyncIterable
from typing import overload, Literal, Protocol, SupportsBytes
from contextlib import AbstractAsyncContextManager
from typing import overload, Literal, Protocol, SupportsBytes, Self, runtime_checkable
class AsyncFileReader(Protocol):
async def read(self, n: int = -1) -> bytes:
async def read(self, n: int = -1, /) -> bytes:
...
async def close(self) -> None:
...
class AsyncFileWriter(Protocol):
async def write(self, s: SupportsBytes) -> int:
async def write(self, s: SupportsBytes, /) -> int:
...
async def close(self) -> None:
...
class AsyncFileLike(AsyncFileReader, AsyncFileWriter):
...
class AsyncFileContextManager[T](AbstractAsyncContextManager[T]):
def __init__(self, fobj: T) -> None:
self.__fobj = fobj
async def __aenter__(self) -> T:
return self.__fobj # type: ignore
async def __aexit__(self, exc_type, exc, tb):
if hasattr(self.__fobj, "close"):
func = getattr(self.__fobj, "close")
await func()
class Storage(ABC):
@classmethod
@@ -28,7 +38,7 @@ class Storage(ABC):
@classmethod
@abstractmethod
def from_config(cls, cfg: StorageItem) -> 'Storage':
def from_config(cls, cfg: StorageItem) -> Self:
...
@abstractmethod