You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
80 lines
2.9 KiB
80 lines
2.9 KiB
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import anyio
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.adapters.storage.local import LocalStorageAdapter
|
|
from app.core.config import get_settings
|
|
from app.core.ids import new_id
|
|
from app.models.entities import Backend, Blob, BlobReplica
|
|
from app.repositories.backend_repository import BackendRepository
|
|
from app.repositories.blob_replica_repository import BlobReplicaRepository
|
|
|
|
DEFAULT_LOCAL_BACKEND_ID = "bkd_local_default"
|
|
DEFAULT_LOCAL_BACKEND_NAME = "local-default"
|
|
|
|
|
|
class LocalStorageService:
|
|
def __init__(self, session: AsyncSession) -> None:
|
|
self.session = session
|
|
self.settings = get_settings()
|
|
self.backend_repository = BackendRepository(session)
|
|
self.blob_replica_repository = BlobReplicaRepository(session)
|
|
self.adapter = LocalStorageAdapter(self.settings.local_storage_dir)
|
|
|
|
async def ensure_backend_ready(self) -> Backend:
|
|
await self.adapter.ensure_base_path()
|
|
|
|
backend = await self.backend_repository.get_by_name(DEFAULT_LOCAL_BACKEND_NAME)
|
|
if backend is not None:
|
|
return backend
|
|
|
|
backend = Backend(
|
|
id=DEFAULT_LOCAL_BACKEND_ID,
|
|
name=DEFAULT_LOCAL_BACKEND_NAME,
|
|
type="local",
|
|
stability_class="local",
|
|
read_priority=100,
|
|
write_priority=100,
|
|
is_enabled=True,
|
|
config_json=json.dumps({"base_path": self.settings.local_storage_dir}),
|
|
)
|
|
await self.backend_repository.create(backend)
|
|
await self.session.commit()
|
|
return backend
|
|
|
|
async def persist_blob_from_temp(self, blob: Blob, temp_path: str) -> BlobReplica:
|
|
backend = await self.ensure_backend_ready()
|
|
existing = await self.blob_replica_repository.get_by_blob_and_backend(blob.id, backend.id)
|
|
if existing is not None:
|
|
return existing
|
|
|
|
storage_key = self._build_storage_key(blob)
|
|
stored_path = await self.adapter.put_file(temp_path, storage_key)
|
|
replica = BlobReplica(
|
|
id=new_id("rep"),
|
|
blob_id=blob.id,
|
|
backend_id=backend.id,
|
|
storage_key=storage_key,
|
|
status="ready",
|
|
checksum=blob.content_hash,
|
|
size_bytes=blob.size_bytes,
|
|
)
|
|
created = await self.blob_replica_repository.create(replica)
|
|
await self.session.commit()
|
|
|
|
await anyio.to_thread.run_sync(lambda: Path(stored_path).exists())
|
|
return created
|
|
|
|
async def resolve_replica_path(self, replica: BlobReplica) -> Path:
|
|
await self.ensure_backend_ready()
|
|
return await self.adapter.resolve_path(replica.storage_key)
|
|
|
|
@staticmethod
|
|
def _build_storage_key(blob: Blob) -> str:
|
|
content_hash = blob.content_hash
|
|
prefix = content_hash[:2] if len(content_hash) >= 2 else "xx"
|
|
return f"blobs/{prefix}/{content_hash}"
|
|
|