US05-02: Orchestrate Album Upload Batches (#72)
This commit was merged in pull request #72.
This commit is contained in:
@@ -14,6 +14,7 @@ from photo_pipeline.models.duplicates import (
|
||||
from photo_pipeline.models.jobs import Job, JobEvent, JobItem
|
||||
from photo_pipeline.models.renames import RenameOperation, RenamePlan
|
||||
from photo_pipeline.models.thumbnails import Thumbnail
|
||||
from photo_pipeline.models.uploads import UploadBatch, UploadItem
|
||||
from photo_pipeline.models.workflow import AnalysisResult, SafetyReview
|
||||
|
||||
__all__ = [
|
||||
@@ -29,6 +30,8 @@ __all__ = [
|
||||
"RenamePlan",
|
||||
"RenameOperation",
|
||||
"Thumbnail",
|
||||
"UploadBatch",
|
||||
"UploadItem",
|
||||
"SafetyReview",
|
||||
"AnalysisResult",
|
||||
]
|
||||
|
||||
90
photo_pipeline/models/uploads.py
Normal file
90
photo_pipeline/models/uploads.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Upload batch persistence (US05-02).
|
||||
|
||||
One batch is one approved album folder handed to ``immich-go``. It is the durable
|
||||
record of an irreversible external action, so it stores everything needed to answer
|
||||
"which exact bytes did we send, with which command, and how did it end?" after a
|
||||
crash: the preflight token that authorised it, the redacted command, the uploader
|
||||
version, the per-asset pre-upload hashes, every attempt, and where the raw report
|
||||
was written.
|
||||
|
||||
Item rows keep both digests: SHA-256 is the app's byte identity and SHA-1 is what
|
||||
Immich/immich-go use to recognise a file it already has (concept §8).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from photo_pipeline.db import Base
|
||||
|
||||
|
||||
class UploadBatch(Base):
|
||||
__tablename__ = "upload_batches"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True)
|
||||
album: Mapped[str] = mapped_column(String, nullable=False, index=True)
|
||||
folder: Mapped[str] = mapped_column(String, nullable=False)
|
||||
album_name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
# planned | running | cancelling | cancelled | succeeded | failed
|
||||
# | unknown_requires_verification
|
||||
state: Mapped[str] = mapped_column(String, nullable=False, default="planned")
|
||||
# The preflight token this batch was approved against; re-checked before every
|
||||
# attempt so changed bytes or decisions cannot be uploaded silently.
|
||||
preflight_token: Mapped[str] = mapped_column(String, nullable=False)
|
||||
allow_partial: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
command: Mapped[str | None] = mapped_column(String) # JSON array, redacted
|
||||
uploader_version: Mapped[str | None] = mapped_column(String)
|
||||
asset_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
attempt_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
# Bumped on every claim and used as the fencing token, so a superseded attempt
|
||||
# cannot commit its outcome.
|
||||
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||
worker_id: Mapped[str | None] = mapped_column(String)
|
||||
|
||||
report_path: Mapped[str | None] = mapped_column(String)
|
||||
report_bytes: Mapped[int | None] = mapped_column(Integer)
|
||||
report_truncated: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
exit_code: Mapped[int | None] = mapped_column(Integer)
|
||||
error_code: Mapped[str | None] = mapped_column(String)
|
||||
error_message: Mapped[str | None] = mapped_column(String)
|
||||
|
||||
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
|
||||
class UploadItem(Base):
|
||||
__tablename__ = "upload_items"
|
||||
|
||||
batch_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("upload_batches.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
asset_id: Mapped[str] = mapped_column(String, primary_key=True)
|
||||
path: Mapped[str] = mapped_column(String, nullable=False)
|
||||
# Hashes of the bytes as they were when the batch was created.
|
||||
sha256: Mapped[str | None] = mapped_column(String)
|
||||
sha1: Mapped[str | None] = mapped_column(String)
|
||||
# pending | sent | failed — the per-asset upload *result* is parsed from the
|
||||
# report in US05-03 and verified in US05-04; ``sent`` only means the batch
|
||||
# process exited successfully.
|
||||
state: Mapped[str] = mapped_column(String, nullable=False, default="pending")
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
Reference in New Issue
Block a user