!111: tests: aircox.management (#114)

!111

Co-authored-by: bkfox <thomas bkfox net>
Reviewed-on: rc/aircox#114
This commit is contained in:
Thomas Kairos
2023-06-30 16:39:55 +02:00
parent faecdf5495
commit f9ad81ddac
27 changed files with 1534 additions and 625 deletions

View File

@ -9,59 +9,13 @@ import logging
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone as tz
from aircox.models import Diffusion, Schedule
from aircox.controllers.diffusions import Diffusions
logger = logging.getLogger("aircox.commands")
class Actions:
date = None
def __init__(self, date):
self.date = date or datetime.date.today()
def update(self):
episodes, diffusions = [], []
for schedule in Schedule.objects.filter(
program__active=True, initial__isnull=True
):
eps, diffs = schedule.diffusions_of_month(self.date)
if eps:
episodes += eps
if diffs:
diffusions += diffs
logger.info(
"[update] %s: %d episodes, %d diffusions and reruns",
str(schedule),
len(eps),
len(diffs),
)
with transaction.atomic():
logger.info(
"[update] save %d episodes and %d diffusions",
len(episodes),
len(diffusions),
)
for episode in episodes:
episode.save()
for diffusion in diffusions:
# force episode id's update
diffusion.episode = diffusion.episode
diffusion.save()
def clean(self):
qs = Diffusion.objects.filter(
type=Diffusion.TYPE_UNCONFIRMED, start__lt=self.date
)
logger.info("[clean] %d diffusions will be removed", qs.count())
qs.delete()
class Command(BaseCommand):
help = __doc__
@ -116,7 +70,7 @@ class Command(BaseCommand):
date += tz.timedelta(days=28)
date = date.replace(day=1)
actions = Actions(date)
actions = Diffusions(date)
if options.get("update"):
actions.update()
if options.get("clean"):

View File

@ -9,7 +9,6 @@ The order of the elements is: {settings.IMPORT_PLAYLIST_CSV_COLS}
If 'minutes' or 'seconds' are given, position will be expressed as timed
position, instead of position in playlist.
"""
import csv
import logging
import os
from argparse import RawTextHelpFormatter
@ -17,109 +16,19 @@ from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand
from aircox.conf import settings
from aircox.models import Sound, Track
from aircox.models import Sound
from aircox.controllers.playlist_import import PlaylistImport
__doc__ = __doc__.format(settings=settings)
__all__ = ("PlaylistImport", "Command")
__all__ = ("Command",)
logger = logging.getLogger("aircox.commands")
class PlaylistImport:
path = None
data = None
tracks = None
track_kwargs = {}
def __init__(self, path=None, **track_kwargs):
self.path = path
self.track_kwargs = track_kwargs
def reset(self):
self.data = None
self.tracks = None
def run(self):
self.read()
if self.track_kwargs.get("sound") is not None:
self.make_playlist()
def read(self):
if not os.path.exists(self.path):
return True
with open(self.path, "r") as file:
logger.info("start reading csv " + self.path)
self.data = list(
csv.DictReader(
(
row
for row in file
if not (
row.startswith("#") or row.startswith("\ufeff#")
)
and row.strip()
),
fieldnames=settings.IMPORT_PLAYLIST_CSV_COLS,
delimiter=settings.IMPORT_PLAYLIST_CSV_DELIMITER,
quotechar=settings.IMPORT_PLAYLIST_CSV_TEXT_QUOTE,
)
)
def make_playlist(self):
"""Make a playlist from the read data, and return it.
If save is true, save it into the database
"""
if self.track_kwargs.get("sound") is None:
logger.error(
"related track's sound is missing. Skip import of "
+ self.path
+ "."
)
return
maps = settings.IMPORT_PLAYLIST_CSV_COLS
tracks = []
logger.info("parse csv file " + self.path)
has_timestamp = ("minutes" or "seconds") in maps
for index, line in enumerate(self.data):
if ("title" or "artist") not in line:
return
try:
timestamp = (
int(line.get("minutes") or 0) * 60
+ int(line.get("seconds") or 0)
if has_timestamp
else None
)
track, created = Track.objects.get_or_create(
title=line.get("title"),
artist=line.get("artist"),
position=index,
**self.track_kwargs
)
track.timestamp = timestamp
track.info = line.get("info")
tags = line.get("tags")
if tags:
track.tags.add(*tags.lower().split(","))
except Exception as err:
logger.warning(
"an error occured for track {index}, it may not "
"have been saved: {err}".format(index=index, err=err)
)
continue
track.save()
tracks.append(track)
self.tracks = tracks
return tracks
class Command(BaseCommand):
help = __doc__

View File

@ -1,4 +1,5 @@
#! /usr/bin/env python3
# TODO: SoundMonitor class
"""Monitor sound files; For each program, check for:
@ -23,20 +24,12 @@ To check quality of files, call the command sound_quality_check using the
parameters given by the setting SOUND_QUALITY. This script requires
Sox (and soxi).
"""
import atexit
import concurrent.futures as futures
import logging
import os
import time
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand
from watchdog.observers import Observer
from aircox.conf import settings
from aircox.management.sound_file import SoundFile
from aircox.management.sound_monitor import MonitorHandler
from aircox.models import Program, Sound
from aircox.controllers.sound_monitor import SoundMonitor
logger = logging.getLogger("aircox.commands")
@ -44,109 +37,6 @@ logger = logging.getLogger("aircox.commands")
class Command(BaseCommand):
help = __doc__
def report(self, program=None, component=None, *content):
if not component:
logger.info(
"%s: %s", str(program), " ".join([str(c) for c in content])
)
else:
logger.info(
"%s, %s: %s",
str(program),
str(component),
" ".join([str(c) for c in content]),
)
def scan(self):
"""For all programs, scan dirs."""
logger.info("scan all programs...")
programs = Program.objects.filter()
dirs = []
for program in programs:
logger.info("#%d %s", program.id, program.title)
self.scan_for_program(
program,
settings.SOUND_ARCHIVES_SUBDIR,
type=Sound.TYPE_ARCHIVE,
)
self.scan_for_program(
program,
settings.SOUND_EXCERPTS_SUBDIR,
type=Sound.TYPE_EXCERPT,
)
dirs.append(os.path.join(program.abspath))
return dirs
def scan_for_program(self, program, subdir, **sound_kwargs):
"""Scan a given directory that is associated to the given program, and
update sounds information."""
logger.info("- %s/", subdir)
if not program.ensure_dir(subdir):
return
subdir = os.path.join(program.abspath, subdir)
sounds = []
# sounds in directory
for path in os.listdir(subdir):
path = os.path.join(subdir, path)
if not path.endswith(settings.SOUND_FILE_EXT):
continue
sound_file = SoundFile(path)
sound_file.sync(program=program, **sound_kwargs)
sounds.append(sound_file.sound.pk)
# sounds in db & unchecked
sounds = Sound.objects.filter(file__startswith=subdir).exclude(
pk__in=sounds
)
self.check_sounds(sounds, program=program)
def check_sounds(self, qs, **sync_kwargs):
"""Only check for the sound existence or update."""
# check files
for sound in qs:
if sound.check_on_file():
SoundFile(sound.file.path).sync(sound=sound, **sync_kwargs)
def monitor(self):
"""Run in monitor mode."""
with futures.ThreadPoolExecutor() as pool:
archives_handler = MonitorHandler(
settings.SOUND_ARCHIVES_SUBDIR,
pool,
type=Sound.TYPE_ARCHIVE,
)
excerpts_handler = MonitorHandler(
settings.SOUND_EXCERPTS_SUBDIR,
pool,
type=Sound.TYPE_EXCERPT,
)
observer = Observer()
observer.schedule(
archives_handler,
settings.PROGRAMS_DIR_ABS,
recursive=True,
)
observer.schedule(
excerpts_handler,
settings.PROGRAMS_DIR_ABS,
recursive=True,
)
observer.start()
def leave():
observer.stop()
observer.join()
atexit.register(leave)
while True:
time.sleep(1)
def add_arguments(self, parser):
parser.formatter_class = RawTextHelpFormatter
parser.add_argument(
@ -172,6 +62,7 @@ class Command(BaseCommand):
)
def handle(self, *args, **options):
SoundMonitor()
if options.get("scan"):
self.scan()
# if options.get('quality_check'):

View File

@ -4,7 +4,7 @@ from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError
from aircox.management.sound_stats import SoundStats, SoxStats
from aircox.controllers.sound_stats import SoundStats, SoxStats
logger = logging.getLogger("aircox.commands")