"""目録とストアの点検(`tiasdata check`.SPEC_03「CLI」).
問題を見つけても止めずに全体を数え,件数だけを返す(error-handling.md「データの問題は止める」の例外).
対応表(`identity.sqlite`)は開かない.
"""
import sqlite3
from dataclasses import dataclass
from pathlib import Path
from tiasdata import catalog, store
@dataclass(frozen=True)
class CheckResult:
"""点検の結果(件数).
Attributes:
catalog_files: 目録のファイルの数(ハッシュを照合した数).
hash_ok: ストアのファイルの中身が SHA-256 と一致した数.
hash_mismatch: ストアのファイルの中身が SHA-256 と一致しない数.
missing_in_store: 目録にあってストアに無いファイルの数.
store_files: ストアの正式な名前のファイルの数.
not_in_catalog: ストアにあって目録に無いファイルの数(拡張子が目録と違うものを含む).
writable: 読み取り専用が外れたストアのファイルの数.
temp_files: 書きかけの一時ファイルの数(取り込みの中断で残ったもの).
unexpected_in_store: ストアの想定外のファイル・フォルダの数.
dangling_annotations: 参照先が無い所見の数.
broken_dataset_members: 凍結した版の構成のうち参照先が欠けている行の数.
"""
catalog_files: int
hash_ok: int
hash_mismatch: int
missing_in_store: int
store_files: int
not_in_catalog: int
writable: int
temp_files: int
unexpected_in_store: int
dangling_annotations: int
broken_dataset_members: int
@property
def problems(self) -> int:
"""問題の合計(0 なら問題なし)."""
return (
self.hash_mismatch
+ self.missing_in_store
+ self.not_in_catalog
+ self.writable
+ self.temp_files
+ self.unexpected_in_store
+ self.dangling_annotations
+ self.broken_dataset_members
)
def run_check(conn: sqlite3.Connection, store_dir: Path) -> CheckResult:
"""目録とストアを点検する.
Args:
conn: 目録への接続(スキーマ最新).
store_dir: ストアのフォルダ.
Returns:
点検の結果(件数).
Raises:
ValueError: ストアのフォルダが無い.
"""
scan = store.scan_store(store_dir)
catalog_files = catalog.list_asset_files(conn)
statuses = [store.verify_stored(store_dir, sha256, ext) for sha256, ext in catalog_files]
registered = set(catalog_files)
return CheckResult(
catalog_files=len(catalog_files),
hash_ok=statuses.count(store.StoredFileStatus.OK),
hash_mismatch=statuses.count(store.StoredFileStatus.MISMATCH),
missing_in_store=statuses.count(store.StoredFileStatus.MISSING),
store_files=len(scan.entries),
not_in_catalog=sum(
1 for entry in scan.entries if (entry.sha256, entry.ext) not in registered
),
writable=sum(1 for entry in scan.entries if entry.writable),
temp_files=scan.temp_files,
unexpected_in_store=scan.unexpected,
dangling_annotations=catalog.count_dangling_annotations(conn),
broken_dataset_members=catalog.count_broken_dataset_members(conn),
)