166 lines
5.3 KiB
Python
166 lines
5.3 KiB
Python
import datetime
|
|
import logging
|
|
|
|
from sqlalchemy import create_engine, event, Column, Integer, String, Boolean, DateTime, Text, text
|
|
from sqlalchemy.orm import declarative_base, sessionmaker
|
|
|
|
Base = declarative_base()
|
|
|
|
logger = logging.getLogger("models")
|
|
|
|
|
|
def utcnow() -> datetime.datetime:
|
|
return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
|
|
|
|
|
|
def iso_utc(dt: datetime.datetime | None) -> str | None:
|
|
if dt is None:
|
|
return None
|
|
if dt.tzinfo is None:
|
|
dt = dt.replace(tzinfo=datetime.timezone.utc)
|
|
return dt.astimezone(datetime.timezone.utc).isoformat()
|
|
|
|
|
|
class Job(Base):
|
|
__tablename__ = "jobs"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
name = Column(String(200), nullable=False)
|
|
stream_url = Column(String(1000), nullable=False)
|
|
stream_type = Column(String(20), default="auto")
|
|
output_format = Column(String(10), default="")
|
|
max_duration = Column(Integer, nullable=True)
|
|
extra_ffmpeg_args = Column(String(500), default="")
|
|
segment_duration = Column(Integer, nullable=True)
|
|
|
|
schedule_enabled = Column(Boolean, default=False)
|
|
schedule_once = Column(Boolean, default=False)
|
|
schedule_date = Column(String(10), default="")
|
|
schedule_days = Column(String(50), default="*")
|
|
schedule_start = Column(String(5), default="")
|
|
schedule_stop = Column(String(5), default="")
|
|
schedule_end_date = Column(String(10), default="")
|
|
|
|
pre_buffer = Column(Integer, default=0)
|
|
post_buffer = Column(Integer, default=0)
|
|
|
|
description = Column(Text, default="")
|
|
|
|
plik_enabled = Column(Boolean, default=False)
|
|
delete_after_upload = Column(Boolean, default=False)
|
|
|
|
ntfy_enabled = Column(Boolean, default=False)
|
|
|
|
station_id = Column(Integer, nullable=True)
|
|
|
|
created_at = Column(DateTime, default=utcnow)
|
|
updated_at = Column(DateTime, default=utcnow, onupdate=utcnow)
|
|
|
|
|
|
class Station(Base):
|
|
__tablename__ = "stations"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
name = Column(String(200), nullable=False)
|
|
stream_url = Column(String(1000), nullable=False)
|
|
stream_type = Column(String(20), default="auto")
|
|
description = Column(Text, default="")
|
|
created_at = Column(DateTime, default=utcnow)
|
|
updated_at = Column(DateTime, default=utcnow, onupdate=utcnow)
|
|
|
|
|
|
class Recording(Base):
|
|
__tablename__ = "recordings"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
job_id = Column(Integer, nullable=True)
|
|
job_name = Column(String(200), nullable=False)
|
|
stream_url = Column(String(1000), nullable=False)
|
|
stream_type = Column(String(20), default="")
|
|
output_dir = Column(String(500), default="")
|
|
output_file = Column(String(500), default="")
|
|
pid = Column(Integer, nullable=True)
|
|
status = Column(String(20), default="running") # running, stopped, completed, error
|
|
started_at = Column(DateTime, default=utcnow)
|
|
stopped_at = Column(DateTime, nullable=True)
|
|
scheduled_stop = Column(DateTime, nullable=True)
|
|
segment_mode = Column(Boolean, default=False)
|
|
is_scheduled = Column(Boolean, default=False)
|
|
error_message = Column(Text, nullable=True)
|
|
file_size = Column(Integer, nullable=True)
|
|
plik_url = Column(String(1000), nullable=True)
|
|
|
|
|
|
class LogEntry(Base):
|
|
__tablename__ = "logs"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
timestamp = Column(DateTime, default=utcnow)
|
|
level = Column(String(10), default="INFO")
|
|
message = Column(Text, nullable=False)
|
|
job_name = Column(String(200), nullable=True)
|
|
|
|
|
|
_engine = None
|
|
_SessionLocal = None
|
|
|
|
|
|
def init_db(db_path: str):
|
|
global _engine, _SessionLocal
|
|
_engine = create_engine(
|
|
f"sqlite:///{db_path}",
|
|
connect_args={"check_same_thread": False, "timeout": 30},
|
|
)
|
|
|
|
@event.listens_for(_engine, "connect")
|
|
def _set_sqlite_pragmas(dbapi_conn, _record):
|
|
cur = dbapi_conn.cursor()
|
|
try:
|
|
cur.execute("PRAGMA journal_mode=WAL")
|
|
cur.execute("PRAGMA busy_timeout=5000")
|
|
finally:
|
|
cur.close()
|
|
|
|
Base.metadata.create_all(_engine)
|
|
_SessionLocal = sessionmaker(bind=_engine)
|
|
_migrate(_engine)
|
|
|
|
|
|
def _migrate(engine):
|
|
with engine.connect() as conn:
|
|
for stmt in [
|
|
"ALTER TABLE recordings ADD COLUMN file_size INTEGER",
|
|
"ALTER TABLE jobs ADD COLUMN station_id INTEGER",
|
|
"ALTER TABLE jobs ADD COLUMN pre_buffer INTEGER DEFAULT 0",
|
|
"ALTER TABLE jobs ADD COLUMN post_buffer INTEGER DEFAULT 0",
|
|
"ALTER TABLE recordings ADD COLUMN plik_url VARCHAR(1000)",
|
|
]:
|
|
try:
|
|
conn.execute(text(stmt))
|
|
conn.commit()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def get_session():
|
|
return _SessionLocal()
|
|
|
|
|
|
def prune_logs(days: int) -> int:
|
|
if days <= 0:
|
|
return 0
|
|
cutoff = utcnow() - datetime.timedelta(days=days)
|
|
session = get_session()
|
|
try:
|
|
count = session.query(LogEntry).filter(LogEntry.timestamp < cutoff).delete(synchronize_session=False)
|
|
session.commit()
|
|
if count:
|
|
logger.info("%d alte Log-Einträge gelöscht (älter als %d Tage)", count, days)
|
|
return count
|
|
except Exception as e:
|
|
session.rollback()
|
|
logger.error("Log-Bereinigung fehlgeschlagen: %s", e)
|
|
return 0
|
|
finally:
|
|
session.close()
|