Files
importarr/importarr/state.py
T

149 lines
6.5 KiB
Python

from __future__ import annotations
import sqlite3
from pathlib import Path
from typing import Any
class State:
def __init__(self, path: Path):
self.path = path
self.path.parent.mkdir(parents=True, exist_ok=True)
self.conn = sqlite3.connect(self.path, check_same_thread=False)
self.conn.row_factory = sqlite3.Row
self.migrate()
def migrate(self) -> None:
self.conn.executescript(
"""
create table if not exists manual_batches (
id integer primary key autoincrement,
path text not null unique,
status text not null default 'active',
created_at text not null default current_timestamp,
completed_at text
);
create table if not exists import_history (
id integer primary key autoincrement,
source text not null,
target text not null,
status text not null,
bytes integer not null default 0,
created_at text not null default current_timestamp,
completed_at text,
error text
);
create table if not exists app_state (key text primary key, value text not null);
create table if not exists import_queue_items (
id integer primary key autoincrement,
source_type text not null,
source_id text not null,
source_path text,
name text not null,
state text not null,
reason text,
relative_path text,
size integer not null default 0,
batch_id integer,
job_id text,
first_seen_at text not null default current_timestamp,
updated_at text not null default current_timestamp,
completed_at text,
unique(source_type, source_id)
);
"""
)
self.conn.commit()
def add_manual_batch(self, path: Path) -> dict[str, Any]:
self.conn.execute("insert or ignore into manual_batches(path) values (?)", (str(path),))
self.conn.commit()
return self.get_manual_batch_by_path(path)
def get_manual_batch_by_path(self, path: Path) -> dict[str, Any]:
row = self.conn.execute("select * from manual_batches where path = ?", (str(path),)).fetchone()
return dict(row)
def list_manual_batches(self, active_only: bool = False) -> list[dict[str, Any]]:
sql = "select * from manual_batches"
if active_only:
sql += " where status = 'active'"
sql += " order by created_at desc"
return [dict(row) for row in self.conn.execute(sql)]
def delete_manual_batch(self, batch_id: int) -> None:
self.conn.execute("delete from manual_batches where id = ?", (batch_id,))
self.conn.execute("delete from import_queue_items where batch_id = ? and source_type = 'manual'", (batch_id,))
self.conn.commit()
def complete_manual_batch(self, batch_id: int) -> None:
self.conn.execute("update manual_batches set status='completed', completed_at=current_timestamp where id=?", (batch_id,))
self.conn.commit()
def add_history(self, source: Path, target: Path, status: str, bytes_count: int = 0, error: str | None = None) -> None:
self.conn.execute(
"insert into import_history(source,target,status,bytes,error,completed_at) values (?,?,?,?,?,case when ? in ('imported','failed') then current_timestamp else null end)",
(str(source), str(target), status, bytes_count, error, status),
)
self.conn.commit()
def upsert_queue_item(
self,
*,
source_type: str,
source_id: str,
name: str,
state: str,
source_path: Path | None = None,
reason: str | None = None,
relative_path: str | None = None,
size: int = 0,
batch_id: int | None = None,
job_id: str | None = None,
) -> dict[str, Any]:
self.conn.execute(
"""
insert into import_queue_items(source_type, source_id, source_path, name, state, reason, relative_path, size, batch_id, job_id)
values (?,?,?,?,?,?,?,?,?,?)
on conflict(source_type, source_id) do update set
source_path=excluded.source_path,
name=excluded.name,
state=excluded.state,
reason=excluded.reason,
relative_path=excluded.relative_path,
size=excluded.size,
batch_id=excluded.batch_id,
job_id=excluded.job_id,
updated_at=current_timestamp,
completed_at=case when excluded.state in ('imported','failed','skipped') then current_timestamp else null end
""",
(source_type, source_id, str(source_path) if source_path else None, name, state, reason, relative_path, size, batch_id, job_id),
)
self.conn.commit()
row = self.conn.execute("select * from import_queue_items where source_type = ? and source_id = ?", (source_type, source_id)).fetchone()
return dict(row)
def mark_queue_item(self, source_type: str, source_id: str, state: str, reason: str | None = None) -> None:
self.conn.execute(
"update import_queue_items set state=?, reason=?, updated_at=current_timestamp, completed_at=case when ? in ('imported','failed','skipped') then current_timestamp else completed_at end where source_type=? and source_id=?",
(state, reason, state, source_type, source_id),
)
self.conn.commit()
def remove_missing_manual_items(self, batch_id: int, source_ids: set[str]) -> None:
rows = self.conn.execute("select source_id from import_queue_items where source_type='manual' and batch_id=?", (batch_id,)).fetchall()
for row in rows:
if row["source_id"] not in source_ids:
self.conn.execute("delete from import_queue_items where source_type='manual' and source_id=?", (row["source_id"],))
self.conn.commit()
def list_queue_items(self, active_only: bool = True) -> list[dict[str, Any]]:
sql = "select * from import_queue_items"
if active_only:
sql += " where state not in ('imported','failed','skipped')"
sql += " order by updated_at desc, id desc"
return [dict(row) for row in self.conn.execute(sql)]
def list_history(self, limit: int = 100) -> list[dict[str, Any]]:
return [dict(row) for row in self.conn.execute("select * from import_history order by id desc limit ?", (limit,))]