from dataclasses import dataclass from pathlib import Path from typing import Protocol from ...dominio import Clipe from ...scanner.modelos import ErroDeAnalise from ..coordenacao import ContextoDeAnalise @dataclass(frozen=True) class ArquivoDescoberto: """Resultado da validação do arquivo associado a um clipe.""" caminho: Path existe: bool acessivel: bool tipo_de_midia: str | None = None tamanho_em_bytes: int | None = None @property def offline(self) -> bool: return not self.existe or not self.acessivel class LeitorDeArquivos(Protocol): def descobrir(self, caminho: str | Path) -> ArquivoDescoberto: ... class LeitorLocalDeArquivos: """Adapter que resolve e valida caminhos no sistema de arquivos local.""" def descobrir(self, caminho: str | Path) -> ArquivoDescoberto: caminho_resolvido = Path(caminho).expanduser().resolve(strict=False) existe = caminho_resolvido.is_file() acessivel = existe tamanho = None if existe: try: tamanho = caminho_resolvido.stat().st_size with caminho_resolvido.open("rb"): pass except (OSError, PermissionError): acessivel = False return ArquivoDescoberto( caminho=caminho_resolvido, existe=existe, acessivel=acessivel, tipo_de_midia=caminho_resolvido.suffix.lower().lstrip(".") or None, tamanho_em_bytes=tamanho, ) class DescobertaDeArquivos: """Relaciona os clipes da timeline aos arquivos físicos de origem.""" nome = "descoberta_de_arquivos" def __init__(self, leitor: LeitorDeArquivos | None = None) -> None: self.leitor = leitor or LeitorLocalDeArquivos() def executar(self, contexto: ContextoDeAnalise) -> ContextoDeAnalise: if contexto.timeline is None: return contexto vistos: dict[Path, str] = {} for clipe in self._clipes(contexto): if not clipe.arquivo: clipe.offline = True self._registrar_erro(contexto, "arquivo_ausente", "Clipe sem sourceFile.", clipe) continue try: descoberto = self.leitor.descobrir(clipe.arquivo) except (OSError, ValueError, TypeError) as exc: clipe.offline = True self._registrar_erro(contexto, "arquivo_inacessivel", str(exc), clipe) continue clipe.arquivo = str(descoberto.caminho) clipe.offline = descoberto.offline contexto.arquivos[clipe.identificador] = descoberto if descoberto.offline: self._registrar_erro(contexto, "arquivo_inacessivel", "Arquivo não encontrado ou sem permissão de leitura.", clipe) elif descoberto.caminho in vistos: contexto.avisos.append( f"Arquivo duplicado entre os clipes {vistos[descoberto.caminho]} e {clipe.identificador}." ) else: vistos[descoberto.caminho] = clipe.identificador return contexto @staticmethod def _clipes(contexto: ContextoDeAnalise) -> list[Clipe]: return [clipe for faixa in contexto.timeline.faixas for clipe in faixa.clipes] @staticmethod def _registrar_erro(contexto: ContextoDeAnalise, codigo: str, mensagem: str, clipe: Clipe) -> None: contexto.erros.append(str(ErroDeAnalise(codigo, mensagem, f"clipe[{clipe.identificador}].sourceFile")))