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("Config:", config.get_config())
print("Hello World") print("Hello World")
from .storage.local_storage import LocalStorage from .storage.local_storage import LocalStorage
from .storage.storage import AsyncFileContextManager as FCTX
from pathlib import PurePosixPath 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(): async for rt in fs.sync_storage():
print(rt) print(rt)
print(fs.dump_fs_tree()) print(fs.dump_fs_tree())

View File

@@ -1,8 +1,11 @@
from backup_box.storage.storage import AsyncFileReader, AsyncFileWriter from backup_box.storage.storage import AsyncFileReader, AsyncFileWriter
from pathlib import PurePath, PurePosixPath from pathlib import PurePosixPath, PurePath
from ..config import StorageItem 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 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): class LocalStorage(VFileSystem):
def __init__(self, base_path: str) -> None: def __init__(self, base_path: str) -> None:
@@ -15,7 +18,7 @@ class LocalStorage(VFileSystem):
raise ValueError("config type mismatch!") raise ValueError("config type mismatch!")
return LocalStorage(cfg["path"]) return LocalStorage(cfg["path"])
def _vnode_path_to_syspath(self, *path: VFile | VDir): def _vnode_path_to_syspath(self, *path: VFile | VDir) -> PurePath:
filtered = [] filtered = []
# filter abslute path # filter abslute path
for n in path: for n in path:
@@ -25,33 +28,53 @@ class LocalStorage(VFileSystem):
if name in [".", "..", ""]: if name in [".", "..", ""]:
continue continue
filtered.append(name) 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): async def sync_storage(self):
curr = 0
total = 1
parents: list[VDir] = []
self._root["c"].clear() self._root["c"].clear()
node = self._root await self._fill_vnode_with_real_fs(self._root, self._base)
yield curr, total yield 1, 1
while True:
if node["ty"] == "dir":
pass
if len(parents) <= 0:
break
async def _on_remove_node(self, node_path: list[VFile | VDir]) -> None: 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: 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 @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 @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["w"]) -> AsyncFileWriter: ...
async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal['r'] | Literal['w'] = "r") -> AsyncFileWriter | AsyncFileReader: async def _on_open_node(self, node_path: list[VFile | VDir], mode: Literal["r"] | Literal["w"] = "r") -> AsyncFileWriter | AsyncFileReader:
raise NotImplementedError 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 abc import ABC, abstractmethod
from pathlib import PurePosixPath from pathlib import PurePosixPath
from collections.abc import Awaitable, AsyncIterable 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): class AsyncFileReader(Protocol):
async def read(self, n: int = -1) -> bytes: async def read(self, n: int = -1, /) -> bytes:
... ...
async def close(self) -> None: async def close(self) -> None:
... ...
class AsyncFileWriter(Protocol): class AsyncFileWriter(Protocol):
async def write(self, s: SupportsBytes) -> int: async def write(self, s: SupportsBytes, /) -> int:
... ...
async def close(self) -> None: 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): class Storage(ABC):
@classmethod @classmethod
@@ -28,7 +38,7 @@ class Storage(ABC):
@classmethod @classmethod
@abstractmethod @abstractmethod
def from_config(cls, cfg: StorageItem) -> 'Storage': def from_config(cls, cfg: StorageItem) -> Self:
... ...
@abstractmethod @abstractmethod