feat: add archive client foundation
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.git
|
||||
__pycache__
|
||||
*.pyc
|
||||
state
|
||||
backups
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.venv/
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
state/
|
||||
backups/
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
FROM python:3.11-slim AS builder
|
||||
WORKDIR /build
|
||||
COPY pyproject.toml ./
|
||||
COPY src ./src
|
||||
RUN pip wheel --no-cache-dir --wheel-dir /wheels .
|
||||
|
||||
FROM builder AS test
|
||||
RUN pip install --no-cache-dir /wheels/*.whl
|
||||
COPY tests ./tests
|
||||
CMD ["python", "-m", "unittest", "discover", "-s", "tests", "-v"]
|
||||
|
||||
FROM python:3.11-slim
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
|
||||
RUN groupadd --gid 1001 archive-control \
|
||||
&& useradd --uid 1001 --gid 1001 --no-create-home archive-control
|
||||
COPY --from=builder /wheels /wheels
|
||||
RUN pip install --no-cache-dir /wheels/*.whl && rm -rf /wheels
|
||||
USER 1001:1001
|
||||
ENTRYPOINT ["archive-client"]
|
||||
CMD ["--config", "/etc/archive-control/client.toml"]
|
||||
@@ -0,0 +1,35 @@
|
||||
# Archive Clients
|
||||
|
||||
One Python daemon runs as either an `archive` or `cache` node for Archive
|
||||
Control. Both roles use the same code and `sodium/archive-clients` image.
|
||||
|
||||
The current foundation provides strict TOML configuration, file-backed secrets,
|
||||
API/local root mapping, fail-fast permission and sparse-file probes, a durable
|
||||
SQLite command inbox, bounded one-writer WebSocket output, registration-first
|
||||
authentication, heartbeat handling, duplicate-command acknowledgements, and
|
||||
indefinite capped exponential reconnect with optional jitter.
|
||||
|
||||
```bash
|
||||
archive-client --config /etc/archive-control/client.toml --check-config
|
||||
archive-client --config /etc/archive-control/client.toml --mode archive
|
||||
```
|
||||
|
||||
`--mode` accepts only `archive` or `cache` and overrides the configured role.
|
||||
Secrets must be regular files without group/world permissions. The daemon never
|
||||
stores them in SQLite or sends the shared token after registration.
|
||||
|
||||
This foundation currently executes heartbeat and state-snapshot commands.
|
||||
Other mutation commands are durably rejected as unsupported until their
|
||||
service and file-operation executors are added; they are never falsely
|
||||
acknowledged as accepted.
|
||||
|
||||
Run tests and build using containers:
|
||||
|
||||
```bash
|
||||
docker build --target test -t archive-clients-test .
|
||||
docker run --rm archive-clients-test
|
||||
docker build -t sodium/archive-clients:dev .
|
||||
```
|
||||
|
||||
Generated bindings are pinned to archive-control-proto commit
|
||||
`4ec852014dad74606d4078b3ae1aa208c814b033`.
|
||||
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
client:
|
||||
image: sodium/archive-clients:dev
|
||||
build: .
|
||||
user: "1001:1001"
|
||||
command: ["--config", "/etc/archive-control/client.toml", "--check-config"]
|
||||
volumes:
|
||||
- ./examples/client.toml:/etc/archive-control/client.toml:ro
|
||||
- ./state:/var/lib/archive-control
|
||||
- ./backups:/var/backups/archive-control
|
||||
- ./secrets:/run/secrets:ro
|
||||
- ./data/qb:/data/qb
|
||||
- ./data/sync:/data/sync
|
||||
@@ -0,0 +1,29 @@
|
||||
client_id = "cache-1"
|
||||
display_name = "Cache 1"
|
||||
role = "cache"
|
||||
control_endpoint = "ws://control:8765/archive_control"
|
||||
shared_token_file = "/run/secrets/archive_control_token"
|
||||
state_db = "/var/lib/archive-control/client.db"
|
||||
backup_dir = "/var/backups/archive-control"
|
||||
|
||||
[connection]
|
||||
registration_timeout = "10s"
|
||||
reconnect_initial = "1s"
|
||||
reconnect_max = "60s"
|
||||
reconnect_reset_after = "60s"
|
||||
reconnect_jitter = true
|
||||
|
||||
[qbittorrent]
|
||||
endpoint = "http://qbittorrent:8080"
|
||||
username = "admin"
|
||||
password_file = "/run/secrets/qb_password"
|
||||
api_root = "/downloads"
|
||||
local_root = "/data/qb"
|
||||
|
||||
[syncthing]
|
||||
endpoint = "http://syncthing:8384"
|
||||
api_key_file = "/run/secrets/syncthing_api_key"
|
||||
api_root = "/sync"
|
||||
local_root = "/data/sync"
|
||||
advertised_addresses = ["dynamic"]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[build-system]
|
||||
requires = ["setuptools==80.9.0", "wheel==0.46.3"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "archive-clients"
|
||||
version = "0.1.0.dev0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = ["protobuf==7.35.1", "websockets==16.0"]
|
||||
|
||||
[project.scripts]
|
||||
archive-client = "archive_clients.cli:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Archive Control data-node daemon."""
|
||||
|
||||
PROTO_COMMIT = "4ec852014dad74606d4078b3ae1aa208c814b033"
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Archive client command-line entry point."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
from archive_clients.config import ClientConfig
|
||||
from archive_clients.daemon import ArchiveClientDaemon
|
||||
from archive_clients.probes import probe_root
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(prog="archive-client")
|
||||
parser.add_argument("--config", type=Path, required=True)
|
||||
parser.add_argument("--mode", choices=("archive", "cache"))
|
||||
parser.add_argument("--check-config", action="store_true")
|
||||
arguments = parser.parse_args(argv)
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
config = ClientConfig.load(arguments.config, arguments.mode)
|
||||
probes = [
|
||||
probe_root(config.qbittorrent.local_root),
|
||||
probe_root(config.syncthing.local_root),
|
||||
]
|
||||
config.read_shared_token()
|
||||
config.qbittorrent.read_password()
|
||||
config.syncthing.read_api_key()
|
||||
if arguments.check_config:
|
||||
print(json.dumps({
|
||||
"client_id": config.client_id,
|
||||
"role": config.role,
|
||||
"filesystems": [probe.__dict__ | {"root": str(probe.root)} for probe in probes],
|
||||
}, sort_keys=True))
|
||||
return 0
|
||||
asyncio.run(ArchiveClientDaemon(config, probes).run())
|
||||
return 0
|
||||
@@ -0,0 +1,259 @@
|
||||
"""Strict TOML configuration with file-backed secrets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import tomllib
|
||||
|
||||
|
||||
class ConfigError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
_CLIENT_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
|
||||
_DURATION = re.compile(r"^([1-9][0-9]*)(ms|s|m|h)$")
|
||||
_FACTORS = {"ms": 0.001, "s": 1, "m": 60, "h": 3600}
|
||||
_ENV = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RootMapping:
|
||||
api_root: PurePosixPath
|
||||
local_root: Path
|
||||
|
||||
def api_to_local(self, api_path: str) -> Path:
|
||||
candidate = PurePosixPath(api_path)
|
||||
try:
|
||||
relative = candidate.relative_to(self.api_root)
|
||||
except ValueError as exc:
|
||||
raise ConfigError("API path is outside its configured root") from exc
|
||||
if any(part in {"", ".", ".."} for part in relative.parts):
|
||||
raise ConfigError("API path contains an unsafe component")
|
||||
return self.local_root.joinpath(*relative.parts)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ServiceConfig:
|
||||
endpoint: str
|
||||
api_root: PurePosixPath
|
||||
local_root: Path
|
||||
username: str | None = None
|
||||
password_file: Path | None = None
|
||||
api_key_file: Path | None = None
|
||||
advertised_addresses: tuple[str, ...] = ()
|
||||
|
||||
@property
|
||||
def roots(self) -> RootMapping:
|
||||
return RootMapping(self.api_root, self.local_root)
|
||||
|
||||
def read_password(self) -> str | None:
|
||||
return _secret(self.password_file, "service password") if self.password_file else None
|
||||
|
||||
def read_api_key(self) -> str | None:
|
||||
return _secret(self.api_key_file, "service API key") if self.api_key_file else None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConnectionConfig:
|
||||
registration_timeout: float = 10
|
||||
heartbeat_interval: float = 15
|
||||
offline_timeout: float = 45
|
||||
reconnect_initial: float = 1
|
||||
reconnect_max: float = 60
|
||||
reconnect_reset_after: float = 60
|
||||
reconnect_jitter: bool = True
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class JobsConfig:
|
||||
stall_after: float = 30 * 60
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ClientConfig:
|
||||
client_id: str
|
||||
display_name: str
|
||||
role: str
|
||||
control_endpoint: str
|
||||
shared_token_file: Path
|
||||
state_db: Path
|
||||
backup_dir: Path
|
||||
qbittorrent: ServiceConfig
|
||||
syncthing: ServiceConfig
|
||||
connection: ConnectionConfig = ConnectionConfig()
|
||||
jobs: JobsConfig = JobsConfig()
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path, mode_override: str | None = None) -> "ClientConfig":
|
||||
try:
|
||||
with path.open("rb") as source:
|
||||
raw = tomllib.load(source)
|
||||
except (OSError, tomllib.TOMLDecodeError) as exc:
|
||||
raise ConfigError("configuration cannot be read") from exc
|
||||
_keys(raw, {
|
||||
"client_id", "display_name", "role", "control_endpoint",
|
||||
"shared_token_file", "state_db", "backup_dir", "connection",
|
||||
"jobs", "qbittorrent", "syncthing",
|
||||
}, "root")
|
||||
role = mode_override or raw.get("role")
|
||||
if role not in {"archive", "cache"}:
|
||||
raise ConfigError("role/--mode must be archive or cache")
|
||||
client_id = raw.get("client_id")
|
||||
if not isinstance(client_id, str) or not _CLIENT_ID.fullmatch(client_id):
|
||||
raise ConfigError("client_id is invalid")
|
||||
display_name = raw.get("display_name")
|
||||
if not isinstance(display_name, str) or not 1 <= len(display_name) <= 128:
|
||||
raise ConfigError("display_name is invalid")
|
||||
connection = _connection(raw.get("connection", {}))
|
||||
jobs = _jobs(raw.get("jobs", {}))
|
||||
return cls(
|
||||
client_id, display_name, role,
|
||||
_endpoint(raw, "control_endpoint", {"ws", "wss"}),
|
||||
_absolute_path(raw, "shared_token_file"),
|
||||
_absolute_path(raw, "state_db"),
|
||||
_absolute_path(raw, "backup_dir"),
|
||||
_service(raw.get("qbittorrent"), "qbittorrent"),
|
||||
_service(raw.get("syncthing"), "syncthing"), connection, jobs,
|
||||
)
|
||||
|
||||
def read_shared_token(self) -> str:
|
||||
return _secret(self.shared_token_file, "shared token")
|
||||
|
||||
|
||||
def _service(value: Any, name: str) -> ServiceConfig:
|
||||
if not isinstance(value, dict):
|
||||
raise ConfigError(f"{name} must be a table")
|
||||
allowed = {
|
||||
"endpoint", "api_root", "local_root", "username", "password_file",
|
||||
"api_key_file", "advertised_addresses",
|
||||
}
|
||||
_keys(value, allowed, name)
|
||||
api_root = PurePosixPath(_string(value, "api_root"))
|
||||
if not api_root.is_absolute() or ".." in api_root.parts:
|
||||
raise ConfigError(f"{name}.api_root must be absolute and normalized")
|
||||
username = value.get("username")
|
||||
if username is not None and (not isinstance(username, str) or not username):
|
||||
raise ConfigError(f"{name}.username must be a non-empty string")
|
||||
addresses = value.get("advertised_addresses", [])
|
||||
if not isinstance(addresses, list) or any(
|
||||
not isinstance(address, str) or not address for address in addresses
|
||||
):
|
||||
raise ConfigError(f"{name}.advertised_addresses must be a string array")
|
||||
return ServiceConfig(
|
||||
_endpoint(value, "endpoint", {"http", "https"}), api_root,
|
||||
_absolute_path(value, "local_root"), username,
|
||||
_absolute_path(value, "password_file")
|
||||
if "password_file" in value else None,
|
||||
_absolute_path(value, "api_key_file")
|
||||
if "api_key_file" in value else None,
|
||||
tuple(addresses),
|
||||
)
|
||||
|
||||
|
||||
def _connection(value: Any) -> ConnectionConfig:
|
||||
if not isinstance(value, dict):
|
||||
raise ConfigError("connection must be a table")
|
||||
_keys(value, {"registration_timeout", "heartbeat_interval", "offline_timeout", "reconnect_initial", "reconnect_max", "reconnect_reset_after", "reconnect_jitter"}, "connection")
|
||||
result = ConnectionConfig(
|
||||
registration_timeout=_duration(value.get("registration_timeout", "10s")),
|
||||
heartbeat_interval=_duration(value.get("heartbeat_interval", "15s")),
|
||||
offline_timeout=_duration(value.get("offline_timeout", "45s")),
|
||||
reconnect_initial=_duration(value.get("reconnect_initial", "1s")),
|
||||
reconnect_max=_duration(value.get("reconnect_max", "60s")),
|
||||
reconnect_reset_after=_duration(value.get("reconnect_reset_after", "60s")),
|
||||
reconnect_jitter=value.get("reconnect_jitter", True),
|
||||
)
|
||||
if result.reconnect_initial > result.reconnect_max:
|
||||
raise ConfigError("reconnect_initial cannot exceed reconnect_max")
|
||||
if result.offline_timeout <= result.heartbeat_interval:
|
||||
raise ConfigError("offline_timeout must exceed heartbeat_interval")
|
||||
if not isinstance(result.reconnect_jitter, bool):
|
||||
raise ConfigError("reconnect_jitter must be boolean")
|
||||
return result
|
||||
|
||||
|
||||
def _jobs(value: Any) -> JobsConfig:
|
||||
if not isinstance(value, dict):
|
||||
raise ConfigError("jobs must be a table")
|
||||
_keys(value, {"stall_after"}, "jobs")
|
||||
return JobsConfig(stall_after=_duration(value.get("stall_after", "30m")))
|
||||
|
||||
|
||||
def _duration(value: Any) -> float:
|
||||
if not isinstance(value, str) or not (match := _DURATION.fullmatch(value)):
|
||||
raise ConfigError("duration must look like 15s or 30m")
|
||||
return int(match.group(1)) * _FACTORS[match.group(2)]
|
||||
|
||||
|
||||
def _string(value: dict[str, Any], key: str) -> str:
|
||||
item = value.get(key)
|
||||
if not isinstance(item, str) or not item:
|
||||
raise ConfigError(f"{key} must be a non-empty string")
|
||||
return _expand(item)
|
||||
|
||||
|
||||
def _path(value: dict[str, Any], key: str) -> Path:
|
||||
return Path(_string(value, key))
|
||||
|
||||
|
||||
def _absolute_path(value: dict[str, Any], key: str) -> Path:
|
||||
path = _path(value, key)
|
||||
if not path.is_absolute():
|
||||
raise ConfigError(f"{key} must be an absolute path")
|
||||
return path
|
||||
|
||||
|
||||
def _endpoint(
|
||||
value: dict[str, Any], key: str, allowed_schemes: set[str]
|
||||
) -> str:
|
||||
endpoint = _string(value, key)
|
||||
parsed = urlsplit(endpoint)
|
||||
if (
|
||||
parsed.scheme not in allowed_schemes
|
||||
or not parsed.hostname
|
||||
or parsed.username is not None
|
||||
or parsed.password is not None
|
||||
or parsed.fragment
|
||||
):
|
||||
schemes = "/".join(sorted(allowed_schemes))
|
||||
raise ConfigError(f"{key} must be a credential-free {schemes} endpoint")
|
||||
return endpoint
|
||||
|
||||
|
||||
def _expand(value: str) -> str:
|
||||
def replace(match: re.Match[str]) -> str:
|
||||
name = match.group(1)
|
||||
if name not in os.environ:
|
||||
raise ConfigError(f"environment variable {name} is not set")
|
||||
return os.environ[name]
|
||||
expanded = _ENV.sub(replace, value)
|
||||
if "$" in expanded:
|
||||
raise ConfigError("only ${NAME} environment interpolation is supported")
|
||||
return expanded
|
||||
|
||||
|
||||
def _secret(path: Path, name: str) -> str:
|
||||
try:
|
||||
metadata = path.stat()
|
||||
exposed = metadata.st_mode & (stat.S_IRWXG | stat.S_IRWXO)
|
||||
if not stat.S_ISREG(metadata.st_mode) or exposed:
|
||||
raise ConfigError(f"{name} file permissions are unsafe")
|
||||
value = path.read_text(encoding="utf-8").strip()
|
||||
except OSError as exc:
|
||||
raise ConfigError(f"{name} file cannot be read") from exc
|
||||
if not value:
|
||||
raise ConfigError(f"{name} is empty")
|
||||
return value
|
||||
|
||||
|
||||
def _keys(value: dict[str, Any], allowed: set[str], name: str) -> None:
|
||||
unknown = sorted(set(value) - allowed)
|
||||
if unknown:
|
||||
raise ConfigError(f"unknown {name} keys: {', '.join(unknown)}")
|
||||
@@ -0,0 +1,198 @@
|
||||
"""Reconnectable Archive Control client transport."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from websockets.asyncio.client import connect
|
||||
|
||||
from archive_clients.config import ClientConfig
|
||||
from archive_clients.probes import FilesystemProbe
|
||||
from archive_clients.protocol import (
|
||||
decode,
|
||||
decode_message,
|
||||
encode,
|
||||
encode_message,
|
||||
new_envelope,
|
||||
)
|
||||
from archive_clients.state import ClientStore, CommandConflict
|
||||
from archive_control.v1 import client_pb2, common_pb2, control_pb2, job_pb2
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ArchiveClientDaemon:
|
||||
def __init__(self, config: ClientConfig, probes: list[FilesystemProbe]):
|
||||
self.config = config
|
||||
self.probes = probes
|
||||
self.store = ClientStore(config.state_db)
|
||||
|
||||
async def run(self) -> None:
|
||||
await asyncio.to_thread(self.store.initialize)
|
||||
delay = self.config.connection.reconnect_initial
|
||||
while True:
|
||||
started = time.monotonic()
|
||||
try:
|
||||
await self._connection()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.warning("control connection ended: %s", type(exc).__name__)
|
||||
if time.monotonic() - started >= self.config.connection.reconnect_reset_after:
|
||||
delay = self.config.connection.reconnect_initial
|
||||
wait = random.uniform(0, delay) if self.config.connection.reconnect_jitter else delay
|
||||
await asyncio.sleep(wait)
|
||||
delay = min(delay * 2, self.config.connection.reconnect_max)
|
||||
|
||||
async def _connection(self) -> None:
|
||||
async with connect(
|
||||
self.config.control_endpoint, ping_interval=None, compression=None,
|
||||
max_size=1024 * 1024,
|
||||
) as websocket:
|
||||
registration = self._registration()
|
||||
await websocket.send(encode(registration))
|
||||
registration.register_request.shared_token = ""
|
||||
response = decode(await asyncio.wait_for(
|
||||
websocket.recv(), self.config.connection.registration_timeout
|
||||
))
|
||||
if response.WhichOneof("payload") != "register_response":
|
||||
raise RuntimeError("control did not answer registration")
|
||||
if response.correlation_id != registration.message_id:
|
||||
raise RuntimeError("registration response correlation mismatch")
|
||||
if response.register_response.status != client_pb2.REGISTRATION_STATUS_ACCEPTED:
|
||||
raise RuntimeError("control rejected registration")
|
||||
if response.register_response.negotiated_version.major != 1:
|
||||
raise RuntimeError("control negotiated an unsupported protocol version")
|
||||
outbound: asyncio.Queue[str] = asyncio.Queue(maxsize=100)
|
||||
writer = asyncio.create_task(self._writer(websocket, outbound))
|
||||
try:
|
||||
async for frame in websocket:
|
||||
await self._handle(decode(frame), outbound)
|
||||
finally:
|
||||
writer.cancel()
|
||||
await asyncio.gather(writer, return_exceptions=True)
|
||||
|
||||
def _registration(self):
|
||||
envelope = new_envelope()
|
||||
request = envelope.register_request
|
||||
request.protocol_version.CopyFrom(envelope.protocol_version)
|
||||
request.client.client_id = self.config.client_id
|
||||
request.client.display_name = self.config.display_name
|
||||
request.client.role = (
|
||||
common_pb2.CLIENT_ROLE_ARCHIVE
|
||||
if self.config.role == "archive" else common_pb2.CLIENT_ROLE_CACHE
|
||||
)
|
||||
request.connection_instance_id = str(uuid.uuid4())
|
||||
request.shared_token = self.config.read_shared_token()
|
||||
request.capabilities.max_envelope_bytes = 1024 * 1024
|
||||
request.capabilities.syncthing_advertised_addresses.extend(
|
||||
self.config.syncthing.advertised_addresses
|
||||
)
|
||||
for root_name, probe in zip(
|
||||
("qbittorrent", "syncthing"), self.probes, strict=True
|
||||
):
|
||||
filesystem = request.capabilities.filesystems.add()
|
||||
filesystem.root_name = root_name
|
||||
filesystem.readable = probe.readable
|
||||
filesystem.writable = probe.writable
|
||||
filesystem.hard_link = probe.hard_link
|
||||
filesystem.sparse_files = probe.sparse_files
|
||||
if all(probe.sparse_files for probe in self.probes):
|
||||
request.capabilities.features.append(client_pb2.CLIENT_FEATURE_SPARSE_FILES)
|
||||
for cursor in self.store.list_active_job_cursors():
|
||||
active = request.active_jobs.add()
|
||||
active.job_id = str(cursor["job_id"])
|
||||
active.job_revision = int(cursor["revision"])
|
||||
active.last_event_sequence = int(cursor["last_event_sequence"])
|
||||
active.state = job_pb2.JobState.Value(str(cursor["state"]))
|
||||
active.committed = bool(cursor["committed"])
|
||||
return envelope
|
||||
|
||||
async def _writer(
|
||||
self, websocket: Any, outbound: asyncio.Queue[str]
|
||||
) -> None:
|
||||
while True:
|
||||
await websocket.send(await outbound.get())
|
||||
|
||||
async def _handle(
|
||||
self, envelope: Any, outbound: asyncio.Queue[str]
|
||||
) -> None:
|
||||
payload = envelope.WhichOneof("payload")
|
||||
if payload == "heartbeat":
|
||||
response = new_envelope()
|
||||
response.correlation_id = envelope.message_id
|
||||
response.heartbeat_ack.sequence = envelope.heartbeat.sequence
|
||||
await outbound.put(encode(response))
|
||||
elif payload == "command":
|
||||
await self._accept_command(envelope, outbound)
|
||||
elif payload == "protocol_error":
|
||||
logger.warning("control reported protocol error code=%s", envelope.protocol_error.error.code)
|
||||
|
||||
async def _accept_command(
|
||||
self, envelope: Any, outbound: asyncio.Queue[str]
|
||||
) -> None:
|
||||
command = envelope.command
|
||||
acknowledgement = self._initial_acknowledgement(command)
|
||||
accepted = None
|
||||
try:
|
||||
accepted = await asyncio.to_thread(
|
||||
self.store.accept_command,
|
||||
command.command_id,
|
||||
encode_message(command),
|
||||
encode_message(acknowledgement),
|
||||
)
|
||||
if accepted.duplicate:
|
||||
acknowledgement = decode_message(
|
||||
accepted.acknowledgement_json, control_pb2.CommandAck()
|
||||
)
|
||||
if (
|
||||
acknowledgement.status
|
||||
== control_pb2.COMMAND_ACK_STATUS_ACCEPTED
|
||||
):
|
||||
acknowledgement.status = (
|
||||
control_pb2.COMMAND_ACK_STATUS_DUPLICATE
|
||||
)
|
||||
except CommandConflict:
|
||||
acknowledgement.status = control_pb2.COMMAND_ACK_STATUS_REJECTED
|
||||
acknowledgement.error.code = common_pb2.ERROR_CODE_CONFLICT
|
||||
acknowledgement.error.message = "command ID content conflict"
|
||||
response = new_envelope()
|
||||
response.correlation_id = envelope.message_id
|
||||
response.command_ack.CopyFrom(acknowledgement)
|
||||
await outbound.put(encode(response))
|
||||
|
||||
if (
|
||||
accepted is not None
|
||||
and not accepted.duplicate
|
||||
and acknowledgement.status
|
||||
== control_pb2.COMMAND_ACK_STATUS_ACCEPTED
|
||||
and command.WhichOneof("payload") == "request_job_snapshot"
|
||||
):
|
||||
snapshot = new_envelope()
|
||||
snapshot.correlation_id = envelope.message_id
|
||||
snapshot.client_state_snapshot.snapshot_id = str(uuid.uuid4())
|
||||
snapshot.client_state_snapshot.observed_at.CopyFrom(snapshot.sent_at)
|
||||
await outbound.put(encode(snapshot))
|
||||
|
||||
@staticmethod
|
||||
def _initial_acknowledgement(command: Any) -> control_pb2.CommandAck:
|
||||
acknowledgement = control_pb2.CommandAck(command_id=command.command_id)
|
||||
if not command.command_id or command.WhichOneof("payload") is None:
|
||||
acknowledgement.status = control_pb2.COMMAND_ACK_STATUS_REJECTED
|
||||
acknowledgement.error.code = common_pb2.ERROR_CODE_INVALID_ARGUMENT
|
||||
acknowledgement.error.message = "command ID and payload are required"
|
||||
elif command.WhichOneof("payload") == "request_job_snapshot":
|
||||
acknowledgement.status = control_pb2.COMMAND_ACK_STATUS_ACCEPTED
|
||||
else:
|
||||
acknowledgement.status = control_pb2.COMMAND_ACK_STATUS_REJECTED
|
||||
acknowledgement.error.code = common_pb2.ERROR_CODE_UNSUPPORTED
|
||||
acknowledgement.error.message = (
|
||||
"command is not supported by this client build"
|
||||
)
|
||||
return acknowledgement
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Fail-fast root and filesystem capability probes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class ProbeError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FilesystemProbe:
|
||||
root: Path
|
||||
readable: bool
|
||||
writable: bool
|
||||
hard_link: bool
|
||||
sparse_files: bool
|
||||
|
||||
|
||||
def probe_root(root: Path) -> FilesystemProbe:
|
||||
if not root.is_absolute() or not root.is_dir():
|
||||
raise ProbeError(f"configured root is not an existing absolute directory: {root}")
|
||||
if not os.access(root, os.R_OK | os.X_OK | os.W_OK):
|
||||
raise ProbeError(f"configured root permissions are insufficient: {root}")
|
||||
source: Path | None = None
|
||||
linked: Path | None = None
|
||||
try:
|
||||
descriptor, raw_path = tempfile.mkstemp(prefix=".archive-control-probe-", dir=root)
|
||||
source = Path(raw_path)
|
||||
with os.fdopen(descriptor, "wb") as probe:
|
||||
probe.seek(1024 * 1024)
|
||||
probe.write(b"x")
|
||||
probe.flush()
|
||||
os.fsync(probe.fileno())
|
||||
allocated = source.stat().st_blocks * 512
|
||||
sparse = allocated < source.stat().st_size
|
||||
linked = source.with_name(f"{source.name}.link")
|
||||
try:
|
||||
os.link(source, linked)
|
||||
hard_link = linked.stat().st_ino == source.stat().st_ino
|
||||
except OSError:
|
||||
hard_link = False
|
||||
return FilesystemProbe(root, True, True, hard_link, sparse)
|
||||
except OSError as exc:
|
||||
raise ProbeError(f"filesystem capability probe failed for {root}") from exc
|
||||
finally:
|
||||
if linked is not None:
|
||||
linked.unlink(missing_ok=True)
|
||||
if source is not None:
|
||||
source.unlink(missing_ok=True)
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Strict protobuf-JSON envelope helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from google.protobuf import json_format
|
||||
|
||||
from archive_control.v1 import envelope_pb2
|
||||
|
||||
|
||||
class ProtocolError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def new_envelope() -> envelope_pb2.Envelope:
|
||||
envelope = envelope_pb2.Envelope()
|
||||
envelope.protocol_version.major = 1
|
||||
envelope.message_id = str(uuid.uuid4())
|
||||
envelope.sent_at.FromDatetime(datetime.now(timezone.utc))
|
||||
return envelope
|
||||
|
||||
|
||||
def encode(envelope: envelope_pb2.Envelope) -> str:
|
||||
return json_format.MessageToJson(
|
||||
envelope, preserving_proto_field_name=False, indent=None,
|
||||
sort_keys=True, ensure_ascii=False,
|
||||
)
|
||||
|
||||
|
||||
def encode_message(message: Any) -> str:
|
||||
return json_format.MessageToJson(
|
||||
message, preserving_proto_field_name=False, indent=None,
|
||||
sort_keys=True, ensure_ascii=False,
|
||||
)
|
||||
|
||||
|
||||
def decode_message(data: str, message: Any) -> Any:
|
||||
try:
|
||||
raw = json.loads(data, object_pairs_hook=_unique)
|
||||
return json_format.ParseDict(raw, message)
|
||||
except (ValueError, json.JSONDecodeError, json_format.ParseError) as exc:
|
||||
raise ProtocolError("invalid protobuf JSON message") from exc
|
||||
|
||||
|
||||
def decode(data: str | bytes, max_bytes: int = 1024 * 1024) -> envelope_pb2.Envelope:
|
||||
if not isinstance(data, str) or len(data.encode()) > max_bytes:
|
||||
raise ProtocolError("control envelope must be bounded text")
|
||||
try:
|
||||
raw = json.loads(data, object_pairs_hook=_unique)
|
||||
envelope = json_format.ParseDict(raw, envelope_pb2.Envelope())
|
||||
except (
|
||||
TypeError,
|
||||
ValueError,
|
||||
json.JSONDecodeError,
|
||||
json_format.ParseError,
|
||||
) as exc:
|
||||
raise ProtocolError("invalid control envelope") from exc
|
||||
if envelope.protocol_version.major != 1:
|
||||
raise ProtocolError("unsupported protocol major version")
|
||||
_canonical_uuid(envelope.message_id, "message_id")
|
||||
if envelope.correlation_id:
|
||||
_canonical_uuid(envelope.correlation_id, "correlation_id")
|
||||
if not envelope.HasField("sent_at"):
|
||||
raise ProtocolError("sent_at is required")
|
||||
try:
|
||||
envelope.sent_at.ToDatetime(tzinfo=timezone.utc)
|
||||
except (ValueError, OverflowError) as exc:
|
||||
raise ProtocolError("sent_at is invalid") from exc
|
||||
if envelope.WhichOneof("payload") is None:
|
||||
raise ProtocolError("unsupported or empty control envelope")
|
||||
return envelope
|
||||
|
||||
|
||||
def _unique(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
||||
result = {}
|
||||
for key, value in pairs:
|
||||
if key in result:
|
||||
raise ValueError("duplicate JSON key")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _canonical_uuid(value: str, name: str) -> None:
|
||||
try:
|
||||
parsed = uuid.UUID(value)
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise ProtocolError(f"{name} must be a canonical UUID") from exc
|
||||
if str(parsed) != value:
|
||||
raise ProtocolError(f"{name} must be a lowercase canonical UUID")
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Durable command inbox and client recovery cursors."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class CommandConflict(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CommandAcceptance:
|
||||
duplicate: bool
|
||||
acknowledgement_json: str
|
||||
|
||||
|
||||
class ClientStore:
|
||||
def __init__(self, database: Path):
|
||||
self.database = database
|
||||
|
||||
def initialize(self) -> None:
|
||||
self.database.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self._connect() as connection:
|
||||
version = connection.execute("PRAGMA user_version").fetchone()[0]
|
||||
if version > 1:
|
||||
raise RuntimeError(
|
||||
f"client database schema {version} is newer than supported"
|
||||
)
|
||||
connection.executescript(_SCHEMA)
|
||||
connection.execute("PRAGMA user_version = 1")
|
||||
if connection.execute("PRAGMA foreign_key_check").fetchall():
|
||||
raise RuntimeError("client database foreign-key check failed")
|
||||
|
||||
def accept_command(
|
||||
self, command_id: str, command_json: str, acknowledgement_json: str
|
||||
) -> CommandAcceptance:
|
||||
payload = _canonical(json.loads(command_json))
|
||||
acknowledgement = _canonical(json.loads(acknowledgement_json))
|
||||
digest = hashlib.sha256(payload.encode()).hexdigest()
|
||||
with self._connect() as connection:
|
||||
connection.execute("BEGIN IMMEDIATE")
|
||||
existing = connection.execute(
|
||||
"SELECT payload_sha256, command_json, acknowledgement_json FROM commands WHERE command_id = ?",
|
||||
(command_id,),
|
||||
).fetchone()
|
||||
if existing:
|
||||
if existing["payload_sha256"] != digest or existing["command_json"] != payload:
|
||||
raise CommandConflict("command ID was reused with different content")
|
||||
return CommandAcceptance(True, existing["acknowledgement_json"])
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO commands (
|
||||
command_id, payload_sha256, command_json,
|
||||
acknowledgement_json, state
|
||||
) VALUES (?, ?, ?, ?, 'received')
|
||||
""",
|
||||
(command_id, digest, payload, acknowledgement),
|
||||
)
|
||||
return CommandAcceptance(False, acknowledgement)
|
||||
|
||||
def list_active_job_cursors(self) -> list[dict[str, object]]:
|
||||
with self._connect() as connection:
|
||||
rows = connection.execute(
|
||||
"""
|
||||
SELECT job_id, revision, last_event_sequence, state, committed
|
||||
FROM jobs WHERE state NOT IN ('JOB_STATE_SUCCEEDED',
|
||||
'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED')
|
||||
ORDER BY job_id
|
||||
"""
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def _connect(self) -> sqlite3.Connection:
|
||||
connection = sqlite3.connect(self.database, isolation_level=None, timeout=5)
|
||||
connection.row_factory = sqlite3.Row
|
||||
connection.execute("PRAGMA foreign_keys = ON")
|
||||
connection.execute("PRAGMA journal_mode = WAL")
|
||||
connection.execute("PRAGMA synchronous = FULL")
|
||||
connection.execute("PRAGMA busy_timeout = 5000")
|
||||
return connection
|
||||
|
||||
|
||||
def _canonical(value: object) -> str:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
||||
|
||||
|
||||
_SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS commands (
|
||||
command_id TEXT PRIMARY KEY,
|
||||
payload_sha256 TEXT NOT NULL,
|
||||
command_json TEXT NOT NULL,
|
||||
acknowledgement_json TEXT NOT NULL,
|
||||
state TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
job_id TEXT PRIMARY KEY,
|
||||
definition_json TEXT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
revision INTEGER NOT NULL,
|
||||
last_event_sequence INTEGER NOT NULL,
|
||||
committed INTEGER NOT NULL CHECK(committed IN (0, 1))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
job_id TEXT NOT NULL REFERENCES jobs(job_id),
|
||||
sequence INTEGER NOT NULL,
|
||||
event_json TEXT NOT NULL,
|
||||
UNIQUE(job_id, sequence)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_journal (
|
||||
operation_id TEXT PRIMARY KEY,
|
||||
job_id TEXT NOT NULL,
|
||||
intent_json TEXT NOT NULL,
|
||||
result_json TEXT,
|
||||
state TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
@@ -0,0 +1,2 @@
|
||||
"""Generated Archive Control protocol package."""
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
"""Generated archive_control.v1 bindings."""
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: archive_control/v1/client.proto
|
||||
# Protobuf Python Version: 7.35.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
7,
|
||||
35,
|
||||
1,
|
||||
'',
|
||||
'archive_control/v1/client.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from archive_control.v1 import common_pb2 as archive__control_dot_v1_dot_common__pb2
|
||||
from archive_control.v1 import job_pb2 as archive__control_dot_v1_dot_job__pb2
|
||||
from archive_control.v1 import route_pb2 as archive__control_dot_v1_dot_route__pb2
|
||||
from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x61rchive_control/v1/client.proto\x12\x12\x61rchive_control.v1\x1a\x1f\x61rchive_control/v1/common.proto\x1a\x1c\x61rchive_control/v1/job.proto\x1a\x1e\x61rchive_control/v1/route.proto\x1a\x1egoogle/protobuf/duration.proto\"\xdf\x01\n\x16\x46ilesystemCapabilities\x12\x1b\n\troot_name\x18\x01 \x01(\tR\x08rootName\x12\x1a\n\x08readable\x18\x02 \x01(\x08R\x08readable\x12\x1a\n\x08writable\x18\x03 \x01(\x08R\x08writable\x12\x1b\n\thard_link\x18\x04 \x01(\x08R\x08hardLink\x12\x18\n\x07reflink\x18\x05 \x01(\x08R\x07reflink\x12!\n\x0csparse_files\x18\x06 \x01(\x08R\x0bsparseFiles\x12\x16\n\x06\x64\x65tail\x18\x07 \x01(\tR\x06\x64\x65tail\"\xc6\x05\n\x12\x43lientCapabilities\x12=\n\x08\x66\x65\x61tures\x18\x01 \x03(\x0e\x32!.archive_control.v1.ClientFeatureR\x08\x66\x65\x61tures\x12=\n\x08services\x18\x02 \x03(\x0b\x32!.archive_control.v1.ServiceHealthR\x08services\x12L\n\x0b\x66ilesystems\x18\x03 \x03(\x0b\x32*.archive_control.v1.FilesystemCapabilitiesR\x0b\x66ilesystems\x12\x36\n\x06routes\x18\x04 \x03(\x0b\x32\x1e.archive_control.v1.LocalRouteR\x06routes\x12.\n\x13syncthing_device_id\x18\x05 \x01(\tR\x11syncthingDeviceId\x12\x44\n\x1esyncthing_advertised_addresses\x18\x06 \x03(\tR\x1csyncthingAdvertisedAddresses\x12/\n\x13qbittorrent_version\x18\x07 \x01(\tR\x12qbittorrentVersion\x12=\n\x1bqbittorrent_web_api_version\x18\x08 \x01(\tR\x18qbittorrentWebApiVersion\x12-\n\x12libtorrent_version\x18\t \x01(\tR\x11libtorrentVersion\x12+\n\x11syncthing_version\x18\n \x01(\tR\x10syncthingVersion\x12<\n\x1asupported_partfile_formats\x18\x0b \x03(\tR\x18supportedPartfileFormats\x12,\n\x12max_envelope_bytes\x18\x0c \x01(\rR\x10maxEnvelopeBytes\"\xcd\x01\n\x0f\x41\x63tiveJobCursor\x12\x15\n\x06job_id\x18\x01 \x01(\tR\x05jobId\x12!\n\x0cjob_revision\x18\x02 \x01(\x04R\x0bjobRevision\x12.\n\x13last_event_sequence\x18\x03 \x01(\x04R\x11lastEventSequence\x12\x32\n\x05state\x18\x04 \x01(\x0e\x32\x1c.archive_control.v1.JobStateR\x05state\x12\x1c\n\tcommitted\x18\x05 \x01(\x08R\tcommitted\"\x88\x03\n\x0fRegisterRequest\x12N\n\x10protocol_version\x18\x01 \x01(\x0b\x32#.archive_control.v1.ProtocolVersionR\x0fprotocolVersion\x12:\n\x06\x63lient\x18\x02 \x01(\x0b\x32\".archive_control.v1.ClientIdentityR\x06\x63lient\x12\x34\n\x16\x63onnection_instance_id\x18\x03 \x01(\tR\x14\x63onnectionInstanceId\x12!\n\x0cshared_token\x18\x04 \x01(\tR\x0bsharedToken\x12J\n\x0c\x63\x61pabilities\x18\x05 \x01(\x0b\x32&.archive_control.v1.ClientCapabilitiesR\x0c\x63\x61pabilities\x12\x44\n\x0b\x61\x63tive_jobs\x18\x06 \x03(\x0b\x32#.archive_control.v1.ActiveJobCursorR\nactiveJobs\"\x8e\x03\n\x10RegisterResponse\x12>\n\x06status\x18\x01 \x01(\x0e\x32&.archive_control.v1.RegistrationStatusR\x06status\x12R\n\x12negotiated_version\x18\x02 \x01(\x0b\x32#.archive_control.v1.ProtocolVersionR\x11negotiatedVersion\x12#\n\rconnection_id\x18\x03 \x01(\tR\x0c\x63onnectionId\x12H\n\x12heartbeat_interval\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationR\x11heartbeatInterval\x12\x46\n\x11heartbeat_timeout\x18\x05 \x01(\x0b\x32\x19.google.protobuf.DurationR\x10heartbeatTimeout\x12/\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x19.archive_control.v1.ErrorR\x05\x65rror\"\'\n\tHeartbeat\x12\x1a\n\x08sequence\x18\x01 \x01(\x04R\x08sequence\"*\n\x0cHeartbeatAck\x12\x1a\n\x08sequence\x18\x01 \x01(\x04R\x08sequence*\x9f\x03\n\rClientFeature\x12\x1e\n\x1a\x43LIENT_FEATURE_UNSPECIFIED\x10\x00\x12#\n\x1f\x43LIENT_FEATURE_INVENTORY_CHUNKS\x10\x01\x12\x1f\n\x1b\x43LIENT_FEATURE_CONTENT_TREE\x10\x02\x12%\n!CLIENT_FEATURE_ROUTE_PROVISIONING\x10\x03\x12\x1c\n\x18\x43LIENT_FEATURE_HARD_LINK\x10\x04\x12\x1a\n\x16\x43LIENT_FEATURE_REFLINK\x10\x05\x12\x1f\n\x1b\x43LIENT_FEATURE_SPARSE_FILES\x10\x06\x12%\n!CLIENT_FEATURE_PARTFILE_MIGRATION\x10\x07\x12!\n\x1d\x43LIENT_FEATURE_PARTFILE_MERGE\x10\x08\x12!\n\x1d\x43LIENT_FEATURE_QB_STOPPED_ADD\x10\t\x12\x1b\n\x17\x43LIENT_FEATURE_EVICTION\x10\n\x12\x1c\n\x18\x43LIENT_FEATURE_DB_BACKUP\x10\x0b*}\n\x12RegistrationStatus\x12#\n\x1fREGISTRATION_STATUS_UNSPECIFIED\x10\x00\x12 \n\x1cREGISTRATION_STATUS_ACCEPTED\x10\x01\x12 \n\x1cREGISTRATION_STATUS_REJECTED\x10\x02\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'archive_control.v1.client_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_CLIENTFEATURE']._serialized_start=2211
|
||||
_globals['_CLIENTFEATURE']._serialized_end=2626
|
||||
_globals['_REGISTRATIONSTATUS']._serialized_start=2628
|
||||
_globals['_REGISTRATIONSTATUS']._serialized_end=2753
|
||||
_globals['_FILESYSTEMCAPABILITIES']._serialized_start=183
|
||||
_globals['_FILESYSTEMCAPABILITIES']._serialized_end=406
|
||||
_globals['_CLIENTCAPABILITIES']._serialized_start=409
|
||||
_globals['_CLIENTCAPABILITIES']._serialized_end=1119
|
||||
_globals['_ACTIVEJOBCURSOR']._serialized_start=1122
|
||||
_globals['_ACTIVEJOBCURSOR']._serialized_end=1327
|
||||
_globals['_REGISTERREQUEST']._serialized_start=1330
|
||||
_globals['_REGISTERREQUEST']._serialized_end=1722
|
||||
_globals['_REGISTERRESPONSE']._serialized_start=1725
|
||||
_globals['_REGISTERRESPONSE']._serialized_end=2123
|
||||
_globals['_HEARTBEAT']._serialized_start=2125
|
||||
_globals['_HEARTBEAT']._serialized_end=2164
|
||||
_globals['_HEARTBEATACK']._serialized_start=2166
|
||||
_globals['_HEARTBEATACK']._serialized_end=2208
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,155 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from archive_control.v1 import common_pb2 as _common_pb2
|
||||
from archive_control.v1 import job_pb2 as _job_pb2
|
||||
from archive_control.v1 import route_pb2 as _route_pb2
|
||||
from google.protobuf import duration_pb2 as _duration_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class ClientFeature(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
CLIENT_FEATURE_UNSPECIFIED: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_INVENTORY_CHUNKS: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_CONTENT_TREE: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_ROUTE_PROVISIONING: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_HARD_LINK: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_REFLINK: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_SPARSE_FILES: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_PARTFILE_MIGRATION: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_PARTFILE_MERGE: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_QB_STOPPED_ADD: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_EVICTION: _ClassVar[ClientFeature]
|
||||
CLIENT_FEATURE_DB_BACKUP: _ClassVar[ClientFeature]
|
||||
|
||||
class RegistrationStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
REGISTRATION_STATUS_UNSPECIFIED: _ClassVar[RegistrationStatus]
|
||||
REGISTRATION_STATUS_ACCEPTED: _ClassVar[RegistrationStatus]
|
||||
REGISTRATION_STATUS_REJECTED: _ClassVar[RegistrationStatus]
|
||||
CLIENT_FEATURE_UNSPECIFIED: ClientFeature
|
||||
CLIENT_FEATURE_INVENTORY_CHUNKS: ClientFeature
|
||||
CLIENT_FEATURE_CONTENT_TREE: ClientFeature
|
||||
CLIENT_FEATURE_ROUTE_PROVISIONING: ClientFeature
|
||||
CLIENT_FEATURE_HARD_LINK: ClientFeature
|
||||
CLIENT_FEATURE_REFLINK: ClientFeature
|
||||
CLIENT_FEATURE_SPARSE_FILES: ClientFeature
|
||||
CLIENT_FEATURE_PARTFILE_MIGRATION: ClientFeature
|
||||
CLIENT_FEATURE_PARTFILE_MERGE: ClientFeature
|
||||
CLIENT_FEATURE_QB_STOPPED_ADD: ClientFeature
|
||||
CLIENT_FEATURE_EVICTION: ClientFeature
|
||||
CLIENT_FEATURE_DB_BACKUP: ClientFeature
|
||||
REGISTRATION_STATUS_UNSPECIFIED: RegistrationStatus
|
||||
REGISTRATION_STATUS_ACCEPTED: RegistrationStatus
|
||||
REGISTRATION_STATUS_REJECTED: RegistrationStatus
|
||||
|
||||
class FilesystemCapabilities(_message.Message):
|
||||
__slots__ = ("root_name", "readable", "writable", "hard_link", "reflink", "sparse_files", "detail")
|
||||
ROOT_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
READABLE_FIELD_NUMBER: _ClassVar[int]
|
||||
WRITABLE_FIELD_NUMBER: _ClassVar[int]
|
||||
HARD_LINK_FIELD_NUMBER: _ClassVar[int]
|
||||
REFLINK_FIELD_NUMBER: _ClassVar[int]
|
||||
SPARSE_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
root_name: str
|
||||
readable: bool
|
||||
writable: bool
|
||||
hard_link: bool
|
||||
reflink: bool
|
||||
sparse_files: bool
|
||||
detail: str
|
||||
def __init__(self, root_name: _Optional[str] = ..., readable: _Optional[bool] = ..., writable: _Optional[bool] = ..., hard_link: _Optional[bool] = ..., reflink: _Optional[bool] = ..., sparse_files: _Optional[bool] = ..., detail: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class ClientCapabilities(_message.Message):
|
||||
__slots__ = ("features", "services", "filesystems", "routes", "syncthing_device_id", "syncthing_advertised_addresses", "qbittorrent_version", "qbittorrent_web_api_version", "libtorrent_version", "syncthing_version", "supported_partfile_formats", "max_envelope_bytes")
|
||||
FEATURES_FIELD_NUMBER: _ClassVar[int]
|
||||
SERVICES_FIELD_NUMBER: _ClassVar[int]
|
||||
FILESYSTEMS_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTES_FIELD_NUMBER: _ClassVar[int]
|
||||
SYNCTHING_DEVICE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SYNCTHING_ADVERTISED_ADDRESSES_FIELD_NUMBER: _ClassVar[int]
|
||||
QBITTORRENT_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
QBITTORRENT_WEB_API_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
LIBTORRENT_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
SYNCTHING_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
SUPPORTED_PARTFILE_FORMATS_FIELD_NUMBER: _ClassVar[int]
|
||||
MAX_ENVELOPE_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
features: _containers.RepeatedScalarFieldContainer[ClientFeature]
|
||||
services: _containers.RepeatedCompositeFieldContainer[_common_pb2.ServiceHealth]
|
||||
filesystems: _containers.RepeatedCompositeFieldContainer[FilesystemCapabilities]
|
||||
routes: _containers.RepeatedCompositeFieldContainer[_route_pb2.LocalRoute]
|
||||
syncthing_device_id: str
|
||||
syncthing_advertised_addresses: _containers.RepeatedScalarFieldContainer[str]
|
||||
qbittorrent_version: str
|
||||
qbittorrent_web_api_version: str
|
||||
libtorrent_version: str
|
||||
syncthing_version: str
|
||||
supported_partfile_formats: _containers.RepeatedScalarFieldContainer[str]
|
||||
max_envelope_bytes: int
|
||||
def __init__(self, features: _Optional[_Iterable[_Union[ClientFeature, str]]] = ..., services: _Optional[_Iterable[_Union[_common_pb2.ServiceHealth, _Mapping]]] = ..., filesystems: _Optional[_Iterable[_Union[FilesystemCapabilities, _Mapping]]] = ..., routes: _Optional[_Iterable[_Union[_route_pb2.LocalRoute, _Mapping]]] = ..., syncthing_device_id: _Optional[str] = ..., syncthing_advertised_addresses: _Optional[_Iterable[str]] = ..., qbittorrent_version: _Optional[str] = ..., qbittorrent_web_api_version: _Optional[str] = ..., libtorrent_version: _Optional[str] = ..., syncthing_version: _Optional[str] = ..., supported_partfile_formats: _Optional[_Iterable[str]] = ..., max_envelope_bytes: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ActiveJobCursor(_message.Message):
|
||||
__slots__ = ("job_id", "job_revision", "last_event_sequence", "state", "committed")
|
||||
JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
JOB_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
LAST_EVENT_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
COMMITTED_FIELD_NUMBER: _ClassVar[int]
|
||||
job_id: str
|
||||
job_revision: int
|
||||
last_event_sequence: int
|
||||
state: _job_pb2.JobState
|
||||
committed: bool
|
||||
def __init__(self, job_id: _Optional[str] = ..., job_revision: _Optional[int] = ..., last_event_sequence: _Optional[int] = ..., state: _Optional[_Union[_job_pb2.JobState, str]] = ..., committed: _Optional[bool] = ...) -> None: ...
|
||||
|
||||
class RegisterRequest(_message.Message):
|
||||
__slots__ = ("protocol_version", "client", "connection_instance_id", "shared_token", "capabilities", "active_jobs")
|
||||
PROTOCOL_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
CLIENT_FIELD_NUMBER: _ClassVar[int]
|
||||
CONNECTION_INSTANCE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SHARED_TOKEN_FIELD_NUMBER: _ClassVar[int]
|
||||
CAPABILITIES_FIELD_NUMBER: _ClassVar[int]
|
||||
ACTIVE_JOBS_FIELD_NUMBER: _ClassVar[int]
|
||||
protocol_version: _common_pb2.ProtocolVersion
|
||||
client: _common_pb2.ClientIdentity
|
||||
connection_instance_id: str
|
||||
shared_token: str
|
||||
capabilities: ClientCapabilities
|
||||
active_jobs: _containers.RepeatedCompositeFieldContainer[ActiveJobCursor]
|
||||
def __init__(self, protocol_version: _Optional[_Union[_common_pb2.ProtocolVersion, _Mapping]] = ..., client: _Optional[_Union[_common_pb2.ClientIdentity, _Mapping]] = ..., connection_instance_id: _Optional[str] = ..., shared_token: _Optional[str] = ..., capabilities: _Optional[_Union[ClientCapabilities, _Mapping]] = ..., active_jobs: _Optional[_Iterable[_Union[ActiveJobCursor, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class RegisterResponse(_message.Message):
|
||||
__slots__ = ("status", "negotiated_version", "connection_id", "heartbeat_interval", "heartbeat_timeout", "error")
|
||||
STATUS_FIELD_NUMBER: _ClassVar[int]
|
||||
NEGOTIATED_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
CONNECTION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
HEARTBEAT_INTERVAL_FIELD_NUMBER: _ClassVar[int]
|
||||
HEARTBEAT_TIMEOUT_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
status: RegistrationStatus
|
||||
negotiated_version: _common_pb2.ProtocolVersion
|
||||
connection_id: str
|
||||
heartbeat_interval: _duration_pb2.Duration
|
||||
heartbeat_timeout: _duration_pb2.Duration
|
||||
error: _common_pb2.Error
|
||||
def __init__(self, status: _Optional[_Union[RegistrationStatus, str]] = ..., negotiated_version: _Optional[_Union[_common_pb2.ProtocolVersion, _Mapping]] = ..., connection_id: _Optional[str] = ..., heartbeat_interval: _Optional[_Union[datetime.timedelta, _duration_pb2.Duration, _Mapping]] = ..., heartbeat_timeout: _Optional[_Union[datetime.timedelta, _duration_pb2.Duration, _Mapping]] = ..., error: _Optional[_Union[_common_pb2.Error, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class Heartbeat(_message.Message):
|
||||
__slots__ = ("sequence",)
|
||||
SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
sequence: int
|
||||
def __init__(self, sequence: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class HeartbeatAck(_message.Message):
|
||||
__slots__ = ("sequence",)
|
||||
SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
sequence: int
|
||||
def __init__(self, sequence: _Optional[int] = ...) -> None: ...
|
||||
@@ -0,0 +1,56 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: archive_control/v1/common.proto
|
||||
# Protobuf Python Version: 7.35.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
7,
|
||||
35,
|
||||
1,
|
||||
'',
|
||||
'archive_control/v1/common.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x61rchive_control/v1/common.proto\x12\x12\x61rchive_control.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"=\n\x0fProtocolVersion\x12\x14\n\x05major\x18\x01 \x01(\rR\x05major\x12\x14\n\x05minor\x18\x02 \x01(\rR\x05minor\"\xf0\x01\n\x05\x45rror\x12\x31\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x1d.archive_control.v1.ErrorCodeR\x04\x63ode\x12\x18\n\x07message\x18\x02 \x01(\tR\x07message\x12\x1c\n\tretryable\x18\x03 \x01(\x08R\tretryable\x12@\n\x07\x64\x65tails\x18\x04 \x03(\x0b\x32&.archive_control.v1.Error.DetailsEntryR\x07\x64\x65tails\x1a:\n\x0c\x44\x65tailsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xee\x01\n\rServiceHealth\x12\x18\n\x07service\x18\x01 \x01(\tR\x07service\x12\x35\n\x05state\x18\x02 \x01(\x0e\x32\x1f.archive_control.v1.HealthStateR\x05state\x12\x18\n\x07version\x18\x03 \x01(\tR\x07version\x12\x1f\n\x0b\x61pi_version\x18\x04 \x01(\tR\napiVersion\x12\x16\n\x06\x64\x65tail\x18\x05 \x01(\tR\x06\x64\x65tail\x12\x39\n\nchecked_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcheckedAt\"\x84\x01\n\x0e\x43lientIdentity\x12\x1b\n\tclient_id\x18\x01 \x01(\tR\x08\x63lientId\x12!\n\x0c\x64isplay_name\x18\x02 \x01(\tR\x0b\x64isplayName\x12\x32\n\x04role\x18\x03 \x01(\x0e\x32\x1e.archive_control.v1.ClientRoleR\x04role\"I\n\x0bPageRequest\x12\x1b\n\tpage_size\x18\x01 \x01(\rR\x08pageSize\x12\x1d\n\npage_token\x18\x02 \x01(\tR\tpageToken*Y\n\nClientRole\x12\x1b\n\x17\x43LIENT_ROLE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x43LIENT_ROLE_ARCHIVE\x10\x01\x12\x15\n\x11\x43LIENT_ROLE_CACHE\x10\x02*\x96\x01\n\x0bHealthState\x12\x1c\n\x18HEALTH_STATE_UNSPECIFIED\x10\x00\x12\x18\n\x14HEALTH_STATE_HEALTHY\x10\x01\x12\x19\n\x15HEALTH_STATE_DEGRADED\x10\x02\x12\x1a\n\x16HEALTH_STATE_UNHEALTHY\x10\x03\x12\x18\n\x14HEALTH_STATE_UNKNOWN\x10\x04*\xe2\x04\n\tErrorCode\x12\x1a\n\x16\x45RROR_CODE_UNSPECIFIED\x10\x00\x12\x1f\n\x1b\x45RROR_CODE_INVALID_ARGUMENT\x10\x01\x12\x1e\n\x1a\x45RROR_CODE_UNAUTHENTICATED\x10\x02\x12(\n$ERROR_CODE_PROTOCOL_VERSION_MISMATCH\x10\x03\x12!\n\x1d\x45RROR_CODE_CLIENT_ID_CONFLICT\x10\x04\x12\x18\n\x14\x45RROR_CODE_NOT_FOUND\x10\x05\x12\x17\n\x13\x45RROR_CODE_CONFLICT\x10\x06\x12\x1a\n\x16\x45RROR_CODE_STALE_STATE\x10\x07\x12\x1a\n\x16\x45RROR_CODE_UNSUPPORTED\x10\x08\x12\"\n\x1e\x45RROR_CODE_PRECONDITION_FAILED\x10\t\x12!\n\x1d\x45RROR_CODE_RESOURCE_EXHAUSTED\x10\n\x12\x1a\n\x16\x45RROR_CODE_UNAVAILABLE\x10\x0b\x12\x16\n\x12\x45RROR_CODE_TIMEOUT\x10\x0c\x12\x18\n\x14\x45RROR_CODE_CANCELLED\x10\r\x12%\n!ERROR_CODE_INTEGRITY_CHECK_FAILED\x10\x0e\x12\x1c\n\x18\x45RROR_CODE_PATH_CONFLICT\x10\x0f\x12 \n\x1c\x45RROR_CODE_PERMISSION_DENIED\x10\x10\x12+\n\'ERROR_CODE_MANUAL_INTERVENTION_REQUIRED\x10\x11\x12\x17\n\x13\x45RROR_CODE_INTERNAL\x10\x12\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'archive_control.v1.common_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_ERROR_DETAILSENTRY']._loaded_options = None
|
||||
_globals['_ERROR_DETAILSENTRY']._serialized_options = b'8\001'
|
||||
_globals['_CLIENTROLE']._serialized_start=845
|
||||
_globals['_CLIENTROLE']._serialized_end=934
|
||||
_globals['_HEALTHSTATE']._serialized_start=937
|
||||
_globals['_HEALTHSTATE']._serialized_end=1087
|
||||
_globals['_ERRORCODE']._serialized_start=1090
|
||||
_globals['_ERRORCODE']._serialized_end=1700
|
||||
_globals['_PROTOCOLVERSION']._serialized_start=88
|
||||
_globals['_PROTOCOLVERSION']._serialized_end=149
|
||||
_globals['_ERROR']._serialized_start=152
|
||||
_globals['_ERROR']._serialized_end=392
|
||||
_globals['_ERROR_DETAILSENTRY']._serialized_start=334
|
||||
_globals['_ERROR_DETAILSENTRY']._serialized_end=392
|
||||
_globals['_SERVICEHEALTH']._serialized_start=395
|
||||
_globals['_SERVICEHEALTH']._serialized_end=633
|
||||
_globals['_CLIENTIDENTITY']._serialized_start=636
|
||||
_globals['_CLIENTIDENTITY']._serialized_end=768
|
||||
_globals['_PAGEREQUEST']._serialized_start=770
|
||||
_globals['_PAGEREQUEST']._serialized_end=843
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,136 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class ClientRole(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
CLIENT_ROLE_UNSPECIFIED: _ClassVar[ClientRole]
|
||||
CLIENT_ROLE_ARCHIVE: _ClassVar[ClientRole]
|
||||
CLIENT_ROLE_CACHE: _ClassVar[ClientRole]
|
||||
|
||||
class HealthState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
HEALTH_STATE_UNSPECIFIED: _ClassVar[HealthState]
|
||||
HEALTH_STATE_HEALTHY: _ClassVar[HealthState]
|
||||
HEALTH_STATE_DEGRADED: _ClassVar[HealthState]
|
||||
HEALTH_STATE_UNHEALTHY: _ClassVar[HealthState]
|
||||
HEALTH_STATE_UNKNOWN: _ClassVar[HealthState]
|
||||
|
||||
class ErrorCode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
ERROR_CODE_UNSPECIFIED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_INVALID_ARGUMENT: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_UNAUTHENTICATED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_PROTOCOL_VERSION_MISMATCH: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_CLIENT_ID_CONFLICT: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_NOT_FOUND: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_CONFLICT: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_STALE_STATE: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_UNSUPPORTED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_PRECONDITION_FAILED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_RESOURCE_EXHAUSTED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_UNAVAILABLE: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_TIMEOUT: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_CANCELLED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_INTEGRITY_CHECK_FAILED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_PATH_CONFLICT: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_PERMISSION_DENIED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_MANUAL_INTERVENTION_REQUIRED: _ClassVar[ErrorCode]
|
||||
ERROR_CODE_INTERNAL: _ClassVar[ErrorCode]
|
||||
CLIENT_ROLE_UNSPECIFIED: ClientRole
|
||||
CLIENT_ROLE_ARCHIVE: ClientRole
|
||||
CLIENT_ROLE_CACHE: ClientRole
|
||||
HEALTH_STATE_UNSPECIFIED: HealthState
|
||||
HEALTH_STATE_HEALTHY: HealthState
|
||||
HEALTH_STATE_DEGRADED: HealthState
|
||||
HEALTH_STATE_UNHEALTHY: HealthState
|
||||
HEALTH_STATE_UNKNOWN: HealthState
|
||||
ERROR_CODE_UNSPECIFIED: ErrorCode
|
||||
ERROR_CODE_INVALID_ARGUMENT: ErrorCode
|
||||
ERROR_CODE_UNAUTHENTICATED: ErrorCode
|
||||
ERROR_CODE_PROTOCOL_VERSION_MISMATCH: ErrorCode
|
||||
ERROR_CODE_CLIENT_ID_CONFLICT: ErrorCode
|
||||
ERROR_CODE_NOT_FOUND: ErrorCode
|
||||
ERROR_CODE_CONFLICT: ErrorCode
|
||||
ERROR_CODE_STALE_STATE: ErrorCode
|
||||
ERROR_CODE_UNSUPPORTED: ErrorCode
|
||||
ERROR_CODE_PRECONDITION_FAILED: ErrorCode
|
||||
ERROR_CODE_RESOURCE_EXHAUSTED: ErrorCode
|
||||
ERROR_CODE_UNAVAILABLE: ErrorCode
|
||||
ERROR_CODE_TIMEOUT: ErrorCode
|
||||
ERROR_CODE_CANCELLED: ErrorCode
|
||||
ERROR_CODE_INTEGRITY_CHECK_FAILED: ErrorCode
|
||||
ERROR_CODE_PATH_CONFLICT: ErrorCode
|
||||
ERROR_CODE_PERMISSION_DENIED: ErrorCode
|
||||
ERROR_CODE_MANUAL_INTERVENTION_REQUIRED: ErrorCode
|
||||
ERROR_CODE_INTERNAL: ErrorCode
|
||||
|
||||
class ProtocolVersion(_message.Message):
|
||||
__slots__ = ("major", "minor")
|
||||
MAJOR_FIELD_NUMBER: _ClassVar[int]
|
||||
MINOR_FIELD_NUMBER: _ClassVar[int]
|
||||
major: int
|
||||
minor: int
|
||||
def __init__(self, major: _Optional[int] = ..., minor: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class Error(_message.Message):
|
||||
__slots__ = ("code", "message", "retryable", "details")
|
||||
class DetailsEntry(_message.Message):
|
||||
__slots__ = ("key", "value")
|
||||
KEY_FIELD_NUMBER: _ClassVar[int]
|
||||
VALUE_FIELD_NUMBER: _ClassVar[int]
|
||||
key: str
|
||||
value: str
|
||||
def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
|
||||
CODE_FIELD_NUMBER: _ClassVar[int]
|
||||
MESSAGE_FIELD_NUMBER: _ClassVar[int]
|
||||
RETRYABLE_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAILS_FIELD_NUMBER: _ClassVar[int]
|
||||
code: ErrorCode
|
||||
message: str
|
||||
retryable: bool
|
||||
details: _containers.ScalarMap[str, str]
|
||||
def __init__(self, code: _Optional[_Union[ErrorCode, str]] = ..., message: _Optional[str] = ..., retryable: _Optional[bool] = ..., details: _Optional[_Mapping[str, str]] = ...) -> None: ...
|
||||
|
||||
class ServiceHealth(_message.Message):
|
||||
__slots__ = ("service", "state", "version", "api_version", "detail", "checked_at")
|
||||
SERVICE_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
API_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
CHECKED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
service: str
|
||||
state: HealthState
|
||||
version: str
|
||||
api_version: str
|
||||
detail: str
|
||||
checked_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, service: _Optional[str] = ..., state: _Optional[_Union[HealthState, str]] = ..., version: _Optional[str] = ..., api_version: _Optional[str] = ..., detail: _Optional[str] = ..., checked_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class ClientIdentity(_message.Message):
|
||||
__slots__ = ("client_id", "display_name", "role")
|
||||
CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
ROLE_FIELD_NUMBER: _ClassVar[int]
|
||||
client_id: str
|
||||
display_name: str
|
||||
role: ClientRole
|
||||
def __init__(self, client_id: _Optional[str] = ..., display_name: _Optional[str] = ..., role: _Optional[_Union[ClientRole, str]] = ...) -> None: ...
|
||||
|
||||
class PageRequest(_message.Message):
|
||||
__slots__ = ("page_size", "page_token")
|
||||
PAGE_SIZE_FIELD_NUMBER: _ClassVar[int]
|
||||
PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
|
||||
page_size: int
|
||||
page_token: str
|
||||
def __init__(self, page_size: _Optional[int] = ..., page_token: _Optional[str] = ...) -> None: ...
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,215 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from archive_control.v1 import common_pb2 as _common_pb2
|
||||
from archive_control.v1 import inventory_pb2 as _inventory_pb2
|
||||
from archive_control.v1 import job_pb2 as _job_pb2
|
||||
from archive_control.v1 import resource_pb2 as _resource_pb2
|
||||
from archive_control.v1 import route_pb2 as _route_pb2
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class CommandAckStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
COMMAND_ACK_STATUS_UNSPECIFIED: _ClassVar[CommandAckStatus]
|
||||
COMMAND_ACK_STATUS_ACCEPTED: _ClassVar[CommandAckStatus]
|
||||
COMMAND_ACK_STATUS_DUPLICATE: _ClassVar[CommandAckStatus]
|
||||
COMMAND_ACK_STATUS_REJECTED: _ClassVar[CommandAckStatus]
|
||||
|
||||
class JobEventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
JOB_EVENT_TYPE_UNSPECIFIED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_ASSIGNED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_STEP_STARTED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_PROGRESS: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_WAITING: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_STALLED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_STEP_SUCCEEDED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_COMMITTED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_CANCELLING: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_ROLLBACK_STARTED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_ROLLBACK_SUCCEEDED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_CLEANUP_REQUIRED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_SUCCEEDED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_FAILED: _ClassVar[JobEventType]
|
||||
JOB_EVENT_TYPE_CANCELLED: _ClassVar[JobEventType]
|
||||
COMMAND_ACK_STATUS_UNSPECIFIED: CommandAckStatus
|
||||
COMMAND_ACK_STATUS_ACCEPTED: CommandAckStatus
|
||||
COMMAND_ACK_STATUS_DUPLICATE: CommandAckStatus
|
||||
COMMAND_ACK_STATUS_REJECTED: CommandAckStatus
|
||||
JOB_EVENT_TYPE_UNSPECIFIED: JobEventType
|
||||
JOB_EVENT_TYPE_ASSIGNED: JobEventType
|
||||
JOB_EVENT_TYPE_STEP_STARTED: JobEventType
|
||||
JOB_EVENT_TYPE_PROGRESS: JobEventType
|
||||
JOB_EVENT_TYPE_WAITING: JobEventType
|
||||
JOB_EVENT_TYPE_STALLED: JobEventType
|
||||
JOB_EVENT_TYPE_STEP_SUCCEEDED: JobEventType
|
||||
JOB_EVENT_TYPE_COMMITTED: JobEventType
|
||||
JOB_EVENT_TYPE_CANCELLING: JobEventType
|
||||
JOB_EVENT_TYPE_ROLLBACK_STARTED: JobEventType
|
||||
JOB_EVENT_TYPE_ROLLBACK_SUCCEEDED: JobEventType
|
||||
JOB_EVENT_TYPE_CLEANUP_REQUIRED: JobEventType
|
||||
JOB_EVENT_TYPE_SUCCEEDED: JobEventType
|
||||
JOB_EVENT_TYPE_FAILED: JobEventType
|
||||
JOB_EVENT_TYPE_CANCELLED: JobEventType
|
||||
|
||||
class AssignJobCommand(_message.Message):
|
||||
__slots__ = ("job", "expected_job_revision", "expected_last_event_sequence")
|
||||
JOB_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_JOB_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_LAST_EVENT_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
job: _job_pb2.JobDefinition
|
||||
expected_job_revision: int
|
||||
expected_last_event_sequence: int
|
||||
def __init__(self, job: _Optional[_Union[_job_pb2.JobDefinition, _Mapping]] = ..., expected_job_revision: _Optional[int] = ..., expected_last_event_sequence: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ExecuteStepCommand(_message.Message):
|
||||
__slots__ = ("job_id", "expected_job_revision", "step", "attempt", "expected_last_event_sequence")
|
||||
JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_JOB_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
STEP_FIELD_NUMBER: _ClassVar[int]
|
||||
ATTEMPT_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_LAST_EVENT_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
job_id: str
|
||||
expected_job_revision: int
|
||||
step: _job_pb2.JobStepKind
|
||||
attempt: int
|
||||
expected_last_event_sequence: int
|
||||
def __init__(self, job_id: _Optional[str] = ..., expected_job_revision: _Optional[int] = ..., step: _Optional[_Union[_job_pb2.JobStepKind, str]] = ..., attempt: _Optional[int] = ..., expected_last_event_sequence: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class CancelJobCommand(_message.Message):
|
||||
__slots__ = ("job_id", "expected_job_revision", "reason", "expected_last_event_sequence")
|
||||
JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_JOB_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
REASON_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_LAST_EVENT_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
job_id: str
|
||||
expected_job_revision: int
|
||||
reason: str
|
||||
expected_last_event_sequence: int
|
||||
def __init__(self, job_id: _Optional[str] = ..., expected_job_revision: _Optional[int] = ..., reason: _Optional[str] = ..., expected_last_event_sequence: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class EnsureRouteCommand(_message.Message):
|
||||
__slots__ = ("route",)
|
||||
ROUTE_FIELD_NUMBER: _ClassVar[int]
|
||||
route: _route_pb2.EnsureRouteSpec
|
||||
def __init__(self, route: _Optional[_Union[_route_pb2.EnsureRouteSpec, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class RequestJobSnapshotCommand(_message.Message):
|
||||
__slots__ = ("job_ids",)
|
||||
JOB_IDS_FIELD_NUMBER: _ClassVar[int]
|
||||
job_ids: _containers.RepeatedScalarFieldContainer[str]
|
||||
def __init__(self, job_ids: _Optional[_Iterable[str]] = ...) -> None: ...
|
||||
|
||||
class Command(_message.Message):
|
||||
__slots__ = ("command_id", "created_at", "assign_job", "execute_step", "cancel_job", "ensure_route", "inventory_query", "request_job_snapshot")
|
||||
COMMAND_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
ASSIGN_JOB_FIELD_NUMBER: _ClassVar[int]
|
||||
EXECUTE_STEP_FIELD_NUMBER: _ClassVar[int]
|
||||
CANCEL_JOB_FIELD_NUMBER: _ClassVar[int]
|
||||
ENSURE_ROUTE_FIELD_NUMBER: _ClassVar[int]
|
||||
INVENTORY_QUERY_FIELD_NUMBER: _ClassVar[int]
|
||||
REQUEST_JOB_SNAPSHOT_FIELD_NUMBER: _ClassVar[int]
|
||||
command_id: str
|
||||
created_at: _timestamp_pb2.Timestamp
|
||||
assign_job: AssignJobCommand
|
||||
execute_step: ExecuteStepCommand
|
||||
cancel_job: CancelJobCommand
|
||||
ensure_route: EnsureRouteCommand
|
||||
inventory_query: _inventory_pb2.InventoryQuery
|
||||
request_job_snapshot: RequestJobSnapshotCommand
|
||||
def __init__(self, command_id: _Optional[str] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., assign_job: _Optional[_Union[AssignJobCommand, _Mapping]] = ..., execute_step: _Optional[_Union[ExecuteStepCommand, _Mapping]] = ..., cancel_job: _Optional[_Union[CancelJobCommand, _Mapping]] = ..., ensure_route: _Optional[_Union[EnsureRouteCommand, _Mapping]] = ..., inventory_query: _Optional[_Union[_inventory_pb2.InventoryQuery, _Mapping]] = ..., request_job_snapshot: _Optional[_Union[RequestJobSnapshotCommand, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class CommandAck(_message.Message):
|
||||
__slots__ = ("command_id", "status", "error")
|
||||
COMMAND_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
STATUS_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
command_id: str
|
||||
status: CommandAckStatus
|
||||
error: _common_pb2.Error
|
||||
def __init__(self, command_id: _Optional[str] = ..., status: _Optional[_Union[CommandAckStatus, str]] = ..., error: _Optional[_Union[_common_pb2.Error, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class JobEvent(_message.Message):
|
||||
__slots__ = ("event_id", "job_id", "sequence", "job_revision", "type", "state", "committed", "progress", "error", "observed_resource", "observed_placement", "occurred_at")
|
||||
EVENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
JOB_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
COMMITTED_FIELD_NUMBER: _ClassVar[int]
|
||||
PROGRESS_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
OBSERVED_RESOURCE_FIELD_NUMBER: _ClassVar[int]
|
||||
OBSERVED_PLACEMENT_FIELD_NUMBER: _ClassVar[int]
|
||||
OCCURRED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
event_id: str
|
||||
job_id: str
|
||||
sequence: int
|
||||
job_revision: int
|
||||
type: JobEventType
|
||||
state: _job_pb2.JobState
|
||||
committed: bool
|
||||
progress: _job_pb2.JobProgress
|
||||
error: _common_pb2.Error
|
||||
observed_resource: _resource_pb2.ResourceStateFingerprint
|
||||
observed_placement: _resource_pb2.Placement
|
||||
occurred_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, event_id: _Optional[str] = ..., job_id: _Optional[str] = ..., sequence: _Optional[int] = ..., job_revision: _Optional[int] = ..., type: _Optional[_Union[JobEventType, str]] = ..., state: _Optional[_Union[_job_pb2.JobState, str]] = ..., committed: _Optional[bool] = ..., progress: _Optional[_Union[_job_pb2.JobProgress, _Mapping]] = ..., error: _Optional[_Union[_common_pb2.Error, _Mapping]] = ..., observed_resource: _Optional[_Union[_resource_pb2.ResourceStateFingerprint, _Mapping]] = ..., observed_placement: _Optional[_Union[_resource_pb2.Placement, _Mapping]] = ..., occurred_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class JobSnapshot(_message.Message):
|
||||
__slots__ = ("job", "last_event_sequence", "in_flight_command_ids")
|
||||
JOB_FIELD_NUMBER: _ClassVar[int]
|
||||
LAST_EVENT_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
IN_FLIGHT_COMMAND_IDS_FIELD_NUMBER: _ClassVar[int]
|
||||
job: _job_pb2.JobRecord
|
||||
last_event_sequence: int
|
||||
in_flight_command_ids: _containers.RepeatedScalarFieldContainer[str]
|
||||
def __init__(self, job: _Optional[_Union[_job_pb2.JobRecord, _Mapping]] = ..., last_event_sequence: _Optional[int] = ..., in_flight_command_ids: _Optional[_Iterable[str]] = ...) -> None: ...
|
||||
|
||||
class ClientStateSnapshot(_message.Message):
|
||||
__slots__ = ("snapshot_id", "active_jobs", "routes", "services", "observed_at")
|
||||
SNAPSHOT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ACTIVE_JOBS_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTES_FIELD_NUMBER: _ClassVar[int]
|
||||
SERVICES_FIELD_NUMBER: _ClassVar[int]
|
||||
OBSERVED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
snapshot_id: str
|
||||
active_jobs: _containers.RepeatedCompositeFieldContainer[JobSnapshot]
|
||||
routes: _containers.RepeatedCompositeFieldContainer[_route_pb2.LocalRoute]
|
||||
services: _containers.RepeatedCompositeFieldContainer[_common_pb2.ServiceHealth]
|
||||
observed_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, snapshot_id: _Optional[str] = ..., active_jobs: _Optional[_Iterable[_Union[JobSnapshot, _Mapping]]] = ..., routes: _Optional[_Iterable[_Union[_route_pb2.LocalRoute, _Mapping]]] = ..., services: _Optional[_Iterable[_Union[_common_pb2.ServiceHealth, _Mapping]]] = ..., observed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class RouteUpdate(_message.Message):
|
||||
__slots__ = ("command_id", "route", "verification", "error", "update_id", "sequence")
|
||||
COMMAND_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_FIELD_NUMBER: _ClassVar[int]
|
||||
VERIFICATION_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
UPDATE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
||||
command_id: str
|
||||
route: _route_pb2.LocalRoute
|
||||
verification: _route_pb2.RouteVerification
|
||||
error: _common_pb2.Error
|
||||
update_id: str
|
||||
sequence: int
|
||||
def __init__(self, command_id: _Optional[str] = ..., route: _Optional[_Union[_route_pb2.LocalRoute, _Mapping]] = ..., verification: _Optional[_Union[_route_pb2.RouteVerification, _Mapping]] = ..., error: _Optional[_Union[_common_pb2.Error, _Mapping]] = ..., update_id: _Optional[str] = ..., sequence: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ProtocolError(_message.Message):
|
||||
__slots__ = ("error", "offending_message_id")
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
OFFENDING_MESSAGE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
error: _common_pb2.Error
|
||||
offending_message_id: str
|
||||
def __init__(self, error: _Optional[_Union[_common_pb2.Error, _Mapping]] = ..., offending_message_id: _Optional[str] = ...) -> None: ...
|
||||
@@ -0,0 +1,42 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: archive_control/v1/envelope.proto
|
||||
# Protobuf Python Version: 7.35.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
7,
|
||||
35,
|
||||
1,
|
||||
'',
|
||||
'archive_control/v1/envelope.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from archive_control.v1 import client_pb2 as archive__control_dot_v1_dot_client__pb2
|
||||
from archive_control.v1 import common_pb2 as archive__control_dot_v1_dot_common__pb2
|
||||
from archive_control.v1 import control_pb2 as archive__control_dot_v1_dot_control__pb2
|
||||
from archive_control.v1 import inventory_pb2 as archive__control_dot_v1_dot_inventory__pb2
|
||||
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!archive_control/v1/envelope.proto\x12\x12\x61rchive_control.v1\x1a\x1f\x61rchive_control/v1/client.proto\x1a\x1f\x61rchive_control/v1/common.proto\x1a archive_control/v1/control.proto\x1a\"archive_control/v1/inventory.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xce\x08\n\x08\x45nvelope\x12N\n\x10protocol_version\x18\x01 \x01(\x0b\x32#.archive_control.v1.ProtocolVersionR\x0fprotocolVersion\x12\x1d\n\nmessage_id\x18\x02 \x01(\tR\tmessageId\x12%\n\x0e\x63orrelation_id\x18\x03 \x01(\tR\rcorrelationId\x12\x33\n\x07sent_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x06sentAt\x12P\n\x10register_request\x18\n \x01(\x0b\x32#.archive_control.v1.RegisterRequestH\x00R\x0fregisterRequest\x12S\n\x11register_response\x18\x0b \x01(\x0b\x32$.archive_control.v1.RegisterResponseH\x00R\x10registerResponse\x12=\n\theartbeat\x18\x0c \x01(\x0b\x32\x1d.archive_control.v1.HeartbeatH\x00R\theartbeat\x12G\n\rheartbeat_ack\x18\r \x01(\x0b\x32 .archive_control.v1.HeartbeatAckH\x00R\x0cheartbeatAck\x12\x37\n\x07\x63ommand\x18\x0e \x01(\x0b\x32\x1b.archive_control.v1.CommandH\x00R\x07\x63ommand\x12\x41\n\x0b\x63ommand_ack\x18\x0f \x01(\x0b\x32\x1e.archive_control.v1.CommandAckH\x00R\ncommandAck\x12M\n\x0finventory_chunk\x18\x10 \x01(\x0b\x32\".archive_control.v1.InventoryChunkH\x00R\x0einventoryChunk\x12;\n\tjob_event\x18\x11 \x01(\x0b\x32\x1c.archive_control.v1.JobEventH\x00R\x08jobEvent\x12\x44\n\x0cjob_snapshot\x18\x12 \x01(\x0b\x32\x1f.archive_control.v1.JobSnapshotH\x00R\x0bjobSnapshot\x12]\n\x15\x63lient_state_snapshot\x18\x13 \x01(\x0b\x32\'.archive_control.v1.ClientStateSnapshotH\x00R\x13\x63lientStateSnapshot\x12\x44\n\x0croute_update\x18\x14 \x01(\x0b\x32\x1f.archive_control.v1.RouteUpdateH\x00R\x0brouteUpdate\x12J\n\x0eprotocol_error\x18\x15 \x01(\x0b\x32!.archive_control.v1.ProtocolErrorH\x00R\rprotocolErrorB\t\n\x07payloadb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'archive_control.v1.envelope_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_ENVELOPE']._serialized_start=227
|
||||
_globals['_ENVELOPE']._serialized_end=1329
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,50 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from archive_control.v1 import client_pb2 as _client_pb2
|
||||
from archive_control.v1 import common_pb2 as _common_pb2
|
||||
from archive_control.v1 import control_pb2 as _control_pb2
|
||||
from archive_control.v1 import inventory_pb2 as _inventory_pb2
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class Envelope(_message.Message):
|
||||
__slots__ = ("protocol_version", "message_id", "correlation_id", "sent_at", "register_request", "register_response", "heartbeat", "heartbeat_ack", "command", "command_ack", "inventory_chunk", "job_event", "job_snapshot", "client_state_snapshot", "route_update", "protocol_error")
|
||||
PROTOCOL_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
MESSAGE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
CORRELATION_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SENT_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
REGISTER_REQUEST_FIELD_NUMBER: _ClassVar[int]
|
||||
REGISTER_RESPONSE_FIELD_NUMBER: _ClassVar[int]
|
||||
HEARTBEAT_FIELD_NUMBER: _ClassVar[int]
|
||||
HEARTBEAT_ACK_FIELD_NUMBER: _ClassVar[int]
|
||||
COMMAND_FIELD_NUMBER: _ClassVar[int]
|
||||
COMMAND_ACK_FIELD_NUMBER: _ClassVar[int]
|
||||
INVENTORY_CHUNK_FIELD_NUMBER: _ClassVar[int]
|
||||
JOB_EVENT_FIELD_NUMBER: _ClassVar[int]
|
||||
JOB_SNAPSHOT_FIELD_NUMBER: _ClassVar[int]
|
||||
CLIENT_STATE_SNAPSHOT_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_UPDATE_FIELD_NUMBER: _ClassVar[int]
|
||||
PROTOCOL_ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
protocol_version: _common_pb2.ProtocolVersion
|
||||
message_id: str
|
||||
correlation_id: str
|
||||
sent_at: _timestamp_pb2.Timestamp
|
||||
register_request: _client_pb2.RegisterRequest
|
||||
register_response: _client_pb2.RegisterResponse
|
||||
heartbeat: _client_pb2.Heartbeat
|
||||
heartbeat_ack: _client_pb2.HeartbeatAck
|
||||
command: _control_pb2.Command
|
||||
command_ack: _control_pb2.CommandAck
|
||||
inventory_chunk: _inventory_pb2.InventoryChunk
|
||||
job_event: _control_pb2.JobEvent
|
||||
job_snapshot: _control_pb2.JobSnapshot
|
||||
client_state_snapshot: _control_pb2.ClientStateSnapshot
|
||||
route_update: _control_pb2.RouteUpdate
|
||||
protocol_error: _control_pb2.ProtocolError
|
||||
def __init__(self, protocol_version: _Optional[_Union[_common_pb2.ProtocolVersion, _Mapping]] = ..., message_id: _Optional[str] = ..., correlation_id: _Optional[str] = ..., sent_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., register_request: _Optional[_Union[_client_pb2.RegisterRequest, _Mapping]] = ..., register_response: _Optional[_Union[_client_pb2.RegisterResponse, _Mapping]] = ..., heartbeat: _Optional[_Union[_client_pb2.Heartbeat, _Mapping]] = ..., heartbeat_ack: _Optional[_Union[_client_pb2.HeartbeatAck, _Mapping]] = ..., command: _Optional[_Union[_control_pb2.Command, _Mapping]] = ..., command_ack: _Optional[_Union[_control_pb2.CommandAck, _Mapping]] = ..., inventory_chunk: _Optional[_Union[_inventory_pb2.InventoryChunk, _Mapping]] = ..., job_event: _Optional[_Union[_control_pb2.JobEvent, _Mapping]] = ..., job_snapshot: _Optional[_Union[_control_pb2.JobSnapshot, _Mapping]] = ..., client_state_snapshot: _Optional[_Union[_control_pb2.ClientStateSnapshot, _Mapping]] = ..., route_update: _Optional[_Union[_control_pb2.RouteUpdate, _Mapping]] = ..., protocol_error: _Optional[_Union[_control_pb2.ProtocolError, _Mapping]] = ...) -> None: ...
|
||||
@@ -0,0 +1,49 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: archive_control/v1/inventory.proto
|
||||
# Protobuf Python Version: 7.35.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
7,
|
||||
35,
|
||||
1,
|
||||
'',
|
||||
'archive_control/v1/inventory.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from archive_control.v1 import common_pb2 as archive__control_dot_v1_dot_common__pb2
|
||||
from archive_control.v1 import resource_pb2 as archive__control_dot_v1_dot_resource__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"archive_control/v1/inventory.proto\x12\x12\x61rchive_control.v1\x1a\x1f\x61rchive_control/v1/common.proto\x1a!archive_control/v1/resource.proto\"\xe1\x02\n\x0eInventoryQuery\x12\x19\n\x08query_id\x18\x01 \x01(\tR\x07queryId\x12\x38\n\x05scope\x18\x02 \x01(\x0e\x32\".archive_control.v1.InventoryScopeR\x05scope\x12\x41\n\x0cresource_ids\x18\x03 \x03(\x0b\x32\x1e.archive_control.v1.ResourceIdR\x0bresourceIds\x12\x33\n\x04page\x18\x04 \x01(\x0b\x32\x1f.archive_control.v1.PageRequestR\x04page\x12\x1f\n\x0bpage_filter\x18\x05 \x01(\tR\npageFilter\x12\x34\n\x16selected_complete_only\x18\x06 \x01(\x08R\x14selectedCompleteOnly\x12+\n\x11\x65xpected_revision\x18\x07 \x01(\tR\x10\x65xpectedRevision\"Y\n\x14ResourceSummaryChunk\x12\x41\n\tresources\x18\x01 \x03(\x0b\x32#.archive_control.v1.ResourceSummaryR\tresources\"\xcf\x01\n\x10\x43ontentTreeChunk\x12?\n\x0bresource_id\x18\x01 \x01(\x0b\x32\x1e.archive_control.v1.ResourceIdR\nresourceId\x12>\n\x07\x65ntries\x18\x02 \x03(\x0b\x32$.archive_control.v1.ContentTreeEntryR\x07\x65ntries\x12:\n\x19resource_content_revision\x18\x03 \x01(\tR\x17resourceContentRevision\"O\n\x0ePlacementChunk\x12=\n\nplacements\x18\x01 \x03(\x0b\x32\x1d.archive_control.v1.PlacementR\nplacements\"\xf8\x03\n\x0eInventoryChunk\x12\x19\n\x08query_id\x18\x01 \x01(\tR\x07queryId\x12\x1f\n\x0bsnapshot_id\x18\x02 \x01(\tR\nsnapshotId\x12\x1a\n\x08revision\x18\x03 \x01(\tR\x08revision\x12\x1f\n\x0b\x63hunk_index\x18\x04 \x01(\rR\nchunkIndex\x12\x1d\n\nlast_chunk\x18\x05 \x01(\x08R\tlastChunk\x12&\n\x0fnext_page_token\x18\x06 \x01(\tR\rnextPageToken\x12/\n\x05\x65rror\x18\x07 \x01(\x0b\x32\x19.archive_control.v1.ErrorR\x05\x65rror\x12Y\n\x12resource_summaries\x18\n \x01(\x0b\x32(.archive_control.v1.ResourceSummaryChunkH\x00R\x11resourceSummaries\x12I\n\x0c\x63ontent_tree\x18\x0b \x01(\x0b\x32$.archive_control.v1.ContentTreeChunkH\x00R\x0b\x63ontentTree\x12\x44\n\nplacements\x18\x0c \x01(\x0b\x32\".archive_control.v1.PlacementChunkH\x00R\nplacementsB\t\n\x07payload*\xc0\x01\n\x0eInventoryScope\x12\x1f\n\x1bINVENTORY_SCOPE_UNSPECIFIED\x10\x00\x12&\n\"INVENTORY_SCOPE_RESOURCE_SUMMARIES\x10\x01\x12#\n\x1fINVENTORY_SCOPE_RESOURCE_LOOKUP\x10\x02\x12 \n\x1cINVENTORY_SCOPE_CONTENT_TREE\x10\x03\x12\x1e\n\x1aINVENTORY_SCOPE_PLACEMENTS\x10\x04\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'archive_control.v1.inventory_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_INVENTORYSCOPE']._serialized_start=1372
|
||||
_globals['_INVENTORYSCOPE']._serialized_end=1564
|
||||
_globals['_INVENTORYQUERY']._serialized_start=127
|
||||
_globals['_INVENTORYQUERY']._serialized_end=480
|
||||
_globals['_RESOURCESUMMARYCHUNK']._serialized_start=482
|
||||
_globals['_RESOURCESUMMARYCHUNK']._serialized_end=571
|
||||
_globals['_CONTENTTREECHUNK']._serialized_start=574
|
||||
_globals['_CONTENTTREECHUNK']._serialized_end=781
|
||||
_globals['_PLACEMENTCHUNK']._serialized_start=783
|
||||
_globals['_PLACEMENTCHUNK']._serialized_end=862
|
||||
_globals['_INVENTORYCHUNK']._serialized_start=865
|
||||
_globals['_INVENTORYCHUNK']._serialized_end=1369
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,88 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
from archive_control.v1 import common_pb2 as _common_pb2
|
||||
from archive_control.v1 import resource_pb2 as _resource_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class InventoryScope(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
INVENTORY_SCOPE_UNSPECIFIED: _ClassVar[InventoryScope]
|
||||
INVENTORY_SCOPE_RESOURCE_SUMMARIES: _ClassVar[InventoryScope]
|
||||
INVENTORY_SCOPE_RESOURCE_LOOKUP: _ClassVar[InventoryScope]
|
||||
INVENTORY_SCOPE_CONTENT_TREE: _ClassVar[InventoryScope]
|
||||
INVENTORY_SCOPE_PLACEMENTS: _ClassVar[InventoryScope]
|
||||
INVENTORY_SCOPE_UNSPECIFIED: InventoryScope
|
||||
INVENTORY_SCOPE_RESOURCE_SUMMARIES: InventoryScope
|
||||
INVENTORY_SCOPE_RESOURCE_LOOKUP: InventoryScope
|
||||
INVENTORY_SCOPE_CONTENT_TREE: InventoryScope
|
||||
INVENTORY_SCOPE_PLACEMENTS: InventoryScope
|
||||
|
||||
class InventoryQuery(_message.Message):
|
||||
__slots__ = ("query_id", "scope", "resource_ids", "page", "page_filter", "selected_complete_only", "expected_revision")
|
||||
QUERY_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SCOPE_FIELD_NUMBER: _ClassVar[int]
|
||||
RESOURCE_IDS_FIELD_NUMBER: _ClassVar[int]
|
||||
PAGE_FIELD_NUMBER: _ClassVar[int]
|
||||
PAGE_FILTER_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_COMPLETE_ONLY_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
query_id: str
|
||||
scope: InventoryScope
|
||||
resource_ids: _containers.RepeatedCompositeFieldContainer[_resource_pb2.ResourceId]
|
||||
page: _common_pb2.PageRequest
|
||||
page_filter: str
|
||||
selected_complete_only: bool
|
||||
expected_revision: str
|
||||
def __init__(self, query_id: _Optional[str] = ..., scope: _Optional[_Union[InventoryScope, str]] = ..., resource_ids: _Optional[_Iterable[_Union[_resource_pb2.ResourceId, _Mapping]]] = ..., page: _Optional[_Union[_common_pb2.PageRequest, _Mapping]] = ..., page_filter: _Optional[str] = ..., selected_complete_only: _Optional[bool] = ..., expected_revision: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class ResourceSummaryChunk(_message.Message):
|
||||
__slots__ = ("resources",)
|
||||
RESOURCES_FIELD_NUMBER: _ClassVar[int]
|
||||
resources: _containers.RepeatedCompositeFieldContainer[_resource_pb2.ResourceSummary]
|
||||
def __init__(self, resources: _Optional[_Iterable[_Union[_resource_pb2.ResourceSummary, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class ContentTreeChunk(_message.Message):
|
||||
__slots__ = ("resource_id", "entries", "resource_content_revision")
|
||||
RESOURCE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ENTRIES_FIELD_NUMBER: _ClassVar[int]
|
||||
RESOURCE_CONTENT_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
resource_id: _resource_pb2.ResourceId
|
||||
entries: _containers.RepeatedCompositeFieldContainer[_resource_pb2.ContentTreeEntry]
|
||||
resource_content_revision: str
|
||||
def __init__(self, resource_id: _Optional[_Union[_resource_pb2.ResourceId, _Mapping]] = ..., entries: _Optional[_Iterable[_Union[_resource_pb2.ContentTreeEntry, _Mapping]]] = ..., resource_content_revision: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class PlacementChunk(_message.Message):
|
||||
__slots__ = ("placements",)
|
||||
PLACEMENTS_FIELD_NUMBER: _ClassVar[int]
|
||||
placements: _containers.RepeatedCompositeFieldContainer[_resource_pb2.Placement]
|
||||
def __init__(self, placements: _Optional[_Iterable[_Union[_resource_pb2.Placement, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class InventoryChunk(_message.Message):
|
||||
__slots__ = ("query_id", "snapshot_id", "revision", "chunk_index", "last_chunk", "next_page_token", "error", "resource_summaries", "content_tree", "placements")
|
||||
QUERY_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SNAPSHOT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
CHUNK_INDEX_FIELD_NUMBER: _ClassVar[int]
|
||||
LAST_CHUNK_FIELD_NUMBER: _ClassVar[int]
|
||||
NEXT_PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
RESOURCE_SUMMARIES_FIELD_NUMBER: _ClassVar[int]
|
||||
CONTENT_TREE_FIELD_NUMBER: _ClassVar[int]
|
||||
PLACEMENTS_FIELD_NUMBER: _ClassVar[int]
|
||||
query_id: str
|
||||
snapshot_id: str
|
||||
revision: str
|
||||
chunk_index: int
|
||||
last_chunk: bool
|
||||
next_page_token: str
|
||||
error: _common_pb2.Error
|
||||
resource_summaries: ResourceSummaryChunk
|
||||
content_tree: ContentTreeChunk
|
||||
placements: PlacementChunk
|
||||
def __init__(self, query_id: _Optional[str] = ..., snapshot_id: _Optional[str] = ..., revision: _Optional[str] = ..., chunk_index: _Optional[int] = ..., last_chunk: _Optional[bool] = ..., next_page_token: _Optional[str] = ..., error: _Optional[_Union[_common_pb2.Error, _Mapping]] = ..., resource_summaries: _Optional[_Union[ResourceSummaryChunk, _Mapping]] = ..., content_tree: _Optional[_Union[ContentTreeChunk, _Mapping]] = ..., placements: _Optional[_Union[PlacementChunk, _Mapping]] = ...) -> None: ...
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,220 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from archive_control.v1 import common_pb2 as _common_pb2
|
||||
from archive_control.v1 import resource_pb2 as _resource_pb2
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class JobOperation(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
JOB_OPERATION_UNSPECIFIED: _ClassVar[JobOperation]
|
||||
JOB_OPERATION_ARCHIVE: _ClassVar[JobOperation]
|
||||
JOB_OPERATION_UNARCHIVE: _ClassVar[JobOperation]
|
||||
JOB_OPERATION_EVICT_CACHE: _ClassVar[JobOperation]
|
||||
|
||||
class JobState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
JOB_STATE_UNSPECIFIED: _ClassVar[JobState]
|
||||
JOB_STATE_QUEUED: _ClassVar[JobState]
|
||||
JOB_STATE_PREPARING: _ClassVar[JobState]
|
||||
JOB_STATE_RUNNING: _ClassVar[JobState]
|
||||
JOB_STATE_WAITING: _ClassVar[JobState]
|
||||
JOB_STATE_STALLED: _ClassVar[JobState]
|
||||
JOB_STATE_CANCELLING: _ClassVar[JobState]
|
||||
JOB_STATE_ROLLING_BACK: _ClassVar[JobState]
|
||||
JOB_STATE_CLEANUP_REQUIRED: _ClassVar[JobState]
|
||||
JOB_STATE_SUCCEEDED: _ClassVar[JobState]
|
||||
JOB_STATE_FAILED: _ClassVar[JobState]
|
||||
JOB_STATE_CANCELLED: _ClassVar[JobState]
|
||||
|
||||
class JobStepKind(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
JOB_STEP_KIND_UNSPECIFIED: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_ROUTE_SETUP: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_PREFLIGHT: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_SOURCE_STAGE: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_SYNCTHING_TRANSFER: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_TARGET_MATERIALIZE: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_QB_VERIFY: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_STAGING_CLEANUP: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_VERIFY_ARCHIVE_COVERAGE: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_QB_REMOVE_ENTRY: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_SAFE_FILE_UNLINK: _ClassVar[JobStepKind]
|
||||
JOB_STEP_KIND_ROLLBACK: _ClassVar[JobStepKind]
|
||||
|
||||
class StepState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
STEP_STATE_UNSPECIFIED: _ClassVar[StepState]
|
||||
STEP_STATE_NOT_STARTED: _ClassVar[StepState]
|
||||
STEP_STATE_QUEUED: _ClassVar[StepState]
|
||||
STEP_STATE_RUNNING: _ClassVar[StepState]
|
||||
STEP_STATE_WAITING: _ClassVar[StepState]
|
||||
STEP_STATE_STALLED: _ClassVar[StepState]
|
||||
STEP_STATE_SUCCEEDED: _ClassVar[StepState]
|
||||
STEP_STATE_FAILED: _ClassVar[StepState]
|
||||
STEP_STATE_CANCELLED: _ClassVar[StepState]
|
||||
JOB_OPERATION_UNSPECIFIED: JobOperation
|
||||
JOB_OPERATION_ARCHIVE: JobOperation
|
||||
JOB_OPERATION_UNARCHIVE: JobOperation
|
||||
JOB_OPERATION_EVICT_CACHE: JobOperation
|
||||
JOB_STATE_UNSPECIFIED: JobState
|
||||
JOB_STATE_QUEUED: JobState
|
||||
JOB_STATE_PREPARING: JobState
|
||||
JOB_STATE_RUNNING: JobState
|
||||
JOB_STATE_WAITING: JobState
|
||||
JOB_STATE_STALLED: JobState
|
||||
JOB_STATE_CANCELLING: JobState
|
||||
JOB_STATE_ROLLING_BACK: JobState
|
||||
JOB_STATE_CLEANUP_REQUIRED: JobState
|
||||
JOB_STATE_SUCCEEDED: JobState
|
||||
JOB_STATE_FAILED: JobState
|
||||
JOB_STATE_CANCELLED: JobState
|
||||
JOB_STEP_KIND_UNSPECIFIED: JobStepKind
|
||||
JOB_STEP_KIND_ROUTE_SETUP: JobStepKind
|
||||
JOB_STEP_KIND_PREFLIGHT: JobStepKind
|
||||
JOB_STEP_KIND_SOURCE_STAGE: JobStepKind
|
||||
JOB_STEP_KIND_SYNCTHING_TRANSFER: JobStepKind
|
||||
JOB_STEP_KIND_TARGET_MATERIALIZE: JobStepKind
|
||||
JOB_STEP_KIND_QB_VERIFY: JobStepKind
|
||||
JOB_STEP_KIND_STAGING_CLEANUP: JobStepKind
|
||||
JOB_STEP_KIND_VERIFY_ARCHIVE_COVERAGE: JobStepKind
|
||||
JOB_STEP_KIND_QB_REMOVE_ENTRY: JobStepKind
|
||||
JOB_STEP_KIND_SAFE_FILE_UNLINK: JobStepKind
|
||||
JOB_STEP_KIND_ROLLBACK: JobStepKind
|
||||
STEP_STATE_UNSPECIFIED: StepState
|
||||
STEP_STATE_NOT_STARTED: StepState
|
||||
STEP_STATE_QUEUED: StepState
|
||||
STEP_STATE_RUNNING: StepState
|
||||
STEP_STATE_WAITING: StepState
|
||||
STEP_STATE_STALLED: StepState
|
||||
STEP_STATE_SUCCEEDED: StepState
|
||||
STEP_STATE_FAILED: StepState
|
||||
STEP_STATE_CANCELLED: StepState
|
||||
|
||||
class TransferJobSpec(_message.Message):
|
||||
__slots__ = ("source_client_id", "target_client_id", "route_id", "requested_files", "source_fingerprint", "target_baseline_fingerprint", "expected_target_placement_generation", "target_baseline_files", "transfer_delta_files", "requested_logical_bytes", "transfer_delta_logical_bytes")
|
||||
SOURCE_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
REQUESTED_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
SOURCE_FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_BASELINE_FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
EXPECTED_TARGET_PLACEMENT_GENERATION_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_BASELINE_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
TRANSFER_DELTA_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
REQUESTED_LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
TRANSFER_DELTA_LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
source_client_id: str
|
||||
target_client_id: str
|
||||
route_id: str
|
||||
requested_files: _resource_pb2.SelectionSet
|
||||
source_fingerprint: _resource_pb2.ResourceStateFingerprint
|
||||
target_baseline_fingerprint: _resource_pb2.ResourceStateFingerprint
|
||||
expected_target_placement_generation: int
|
||||
target_baseline_files: _resource_pb2.SelectionSet
|
||||
transfer_delta_files: _resource_pb2.SelectionSet
|
||||
requested_logical_bytes: int
|
||||
transfer_delta_logical_bytes: int
|
||||
def __init__(self, source_client_id: _Optional[str] = ..., target_client_id: _Optional[str] = ..., route_id: _Optional[str] = ..., requested_files: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., source_fingerprint: _Optional[_Union[_resource_pb2.ResourceStateFingerprint, _Mapping]] = ..., target_baseline_fingerprint: _Optional[_Union[_resource_pb2.ResourceStateFingerprint, _Mapping]] = ..., expected_target_placement_generation: _Optional[int] = ..., target_baseline_files: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., transfer_delta_files: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., requested_logical_bytes: _Optional[int] = ..., transfer_delta_logical_bytes: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class ArchiveCoverage(_message.Message):
|
||||
__slots__ = ("archive_client_id", "covered_files", "placement_generation", "fingerprint")
|
||||
ARCHIVE_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
COVERED_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
PLACEMENT_GENERATION_FIELD_NUMBER: _ClassVar[int]
|
||||
FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
archive_client_id: str
|
||||
covered_files: _resource_pb2.SelectionSet
|
||||
placement_generation: int
|
||||
fingerprint: _resource_pb2.ResourceStateFingerprint
|
||||
def __init__(self, archive_client_id: _Optional[str] = ..., covered_files: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., placement_generation: _Optional[int] = ..., fingerprint: _Optional[_Union[_resource_pb2.ResourceStateFingerprint, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class EvictionJobSpec(_message.Message):
|
||||
__slots__ = ("cache_client_id", "files_to_evict", "cache_placement_generation", "cache_fingerprint", "archive_coverage")
|
||||
CACHE_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
FILES_TO_EVICT_FIELD_NUMBER: _ClassVar[int]
|
||||
CACHE_PLACEMENT_GENERATION_FIELD_NUMBER: _ClassVar[int]
|
||||
CACHE_FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
ARCHIVE_COVERAGE_FIELD_NUMBER: _ClassVar[int]
|
||||
cache_client_id: str
|
||||
files_to_evict: _resource_pb2.SelectionSet
|
||||
cache_placement_generation: int
|
||||
cache_fingerprint: _resource_pb2.ResourceStateFingerprint
|
||||
archive_coverage: _containers.RepeatedCompositeFieldContainer[ArchiveCoverage]
|
||||
def __init__(self, cache_client_id: _Optional[str] = ..., files_to_evict: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., cache_placement_generation: _Optional[int] = ..., cache_fingerprint: _Optional[_Union[_resource_pb2.ResourceStateFingerprint, _Mapping]] = ..., archive_coverage: _Optional[_Iterable[_Union[ArchiveCoverage, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class JobDefinition(_message.Message):
|
||||
__slots__ = ("job_id", "idempotency_key", "operation", "resource_id", "resource_display_name", "created_at", "transfer", "eviction")
|
||||
JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
IDEMPOTENCY_KEY_FIELD_NUMBER: _ClassVar[int]
|
||||
OPERATION_FIELD_NUMBER: _ClassVar[int]
|
||||
RESOURCE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
RESOURCE_DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
TRANSFER_FIELD_NUMBER: _ClassVar[int]
|
||||
EVICTION_FIELD_NUMBER: _ClassVar[int]
|
||||
job_id: str
|
||||
idempotency_key: str
|
||||
operation: JobOperation
|
||||
resource_id: _resource_pb2.ResourceId
|
||||
resource_display_name: str
|
||||
created_at: _timestamp_pb2.Timestamp
|
||||
transfer: TransferJobSpec
|
||||
eviction: EvictionJobSpec
|
||||
def __init__(self, job_id: _Optional[str] = ..., idempotency_key: _Optional[str] = ..., operation: _Optional[_Union[JobOperation, str]] = ..., resource_id: _Optional[_Union[_resource_pb2.ResourceId, _Mapping]] = ..., resource_display_name: _Optional[str] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., transfer: _Optional[_Union[TransferJobSpec, _Mapping]] = ..., eviction: _Optional[_Union[EvictionJobSpec, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class JobProgress(_message.Message):
|
||||
__slots__ = ("step", "display_step_number", "display_step_total", "state", "fraction_complete", "bytes_complete", "bytes_total", "approximate_bytes_per_second", "detail", "last_progress_at", "overall_fraction_complete")
|
||||
STEP_FIELD_NUMBER: _ClassVar[int]
|
||||
DISPLAY_STEP_NUMBER_FIELD_NUMBER: _ClassVar[int]
|
||||
DISPLAY_STEP_TOTAL_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
FRACTION_COMPLETE_FIELD_NUMBER: _ClassVar[int]
|
||||
BYTES_COMPLETE_FIELD_NUMBER: _ClassVar[int]
|
||||
BYTES_TOTAL_FIELD_NUMBER: _ClassVar[int]
|
||||
APPROXIMATE_BYTES_PER_SECOND_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
LAST_PROGRESS_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
OVERALL_FRACTION_COMPLETE_FIELD_NUMBER: _ClassVar[int]
|
||||
step: JobStepKind
|
||||
display_step_number: int
|
||||
display_step_total: int
|
||||
state: StepState
|
||||
fraction_complete: float
|
||||
bytes_complete: int
|
||||
bytes_total: int
|
||||
approximate_bytes_per_second: float
|
||||
detail: str
|
||||
last_progress_at: _timestamp_pb2.Timestamp
|
||||
overall_fraction_complete: float
|
||||
def __init__(self, step: _Optional[_Union[JobStepKind, str]] = ..., display_step_number: _Optional[int] = ..., display_step_total: _Optional[int] = ..., state: _Optional[_Union[StepState, str]] = ..., fraction_complete: _Optional[float] = ..., bytes_complete: _Optional[int] = ..., bytes_total: _Optional[int] = ..., approximate_bytes_per_second: _Optional[float] = ..., detail: _Optional[str] = ..., last_progress_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., overall_fraction_complete: _Optional[float] = ...) -> None: ...
|
||||
|
||||
class JobRecord(_message.Message):
|
||||
__slots__ = ("definition", "state", "committed", "revision", "progress", "error", "updated_at", "committed_at", "finished_at")
|
||||
DEFINITION_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
COMMITTED_FIELD_NUMBER: _ClassVar[int]
|
||||
REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
PROGRESS_FIELD_NUMBER: _ClassVar[int]
|
||||
ERROR_FIELD_NUMBER: _ClassVar[int]
|
||||
UPDATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
COMMITTED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
FINISHED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
definition: JobDefinition
|
||||
state: JobState
|
||||
committed: bool
|
||||
revision: int
|
||||
progress: JobProgress
|
||||
error: _common_pb2.Error
|
||||
updated_at: _timestamp_pb2.Timestamp
|
||||
committed_at: _timestamp_pb2.Timestamp
|
||||
finished_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, definition: _Optional[_Union[JobDefinition, _Mapping]] = ..., state: _Optional[_Union[JobState, str]] = ..., committed: _Optional[bool] = ..., revision: _Optional[int] = ..., progress: _Optional[_Union[JobProgress, _Mapping]] = ..., error: _Optional[_Union[_common_pb2.Error, _Mapping]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., committed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., finished_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,199 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class TorrentRuntimeState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
TORRENT_RUNTIME_STATE_UNSPECIFIED: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_STOPPED: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_QUEUED: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_CHECKING: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_DOWNLOADING: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_SEEDING: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_STALLED: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_ERROR: _ClassVar[TorrentRuntimeState]
|
||||
TORRENT_RUNTIME_STATE_MISSING: _ClassVar[TorrentRuntimeState]
|
||||
|
||||
class ContentEntryType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
CONTENT_ENTRY_TYPE_UNSPECIFIED: _ClassVar[ContentEntryType]
|
||||
CONTENT_ENTRY_TYPE_FILE: _ClassVar[ContentEntryType]
|
||||
CONTENT_ENTRY_TYPE_DIRECTORY: _ClassVar[ContentEntryType]
|
||||
CONTENT_ENTRY_TYPE_PADDING_FILE: _ClassVar[ContentEntryType]
|
||||
|
||||
class PlacementState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
PLACEMENT_STATE_UNSPECIFIED: _ClassVar[PlacementState]
|
||||
PLACEMENT_STATE_PRESENT: _ClassVar[PlacementState]
|
||||
PLACEMENT_STATE_MERGING: _ClassVar[PlacementState]
|
||||
PLACEMENT_STATE_EVICTING: _ClassVar[PlacementState]
|
||||
PLACEMENT_STATE_ABSENT: _ClassVar[PlacementState]
|
||||
PLACEMENT_STATE_ERROR: _ClassVar[PlacementState]
|
||||
PLACEMENT_STATE_UNKNOWN: _ClassVar[PlacementState]
|
||||
TORRENT_RUNTIME_STATE_UNSPECIFIED: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_STOPPED: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_QUEUED: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_CHECKING: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_DOWNLOADING: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_SEEDING: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_STALLED: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_ERROR: TorrentRuntimeState
|
||||
TORRENT_RUNTIME_STATE_MISSING: TorrentRuntimeState
|
||||
CONTENT_ENTRY_TYPE_UNSPECIFIED: ContentEntryType
|
||||
CONTENT_ENTRY_TYPE_FILE: ContentEntryType
|
||||
CONTENT_ENTRY_TYPE_DIRECTORY: ContentEntryType
|
||||
CONTENT_ENTRY_TYPE_PADDING_FILE: ContentEntryType
|
||||
PLACEMENT_STATE_UNSPECIFIED: PlacementState
|
||||
PLACEMENT_STATE_PRESENT: PlacementState
|
||||
PLACEMENT_STATE_MERGING: PlacementState
|
||||
PLACEMENT_STATE_EVICTING: PlacementState
|
||||
PLACEMENT_STATE_ABSENT: PlacementState
|
||||
PLACEMENT_STATE_ERROR: PlacementState
|
||||
PLACEMENT_STATE_UNKNOWN: PlacementState
|
||||
|
||||
class ResourceId(_message.Message):
|
||||
__slots__ = ("info_hash_v1_hex", "info_hash_v2_hex")
|
||||
INFO_HASH_V1_HEX_FIELD_NUMBER: _ClassVar[int]
|
||||
INFO_HASH_V2_HEX_FIELD_NUMBER: _ClassVar[int]
|
||||
info_hash_v1_hex: str
|
||||
info_hash_v2_hex: str
|
||||
def __init__(self, info_hash_v1_hex: _Optional[str] = ..., info_hash_v2_hex: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class FileIndexRange(_message.Message):
|
||||
__slots__ = ("first", "last")
|
||||
FIRST_FIELD_NUMBER: _ClassVar[int]
|
||||
LAST_FIELD_NUMBER: _ClassVar[int]
|
||||
first: int
|
||||
last: int
|
||||
def __init__(self, first: _Optional[int] = ..., last: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class SelectionSet(_message.Message):
|
||||
__slots__ = ("ranges",)
|
||||
RANGES_FIELD_NUMBER: _ClassVar[int]
|
||||
ranges: _containers.RepeatedCompositeFieldContainer[FileIndexRange]
|
||||
def __init__(self, ranges: _Optional[_Iterable[_Union[FileIndexRange, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class TorrentFile(_message.Message):
|
||||
__slots__ = ("file_index", "canonical_path", "logical_bytes", "allocated_bytes", "completed_bytes", "selected", "sparse", "padding")
|
||||
FILE_INDEX_FIELD_NUMBER: _ClassVar[int]
|
||||
CANONICAL_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
ALLOCATED_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
COMPLETED_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_FIELD_NUMBER: _ClassVar[int]
|
||||
SPARSE_FIELD_NUMBER: _ClassVar[int]
|
||||
PADDING_FIELD_NUMBER: _ClassVar[int]
|
||||
file_index: int
|
||||
canonical_path: str
|
||||
logical_bytes: int
|
||||
allocated_bytes: int
|
||||
completed_bytes: int
|
||||
selected: bool
|
||||
sparse: bool
|
||||
padding: bool
|
||||
def __init__(self, file_index: _Optional[int] = ..., canonical_path: _Optional[str] = ..., logical_bytes: _Optional[int] = ..., allocated_bytes: _Optional[int] = ..., completed_bytes: _Optional[int] = ..., selected: _Optional[bool] = ..., sparse: _Optional[bool] = ..., padding: _Optional[bool] = ...) -> None: ...
|
||||
|
||||
class ContentTreeEntry(_message.Message):
|
||||
__slots__ = ("canonical_path", "parent_path", "display_name", "type", "file_index", "available_file_indices", "available_logical_bytes", "available_file_count", "file")
|
||||
CANONICAL_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
PARENT_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
FILE_INDEX_FIELD_NUMBER: _ClassVar[int]
|
||||
AVAILABLE_FILE_INDICES_FIELD_NUMBER: _ClassVar[int]
|
||||
AVAILABLE_LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
AVAILABLE_FILE_COUNT_FIELD_NUMBER: _ClassVar[int]
|
||||
FILE_FIELD_NUMBER: _ClassVar[int]
|
||||
canonical_path: str
|
||||
parent_path: str
|
||||
display_name: str
|
||||
type: ContentEntryType
|
||||
file_index: int
|
||||
available_file_indices: SelectionSet
|
||||
available_logical_bytes: int
|
||||
available_file_count: int
|
||||
file: TorrentFile
|
||||
def __init__(self, canonical_path: _Optional[str] = ..., parent_path: _Optional[str] = ..., display_name: _Optional[str] = ..., type: _Optional[_Union[ContentEntryType, str]] = ..., file_index: _Optional[int] = ..., available_file_indices: _Optional[_Union[SelectionSet, _Mapping]] = ..., available_logical_bytes: _Optional[int] = ..., available_file_count: _Optional[int] = ..., file: _Optional[_Union[TorrentFile, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class ResourceSummary(_message.Message):
|
||||
__slots__ = ("resource_id", "qb_torrent_id", "display_name", "runtime_state", "selected_files", "selected_complete_files", "selected_logical_bytes", "selected_complete_bytes", "total_logical_bytes", "total_file_count", "content_revision", "canonical_paths", "observed_at")
|
||||
RESOURCE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
QB_TORRENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int]
|
||||
RUNTIME_STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_COMPLETE_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_COMPLETE_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
TOTAL_LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
TOTAL_FILE_COUNT_FIELD_NUMBER: _ClassVar[int]
|
||||
CONTENT_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
CANONICAL_PATHS_FIELD_NUMBER: _ClassVar[int]
|
||||
OBSERVED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
resource_id: ResourceId
|
||||
qb_torrent_id: str
|
||||
display_name: str
|
||||
runtime_state: TorrentRuntimeState
|
||||
selected_files: SelectionSet
|
||||
selected_complete_files: SelectionSet
|
||||
selected_logical_bytes: int
|
||||
selected_complete_bytes: int
|
||||
total_logical_bytes: int
|
||||
total_file_count: int
|
||||
content_revision: str
|
||||
canonical_paths: bool
|
||||
observed_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, resource_id: _Optional[_Union[ResourceId, _Mapping]] = ..., qb_torrent_id: _Optional[str] = ..., display_name: _Optional[str] = ..., runtime_state: _Optional[_Union[TorrentRuntimeState, str]] = ..., selected_files: _Optional[_Union[SelectionSet, _Mapping]] = ..., selected_complete_files: _Optional[_Union[SelectionSet, _Mapping]] = ..., selected_logical_bytes: _Optional[int] = ..., selected_complete_bytes: _Optional[int] = ..., total_logical_bytes: _Optional[int] = ..., total_file_count: _Optional[int] = ..., content_revision: _Optional[str] = ..., canonical_paths: _Optional[bool] = ..., observed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class ResourceStateFingerprint(_message.Message):
|
||||
__slots__ = ("resource_id", "client_id", "qb_torrent_id", "content_revision", "selected_files", "selected_complete_files", "runtime_state", "save_path_fingerprint", "observed_at")
|
||||
RESOURCE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
QB_TORRENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
CONTENT_REVISION_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
SELECTED_COMPLETE_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
RUNTIME_STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
SAVE_PATH_FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
OBSERVED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
resource_id: ResourceId
|
||||
client_id: str
|
||||
qb_torrent_id: str
|
||||
content_revision: str
|
||||
selected_files: SelectionSet
|
||||
selected_complete_files: SelectionSet
|
||||
runtime_state: TorrentRuntimeState
|
||||
save_path_fingerprint: str
|
||||
observed_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, resource_id: _Optional[_Union[ResourceId, _Mapping]] = ..., client_id: _Optional[str] = ..., qb_torrent_id: _Optional[str] = ..., content_revision: _Optional[str] = ..., selected_files: _Optional[_Union[SelectionSet, _Mapping]] = ..., selected_complete_files: _Optional[_Union[SelectionSet, _Mapping]] = ..., runtime_state: _Optional[_Union[TorrentRuntimeState, str]] = ..., save_path_fingerprint: _Optional[str] = ..., observed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class Placement(_message.Message):
|
||||
__slots__ = ("resource_id", "client_id", "state", "verified_files", "verified_logical_bytes", "generation", "fingerprint", "verified_at", "created_by_job_id")
|
||||
RESOURCE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
VERIFIED_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
VERIFIED_LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
GENERATION_FIELD_NUMBER: _ClassVar[int]
|
||||
FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
VERIFIED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
CREATED_BY_JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
resource_id: ResourceId
|
||||
client_id: str
|
||||
state: PlacementState
|
||||
verified_files: SelectionSet
|
||||
verified_logical_bytes: int
|
||||
generation: int
|
||||
fingerprint: ResourceStateFingerprint
|
||||
verified_at: _timestamp_pb2.Timestamp
|
||||
created_by_job_id: str
|
||||
def __init__(self, resource_id: _Optional[_Union[ResourceId, _Mapping]] = ..., client_id: _Optional[str] = ..., state: _Optional[_Union[PlacementState, str]] = ..., verified_files: _Optional[_Union[SelectionSet, _Mapping]] = ..., verified_logical_bytes: _Optional[int] = ..., generation: _Optional[int] = ..., fingerprint: _Optional[_Union[ResourceStateFingerprint, _Mapping]] = ..., verified_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., created_by_job_id: _Optional[str] = ...) -> None: ...
|
||||
@@ -0,0 +1,48 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: archive_control/v1/route.proto
|
||||
# Protobuf Python Version: 7.35.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
7,
|
||||
35,
|
||||
1,
|
||||
'',
|
||||
'archive_control/v1/route.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1e\x61rchive_control/v1/route.proto\x12\x12\x61rchive_control.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xa1\x04\n\nLocalRoute\x12\x19\n\x08route_id\x18\x01 \x01(\tR\x07routeId\x12.\n\x13local_relative_path\x18\x02 \x01(\tR\x11localRelativePath\x12H\n\x0b\x66older_type\x18\x03 \x01(\x0e\x32\'.archive_control.v1.SyncthingFolderTypeR\nfolderType\x12\x39\n\x19local_syncthing_device_id\x18\x04 \x01(\tR\x16localSyncthingDeviceId\x12\x39\n\x19peer_syncthing_device_ids\x18\x05 \x03(\tR\x16peerSyncthingDeviceIds\x12\x34\n\x05state\x18\x06 \x01(\x0e\x32\x1e.archive_control.v1.RouteStateR\x05state\x12\x1a\n\x08writable\x18\x07 \x01(\x08R\x08writable\x12)\n\x10sparse_supported\x18\x08 \x01(\x08R\x0fsparseSupported\x12\x36\n\x17\x61rchive_control_created\x18\t \x01(\x08R\x15\x61rchiveControlCreated\x12\x16\n\x06\x64\x65tail\x18\n \x01(\tR\x06\x64\x65tail\x12;\n\x0bobserved_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nobservedAt\"\x85\x03\n\x0fRouteDescriptor\x12\x19\n\x08route_id\x18\x01 \x01(\tR\x07routeId\x12*\n\x11\x61rchive_client_id\x18\x02 \x01(\tR\x0f\x61rchiveClientId\x12&\n\x0f\x63\x61\x63he_client_id\x18\x03 \x01(\tR\rcacheClientId\x12=\n\x1b\x61rchive_syncthing_device_id\x18\x04 \x01(\tR\x18\x61rchiveSyncthingDeviceId\x12\x39\n\x19\x63\x61\x63he_syncthing_device_id\x18\x05 \x01(\tR\x16\x63\x61\x63heSyncthingDeviceId\x12\x34\n\x05state\x18\x06 \x01(\x0e\x32\x1e.archive_control.v1.RouteStateR\x05state\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x16\n\x06\x64\x65tail\x18\x08 \x01(\tR\x06\x64\x65tail\"\x96\x02\n\x0f\x45nsureRouteSpec\x12\x19\n\x08route_id\x18\x01 \x01(\tR\x07routeId\x12$\n\x0epeer_client_id\x18\x02 \x01(\tR\x0cpeerClientId\x12\x37\n\x18peer_syncthing_device_id\x18\x03 \x01(\tR\x15peerSyncthingDeviceId\x12%\n\x0epeer_addresses\x18\x04 \x03(\tR\rpeerAddresses\x12.\n\x13local_relative_path\x18\x05 \x01(\tR\x11localRelativePath\x12\x32\n\x15setup_timeout_seconds\x18\x06 \x01(\rR\x13setupTimeoutSeconds\"\xbe\x02\n\x11RouteVerification\x12\x19\n\x08route_id\x18\x01 \x01(\tR\x07routeId\x12\x14\n\x05nonce\x18\x02 \x01(\tR\x05nonce\x12\x36\n\x18local_nonce_seen_by_peer\x18\x03 \x01(\x08R\x14localNonceSeenByPeer\x12\x35\n\x17peer_nonce_seen_locally\x18\x04 \x01(\x08R\x14peerNonceSeenLocally\x12\x34\n\x05state\x18\x05 \x01(\x0e\x32\x1e.archive_control.v1.RouteStateR\x05state\x12\x16\n\x06\x64\x65tail\x18\x06 \x01(\tR\x06\x64\x65tail\x12;\n\x0bobserved_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nobservedAt*\xd2\x01\n\x13SyncthingFolderType\x12%\n!SYNCTHING_FOLDER_TYPE_UNSPECIFIED\x10\x00\x12&\n\"SYNCTHING_FOLDER_TYPE_SEND_RECEIVE\x10\x01\x12#\n\x1fSYNCTHING_FOLDER_TYPE_SEND_ONLY\x10\x02\x12&\n\"SYNCTHING_FOLDER_TYPE_RECEIVE_ONLY\x10\x03\x12\x1f\n\x1bSYNCTHING_FOLDER_TYPE_OTHER\x10\x04*\xe5\x01\n\nRouteState\x12\x1b\n\x17ROUTE_STATE_UNSPECIFIED\x10\x00\x12\x1a\n\x16ROUTE_STATE_DISCOVERED\x10\x01\x12\x1c\n\x18ROUTE_STATE_PROVISIONING\x10\x02\x12\x19\n\x15ROUTE_STATE_VERIFYING\x10\x03\x12\x15\n\x11ROUTE_STATE_READY\x10\x04\x12\x16\n\x12ROUTE_STATE_PAUSED\x10\x05\x12\x19\n\x15ROUTE_STATE_UNHEALTHY\x10\x06\x12\x1b\n\x17ROUTE_STATE_UNSUPPORTED\x10\x07\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'archive_control.v1.route_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_SYNCTHINGFOLDERTYPE']._serialized_start=1630
|
||||
_globals['_SYNCTHINGFOLDERTYPE']._serialized_end=1840
|
||||
_globals['_ROUTESTATE']._serialized_start=1843
|
||||
_globals['_ROUTESTATE']._serialized_end=2072
|
||||
_globals['_LOCALROUTE']._serialized_start=88
|
||||
_globals['_LOCALROUTE']._serialized_end=633
|
||||
_globals['_ROUTEDESCRIPTOR']._serialized_start=636
|
||||
_globals['_ROUTEDESCRIPTOR']._serialized_end=1025
|
||||
_globals['_ENSUREROUTESPEC']._serialized_start=1028
|
||||
_globals['_ENSUREROUTESPEC']._serialized_end=1306
|
||||
_globals['_ROUTEVERIFICATION']._serialized_start=1309
|
||||
_globals['_ROUTEVERIFICATION']._serialized_end=1627
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,124 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class SyncthingFolderType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
SYNCTHING_FOLDER_TYPE_UNSPECIFIED: _ClassVar[SyncthingFolderType]
|
||||
SYNCTHING_FOLDER_TYPE_SEND_RECEIVE: _ClassVar[SyncthingFolderType]
|
||||
SYNCTHING_FOLDER_TYPE_SEND_ONLY: _ClassVar[SyncthingFolderType]
|
||||
SYNCTHING_FOLDER_TYPE_RECEIVE_ONLY: _ClassVar[SyncthingFolderType]
|
||||
SYNCTHING_FOLDER_TYPE_OTHER: _ClassVar[SyncthingFolderType]
|
||||
|
||||
class RouteState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
ROUTE_STATE_UNSPECIFIED: _ClassVar[RouteState]
|
||||
ROUTE_STATE_DISCOVERED: _ClassVar[RouteState]
|
||||
ROUTE_STATE_PROVISIONING: _ClassVar[RouteState]
|
||||
ROUTE_STATE_VERIFYING: _ClassVar[RouteState]
|
||||
ROUTE_STATE_READY: _ClassVar[RouteState]
|
||||
ROUTE_STATE_PAUSED: _ClassVar[RouteState]
|
||||
ROUTE_STATE_UNHEALTHY: _ClassVar[RouteState]
|
||||
ROUTE_STATE_UNSUPPORTED: _ClassVar[RouteState]
|
||||
SYNCTHING_FOLDER_TYPE_UNSPECIFIED: SyncthingFolderType
|
||||
SYNCTHING_FOLDER_TYPE_SEND_RECEIVE: SyncthingFolderType
|
||||
SYNCTHING_FOLDER_TYPE_SEND_ONLY: SyncthingFolderType
|
||||
SYNCTHING_FOLDER_TYPE_RECEIVE_ONLY: SyncthingFolderType
|
||||
SYNCTHING_FOLDER_TYPE_OTHER: SyncthingFolderType
|
||||
ROUTE_STATE_UNSPECIFIED: RouteState
|
||||
ROUTE_STATE_DISCOVERED: RouteState
|
||||
ROUTE_STATE_PROVISIONING: RouteState
|
||||
ROUTE_STATE_VERIFYING: RouteState
|
||||
ROUTE_STATE_READY: RouteState
|
||||
ROUTE_STATE_PAUSED: RouteState
|
||||
ROUTE_STATE_UNHEALTHY: RouteState
|
||||
ROUTE_STATE_UNSUPPORTED: RouteState
|
||||
|
||||
class LocalRoute(_message.Message):
|
||||
__slots__ = ("route_id", "local_relative_path", "folder_type", "local_syncthing_device_id", "peer_syncthing_device_ids", "state", "writable", "sparse_supported", "archive_control_created", "detail", "observed_at")
|
||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
LOCAL_RELATIVE_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
FOLDER_TYPE_FIELD_NUMBER: _ClassVar[int]
|
||||
LOCAL_SYNCTHING_DEVICE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
PEER_SYNCTHING_DEVICE_IDS_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
WRITABLE_FIELD_NUMBER: _ClassVar[int]
|
||||
SPARSE_SUPPORTED_FIELD_NUMBER: _ClassVar[int]
|
||||
ARCHIVE_CONTROL_CREATED_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
OBSERVED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
route_id: str
|
||||
local_relative_path: str
|
||||
folder_type: SyncthingFolderType
|
||||
local_syncthing_device_id: str
|
||||
peer_syncthing_device_ids: _containers.RepeatedScalarFieldContainer[str]
|
||||
state: RouteState
|
||||
writable: bool
|
||||
sparse_supported: bool
|
||||
archive_control_created: bool
|
||||
detail: str
|
||||
observed_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, route_id: _Optional[str] = ..., local_relative_path: _Optional[str] = ..., folder_type: _Optional[_Union[SyncthingFolderType, str]] = ..., local_syncthing_device_id: _Optional[str] = ..., peer_syncthing_device_ids: _Optional[_Iterable[str]] = ..., state: _Optional[_Union[RouteState, str]] = ..., writable: _Optional[bool] = ..., sparse_supported: _Optional[bool] = ..., archive_control_created: _Optional[bool] = ..., detail: _Optional[str] = ..., observed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class RouteDescriptor(_message.Message):
|
||||
__slots__ = ("route_id", "archive_client_id", "cache_client_id", "archive_syncthing_device_id", "cache_syncthing_device_id", "state", "verified_at", "detail")
|
||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ARCHIVE_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
CACHE_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ARCHIVE_SYNCTHING_DEVICE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
CACHE_SYNCTHING_DEVICE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
VERIFIED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
route_id: str
|
||||
archive_client_id: str
|
||||
cache_client_id: str
|
||||
archive_syncthing_device_id: str
|
||||
cache_syncthing_device_id: str
|
||||
state: RouteState
|
||||
verified_at: _timestamp_pb2.Timestamp
|
||||
detail: str
|
||||
def __init__(self, route_id: _Optional[str] = ..., archive_client_id: _Optional[str] = ..., cache_client_id: _Optional[str] = ..., archive_syncthing_device_id: _Optional[str] = ..., cache_syncthing_device_id: _Optional[str] = ..., state: _Optional[_Union[RouteState, str]] = ..., verified_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., detail: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class EnsureRouteSpec(_message.Message):
|
||||
__slots__ = ("route_id", "peer_client_id", "peer_syncthing_device_id", "peer_addresses", "local_relative_path", "setup_timeout_seconds")
|
||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
PEER_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
PEER_SYNCTHING_DEVICE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
PEER_ADDRESSES_FIELD_NUMBER: _ClassVar[int]
|
||||
LOCAL_RELATIVE_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
SETUP_TIMEOUT_SECONDS_FIELD_NUMBER: _ClassVar[int]
|
||||
route_id: str
|
||||
peer_client_id: str
|
||||
peer_syncthing_device_id: str
|
||||
peer_addresses: _containers.RepeatedScalarFieldContainer[str]
|
||||
local_relative_path: str
|
||||
setup_timeout_seconds: int
|
||||
def __init__(self, route_id: _Optional[str] = ..., peer_client_id: _Optional[str] = ..., peer_syncthing_device_id: _Optional[str] = ..., peer_addresses: _Optional[_Iterable[str]] = ..., local_relative_path: _Optional[str] = ..., setup_timeout_seconds: _Optional[int] = ...) -> None: ...
|
||||
|
||||
class RouteVerification(_message.Message):
|
||||
__slots__ = ("route_id", "nonce", "local_nonce_seen_by_peer", "peer_nonce_seen_locally", "state", "detail", "observed_at")
|
||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
NONCE_FIELD_NUMBER: _ClassVar[int]
|
||||
LOCAL_NONCE_SEEN_BY_PEER_FIELD_NUMBER: _ClassVar[int]
|
||||
PEER_NONCE_SEEN_LOCALLY_FIELD_NUMBER: _ClassVar[int]
|
||||
STATE_FIELD_NUMBER: _ClassVar[int]
|
||||
DETAIL_FIELD_NUMBER: _ClassVar[int]
|
||||
OBSERVED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
route_id: str
|
||||
nonce: str
|
||||
local_nonce_seen_by_peer: bool
|
||||
peer_nonce_seen_locally: bool
|
||||
state: RouteState
|
||||
detail: str
|
||||
observed_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, route_id: _Optional[str] = ..., nonce: _Optional[str] = ..., local_nonce_seen_by_peer: _Optional[bool] = ..., peer_nonce_seen_locally: _Optional[bool] = ..., state: _Optional[_Union[RouteState, str]] = ..., detail: _Optional[str] = ..., observed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
@@ -0,0 +1,49 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: archive_control/v1/transfer.proto
|
||||
# Protobuf Python Version: 7.35.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
7,
|
||||
35,
|
||||
1,
|
||||
'',
|
||||
'archive_control/v1/transfer.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from archive_control.v1 import resource_pb2 as archive__control_dot_v1_dot_resource__pb2
|
||||
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!archive_control/v1/transfer.proto\x12\x12\x61rchive_control.v1\x1a!archive_control/v1/resource.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xd7\x03\n\x0cManifestFile\x12\x1d\n\nfile_index\x18\x01 \x01(\rR\tfileIndex\x12\x32\n\x15payload_relative_path\x18\x02 \x01(\tR\x13payloadRelativePath\x12\x32\n\x15target_canonical_path\x18\x03 \x01(\tR\x13targetCanonicalPath\x12#\n\rlogical_bytes\x18\x04 \x01(\x04R\x0clogicalBytes\x12\'\n\x0f\x61llocated_bytes\x18\x05 \x01(\x04R\x0e\x61llocatedBytes\x12\x16\n\x06sparse\x18\x06 \x01(\x08R\x06sparse\x12]\n\x15source_staging_method\x18\x07 \x01(\x0e\x32).archive_control.v1.MaterializationMethodR\x13sourceStagingMethod\x12N\n\rtarget_method\x18\x08 \x01(\x0e\x32).archive_control.v1.MaterializationMethodR\x0ctargetMethod\x12+\n\x11target_preexisted\x18\t \x01(\x08R\x10targetPreexisted\"\x99\x02\n\x10ManifestArtifact\x12\x34\n\x04kind\x18\x01 \x01(\x0e\x32 .archive_control.v1.ArtifactKindR\x04kind\x12\x32\n\x15payload_relative_path\x18\x02 \x01(\tR\x13payloadRelativePath\x12#\n\rlogical_bytes\x18\x03 \x01(\x04R\x0clogicalBytes\x12\'\n\x0f\x61llocated_bytes\x18\x04 \x01(\x04R\x0e\x61llocatedBytes\x12\x16\n\x06sparse\x18\x05 \x01(\x08R\x06sparse\x12\x16\n\x06\x66ormat\x18\x06 \x01(\tR\x06\x66ormat\x12\x1d\n\nsha256_hex\x18\x07 \x01(\tR\tsha256Hex\"\xfb\x06\n\x10TransferManifest\x12)\n\x10manifest_version\x18\x01 \x01(\rR\x0fmanifestVersion\x12\x15\n\x06job_id\x18\x02 \x01(\tR\x05jobId\x12?\n\x0bresource_id\x18\x03 \x01(\x0b\x32\x1e.archive_control.v1.ResourceIdR\nresourceId\x12(\n\x10source_client_id\x18\x04 \x01(\tR\x0esourceClientId\x12(\n\x10target_client_id\x18\x05 \x01(\tR\x0etargetClientId\x12\x19\n\x08route_id\x18\x06 \x01(\tR\x07routeId\x12I\n\x0frequested_files\x18\x07 \x01(\x0b\x32 .archive_control.v1.SelectionSetR\x0erequestedFiles\x12T\n\x15target_baseline_files\x18\x08 \x01(\x0b\x32 .archive_control.v1.SelectionSetR\x13targetBaselineFiles\x12R\n\x14transfer_delta_files\x18\t \x01(\x0b\x32 .archive_control.v1.SelectionSetR\x12transferDeltaFiles\x12\x36\n\x05\x66iles\x18\n \x03(\x0b\x32 .archive_control.v1.ManifestFileR\x05\x66iles\x12\x42\n\tartifacts\x18\x0b \x03(\x0b\x32$.archive_control.v1.ManifestArtifactR\tartifacts\x12[\n\x12source_fingerprint\x18\x0c \x01(\x0b\x32,.archive_control.v1.ResourceStateFingerprintR\x11sourceFingerprint\x12l\n\x1btarget_baseline_fingerprint\x18\r \x01(\x0b\x32,.archive_control.v1.ResourceStateFingerprintR\x19targetBaselineFingerprint\x12\x39\n\ncreated_at\x18\x0e \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\"\x8b\x01\n\x0bReadyMarker\x12\x15\n\x06job_id\x18\x01 \x01(\tR\x05jobId\x12.\n\x13manifest_sha256_hex\x18\x02 \x01(\tR\x11manifestSha256Hex\x12\x35\n\x08ready_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x07readyAt*\xd9\x01\n\x15MaterializationMethod\x12&\n\"MATERIALIZATION_METHOD_UNSPECIFIED\x10\x00\x12$\n MATERIALIZATION_METHOD_HARD_LINK\x10\x01\x12\"\n\x1eMATERIALIZATION_METHOD_REFLINK\x10\x02\x12\x1f\n\x1bMATERIALIZATION_METHOD_COPY\x10\x03\x12-\n)MATERIALIZATION_METHOD_PREEXISTING_REUSED\x10\x04*\x97\x01\n\x0c\x41rtifactKind\x12\x1d\n\x19\x41RTIFACT_KIND_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x41RTIFACT_KIND_TORRENT_FILE\x10\x01\x12%\n!ARTIFACT_KIND_LIBTORRENT_PARTFILE\x10\x02\x12!\n\x1d\x41RTIFACT_KIND_OTHER_AUXILIARY\x10\x03\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'archive_control.v1.transfer_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_MATERIALIZATIONMETHOD']._serialized_start=1920
|
||||
_globals['_MATERIALIZATIONMETHOD']._serialized_end=2137
|
||||
_globals['_ARTIFACTKIND']._serialized_start=2140
|
||||
_globals['_ARTIFACTKIND']._serialized_end=2291
|
||||
_globals['_MANIFESTFILE']._serialized_start=126
|
||||
_globals['_MANIFESTFILE']._serialized_end=597
|
||||
_globals['_MANIFESTARTIFACT']._serialized_start=600
|
||||
_globals['_MANIFESTARTIFACT']._serialized_end=881
|
||||
_globals['_TRANSFERMANIFEST']._serialized_start=884
|
||||
_globals['_TRANSFERMANIFEST']._serialized_end=1775
|
||||
_globals['_READYMARKER']._serialized_start=1778
|
||||
_globals['_READYMARKER']._serialized_end=1917
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,119 @@
|
||||
# archive-control-proto commit: 4ec852014dad74606d4078b3ae1aa208c814b033
|
||||
import datetime
|
||||
|
||||
from archive_control.v1 import resource_pb2 as _resource_pb2
|
||||
from google.protobuf import timestamp_pb2 as _timestamp_pb2
|
||||
from google.protobuf.internal import containers as _containers
|
||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
||||
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
||||
|
||||
DESCRIPTOR: _descriptor.FileDescriptor
|
||||
|
||||
class MaterializationMethod(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
MATERIALIZATION_METHOD_UNSPECIFIED: _ClassVar[MaterializationMethod]
|
||||
MATERIALIZATION_METHOD_HARD_LINK: _ClassVar[MaterializationMethod]
|
||||
MATERIALIZATION_METHOD_REFLINK: _ClassVar[MaterializationMethod]
|
||||
MATERIALIZATION_METHOD_COPY: _ClassVar[MaterializationMethod]
|
||||
MATERIALIZATION_METHOD_PREEXISTING_REUSED: _ClassVar[MaterializationMethod]
|
||||
|
||||
class ArtifactKind(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
||||
__slots__ = ()
|
||||
ARTIFACT_KIND_UNSPECIFIED: _ClassVar[ArtifactKind]
|
||||
ARTIFACT_KIND_TORRENT_FILE: _ClassVar[ArtifactKind]
|
||||
ARTIFACT_KIND_LIBTORRENT_PARTFILE: _ClassVar[ArtifactKind]
|
||||
ARTIFACT_KIND_OTHER_AUXILIARY: _ClassVar[ArtifactKind]
|
||||
MATERIALIZATION_METHOD_UNSPECIFIED: MaterializationMethod
|
||||
MATERIALIZATION_METHOD_HARD_LINK: MaterializationMethod
|
||||
MATERIALIZATION_METHOD_REFLINK: MaterializationMethod
|
||||
MATERIALIZATION_METHOD_COPY: MaterializationMethod
|
||||
MATERIALIZATION_METHOD_PREEXISTING_REUSED: MaterializationMethod
|
||||
ARTIFACT_KIND_UNSPECIFIED: ArtifactKind
|
||||
ARTIFACT_KIND_TORRENT_FILE: ArtifactKind
|
||||
ARTIFACT_KIND_LIBTORRENT_PARTFILE: ArtifactKind
|
||||
ARTIFACT_KIND_OTHER_AUXILIARY: ArtifactKind
|
||||
|
||||
class ManifestFile(_message.Message):
|
||||
__slots__ = ("file_index", "payload_relative_path", "target_canonical_path", "logical_bytes", "allocated_bytes", "sparse", "source_staging_method", "target_method", "target_preexisted")
|
||||
FILE_INDEX_FIELD_NUMBER: _ClassVar[int]
|
||||
PAYLOAD_RELATIVE_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_CANONICAL_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
ALLOCATED_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
SPARSE_FIELD_NUMBER: _ClassVar[int]
|
||||
SOURCE_STAGING_METHOD_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_METHOD_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_PREEXISTED_FIELD_NUMBER: _ClassVar[int]
|
||||
file_index: int
|
||||
payload_relative_path: str
|
||||
target_canonical_path: str
|
||||
logical_bytes: int
|
||||
allocated_bytes: int
|
||||
sparse: bool
|
||||
source_staging_method: MaterializationMethod
|
||||
target_method: MaterializationMethod
|
||||
target_preexisted: bool
|
||||
def __init__(self, file_index: _Optional[int] = ..., payload_relative_path: _Optional[str] = ..., target_canonical_path: _Optional[str] = ..., logical_bytes: _Optional[int] = ..., allocated_bytes: _Optional[int] = ..., sparse: _Optional[bool] = ..., source_staging_method: _Optional[_Union[MaterializationMethod, str]] = ..., target_method: _Optional[_Union[MaterializationMethod, str]] = ..., target_preexisted: _Optional[bool] = ...) -> None: ...
|
||||
|
||||
class ManifestArtifact(_message.Message):
|
||||
__slots__ = ("kind", "payload_relative_path", "logical_bytes", "allocated_bytes", "sparse", "format", "sha256_hex")
|
||||
KIND_FIELD_NUMBER: _ClassVar[int]
|
||||
PAYLOAD_RELATIVE_PATH_FIELD_NUMBER: _ClassVar[int]
|
||||
LOGICAL_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
ALLOCATED_BYTES_FIELD_NUMBER: _ClassVar[int]
|
||||
SPARSE_FIELD_NUMBER: _ClassVar[int]
|
||||
FORMAT_FIELD_NUMBER: _ClassVar[int]
|
||||
SHA256_HEX_FIELD_NUMBER: _ClassVar[int]
|
||||
kind: ArtifactKind
|
||||
payload_relative_path: str
|
||||
logical_bytes: int
|
||||
allocated_bytes: int
|
||||
sparse: bool
|
||||
format: str
|
||||
sha256_hex: str
|
||||
def __init__(self, kind: _Optional[_Union[ArtifactKind, str]] = ..., payload_relative_path: _Optional[str] = ..., logical_bytes: _Optional[int] = ..., allocated_bytes: _Optional[int] = ..., sparse: _Optional[bool] = ..., format: _Optional[str] = ..., sha256_hex: _Optional[str] = ...) -> None: ...
|
||||
|
||||
class TransferManifest(_message.Message):
|
||||
__slots__ = ("manifest_version", "job_id", "resource_id", "source_client_id", "target_client_id", "route_id", "requested_files", "target_baseline_files", "transfer_delta_files", "files", "artifacts", "source_fingerprint", "target_baseline_fingerprint", "created_at")
|
||||
MANIFEST_VERSION_FIELD_NUMBER: _ClassVar[int]
|
||||
JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
RESOURCE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
SOURCE_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
REQUESTED_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_BASELINE_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
TRANSFER_DELTA_FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
FILES_FIELD_NUMBER: _ClassVar[int]
|
||||
ARTIFACTS_FIELD_NUMBER: _ClassVar[int]
|
||||
SOURCE_FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
TARGET_BASELINE_FINGERPRINT_FIELD_NUMBER: _ClassVar[int]
|
||||
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
manifest_version: int
|
||||
job_id: str
|
||||
resource_id: _resource_pb2.ResourceId
|
||||
source_client_id: str
|
||||
target_client_id: str
|
||||
route_id: str
|
||||
requested_files: _resource_pb2.SelectionSet
|
||||
target_baseline_files: _resource_pb2.SelectionSet
|
||||
transfer_delta_files: _resource_pb2.SelectionSet
|
||||
files: _containers.RepeatedCompositeFieldContainer[ManifestFile]
|
||||
artifacts: _containers.RepeatedCompositeFieldContainer[ManifestArtifact]
|
||||
source_fingerprint: _resource_pb2.ResourceStateFingerprint
|
||||
target_baseline_fingerprint: _resource_pb2.ResourceStateFingerprint
|
||||
created_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, manifest_version: _Optional[int] = ..., job_id: _Optional[str] = ..., resource_id: _Optional[_Union[_resource_pb2.ResourceId, _Mapping]] = ..., source_client_id: _Optional[str] = ..., target_client_id: _Optional[str] = ..., route_id: _Optional[str] = ..., requested_files: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., target_baseline_files: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., transfer_delta_files: _Optional[_Union[_resource_pb2.SelectionSet, _Mapping]] = ..., files: _Optional[_Iterable[_Union[ManifestFile, _Mapping]]] = ..., artifacts: _Optional[_Iterable[_Union[ManifestArtifact, _Mapping]]] = ..., source_fingerprint: _Optional[_Union[_resource_pb2.ResourceStateFingerprint, _Mapping]] = ..., target_baseline_fingerprint: _Optional[_Union[_resource_pb2.ResourceStateFingerprint, _Mapping]] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class ReadyMarker(_message.Message):
|
||||
__slots__ = ("job_id", "manifest_sha256_hex", "ready_at")
|
||||
JOB_ID_FIELD_NUMBER: _ClassVar[int]
|
||||
MANIFEST_SHA256_HEX_FIELD_NUMBER: _ClassVar[int]
|
||||
READY_AT_FIELD_NUMBER: _ClassVar[int]
|
||||
job_id: str
|
||||
manifest_sha256_hex: str
|
||||
ready_at: _timestamp_pb2.Timestamp
|
||||
def __init__(self, job_id: _Optional[str] = ..., manifest_sha256_hex: _Optional[str] = ..., ready_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
|
||||
@@ -0,0 +1,101 @@
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from archive_clients.config import ClientConfig, ConfigError, RootMapping
|
||||
|
||||
|
||||
class ConfigTests(unittest.TestCase):
|
||||
def test_strict_config_mode_override_secrets_and_mapping(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
for name in ("token", "qb-password", "syncthing-key"):
|
||||
path = root / name
|
||||
path.write_text(name, encoding="utf-8")
|
||||
os.chmod(path, 0o600)
|
||||
(root / "qb").mkdir()
|
||||
(root / "sync").mkdir()
|
||||
config_path = root / "client.toml"
|
||||
config_path.write_text(
|
||||
_config(root, role="cache"), encoding="utf-8"
|
||||
)
|
||||
config = ClientConfig.load(config_path, "archive")
|
||||
self.assertEqual(config.role, "archive")
|
||||
self.assertEqual(config.read_shared_token(), "token")
|
||||
self.assertEqual(
|
||||
config.qbittorrent.roots.api_to_local("/downloads/a/b"),
|
||||
root / "qb/a/b",
|
||||
)
|
||||
self.assertEqual(config.jobs.stall_after, 30 * 60)
|
||||
self.assertEqual(
|
||||
config.syncthing.advertised_addresses, ("dynamic",)
|
||||
)
|
||||
|
||||
def test_unknown_key_and_unsafe_secret_fail(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
for name in ("token", "qb-password", "syncthing-key"):
|
||||
path = root / name
|
||||
path.write_text(name, encoding="utf-8")
|
||||
os.chmod(path, 0o600)
|
||||
config_path = root / "client.toml"
|
||||
invalid = _config(root).replace(
|
||||
"[qbittorrent]", "typo = true\n[qbittorrent]"
|
||||
)
|
||||
config_path.write_text(invalid, encoding="utf-8")
|
||||
with self.assertRaisesRegex(ConfigError, "unknown root"):
|
||||
ClientConfig.load(config_path)
|
||||
os.chmod(root / "token", 0o640)
|
||||
config_path.write_text(_config(root), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ConfigError, "permissions"):
|
||||
ClientConfig.load(config_path).read_shared_token()
|
||||
|
||||
def test_mapping_rejects_escape(self):
|
||||
mapping = RootMapping(Path("/api"), Path("/local"))
|
||||
with self.assertRaises(ConfigError):
|
||||
mapping.api_to_local("/elsewhere/file")
|
||||
|
||||
def test_endpoint_scheme_and_job_keys_are_strict(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
invalid_endpoint = _config(root).replace(
|
||||
'control_endpoint = "ws://control/archive_control"',
|
||||
'control_endpoint = "http://control/archive_control"',
|
||||
)
|
||||
path = root / "client.toml"
|
||||
path.write_text(invalid_endpoint, encoding="utf-8")
|
||||
with self.assertRaisesRegex(ConfigError, "ws/wss"):
|
||||
ClientConfig.load(path)
|
||||
path.write_text(
|
||||
_config(root) + "\n[jobs]\nunknown = true\n", encoding="utf-8"
|
||||
)
|
||||
with self.assertRaisesRegex(ConfigError, "unknown jobs"):
|
||||
ClientConfig.load(path)
|
||||
|
||||
|
||||
def _config(root: Path, role: str = "cache") -> str:
|
||||
return f'''client_id = "cache-1"
|
||||
display_name = "Cache 1"
|
||||
role = "{role}"
|
||||
control_endpoint = "ws://control/archive_control"
|
||||
shared_token_file = "{root / 'token'}"
|
||||
state_db = "{root / 'state.db'}"
|
||||
backup_dir = "{root / 'backups'}"
|
||||
[qbittorrent]
|
||||
endpoint = "http://qb"
|
||||
username = "admin"
|
||||
password_file = "{root / 'qb-password'}"
|
||||
api_root = "/downloads"
|
||||
local_root = "{root / 'qb'}"
|
||||
[syncthing]
|
||||
endpoint = "http://syncthing"
|
||||
api_key_file = "{root / 'syncthing-key'}"
|
||||
api_root = "/sync"
|
||||
local_root = "{root / 'sync'}"
|
||||
advertised_addresses = ["dynamic"]
|
||||
'''
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,112 @@
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path, PurePosixPath
|
||||
from uuid import uuid4
|
||||
|
||||
from websockets.asyncio.server import serve
|
||||
|
||||
from archive_clients.config import ClientConfig, ConnectionConfig, ServiceConfig
|
||||
from archive_clients.daemon import ArchiveClientDaemon
|
||||
from archive_clients.probes import FilesystemProbe
|
||||
from archive_clients.protocol import decode, encode, new_envelope
|
||||
from archive_control.v1 import client_pb2, common_pb2, control_pb2
|
||||
|
||||
|
||||
class DaemonTransportTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_registration_heartbeat_and_duplicate_command(self):
|
||||
observed = {}
|
||||
|
||||
async def control(websocket):
|
||||
registration = decode(await websocket.recv())
|
||||
observed["token"] = registration.register_request.shared_token
|
||||
observed["root_names"] = [
|
||||
item.root_name
|
||||
for item in registration.register_request.capabilities.filesystems
|
||||
]
|
||||
observed["addresses"] = list(
|
||||
registration.register_request.capabilities
|
||||
.syncthing_advertised_addresses
|
||||
)
|
||||
response = new_envelope()
|
||||
response.correlation_id = registration.message_id
|
||||
response.register_response.status = client_pb2.REGISTRATION_STATUS_ACCEPTED
|
||||
response.register_response.negotiated_version.major = 1
|
||||
await websocket.send(encode(response))
|
||||
heartbeat = new_envelope()
|
||||
heartbeat.heartbeat.sequence = 7
|
||||
await websocket.send(encode(heartbeat))
|
||||
observed["heartbeat"] = decode(await websocket.recv()).heartbeat_ack.sequence
|
||||
command_id = str(uuid4())
|
||||
command = new_envelope()
|
||||
command.command.command_id = command_id
|
||||
command.command.created_at.CopyFrom(command.sent_at)
|
||||
command.command.request_job_snapshot.job_ids.append(str(uuid4()))
|
||||
await websocket.send(encode(command))
|
||||
observed["first"] = decode(await websocket.recv()).command_ack.status
|
||||
observed["snapshot"] = (
|
||||
decode(await websocket.recv()).WhichOneof("payload")
|
||||
)
|
||||
duplicate = new_envelope()
|
||||
duplicate.command.CopyFrom(command.command)
|
||||
await websocket.send(encode(duplicate))
|
||||
observed["second"] = decode(await websocket.recv()).command_ack.status
|
||||
unsupported = new_envelope()
|
||||
unsupported.command.command_id = str(uuid4())
|
||||
unsupported.command.created_at.CopyFrom(unsupported.sent_at)
|
||||
unsupported.command.assign_job.SetInParent()
|
||||
await websocket.send(encode(unsupported))
|
||||
rejected = decode(await websocket.recv()).command_ack
|
||||
observed["rejected"] = (rejected.status, rejected.error.code)
|
||||
unsupported_duplicate = new_envelope()
|
||||
unsupported_duplicate.command.CopyFrom(unsupported.command)
|
||||
await websocket.send(encode(unsupported_duplicate))
|
||||
observed["rejected_duplicate"] = (
|
||||
decode(await websocket.recv()).command_ack.status
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
token = root / "token"
|
||||
token.write_text("shared-secret", encoding="utf-8")
|
||||
os.chmod(token, 0o600)
|
||||
async with serve(control, "127.0.0.1", 0, ping_interval=None) as server:
|
||||
port = server.sockets[0].getsockname()[1]
|
||||
service = ServiceConfig(
|
||||
"http://local", PurePosixPath("/api"), root,
|
||||
advertised_addresses=("dynamic",),
|
||||
)
|
||||
config = ClientConfig(
|
||||
"cache-1", "Cache 1", "cache",
|
||||
f"ws://127.0.0.1:{port}", token,
|
||||
root / "state.db", root / "backups", service, service,
|
||||
ConnectionConfig(registration_timeout=2),
|
||||
)
|
||||
probe = FilesystemProbe(root, True, True, True, True)
|
||||
daemon = ArchiveClientDaemon(config, [probe, probe])
|
||||
await asyncio.to_thread(daemon.store.initialize)
|
||||
await daemon._connection()
|
||||
|
||||
self.assertEqual(observed["token"], "shared-secret")
|
||||
self.assertEqual(observed["root_names"], ["qbittorrent", "syncthing"])
|
||||
self.assertEqual(observed["addresses"], ["dynamic"])
|
||||
self.assertEqual(observed["heartbeat"], 7)
|
||||
self.assertEqual(observed["first"], control_pb2.COMMAND_ACK_STATUS_ACCEPTED)
|
||||
self.assertEqual(observed["second"], control_pb2.COMMAND_ACK_STATUS_DUPLICATE)
|
||||
self.assertEqual(observed["snapshot"], "client_state_snapshot")
|
||||
self.assertEqual(
|
||||
observed["rejected"],
|
||||
(
|
||||
control_pb2.COMMAND_ACK_STATUS_REJECTED,
|
||||
common_pb2.ERROR_CODE_UNSUPPORTED,
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
observed["rejected_duplicate"],
|
||||
control_pb2.COMMAND_ACK_STATUS_REJECTED,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,17 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from archive_clients import PROTO_COMMIT
|
||||
|
||||
|
||||
class GeneratedContractTests(unittest.TestCase):
|
||||
def test_all_generated_files_record_proto_commit(self):
|
||||
files = list(Path("src/archive_control/v1").glob("*_pb2.py"))
|
||||
files += list(Path("src/archive_control/v1").glob("*_pb2.pyi"))
|
||||
self.assertEqual(len(files), 18)
|
||||
for path in files:
|
||||
self.assertIn(PROTO_COMMIT, path.read_text(encoding="utf-8")[:256])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,33 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from archive_clients.probes import probe_root
|
||||
from archive_clients.protocol import ProtocolError, decode, encode, new_envelope
|
||||
|
||||
|
||||
class ProtocolAndProbeTests(unittest.TestCase):
|
||||
def test_protocol_round_trip_and_duplicate_key_rejection(self):
|
||||
envelope = new_envelope()
|
||||
envelope.heartbeat.sequence = 1
|
||||
self.assertEqual(decode(encode(envelope)).heartbeat.sequence, 1)
|
||||
raw = json.loads(encode(envelope))
|
||||
duplicate = json.dumps(raw).replace("{", '{"messageId":"duplicate",', 1)
|
||||
with self.assertRaises(ProtocolError):
|
||||
decode(duplicate)
|
||||
raw["messageId"] = "not-a-uuid"
|
||||
with self.assertRaisesRegex(ProtocolError, "canonical UUID"):
|
||||
decode(json.dumps(raw))
|
||||
|
||||
def test_filesystem_probe_cleans_up(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
result = probe_root(root)
|
||||
self.assertTrue(result.readable)
|
||||
self.assertTrue(result.writable)
|
||||
self.assertEqual(list(root.iterdir()), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,28 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from archive_clients.state import ClientStore, CommandConflict
|
||||
|
||||
|
||||
class ClientStoreTests(unittest.TestCase):
|
||||
def test_command_acceptance_is_durable_and_content_addressed(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
database = Path(directory) / "state.db"
|
||||
store = ClientStore(database)
|
||||
store.initialize()
|
||||
command_id = str(uuid4())
|
||||
first = store.accept_command(command_id, '{"b":2,"a":1}', '{"ok":true}')
|
||||
duplicate = ClientStore(database).accept_command(
|
||||
command_id, '{"a":1,"b":2}', '{"ok":false}'
|
||||
)
|
||||
self.assertFalse(first.duplicate)
|
||||
self.assertTrue(duplicate.duplicate)
|
||||
self.assertEqual(duplicate.acknowledgement_json, '{"ok":true}')
|
||||
with self.assertRaises(CommandConflict):
|
||||
store.accept_command(command_id, '{"a":2}', '{"ok":true}')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user