diff --git a/.dockerignore b/.dockerignore index 51bd219f82a5bc8ff8c5273fceb352697c26aad0..a3faf99806bbae64e341328e935bd35596691e5c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -77,7 +77,7 @@ python .noseids # Configurations -config/engine.docker.ini +config/engine.docker.yaml env.list # Socket diff --git a/.gitignore b/.gitignore index 2bb85a609a5dacda35c3c8d062a5448cce2bdcf8..6f08aff2c316636c01ddb65e308b6b6b185e4ea5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,10 +13,10 @@ docker.env /.cache /.build /.bash_history -/config/engine.ini +/config/engine.yaml /config/systemd/dev/ /audio -/config/docker.engine.ini -/config/engine.docker.ini +/config/docker.engine.yaml +/config/engine.docker.yaml .coverage coverage.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2333157867e10114dfb9307a8ec97df98aa2ffb4..4c9a67ee42db4b053470f09ec488ebd270503740 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ run_test_cases: stage: test before_script: - *install_requirements - - cp config/sample.engine.ini config/engine.ini + - cp config/sample.engine.yaml config/engine.yaml script: - make coverage coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3ac07b29466c4518e7b94237f4806c628f9b195..36d930d3b8242c05ccbfca04df408c1bf71bf75a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.7.0 hooks: - id: black - repo: https://github.com/PyCQA/isort @@ -8,12 +8,12 @@ repos: hooks: - id: isort - repo: https://github.com/codespell-project/codespell - rev: "v2.2.2" + rev: "v2.2.5" hooks: - id: codespell args: [""] - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 args: ["--config=.flake8"] diff --git a/Dockerfile b/Dockerfile index 79d396c86140664a418ac29ea71400873db2db0e..4aea77b5d2b5d355c4d11f62cfc6bfd557220119 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,7 @@ WORKDIR /srv # Init Application COPY ./src/aura_engine /srv/src/aura_engine -COPY config/sample.engine.docker.ini /srv/config/engine.ini +COPY config/sample.engine.docker.yaml /srv/config/engine.yaml RUN poetry install --no-interaction --no-ansi # Update Permissions diff --git a/Makefile b/Makefile index cdc610c841ad1dbc7cdc7f2a79e9426b1f3b46f1..95eaa86bdb7b984da4e20db8d939cb259009a74a 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ help:: TIMEZONE := "Europe/Vienna" AURA_ENGINE_CORE_SOCKET := "aura_engine_socket" -AURA_ENGINE_CONFIG := ${CURDIR}/config/engine.docker.ini +AURA_ENGINE_CONFIG := ${CURDIR}/config/engine.docker.yaml AURA_AUDIO_STORE_SOURCE := ${CURDIR}/../engine-core/audio/source AURA_AUDIO_STORE_PLAYLIST := ${CURDIR}/../engine-core/audio/playlist AURA_LOGS := ${CURDIR}/logs @@ -35,7 +35,7 @@ DOCKER_RUN = @docker run \ --mount type=tmpfs,destination=/tmp \ --env-file docker.env \ -v aura_engine_socket:"/srv/socket" \ - -v "$(AURA_ENGINE_CONFIG)":"/etc/aura/engine.ini":ro \ + -v "$(AURA_ENGINE_CONFIG)":"/etc/aura/engine.yaml":ro \ -v "$(AURA_AUDIO_STORE_SOURCE)":"/var/audio/source":ro \ -v "$(AURA_AUDIO_STORE_PLAYLIST)":"/var/audio/playlist":ro \ -v "$(AURA_LOGS)":"/srv/logs" \ @@ -47,14 +47,14 @@ DOCKER_RUN = @docker run \ init.app:: pyproject.toml poetry install - cp -n config/sample.engine.ini config/engine.ini + cp -n config/sample.engine.yaml config/engine.yaml mkdir -p .cache init.dev:: pyproject.toml poetry install --with dev poetry run pre-commit autoupdate poetry run pre-commit install - cp -n config/sample.engine.ini config/engine.ini + cp -n config/sample.engine.yaml config/engine.yaml mkdir -p .cache api:: diff --git a/README.md b/README.md index 4f85caf5b343557e38c6e852aa35f62e513a0252..cb426757fca729ea412d529ee2b8d4f5809ff4da 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Install dependencies and prepare config file: make init.app ``` -This also creates a default configuration file at `config/engine.ini`. +This also creates a default configuration file at `config/engine.yaml`. For development install with: @@ -98,15 +98,15 @@ For development install with: make init.dev ``` -Note, if some configuration exists under `/etc/aura/engine.ini` the configuration by default is drawn from there. This overrides any configuration located in the local configuration file. +Note, if some configuration exists under `/etc/aura/engine.yaml` the configuration by default is drawn from there. This overrides any configuration located in the local configuration file. ## Configuration -Edit the configuration file `config/engine.ini`. Verify or change at least these config options: +Edit the configuration file `config/engine.yaml`. Verify or change at least these config options: -```ini +```yaml # The secret which is used to authenticate against Tank -api_tank_secret="aura-engine-secret" +api_tank_secret: aura-engine-secret ``` ## Running Engine diff --git a/config/sample.engine.docker.ini b/config/sample.engine.docker.ini deleted file mode 100644 index 221624aa3c7bb5ee8963ecfd0074ea69732d3cf4..0000000000000000000000000000000000000000 --- a/config/sample.engine.docker.ini +++ /dev/null @@ -1,82 +0,0 @@ -############################################## -# Engine Configuration # -############################################## - -[general] -# Path to the engine-core socket directory relative to the engine project root -socket_dir="/srv/socket" -# Directory to store temporary data -cache_dir="/tmp" -# Directory where the log file resides -log_dir="logs" -# Possible values: debug, info, warning, error, critical -log_level="${AURA_ENGINE_LOG_LEVEL}" -# Details for the Station Fallback -fallback_show_name="${AURA_ENGINE_FALLBACK_SHOW_NAME}" -fallback_show_id="${AURA_ENGINE_FALLBACK_SHOW_ID}" - -[monitoring] -# Seconds how often the vitality of Engine Core should be checked (default=1) -heartbeat_frequency="${AURA_ENGINE_HEARTBEAT_FREQUENCY}" -# Host where heartbeat is sent to (disabled if empty string) -heartbeat_server="${AURA_ENGINE_HEARTBEAT_SERVER}" -# Some UDP port -heartbeat_port="${AURA_ENGINE_HEARTBEAT_SERVER_PORT}" - -[api] -## STEERING ## -# The URL to get the health status -api_steering_status="${AURA_STEERING_BASE_URL}api/v1/" -# The URL to get the Calendar via Steering -api_steering_calendar="${AURA_STEERING_BASE_URL}api/v1/playout" - -## TANK ## -# The session name which is used to authenticate against Tank -api_tank_session="${AURA_TANK_ENGINE_USER}" -# The secret which is used to authenticate against Tank -api_tank_secret="${AURA_TANK_ENGINE_PASSWORD}" -# The URL to get the health status -api_tank_status="${AURA_TANK_BASE_URL}healthz" -# The URL to get playlist details via Tank -api_tank_playlist="${AURA_TANK_BASE_URL}api/v1/playlists/${ID}" - -## ENGINE-API ## -# Engine ID (1 or 2) -api_engine_number=1 -# Engine API availability check -api_engine_status="${AURA_ENGINE_API_BASE_URL}api/v1/ui/" -# Engine API endpoint to store playlogs -api_engine_store_playlog="${AURA_ENGINE_API_BASE_URL}api/v1/playlog" -# Engine API endpoint to store clock information -api_engine_store_clock="${AURA_ENGINE_API_BASE_URL}api/v1/clock" -# Engine API endpoint to store health information -api_engine_store_health="${AURA_ENGINE_API_BASE_URL}api/v1/source/health/${ENGINE_NUMBER}" - -[scheduler] -# Base path as seen by "engine-core", not accessed by "engine"; this is required to construct the absolute audio file path (check "Audio Store" in the docs) -# Either provide an absolute base path or a relative one starting in the `engine` directory. In case of `engine-core` running in docker use `/var/audio/source` -audio_source_folder="/var/audio/source" -audio_source_extension=".flac" -# Folder holding M3U Playlists to be scheduled in form of Engine Playlists (similar as audio source folder above) -audio_playlist_folder="/var/audio/playlist" -# Offset in seconds how long it takes for Liquidsoap to actually execute a scheduler command; Crucial to keep things in sync -engine_latency_offset="${AURA_ENGINE_LATENCY_OFFSET}" -# How often should the calendar be fetched in seconds. This determines the time of the last changes applied, before a specific show is aired -fetching_frequency=30 -# The scheduling window defines when the items of each timeslot are queued for play-out. The windows start at (timeslot.start - window_start) seconds -# and ends at (timeslot.end - window.end) seconds. Its also worth noting, that timeslots can only be deleted before the start of the window. -scheduling_window_start=60 -scheduling_window_end=60 -# How many seconds before the actual schedule time the item should be pre-loaded. Note to provide enough timeout for -# contents which take longer to load (big files, bad connectivity to streams etc.). If the planned start time is in -# the past the offset is ignored and the item is played as soon as possible -preload_offset=15 -# Sometimes it might take longer to get a stream connected. Here you can define a viable length. -# But note, that this may affect the preloading time (see `preload_offset`), hence affecting the -# overall playout, its delays and possible fallbacks -input_stream_retry_delay=1 -input_stream_max_retries=10 -input_stream_buffer=3.0 -# Fade duration when selecting another mixer input (seconds) -fade_in_time="${AURA_ENGINE_FADE_IN_TIME}" -fade_out_time="${AURA_ENGINE_FADE_OUT_TIME}" diff --git a/config/sample.engine.docker.yaml b/config/sample.engine.docker.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1ab33b0d66a71287831b2bce52422769e970bc0c --- /dev/null +++ b/config/sample.engine.docker.yaml @@ -0,0 +1,126 @@ +############################################## +# Engine Configuration # +############################################## + +general: + # Path to the engine-core socket directory relative to the engine project root + socket_dir: /srv/socket + # Directory to store temporary data + cache_dir: /tmp + + # Details for the Station Fallback + fallback_show_name: ${AURA_ENGINE_FALLBACK_SHOW_NAME} + fallback_show_id: ${AURA_ENGINE_FALLBACK_SHOW_ID} + +log: + # Directory where the log file resides + directory: logs + # Possible values: debug, info, warning, error, critical + level: ${AURA_ENGINE_LOG_LEVEL} + +monitoring: + mail: + # Mail server credentials for sending email notifications (Admin and Programme Coordination) + host: mail.your-radio.org + port: 587 + user: aura@subsquare.at + pwd: ---SECRET--PASSWORD--- + + coordinator: + # Set to "true" if you want to notify programme-coordinators about about fallback situations, otherwise "false" + enabled: false + # If you want to address multiple programme-coordinators separate their emails by space + mail: programme-coordinator@your-radio.org + + admin: + # Set to "true" if you want to notify admins about incidents, otherwise "false" + enabled: false + # If you want to address multiple administrators separate their emails by space + mail: david@subsquare.at + + # The FROM email address used when sending + from: monitoring@aura.engine + # A subject prefix allows applying filter rules in your mail client + subject_prefix: "[AURA Engine]" # default: [AURA Engine] + + heartbeat: + # Seconds how often the vitality of Engine Core should be checked (default: 1) + frequency: ${AURA_ENGINE_HEARTBEAT_FREQUENCY} + # Host where heartbeat is sent to (disabled if empty string) + host: ${AURA_ENGINE_HEARTBEAT_SERVER} + # Some UDP port + port: ${AURA_ENGINE_HEARTBEAT_SERVER_PORT} + +api: + ## STEERING ## + steering: + # The URL to get the health status + status: ${AURA_STEERING_BASE_URL}api/v1/ + # The URL to get the Calendar via Steering + calendar: ${AURA_STEERING_BASE_URL}api/v1/playout + + ## TANK ## + tank: + # The session name which is used to authenticate against Tank + session: ${AURA_TANK_ENGINE_USER} + # The secret which is used to authenticate against Tank + secret: ${AURA_TANK_ENGINE_PASSWORD} + # The URL to get the health status + status: ${AURA_TANK_BASE_URL}healthz + # The URL to get playlist details via Tank + playlist: ${AURA_TANK_BASE_URL}api/v1/playlists/${ID} + + ## ENGINE-API ## + engine: + # Engine ID (1 or 2) + number: 1 + # Engine API availability check + status: ${AURA_ENGINE_API_BASE_URL}api/v1/ui/ + # Engine API endpoint to store playlogs + store_playlog: ${AURA_ENGINE_API_BASE_URL}api/v1/playlog + # Engine API endpoint to store clock information + store_clock: ${AURA_ENGINE_API_BASE_URL}api/v1/clock + # Engine API endpoint to store health information + store_health: ${AURA_ENGINE_API_BASE_URL}api/v1/source/health/${ENGINE_NUMBER} +scheduler: + # Database settings: Use 'postgresql', 'sqlite' or 'mysql'. In case of SQLite the "db_name" is the name of the file. + db: + type: postgresql + name: ${AURA_ENGINE_DB_NAME} + user: ${AURA_ENGINE_DB_USER} + pwd: ${AURA_ENGINE_DB_PASS} + host: ${AURA_ENGINE_DB_HOST} + charset: utf8 + + # Base path as seen by "engine-core", not accessed by "engine"; this is required to construct the absolute audio file path (check "Audio Store" in the docs) + # Either provide an absolute base path or a relative one starting in the `engine` directory. In case of `engine-core` running in docker use `/var/audio/source` + audio: + source_folder: /var/audio/source + source_extension: .flac + # Folder holding M3U Playlists to be scheduled in form of Engine Playlists (similar as audio source folder above) + playlist_folder: /var/audio/playlist + # Offset in seconds how long it takes for Liquidsoap to actually execute a scheduler command; Crucial to keep things in sync + engine_latency_offset: ${AURA_ENGINE_LATENCY_OFFSET} + + # How often should the calendar be fetched in seconds. This determines the time of the last changes applied, before a specific show is aired + fetching_frequency: 30 + # The scheduling window defines when the entries of each timeslot are queued for play-out. The windows start at (timeslot.start - window_start) seconds + # and ends at (timeslot.end - window.end) seconds. Its also worth noting, that timeslots can only be deleted before the start of the window. + scheduling_window_start: 60 + scheduling_window_end: 60 + # How many seconds before the actual schedule time the entry should be pre-loaded. Note to provide enough timeout for + # contents which take longer to load (big files, bad connectivity to streams etc.). If the planned start time is in + # the past the offset is ignored and the entry is played as soon as possible + preload_offset: 15 + + # Sometimes it might take longer to get a stream connected. Here you can define a viable length. + # But note, that this may affect the preloading time (see `preload_offset`), hence affecting the + # overall playout, its delays and possible fallbacks + input_stream: + retry_delay: 1 + max_retries: 10 + buffer: 3.0 + + # Fade duration when selecting another mixer input (seconds) + fade_in_time: ${AURA_ENGINE_FADE_IN_TIME} + fade_out_time: ${AURA_ENGINE_FADE_OUT_TIME} diff --git a/config/sample.engine.ini b/config/sample.engine.ini deleted file mode 100644 index acf1cdcc6021b96fb8d76f522b364357b3ff2002..0000000000000000000000000000000000000000 --- a/config/sample.engine.ini +++ /dev/null @@ -1,82 +0,0 @@ -############################################## -# Engine Configuration # -############################################## - -[general] -# Path to the engine-core socket directory relative to the engine project root -socket_dir="../engine-core/socket" -# Directory to store temporary data -cache_dir="./.cache" -# Directory where the log file resides -log_dir="logs" -# Possible values: debug, info, warning, error, critical -log_level="info" -# Details for the Station Fallback -fallback_show_name="Random Music" -fallback_show_id="-1" - -[monitoring] -# Seconds how often the vitality of Engine Core should be checked (default=1) -heartbeat_frequency=1 -# Host where heartbeat is sent to (disabled if empty string) -heartbeat_server="" -# Some UDP port -heartbeat_port=43334 - -[api] -## STEERING ## -# The URL to get the health status -api_steering_status="http://localhost:8000/api/v1/" -# The URL to get the Calendar via Steering -api_steering_calendar="http://localhost:8000/api/v1/playout" - -## TANK ## -# The session name which is used to authenticate against Tank -api_tank_session="engine" -# The secret which is used to authenticate against Tank -api_tank_secret="rather-secret" -# The URL to get the health status -api_tank_status="http://localhost:8040/healthz" -# The URL to get playlist details via Tank -api_tank_playlist="http://localhost:8040/api/v1/playlists/${ID}" - -## ENGINE-API ## -# Engine ID (1 or 2) -api_engine_number=1 -# Engine API availability check -api_engine_status="http://localhost:8008/api/v1/ui/" -# Engine API endpoint to store playlogs -api_engine_store_playlog="http://localhost:8008/api/v1/playlog" -# Engine API endpoint to store clock information -api_engine_store_clock="http://localhost:8008/api/v1/clock" -# Engine API endpoint to store health information -api_engine_store_health="http://localhost:8008/api/v1/source/health/${ENGINE_NUMBER}" - -[scheduler] -# Base path as seen by "engine-core", not accessed by "engine"; this is required to construct the absolute audio file path (check "Audio Store" in the docs) -# Either provide an absolute base path or a relative one starting in the `engine` directory. In case of `engine-core` running in docker use `/var/audio/source` -audio_source_folder="../engine-core/audio/source" -audio_source_extension=".flac" -# Folder holding M3U Playlists to be scheduled in form of Engine Playlists (similar as audio source folder above) -audio_playlist_folder="../engine-core/audio/playlist" -# Offset in seconds how long it takes for Liquidsoap to actually execute a scheduler command; Crucial to keep things in sync -engine_latency_offset=0.5 -# How often should the calendar be fetched in seconds. This determines the time of the last changes applied, before a specific show is aired -fetching_frequency=30 -# The scheduling window defines when the items of each timeslot are queued for play-out. The windows start at (timeslot.start - window_start) seconds -# and ends at (timeslot.end - window.end) seconds. Its also worth noting, that timeslots can only be deleted before the start of the window. -scheduling_window_start=60 -scheduling_window_end=60 -# How many seconds before the actual schedule time the item should be pre-loaded. Note to provide enough timeout for -# contents which take longer to load (big files, bad connectivity to streams etc.). If the planned start time is in -# the past the offset is ignored and the item is played as soon as possible -preload_offset=15 -# Sometimes it might take longer to get a stream connected. Here you can define a viable length. -# But note, that this may affect the preloading time (see `preload_offset`), hence affecting the -# overall playout, its delays and possible fallbacks -input_stream_retry_delay=1 -input_stream_max_retries=10 -input_stream_buffer=3.0 -# Fade duration when selecting another mixer input (seconds) -fade_in_time="1.5" -fade_out_time="1.5" diff --git a/config/sample.engine.yaml b/config/sample.engine.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3a6457520ebd1fefc1be94c8806baf086e7f8c21 --- /dev/null +++ b/config/sample.engine.yaml @@ -0,0 +1,127 @@ +############################################## +# Engine Configuration # +############################################## + +general: + # Path to the engine-core socket directory relative to the engine project root + socket_dir: ../engine-core/socket + # Directory to store temporary data + cache_dir: ./.cache + + # Details for the Station Fallback + fallback_show_name: Random Music + fallback_show_id: -1 + +log: + # Directory where the log file resides + directory: logs + # Possible values: debug, info, warning, error, critical + level: info + +monitoring: + mail: + # Mail server credentials for sending email notifications (Admin and Programme Coordination) + host: mail.your-radio.org + port: 587 + user: aura@subsquare.at + pwd: ---SECRET--PASSWORD--- + + coordinator: + # Set to "true" if you want to notify programme-coordinators about about fallback situations, otherwise "false" + enabled: false + # If you want to address multiple programme-coordinators separate their emails by space + mail: programme-coordinator@your-radio.org + + admin: + # Set to "true" if you want to notify admins about incidents, otherwise "false" + enabled: false + # If you want to address multiple administrators separate their emails by space + mail: david@subsquare.at + + # The FROM email address used when sending + from: monitoring@aura.engine + # A subject prefix allows applying filter rules in your mail client + subject_prefix: "[AURA Engine]" # default: [AURA Engine] + + heartbeat: + # Seconds how often the vitality of Engine Core should be checked (default: 1) + frequency: 1 + # Host where heartbeat is sent to (disabled if empty string) + host: "" + # Some UDP port + port: 43334 + +api: + ## STEERING ## + steering: + # The URL to get the health status + status: http://localhost:8000/api/v1/ + # The URL to get the Calendar via Steering + calendar: http://localhost:8000/api/v1/playout + + ## TANK ## + tank: + # The session name which is used to authenticate against Tank + session: engine + # The secret which is used to authenticate against Tank + secret: rather-secret + # The URL to get the health status + status: http://localhost:8040/healthz + # The URL to get playlist details via Tank + playlist: http://localhost:8040/api/v1/playlists/${ID} + + ## ENGINE-API ## + engine: + # Engine ID (1 or 2) + number: 1 + # Engine API availability check + status: http://localhost:8008/api/v1/ui/ + # Engine API endpoint to store playlogs + store_playlog: http://localhost:8008/api/v1/playlog + # Engine API endpoint to store clock information + store_clock: http://localhost:8008/api/v1/clock + # Engine API endpoint to store health information + store_health: http://localhost:8008/api/v1/source/health/${ENGINE_NUMBER} + +scheduler: + # Database settings: Use 'postgresql', 'sqlite' or 'mysql'. In case of SQLite the "db_name" is the name of the file. + db: + type: postgresql + name: aura_engine + user: aura_engine + pwd: "1234" + host: localhost + charset: utf8 + + # Base path as seen by "engine-core", not accessed by "engine"; this is required to construct the absolute audio file path (check "Audio Store" in the docs) + # Either provide an absolute base path or a relative one starting in the `engine` directory. In case of `engine-core` running in docker use `/var/audio/source` + audio: + source_folder: ../engine-core/audio/source + source_extension: .flac + # Folder holding M3U Playlists to be scheduled in form of Engine Playlists (similar as audio source folder above) + playlist_folder: ../engine-core/audio/playlist + # Offset in seconds how long it takes for Liquidsoap to actually execute a scheduler command; Crucial to keep things in sync + engine_latency_offset: 0.5 + + # How often should the calendar be fetched in seconds. This determines the time of the last changes applied, before a specific show is aired + fetching_frequency: 30 + # The scheduling window defines when the entries of each timeslot are queued for play-out. The windows start at (timeslot.start - window_start) seconds + # and ends at (timeslot.end - window.end) seconds. Its also worth noting, that timeslots can only be deleted before the start of the window. + scheduling_window_start: 60 + scheduling_window_end: 60 + # How many seconds before the actual schedule time the entry should be pre-loaded. Note to provide enough timeout for + # contents which take longer to load (big files, bad connectivity to streams etc.). If the planned start time is in + # the past the offset is ignored and the entry is played as soon as possible + preload_offset: 15 + + # Sometimes it might take longer to get a stream connected. Here you can define a viable length. + # But note, that this may affect the preloading time (see `preload_offset`), hence affecting the + # overall playout, its delays and possible fallbacks + input_stream: + retry_delay: 1 + max_retries: 10 + buffer: 3.0 + + # Fade duration when selecting another mixer input (seconds) + fade_in_time: 1.5 + fade_out_time: 1.5 diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 2ee255ffe0f03d003d3115eeaac8c9489767406f..dd6d64e775d56903d4442a1b721f6a1c6716b891 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -118,12 +118,12 @@ point in time and the involved phase before: The start and the end of the window is defined by the start of the timeslot minus a configured amount of seconds (see `scheduling_window_start` and `scheduling_window_end` - in `engine.ini`). The actual start of the window is calculated by (timeslot start - window start) + in `engine.yaml`). The actual start of the window is calculated by (timeslot start - window start) and the end by (timeslot end - window end) During the scheduling window, the external API Endpoints are pulled continuously, to check for updated timeslots and related playlists. Also, any changes to playlists and - its items are respected within that window (see `fetching_frequency` in `engine.ini`). + its items are respected within that window (see `fetching_frequency` in `engine.yaml`). > Important: It is vital that the the scheduling window is wider than the fetching frequency. > Otherwise one fetch might never hit a scheduling window, hence not being able to schedule stuff. @@ -136,7 +136,7 @@ point in time and the involved phase before: - **Queuing and Pre-Loading**: Before any playlist items of the timeslot can be turned into sound, they need to be queued and pre-loaded. Ideally the pre-loading happens somewhat before the scheduled play-out time to avoid any delays in timing. Set the maximum time reserved for - pre-loading in your configuration (compare `preload_offset`in `engine.ini`). + pre-loading in your configuration (compare `preload_offset`in `engine.yaml`). If there is not enough time to reserve the given amount of time for preloading (i.e. some playlist item should have started in the past already) the offset is ignored and the item is played as soon as possible. diff --git a/poetry.lock b/poetry.lock index 58885a8624e7597a87c933068f400c9c15546b10..003f511549c0d972e7123d71ac1b2948d82c544e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "anyio" @@ -293,6 +293,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "confuse" +version = "2.0.1" +description = "Painless YAML configuration." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "confuse-2.0.1-py3-none-any.whl", hash = "sha256:9b9e5bbc70e2cb9b318bcab14d917ec88e21bf1b724365e3815eb16e37aabd2a"}, + {file = "confuse-2.0.1.tar.gz", hash = "sha256:7379a2ad49aaa862b79600cc070260c1b7974d349f4fa5e01f9afa6c4dd0611f"}, +] + +[package.dependencies] +pyyaml = "*" + [[package]] name = "coverage" version = "7.3.2" diff --git a/pyproject.toml b/pyproject.toml index 71eaeaf40c97d4743312b651a75dc705db8148f2..75d63311fe2c63be7331a37fc3db35b6225d97ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ http-parser = "^0.9.0" openapi-python-client = "^0.14.0" flake8-bandit = "^4.1.1" tomli = "^2.0.1" +confuse = "^2.0.1" jsonpickle = "^3.0.2" [tool.poetry.group.dev.dependencies] diff --git a/scripts/initialize-systemd.sh b/scripts/initialize-systemd.sh index 7fce56f158c1a0f4dce10b6500a1914a816a7e0c..3a41a810eabcacb5b2f3cf9d4afb92769a1f3ad5 100755 --- a/scripts/initialize-systemd.sh +++ b/scripts/initialize-systemd.sh @@ -6,7 +6,7 @@ # You'll need sudo/root privileges. # -echo "Set Ownership of '/opt/aura/engine', '/var/log/aura/' and '/etc/aura/engine.ini' to Engine User" +echo "Set Ownership of '/opt/aura/engine', '/var/log/aura/' and '/etc/aura/engine.yaml' to Engine User" chown -R engineuser:engineuser /opt/aura chown -R engineuser:engineuser /etc/aura chown -R engineuser:engineuser /var/log/aura diff --git a/src/aura_engine/app.py b/src/aura_engine/app.py index d8aa11a2f1c1f97b8f9290c6739466bbd9b75c2a..c3d4671f13afb96a06208f354851783e99ab8d2d 100755 --- a/src/aura_engine/app.py +++ b/src/aura_engine/app.py @@ -31,7 +31,7 @@ from aura_engine.base.config import AuraConfig from aura_engine.base.logger import AuraLogger from aura_engine.engine import Engine -config = AuraConfig() +config = AuraConfig.instance class EngineRunner: @@ -47,7 +47,7 @@ class EngineRunner: """ Constructor. """ - self.config = config + self.config = config.config AuraLogger(self.config) self.logger = logging.getLogger("engine") self.engine = Engine() diff --git a/src/aura_engine/base/config.py b/src/aura_engine/base/config.py index d358e4295d19c109c7d4a686203c6f7e99f2f26e..385a7b18cb479cda89fa1b0c21f2f434bf6cec0c 100644 --- a/src/aura_engine/base/config.py +++ b/src/aura_engine/base/config.py @@ -24,124 +24,160 @@ Dealing with configuration data. import logging import os import os.path +import re import sys -from configparser import ConfigParser from pathlib import Path +import confuse +import yaml + +template = { + "general": { + "socket_dir": str, + "cache_dir": str, + "fallback_show_name": str, + "fallback_show_id": int, + }, + "log": { + "directory": str, + "level": confuse.OneOf(["debug", "info", "warning", "error", "critical"]), + }, + "monitoring": { + "mail": { + "host": str, + "port": int, + "user": str, + "pwd": str, + "from": str, + "subject_prefix": str, + "coordinator": {"enabled": bool, "mail": str}, + "admin": {"enabled": bool, "mail": str}, + }, + "heartbeat": {"host": str, "port": int, "frequency": int}, + }, + "api": { + "steering": {"status": str, "calendar": str}, + "tank": {"session": str, "secret": str, "status": str, "playlist": str}, + "engine": { + "number": int, + "status": str, + "store_playlog": str, + "store_clock": str, + "store_health": str, + }, + }, + "scheduler": { + "db": { + "type": confuse.OneOf(["postgresql", "mysql", "sqlite"]), + "name": str, + "user": str, + "pwd": str, + "host": str, + "charset": str, + }, + "audio": { + "source_folder": str, + "source_extension": str, + "playlist_folder": str, + "engine_latency_offset": float, + }, + "fetching_frequency": int, + "scheduling_window_start": int, + "scheduling_window_end": int, + "preload_offset": int, + "input_stream": {"retry_delay": int, "max_retries": int, "buffer": float}, + "fade_in_time": float, + "fade_out_time": float, + }, +} + class AuraConfig: """ - Holds the Engine Configuration as in the file `engine.ini`. + Creates config by reading yaml file according to template above. """ - instance = None - ini_path = "" + _instance = None + yaml_path = "" + confuse_config = None + config = None # points to a validated config (hopefully later) logger = None - def __init__(self, ini_path="/etc/aura/engine.ini"): + @classmethod + @property + def instance(cls): + """Create and return singleton instance.""" + if cls._instance is None: + cls._instance = AuraConfig() + return cls._instance + + def __init__(self, yaml_path="/etc/aura/engine.yaml"): """ - Initialize the configuration, defaults to `/etc/aura/engine.ini`. + Initialize the configuration, defaults to `/etc/aura/engine.yaml`. - If this file doesn't exist it uses `./config/engine.ini` from + If this file doesn't exist it uses `./config/engine.yaml` from the project directory. Args: - ini_path(String): The path to the configuration file `engine.ini` + yaml_path(String): The path to the configuration file `engine.yaml` """ self.logger = logging.getLogger("engine") - config_file = Path(ini_path) + config_file = Path(yaml_path) project_root = Path(__file__).parent.parent.parent.parent.absolute() if not config_file.is_file(): - ini_path = f"{project_root}/config/engine.ini" - - self.ini_path = ini_path + yaml_path = f"{project_root}/config/engine.yaml" + + self.yaml_path = yaml_path + print(f"Using configuration at: {yaml_path}") + + envar_matcher = re.compile(r"\$\{([^}^{]+)\}") + + def envar_constructor(loader, node): + value = os.path.expandvars(node.value) + # workaround not to parse numerics as strings + try: + value = int(value) + except ValueError: + pass + try: + value = float(value) + except ValueError: + pass + return value + + envar_loader = yaml.SafeLoader + envar_loader.add_implicit_resolver("!envar", envar_matcher, None) + envar_loader.add_constructor("!envar", envar_constructor) + + self.confuse_config = confuse.Configuration("engine", loader=envar_loader) + self.confuse_config.set_file(yaml_path) self.load_config() - AuraConfig.instance = self - # Defaults - self.set("install_dir", os.path.realpath(project_root)) - self.set("config_dir", os.path.dirname(ini_path)) - print(f"Using configuration at: {ini_path}") + # custom overrides and defaults + self.confuse_config["install_dir"].set(os.path.realpath(project_root)) + self.confuse_config["config_dir"].set(os.path.dirname(yaml_path)) + + AuraConfig.instance = self def init_version(self, version: dict): """ Read and set the component version from VERSION file in project root. """ - self.set("version_control", version.get("control")) - self.set("version_core", version.get("core")) - self.set("version_liquidsoap", version.get("liquidsoap")) - - @staticmethod - def config(): - """ - Retrieve the global instances of the configuration. - """ - return AuraConfig.instance - - def set(self, key, value): - """ - Set specific config property. - - Args: - key (String): key - default (*): value - - """ - try: - self.__dict__[key] = int(value) - except ValueError: - self.__dict__[key] = str(value) - - def get(self, key, default=None): - """ - Get for some specific config property. - - Args: - key (String): key - default (*): value - - """ - if key not in self.__dict__: - if default: - self.set(key, default) - else: - self.logger.warning( - "Key " + key + " not found in configfile " + self.ini_path + "!" - ) - return None - - value = self.__dict__[key] - if value and isinstance(value, str): - value = os.path.expandvars(value) - return value + self.confuse_config["version_control"].set(version.get("control")) + self.confuse_config["version_core"].set(version.get("core")) + self.confuse_config["version_liquidsoap"].set(version.get("liquidsoap")) def load_config(self): """ Set config defaults and load settings from file. """ - if not os.path.isfile(self.ini_path): - self.logger.critical(self.ini_path + " not found :(") + if not os.path.isfile(self.yaml_path): + self.logger.critical(self.yaml_path + " not found :(") sys.exit(1) - # Read the file - f = open(self.ini_path, "r") - ini_str = f.read() - f.close() - - # Parse the values - config_parser = ConfigParser() - try: - config_parser.read_string(ini_str) - except Exception as e: - self.logger.critical("Cannot read " + self.ini_path + "! Reason: " + str(e)) - sys.exit(0) - - for section in config_parser.sections(): - for key, value in config_parser.items(section): - v = config_parser.get(section, key).replace('"', "").strip() - self.set(key, v) + self.config = self.confuse_config.get(template) def to_abs_path(self, path): """ @@ -152,16 +188,16 @@ class AuraConfig: if path.startswith("/"): return path else: - return self.get("install_dir") + "/" + path + return self.confuse_config["install_dir"].get() + "/" + path def abs_audio_store_path(self): """ Return the absolute path to the audio store, based on the `audio_source_folder` setting. """ - return self.to_abs_path(self.get("audio_source_folder")) + return self.to_abs_path(self.config.scheduler.audio.source_folder) def abs_playlist_path(self): """ Return the absolute path to the playlist folder. """ - return self.to_abs_path(self.get("audio_playlist_folder")) + return self.to_abs_path(self.config.scheduler.audio.playlist_folder) diff --git a/src/aura_engine/base/logger.py b/src/aura_engine/base/logger.py index a86ebc7d4795da7b784e071f03335d184b78463b..430547252cfed3dac930ee1c9e03ca5f7a9696c7 100644 --- a/src/aura_engine/base/logger.py +++ b/src/aura_engine/base/logger.py @@ -53,7 +53,7 @@ class AuraLogger: """ Retrieve the configured log level (default=INFO). """ - lvl = self.config.get("log_level") + lvl = self.config.log.level mapping = { "debug": logging.DEBUG, "info": logging.INFO, @@ -83,7 +83,7 @@ class AuraLogger: if not self.logger.hasHandlers(): # create file handler for logger - file_handler = logging.FileHandler(self.config.get("log_dir") + "/" + name + ".log") + file_handler = logging.FileHandler(self.config.log.directory + "/" + name + ".log") file_handler.setLevel(lvl) # create stream handler for logger diff --git a/src/aura_engine/core/channels.py b/src/aura_engine/core/channels.py index 810101b4454fc005e18ab5dfcb4208aea46f6d5e..41e3258a9d39c0a7f84321e79c5eee0b9afd0be9 100644 --- a/src/aura_engine/core/channels.py +++ b/src/aura_engine/core/channels.py @@ -148,7 +148,7 @@ class GenericChannel: mixer (Mixer): The mixer instance """ - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.logger = logging.getLogger("engine") self.mixer = mixer self.name = channel_name @@ -576,7 +576,7 @@ class ChannelFactory: mixer (Mixer): The mixer instance """ - self.config = AuraConfig() + self.config = AuraConfig.instance self.logger = logging.getLogger("engine") self.mixer = mixer diff --git a/src/aura_engine/core/client.py b/src/aura_engine/core/client.py index d68d5b017cd1929366eaf2950d55a403c19c9654..96704b8a1174a84a896fd3e5d15e96d197e09117 100644 --- a/src/aura_engine/core/client.py +++ b/src/aura_engine/core/client.py @@ -50,7 +50,7 @@ class CoreClient: Initialize the client. """ self.logger = logging.getLogger("engine") - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.event_dispatcher = event_dispatcher self.conn = CoreConnection() @@ -151,7 +151,7 @@ class CoreClient: @private """ - if self.config.get("log_level") == "debug": + if self.config.log.level == "debug": cmds = CoreClient.skip_log_commands base_cmd = command.split(" ")[0] if not base_cmd.startswith(cmds): @@ -234,8 +234,8 @@ class CoreConnection: Initialize the connection. """ self.logger = logging.getLogger("engine") - config = AuraConfig.config() - socket_path = config.get("socket_dir") + "/engine.sock" + config = AuraConfig.instance + socket_path = config.config.general.socket_dir + "/engine.sock" self.socket_path = config.to_abs_path(socket_path) self.logger.debug(f"Using socket at '{self.socket_path}'") diff --git a/src/aura_engine/core/mixer.py b/src/aura_engine/core/mixer.py index 83022296781e1755f9c69c08ea53f767db173f69..ca933fce70ccb10be26a906f438d008c03501381 100644 --- a/src/aura_engine/core/mixer.py +++ b/src/aura_engine/core/mixer.py @@ -63,7 +63,7 @@ class Mixer: client (PlayoutClient): The client for controlling playout """ - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.logger = logging.getLogger("engine") self.mixer_id = mixer_id self.client = client @@ -234,7 +234,7 @@ class Mixer: self.logger.info(msg) return - fade_in_time = float(self.config.get("fade_in_time")) + fade_in_time = self.config.scheduler.fade_in_time if fade_in_time > 0: self.fade_in_active = True @@ -274,7 +274,7 @@ class Mixer: self.logger.info(msg) return - fade_out_time = float(self.config.get("fade_out_time")) + fade_out_time = self.config.scheduler.fade_out_time if fade_out_time > 0: step = abs(fade_out_time) / current_volume diff --git a/src/aura_engine/engine.py b/src/aura_engine/engine.py index 08a7cd325c4e774b46156127443b12f038f96969..93d032f861bbb969daf407bf7295c48ff80d6f0c 100644 --- a/src/aura_engine/engine.py +++ b/src/aura_engine/engine.py @@ -71,8 +71,8 @@ class Engine: raise Exception("Engine is already running!") Engine.instance = self self.logger = logging.getLogger("engine") - self.config = AuraConfig.config() - Engine.engine_time_offset = float(self.config.get("engine_latency_offset")) + self.config = AuraConfig.instance.config + Engine.engine_time_offset = self.config.scheduler.audio.engine_latency_offset def start(self): """ @@ -143,7 +143,7 @@ class Engine: def dispatch_fallback_event(): timeslot = self.scheduler.timetable.get_current_timeslot() - fallback_show_name = self.config.get("fallback_show_name") + fallback_show_name = self.config.general.fallback_show_name self.event_dispatcher.on_fallback_active(timeslot, fallback_show_name) # Initialize state @@ -173,8 +173,8 @@ class Engine: Update the config of playout with the current values. """ playout_config = { - "fallback_show_id": int(self.config.get("fallback_show_id")), - "fallback_show_name": self.config.get("fallback_show_name"), + "fallback_show_id": self.config.general.fallback_show_id, + "fallback_show_name": self.config.general.fallback_show_name, } json_config = json.dumps(playout_config, ensure_ascii=False) response = self.playout.set_config(json_config) @@ -197,9 +197,8 @@ class Engine: ctrl_version = toml_file["tool"]["poetry"]["version"] versions = self.playout.get_version() versions = DotDict(json.loads(versions)) - self.config.set("version_control", ctrl_version) - self.config.set("version_core", versions.core) - self.config.set("version_liquidsoap", versions.liquidsoap) + versions.control = ctrl_version + AuraConfig.instance.init_version(versions) @staticmethod def engine_time(): @@ -275,7 +274,7 @@ class Player: event_dispatcher (EventDispather): Dispatcher for issuing events """ - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.logger = logging.getLogger("engine") self.event_dispatcher = event_dispatcher self.resource_map = ResourceMapping() diff --git a/src/aura_engine/events.py b/src/aura_engine/events.py index 1355affe6256f674df1c70c6adbff849fc595aa4..172dce0d27c6a86a62e8ad212e115e078e4d4a26 100644 --- a/src/aura_engine/events.py +++ b/src/aura_engine/events.py @@ -88,7 +88,7 @@ class EngineEventDispatcher: """ self.subscriber_registry = dict() self.logger = logging.getLogger("engine") - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.engine = engine # diff --git a/src/aura_engine/plugins/clock.py b/src/aura_engine/plugins/clock.py index 7bc60af6af2d57d63652590cf2164b61b2432b9b..ea6c6dd13b15dd56c1b4673277a0911f5c27f478 100644 --- a/src/aura_engine/plugins/clock.py +++ b/src/aura_engine/plugins/clock.py @@ -53,7 +53,7 @@ class ClockInfoHandler: Initialize. """ self.logger = logging.getLogger("engine") - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.api = SimpleRestApi() self.engine = engine @@ -77,8 +77,8 @@ class ClockInfoHandler: return self.logger.info(f"Fallback '{fallback_name}' activated, clock update required") - fallback_show_id = self.config.get("fallback_show_id") - fallback_show_name = self.config.get("fallback_show_name") + fallback_show_id = self.config.general.fallback_show_id + fallback_show_name = self.config.general.fallback_show_name # Interpolate timeslot-less slot # TODO start time to be calculated based on previous timeslot (future station logic) @@ -139,12 +139,12 @@ class ClockInfoHandler: built_upcoming.append(self.build_timeslot(upcoming_timeslot, None)) data = { - "engineSource": self.config.get("api_engine_number"), + "engineSource": self.config.api.engine.number, "currentTimeslot": self.build_timeslot(active_timeslot, active_playlist), "upcomingTimeslots": built_upcoming, "plannedPlaylist": self.build_playlist(active_playlist), } - url = self.config.get("api_engine_store_clock") + url = self.config.api.engine.store_clock self.logger.info(f"PUT clock info to '{url}': \n{data}") self.api.put(url, data=data) diff --git a/src/aura_engine/plugins/monitor.py b/src/aura_engine/plugins/monitor.py index 160d2a0263ecfe4e0af5f50712cacede2e425f0e..f3ebd04ccf1d7affa18b39cd37f585aff2a90d75 100644 --- a/src/aura_engine/plugins/monitor.py +++ b/src/aura_engine/plugins/monitor.py @@ -90,7 +90,7 @@ class AuraMonitor: """ self.api = SimpleRestApi() self.logger = logging.getLogger("engine") - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.engine = engine self.status = dict() self.status["engine"] = dict() @@ -103,9 +103,9 @@ class AuraMonitor: # Heartbeat settings self.heartbeat_running = False - self.heartbeat_server = self.config.get("heartbeat_server") - self.heartbeat_port = self.config.get("heartbeat_port") - self.heartbeat_frequency = self.config.get("heartbeat_frequency") + self.heartbeat_server = self.config.monitoring.heartbeat.host + self.heartbeat_port = self.config.monitoring.heartbeat.port + self.heartbeat_frequency = self.config.monitoring.heartbeat.frequency self.heartbeat_socket = socket(AF_INET, SOCK_DGRAM) self.engine_id = self.get_engine_id() @@ -201,7 +201,7 @@ class AuraMonitor: body["details"] = json.dumps(data, default=str) json_data = json.dumps(body, default=str) timeout = 5 - url = self.config.get("api_engine_store_health") + url = self.config.api.engine.store_health url = url.replace("${ENGINE_NUMBER}", str(self.config.get("api_engine_number"))) headers = {"content-type": "application/json"} response = requests.Response() @@ -234,28 +234,25 @@ class AuraMonitor: Request the current status of all components. """ self.engine.init_version() - ctrl_version = self.config.get("version_control") - core_version = self.config.get("version_core") - liq_version = self.config.get("version_liquidsoap") - + ctrl_version = AuraConfig.instance.confuse_config["version_control"].get() + core_version = AuraConfig.instance.confuse_config["version_core"].get() + liq_version = AuraConfig.instance.confuse_config["version_liquidsoap"].get() self.status["engine"]["version"] = ctrl_version self.status["lqs"]["version"] = {"core": core_version, "liquidsoap": liq_version} self.status["lqs"]["outputs"] = self.engine.player.mixer.get_outputs() self.status["lqs"]["mixer"] = self.engine.player.mixer.get_inputs() - self.status["api"]["steering"]["url"] = self.config.get("api_steering_status") + self.status["api"]["steering"]["url"] = self.config.api.steering.status self.status["api"]["steering"]["available"] = self.validate_url_connection( - self.config.get("api_steering_status") + self.config.api.steering.status ) - self.status["api"]["tank"]["url"] = self.config.get("api_tank_status") + self.status["api"]["tank"]["url"] = self.config.api.tank.status self.status["api"]["tank"]["available"] = self.validate_url_connection( - self.config.get("api_tank_status") - ) - self.status["api"]["tank"]["status"] = self.get_url_response( - self.config.get("api_tank_status") + self.config.api.tank.status ) - self.status["api"]["engine"]["url"] = self.config.get("api_engine_status") + self.status["api"]["tank"]["status"] = self.get_url_response(self.config.api.tank.status) + self.status["api"]["engine"]["url"] = self.config.api.engine.status self.status["api"]["engine"]["available"] = self.validate_url_connection( - self.config.get("api_engine_status") + self.config.api.engine.status ) self.update_vitality_status() @@ -266,7 +263,9 @@ class AuraMonitor: """ self.status["lqs"]["status"] = self.engine.update_playout_state() self.status["lqs"]["available"] = self.status["lqs"]["status"] is not None - self.status["audio_source"] = self.validate_directory(self.config.abs_audio_store_path()) + self.status["audio_source"] = self.validate_directory( + AuraConfig.instance.abs_audio_store_path() + ) # After first update start the Heartbeat Monitor if not self.heartbeat_running: @@ -282,7 +281,7 @@ class AuraMonitor: """ if self.has_valid_status(True): # Always check status, but only send heartbeat if wanted so - if self.config.get("heartbeat_server") != "": + if self.config.monitoring.heartbeat.host != "": self.heartbeat_socket.sendto( str.encode("OK"), (self.heartbeat_server, self.heartbeat_port) ) @@ -310,7 +309,7 @@ class AuraMonitor: {"engine_id": self.engine_id, "status": status} ) - heartbeat_frq = self.config.get("heartbeat_frequency", 1) + heartbeat_frq = self.config.monitoring.heartbeat.frequency # default: 1 if int(heartbeat_frq or 0) < 1: heartbeat_frq = 1 threading.Timer(heartbeat_frq, self.heartbeat).start() diff --git a/src/aura_engine/scheduling/api.py b/src/aura_engine/scheduling/api.py index d03ad07fd3cd03936f6b35c378cd3cf84ffe4ba0..a1c12035ee9a889494a709c25b3090a695136bd6 100644 --- a/src/aura_engine/scheduling/api.py +++ b/src/aura_engine/scheduling/api.py @@ -79,12 +79,12 @@ class ApiFetcher(threading.Thread): """ Initialize the API Fetcher. """ - self.config = AuraConfig.config() + self.config = AuraConfig.instance.config self.logger = logging.getLogger("engine") - cache_location = self.config.get("cache_dir") + cache_location = self.config.general.cache_dir self.api = SimpleCachedRestApi(SimpleRestApi(), cache_location) - self.url_api_timeslots = self.config.get("api_steering_calendar") - self.url_api_playlist = self.config.get("api_tank_playlist") + self.url_api_timeslots = self.config.api.steering.calendar + self.url_api_playlist = self.config.api.tank.playlist self.queue = queue.Queue() self.stop_event = threading.Event() diff --git a/src/aura_engine/scheduling/scheduler.py b/src/aura_engine/scheduling/scheduler.py index aed67a5cd95fe0a25743b56695d62600c8ba365e..2e4e9336a894dd5edf0ca707d173470bbe1c4d21 100644 --- a/src/aura_engine/scheduling/scheduler.py +++ b/src/aura_engine/scheduling/scheduler.py @@ -278,8 +278,8 @@ class AuraScheduler(threading.Thread): """ while not self.exit_event.is_set(): try: - self.config.load_config() - seconds_to_wait = int(self.config.get("fetching_frequency")) + AuraConfig.instance.load_config() + seconds_to_wait = self.config.scheduler.fetching_frequency msg = f"== start fetching new timeslots (every {seconds_to_wait} seconds) ==" self.logger.info(SU.cyan(msg)) diff --git a/src/aura_engine/scheduling/utils.py b/src/aura_engine/scheduling/utils.py index a35f54fa678450d62d2ebe19a724884bf1fade25..d96b7a0abd128cc72e8b362bbfef89f0152cd831 100644 --- a/src/aura_engine/scheduling/utils.py +++ b/src/aura_engine/scheduling/utils.py @@ -45,7 +45,7 @@ class M3UPlaylistProcessor: """ Initialize. """ - self.config = AuraConfig.config() + self.config = AuraConfig.instance self.logger = logging.getLogger("engine") self.playlist_folder = self.config.abs_playlist_path() diff --git a/tests/test_config.py b/tests/test_config.py index 11051e7ae43a98f2c4b7d2baa1b965379e1a4ed7..f5fc86511dd6a2efa1a70331f4679dc2ef518347 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -39,23 +39,24 @@ class TestConfig(unittest.TestCase): print(self._testMethodName) # Check if config is available - self.assertIsNotNone(self.config.ini_path) + self.assertIsNotNone(self.config.yaml_path) # Check if "install_dir" is a valid directory (is evaluated at runtime) - self.assertTrue(os.path.isdir(self.config.get("install_dir"))) + self.assertTrue(os.path.isdir(self.config.confuse_config["install_dir"].get())) + + # Reference to confuse config + cfg = self.config.config # Check API Urls - self.assertTrue(validators.url(self.config.get("api_steering_status"))) - self.assertTrue(validators.url(self.config.get("api_steering_calendar"))) - self.assertTrue(validators.url(self.config.get("api_tank_status"))) - tank_playlist_url = self.config.get("api_tank_playlist").replace("${ID}", "1") + self.assertTrue(validators.url(cfg.api.steering.status)) + self.assertTrue(validators.url(cfg.api.steering.calendar)) + self.assertTrue(validators.url(cfg.api.tank.status)) + tank_playlist_url = cfg.api.tank.playlist.replace("${ID}", "1") self.assertTrue(validators.url(tank_playlist_url)) - self.assertTrue(validators.url(self.config.get("api_engine_status"))) - self.assertTrue(validators.url(self.config.get("api_engine_store_playlog"))) - self.assertTrue(validators.url(self.config.get("api_engine_store_clock"))) - engine_health_url = self.config.get("api_engine_store_health").replace( - "${ENGINE_NUMBER}", "1" - ) + self.assertTrue(validators.url(cfg.api.engine.status)) + self.assertTrue(validators.url(cfg.api.engine.store_playlog)) + self.assertTrue(validators.url(cfg.api.engine.store_clock)) + engine_health_url = cfg.api.engine.store_health.replace("${ENGINE_NUMBER}", "1") self.assertTrue(validators.url(engine_health_url)) diff --git a/tests/test_logger.py b/tests/test_logger.py index ac532bd80f275a74bc8ebf9244f8bc9d709811ef..4344b55c9b30b13e76c3bfce3b4a8b916602a783 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -31,7 +31,7 @@ class TestLogger(unittest.TestCase): aura_logger = None def setUp(self): - self.config = AuraConfig() + self.config = AuraConfig().config self.aura_logger = AuraLogger(self.config) def test_logger(self): diff --git a/tests/test_simple_api.py b/tests/test_simple_api.py index 5f4450309d8e690777385b78092079aa56d14a14..7097198fab74d6e63c4c6337a09cf9760a68b14c 100644 --- a/tests/test_simple_api.py +++ b/tests/test_simple_api.py @@ -101,7 +101,7 @@ class TestApi(unittest.TestCase): print(self._testMethodName) # Check if config is available - self.assertIsNotNone(self.config.ini_path) + self.assertIsNotNone(self.config.yaml_path) def test_clean_dict(self): print(self._testMethodName) diff --git a/tests/test_simple_api_cached.py b/tests/test_simple_api_cached.py index e1168b550c9d6082e31eb29eafcd941ba92b51f3..6c96c5c822bac368b4f4188d91145509bacadf32 100644 --- a/tests/test_simple_api_cached.py +++ b/tests/test_simple_api_cached.py @@ -63,7 +63,7 @@ class TestCachedApi(unittest.TestCase): def setUp(self): self.config = AuraConfig() - cache_location = self.config.get("cache_dir") + cache_location = self.config.config.general.cache_dir self.api = SimpleCachedRestApi(SimpleRestApi(), cache_location) # @@ -74,7 +74,7 @@ class TestCachedApi(unittest.TestCase): print(self._testMethodName) # Check if config is available - self.assertIsNotNone(self.config.ini_path) + self.assertIsNotNone(self.config.yaml_path) def test_build_filename(self): url = "https://dashboard.aura.radio/steering/api/v1/playout"