mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
"""3.0.13 为整理准入记录增加稳定身份与持久状态。
|
|
|
|
Revision ID: b1e7d3f5a9c2
|
|
Revises: 5f2a9c1e7b4d
|
|
Create Date: 2026-08-27
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from uuid import NAMESPACE_URL, uuid5
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision = "b1e7d3f5a9c2"
|
|
down_revision = "5f2a9c1e7b4d"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
_TABLE_NAME = "transferpending"
|
|
_TASK_ID_CONSTRAINT = "uq_transferpending_task_id"
|
|
_STATE_CREATED_INDEX = "ix_transferpending_state_created"
|
|
_NEW_COLUMNS = {"task_id", "state", "updated_at", "last_error"}
|
|
|
|
|
|
def _column_names() -> set[str]:
|
|
"""返回当前待整理登记表的字段集合。"""
|
|
inspector = sa.inspect(op.get_bind())
|
|
if _TABLE_NAME not in inspector.get_table_names():
|
|
return set()
|
|
return {
|
|
column["name"]
|
|
for column in inspector.get_columns(_TABLE_NAME)
|
|
}
|
|
|
|
|
|
def _has_task_id_constraint() -> bool:
|
|
"""判断稳定任务标识唯一约束是否已经存在。"""
|
|
inspector = sa.inspect(op.get_bind())
|
|
return any(
|
|
constraint.get("name") == _TASK_ID_CONSTRAINT
|
|
for constraint in inspector.get_unique_constraints(_TABLE_NAME)
|
|
)
|
|
|
|
|
|
def _has_state_created_index() -> bool:
|
|
"""判断恢复主查询的复合索引是否已经存在。"""
|
|
inspector = sa.inspect(op.get_bind())
|
|
return any(
|
|
index.get("name") == _STATE_CREATED_INDEX
|
|
for index in inspector.get_indexes(_TABLE_NAME)
|
|
)
|
|
|
|
|
|
def _backfill_admission_state() -> None:
|
|
"""为旧登记保守生成稳定身份、接纳状态与更新时间。"""
|
|
pending = sa.table(
|
|
_TABLE_NAME,
|
|
sa.column("id", sa.Integer()),
|
|
sa.column("storage", sa.String()),
|
|
sa.column("src_path", sa.String()),
|
|
sa.column("created_at", sa.String()),
|
|
sa.column("task_id", sa.String()),
|
|
sa.column("state", sa.String()),
|
|
sa.column("updated_at", sa.String()),
|
|
)
|
|
connection = op.get_bind()
|
|
rows = connection.execute(
|
|
sa.select(
|
|
pending.c.id,
|
|
pending.c.storage,
|
|
pending.c.src_path,
|
|
pending.c.created_at,
|
|
pending.c.task_id,
|
|
pending.c.state,
|
|
pending.c.updated_at,
|
|
)
|
|
).mappings().all()
|
|
fallback_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
for row in rows:
|
|
task_id = row["task_id"] or uuid5(
|
|
NAMESPACE_URL,
|
|
(
|
|
"moviepilot:transferpending:"
|
|
f"{row['id']}:{row['storage']}:{row['src_path']}"
|
|
),
|
|
).hex
|
|
values = {
|
|
"task_id": task_id,
|
|
"state": row["state"] or "accepted",
|
|
"updated_at": (
|
|
row["updated_at"] or row["created_at"] or fallback_time
|
|
),
|
|
}
|
|
connection.execute(
|
|
pending.update()
|
|
.where(pending.c.id == row["id"])
|
|
.values(**values)
|
|
)
|
|
|
|
|
|
def upgrade() -> None:
|
|
"""增加准入状态字段,回填旧行并建立稳定任务标识约束。"""
|
|
columns = _column_names()
|
|
if not columns:
|
|
return
|
|
if "task_id" not in columns:
|
|
op.add_column(
|
|
_TABLE_NAME,
|
|
sa.Column("task_id", sa.String(length=64), nullable=True),
|
|
)
|
|
if "state" not in columns:
|
|
op.add_column(
|
|
_TABLE_NAME,
|
|
sa.Column("state", sa.String(length=32), nullable=True),
|
|
)
|
|
if "updated_at" not in columns:
|
|
op.add_column(
|
|
_TABLE_NAME,
|
|
sa.Column("updated_at", sa.String(length=40), nullable=True),
|
|
)
|
|
if "last_error" not in columns:
|
|
op.add_column(
|
|
_TABLE_NAME,
|
|
sa.Column("last_error", sa.Text(), nullable=True),
|
|
)
|
|
|
|
_backfill_admission_state()
|
|
with op.batch_alter_table(_TABLE_NAME) as batch_op:
|
|
batch_op.alter_column(
|
|
"task_id", existing_type=sa.String(length=64), nullable=False
|
|
)
|
|
batch_op.alter_column(
|
|
"state", existing_type=sa.String(length=32), nullable=False
|
|
)
|
|
batch_op.alter_column(
|
|
"updated_at", existing_type=sa.String(length=40), nullable=False
|
|
)
|
|
if not _has_task_id_constraint():
|
|
with op.batch_alter_table(_TABLE_NAME) as batch_op:
|
|
batch_op.create_unique_constraint(
|
|
_TASK_ID_CONSTRAINT,
|
|
["task_id"],
|
|
)
|
|
if not _has_state_created_index():
|
|
op.create_index(
|
|
_STATE_CREATED_INDEX,
|
|
_TABLE_NAME,
|
|
["state", "created_at", "id"],
|
|
unique=False,
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
"""移除准入状态字段并保留旧版可识别的登记事实。"""
|
|
columns = _column_names()
|
|
if not columns or not (_NEW_COLUMNS & columns):
|
|
return
|
|
if _has_state_created_index():
|
|
op.drop_index(_STATE_CREATED_INDEX, table_name=_TABLE_NAME)
|
|
with op.batch_alter_table(_TABLE_NAME) as batch_op:
|
|
if "task_id" in columns and _has_task_id_constraint():
|
|
batch_op.drop_constraint(_TASK_ID_CONSTRAINT, type_="unique")
|
|
for column_name in ("last_error", "updated_at", "state", "task_id"):
|
|
if column_name in columns:
|
|
batch_op.drop_column(column_name)
|