chore: import upstream snapshot with attribution
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:44 +08:00
commit bcbd1bdb22
5748 changed files with 562488 additions and 0 deletions
@@ -0,0 +1,3 @@
SCOPE_WARN = 500
SCOPE_ERROR = 5000
DEFAULT_CHUNK_SIZE = 8192
+22
View File
@@ -0,0 +1,22 @@
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_write
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def copy(accessor: NextcloudAccessor, src: PathSpec,
dst: PathSpec) -> None:
if isinstance(src, str):
src = PathSpec.from_str_path(src)
if isinstance(dst, str):
dst = PathSpec.from_str_path(dst)
src_key = src.mount_path.lstrip("/")
dst_key = dst.mount_path.lstrip("/")
op = accessor.operator()
try:
await op.copy(src_key, dst_key)
except NotFound as exc:
raise enoent(src) from exc
await invalidate_after_write(dst)
+12
View File
@@ -0,0 +1,12 @@
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_write
from mirage.types import PathSpec
async def create(accessor: NextcloudAccessor, path: PathSpec) -> None:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
key = path.mount_path.lstrip("/")
op = accessor.operator()
await op.write(key, b"")
await invalidate_after_write(path)
+63
View File
@@ -0,0 +1,63 @@
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.core.nextcloud.stat import stat
from mirage.types import FileType, PathSpec
async def du(accessor: NextcloudAccessor, path: PathSpec) -> int:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
try:
info = await stat(accessor, path)
except FileNotFoundError:
info = None
if info is not None and info.type != FileType.DIRECTORY:
return info.size or 0
target = path.mount_path
pfx = target.strip("/")
scan_path = pfx + "/" if pfx else "/"
op = accessor.operator()
total = 0
try:
async for entry in await op.scan(scan_path):
if entry.path.endswith("/"):
continue
meta = entry.metadata
if meta is not None:
total += int(meta.content_length or 0)
except NotFound:
return 0
return total
async def du_all(accessor: NextcloudAccessor,
path: PathSpec) -> list[tuple[str, int]]:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
try:
info = await stat(accessor, path)
except FileNotFoundError:
info = None
if info is not None and info.type != FileType.DIRECTORY:
return []
target = path.mount_path
pfx = target.strip("/")
scan_path = pfx + "/" if pfx else "/"
op = accessor.operator()
results: list[tuple[str, int]] = []
total = 0
try:
async for entry in await op.scan(scan_path):
rel = entry.path
if not rel or rel.endswith("/"):
continue
meta = entry.metadata
sz = int(meta.content_length or 0) if meta is not None else 0
results.append(("/" + rel.lstrip("/"), sz))
total += sz
except NotFound:
pass
results.sort()
results.append((target, total))
return results
+11
View File
@@ -0,0 +1,11 @@
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.core.nextcloud.stat import stat
from mirage.types import PathSpec
async def exists(accessor: NextcloudAccessor, path: PathSpec) -> bool:
try:
await stat(accessor, path)
return True
except (FileNotFoundError, ValueError):
return False
+122
View File
@@ -0,0 +1,122 @@
from opendal.exceptions import NotFound
from opendal.types import EntryMode
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.commands.builtin.find_eval import (FindEntry, PredNode, build_tree,
emit_start_path, keep,
start_basename)
from mirage.types import PathSpec
async def find(
accessor: NextcloudAccessor,
path: PathSpec,
name: str | None = None,
type: str | None = None,
min_size: int | None = None,
max_size: int | None = None,
maxdepth: int | None = None,
name_exclude: str | None = None,
or_names: list[str] | None = None,
mtime_min: float | None = None,
mtime_max: float | None = None,
iname: str | None = None,
path_pattern: str | None = None,
mindepth: int | None = None,
empty: bool = False,
tree: PredNode | None = None,
) -> list[str]:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
start_name = start_basename(path)
target = path.mount_path
pfx = target.strip("/")
scan_path = pfx + "/" if pfx else "/"
base = "/" + pfx if pfx else "/"
base_depth = 0 if base == "/" else base.count("/")
op = accessor.operator()
results: list[str] = []
seen_dirs: set[str] = set()
saw_descendant = False
dir_exists = False
tree = tree if tree is not None else build_tree(name=name,
iname=iname,
path_pattern=path_pattern,
type=type,
name_exclude=name_exclude,
or_names=or_names)
try:
async for entry in await op.scan(scan_path):
rel = entry.path
if not rel:
continue
meta = entry.metadata
is_dir = (rel.endswith("/")
or getattr(meta, "mode", None) == EntryMode.Dir)
entry_path = "/" + rel.rstrip("/").lstrip("/")
if entry_path == base:
dir_exists = True
continue
saw_descendant = True
kind = "d" if is_dir else "f"
content_length = getattr(meta, "content_length", 0) or 0
last_modified = getattr(meta, "last_modified", None)
file_entries: list[tuple[str, str]] = [(entry_path, kind)]
if not is_dir:
parent = entry_path.rsplit("/", 1)[0] or "/"
while parent and parent != base and parent != "/":
if parent not in seen_dirs:
seen_dirs.add(parent)
file_entries.append((parent, "d"))
parent = parent.rsplit("/", 1)[0] or "/"
for ep, k in file_entries:
en = ep.rsplit("/", 1)[-1]
depth = ep.count("/") - base_depth
if maxdepth is not None and depth > maxdepth:
continue
fe = FindEntry(
key=ep,
name=en,
kind=k,
depth=depth,
is_empty=False if k == "d" else content_length == 0)
if not keep(fe, tree, mindepth):
continue
if min_size is not None or max_size is not None:
# Directories count as size 0 for -size (deliberate GNU
# divergence).
size = content_length if k == "f" else 0
if min_size is not None and size < min_size:
continue
if max_size is not None and size > max_size:
continue
if mtime_min is not None or mtime_max is not None:
if last_modified is None:
continue
mt = last_modified.timestamp()
if mtime_min is not None and mt < mtime_min:
continue
if mtime_max is not None and mt > mtime_max:
continue
results.append(ep)
except NotFound:
return []
if saw_descendant or dir_exists:
emit_start_path(results,
base,
start_name,
kind="d",
is_empty=False,
exists=True,
tree=tree,
maxdepth=maxdepth,
mindepth=mindepth,
min_size=min_size,
max_size=max_size)
return sorted(set(results))
+15
View File
@@ -0,0 +1,15 @@
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.index import IndexCacheStore
from mirage.core.nextcloud.constants import SCOPE_ERROR
from mirage.core.nextcloud.readdir import readdir
from mirage.types import PathSpec
from mirage.utils.glob_walk import resolve_glob_with
async def resolve_glob(
accessor: NextcloudAccessor,
paths: list[PathSpec],
index: IndexCacheStore,
) -> list[PathSpec]:
return await resolve_glob_with(readdir, accessor, paths, index,
SCOPE_ERROR)
+15
View File
@@ -0,0 +1,15 @@
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_write
from mirage.types import PathSpec
async def mkdir(accessor: NextcloudAccessor,
path: PathSpec,
parents: bool = False) -> None:
# opendal create_dir creates missing parents; parents is implicit.
if isinstance(path, str):
path = PathSpec.from_str_path(path)
key = path.mount_path.strip("/") + "/"
op = accessor.operator()
await op.create_dir(key)
await invalidate_after_write(path)
+35
View File
@@ -0,0 +1,35 @@
import time
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.index import IndexCacheStore
from mirage.observe.context import record
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def read_bytes(accessor: NextcloudAccessor,
path: PathSpec,
index: IndexCacheStore = None,
offset: int = 0,
size: int | None = None) -> bytes:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
raw = path.mount_path
key = raw.lstrip("/")
op = accessor.operator()
start_ms = int(time.monotonic() * 1000)
try:
if offset or size is not None:
async with await op.open(key, "rb") as f:
if offset:
await f.seek(offset)
data = await f.read(size
) if size is not None else await f.read()
else:
data = bytes(await op.read(key))
except NotFound as exc:
raise enoent(path) from exc
record("read", raw, "nextcloud", len(data), start_ms)
return data
+85
View File
@@ -0,0 +1,85 @@
import logging
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.index import IndexCacheStore, IndexEntry, ResourceType
from mirage.core.nextcloud.constants import SCOPE_ERROR
from mirage.types import PathSpec
from mirage.utils.errors import enoent, enotdir
from mirage.utils.key_prefix import mount_prefix_of
logger = logging.getLogger(__name__)
async def readdir(accessor: NextcloudAccessor, path: PathSpec,
index: IndexCacheStore) -> list[str]:
if isinstance(path, str):
path = PathSpec(virtual=path,
directory=path,
resource_path=path.strip("/"))
prefix = mount_prefix_of(path.virtual, path.resource_path)
target = path.directory if path.pattern else path.virtual
if prefix and target.startswith(prefix):
rest = target[len(prefix):]
if prefix.endswith("/") or rest == "" or rest.startswith("/"):
target = rest or "/"
virtual_key = (prefix + target if prefix else target).rstrip("/") or "/"
listing = await index.list_dir(virtual_key)
if listing.entries is not None:
return listing.entries
list_path = target.strip("/")
list_path = list_path + "/" if list_path else "/"
op = accessor.operator()
names: list[str] = []
dir_keys: set[str] = set()
sizes: dict[str, int | None] = {}
times: dict[str, str] = {}
try:
async for entry in await op.list(list_path):
relative = entry.path
if not relative or relative == list_path:
continue
is_dir = relative.endswith("/")
base = "/" + relative.rstrip("/")
names.append(base)
meta = entry.metadata
if meta and meta.last_modified:
times[base] = meta.last_modified.isoformat()
if is_dir:
dir_keys.add(base)
else:
sizes[base] = meta.content_length if meta else None
except NotFound as exc:
raise enoent(path) from exc
# WebDAV PROPFIND on a file returns the file itself; POSIX readdir of a
# non-directory raises ENOTDIR instead.
target_key = "/" + target.strip("/")
if names == [target_key] and target_key not in dir_keys:
raise enotdir(path)
names = sorted(names)
if len(names) > SCOPE_ERROR:
logger.warning(
"nextcloud readdir: %s returned %d entries (limit %d)",
virtual_key,
len(names),
SCOPE_ERROR,
)
virtual_entries = sorted((prefix + e if prefix else e) for e in names)
index_entries: list[tuple[str, IndexEntry]] = []
for e in names:
name = e.rsplit("/", 1)[-1]
if e in dir_keys:
entry_obj = IndexEntry(id=e,
name=name,
resource_type=ResourceType.FOLDER,
remote_time=times.get(e, ""))
else:
entry_obj = IndexEntry(id=e,
name=name,
resource_type=ResourceType.FILE,
size=sizes.get(e),
remote_time=times.get(e, ""))
index_entries.append((name, entry_obj))
await index.set_dir(virtual_key, index_entries)
return virtual_entries
+24
View File
@@ -0,0 +1,24 @@
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import (invalidate_after_unlink,
invalidate_after_write)
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def rename(accessor: NextcloudAccessor, src: PathSpec,
dst: PathSpec) -> None:
if isinstance(src, str):
src = PathSpec.from_str_path(src)
if isinstance(dst, str):
dst = PathSpec.from_str_path(dst)
src_key = src.mount_path.lstrip("/")
dst_key = dst.mount_path.lstrip("/")
op = accessor.operator()
try:
await op.rename(src_key, dst_key)
except NotFound as exc:
raise enoent(src) from exc
await invalidate_after_write(dst)
await invalidate_after_unlink(src)
+19
View File
@@ -0,0 +1,19 @@
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_unlink
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def rm_r(accessor: NextcloudAccessor, path: PathSpec) -> None:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
raw = path.mount_path
key = raw.strip("/") + "/"
op = accessor.operator()
try:
await op.remove_all(key)
except NotFound as exc:
raise enoent(path) from exc
await invalidate_after_unlink(path)
+19
View File
@@ -0,0 +1,19 @@
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_unlink
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def rmdir(accessor: NextcloudAccessor, path: PathSpec) -> None:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
raw = path.mount_path
key = raw.strip("/") + "/"
op = accessor.operator()
try:
await op.delete(key)
except NotFound as exc:
raise enoent(path) from exc
await invalidate_after_unlink(path)
+67
View File
@@ -0,0 +1,67 @@
from opendal.exceptions import NotFound
from opendal.types import EntryMode
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.index import IndexCacheStore, ResourceType
from mirage.types import FileStat, FileType, PathSpec
from mirage.utils.errors import enoent
from mirage.utils.filetype import guess_type
from mirage.utils.key_prefix import mount_prefix_of
async def stat(accessor: NextcloudAccessor,
path: PathSpec,
index: IndexCacheStore = None) -> FileStat:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
original_prefix = mount_prefix_of(path.virtual, path.resource_path)
raw = path.virtual
if original_prefix and raw.startswith(original_prefix):
raw = raw[len(original_prefix):] or "/"
stripped = raw.strip("/")
if not stripped:
return FileStat(name="/", type=FileType.DIRECTORY)
if index is not None:
virtual_key = (original_prefix + "/" +
stripped if original_prefix else "/" + stripped)
lookup = await index.get(virtual_key)
if lookup.entry is not None:
entry = lookup.entry
if entry.resource_type == ResourceType.FOLDER:
return FileStat(name=entry.name,
type=FileType.DIRECTORY,
modified=entry.remote_time or None)
return FileStat(name=entry.name,
size=entry.size,
modified=entry.remote_time or None,
type=guess_type(entry.name))
parent = virtual_key.rsplit("/", 1)[0] or "/"
parent_listing = await index.list_dir(parent)
if parent_listing.entries is not None:
raise enoent(path)
op = accessor.operator()
key = stripped
try:
md = await op.stat(key)
except NotFound:
md = None
if md is not None and md.mode != EntryMode.Dir:
modified = md.last_modified.isoformat() if md.last_modified else None
return FileStat(
name=stripped.rsplit("/", 1)[-1],
size=md.content_length,
modified=modified,
type=guess_type(raw),
fingerprint=md.etag,
extra={"etag": md.etag} if md.etag else {},
)
try:
md_dir = await op.stat(key + "/")
if md_dir and md_dir.mode == EntryMode.Dir:
return FileStat(
name=stripped.rsplit("/", 1)[-1] or "/",
type=FileType.DIRECTORY,
)
except NotFound:
pass
raise enoent(path)
+56
View File
@@ -0,0 +1,56 @@
import time
from collections.abc import AsyncIterator
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.index import IndexCacheStore
from mirage.core.nextcloud.constants import DEFAULT_CHUNK_SIZE
from mirage.observe.context import record, record_stream
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def range_read(accessor: NextcloudAccessor, path: PathSpec, start: int,
end: int) -> bytes:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
raw = path.mount_path
key = raw.lstrip("/")
op = accessor.operator()
start_ms = int(time.monotonic() * 1000)
try:
async with await op.open(key, "rb") as f:
if start:
await f.seek(start)
data = await f.read(end - start)
except NotFound as exc:
raise enoent(path) from exc
record("read", raw, "nextcloud", len(data), start_ms)
return data
async def read_stream(
accessor: NextcloudAccessor,
path: PathSpec,
index: IndexCacheStore = None,
chunk_size: int = DEFAULT_CHUNK_SIZE,
) -> AsyncIterator[bytes]:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
raw = path.mount_path
key = raw.lstrip("/")
op = accessor.operator()
rec = record_stream("read", raw, "nextcloud")
try:
async with await op.open(key, "rb") as f:
while True:
chunk = await f.read(chunk_size)
if not chunk:
break
chunk_bytes = bytes(chunk)
if rec is not None:
rec.bytes += len(chunk_bytes)
yield chunk_bytes
except NotFound as exc:
raise enoent(path) from exc
+20
View File
@@ -0,0 +1,20 @@
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_write
from mirage.types import PathSpec
async def truncate(accessor: NextcloudAccessor, path: PathSpec,
length: int) -> None:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
key = path.mount_path.lstrip("/")
op = accessor.operator()
try:
data = bytes(await op.read(key))
except NotFound:
data = b""
result = data[:length].ljust(length, b"\0")
await op.write(key, result)
await invalidate_after_write(path)
+24
View File
@@ -0,0 +1,24 @@
import time
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_unlink
from mirage.observe.context import record
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def unlink(accessor: NextcloudAccessor, path: PathSpec) -> None:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
raw = path.mount_path
key = raw.lstrip("/")
op = accessor.operator()
start_ms = int(time.monotonic() * 1000)
try:
await op.delete(key)
except NotFound as exc:
raise enoent(path) from exc
record("unlink", path.virtual, "nextcloud", 0, start_ms)
await invalidate_after_unlink(path)
+28
View File
@@ -0,0 +1,28 @@
import time
from opendal.exceptions import NotFound
from mirage.accessor.nextcloud import NextcloudAccessor
from mirage.cache.context import invalidate_after_write
from mirage.cache.index import IndexCacheStore
from mirage.observe.context import record
from mirage.types import PathSpec
from mirage.utils.errors import enoent
async def write_bytes(accessor: NextcloudAccessor,
path: PathSpec,
data: bytes,
index: IndexCacheStore = None) -> None:
if isinstance(path, str):
path = PathSpec.from_str_path(path)
raw = path.mount_path
key = raw.lstrip("/")
op = accessor.operator()
start_ms = int(time.monotonic() * 1000)
try:
await op.write(key, data)
except NotFound as exc:
raise enoent(path) from exc
record("write", path.virtual, "nextcloud", len(data), start_ms)
await invalidate_after_write(path)