Commit b50e8190 authored by David Trattnig's avatar David Trattnig
Browse files

Renamed entry points and updated user.

parent 609af456
......@@ -4,10 +4,10 @@ After=network.target
[Service]
Type=simple
User=david
WorkingDirectory=/home/david/Code/aura/engine
ExecStart=/home/david/Code/aura/engine/aura.py
ExecStop=/home/david/Code/aura/engine/guru.py --shutdown --quiet
User=engineuser
WorkingDirectory=/opt/Code/aura/engine
ExecStart=/opt/aura/engine/engine-core.py
ExecStop=/opt/aura/engine/guru.py --shutdown --quiet
Restart=always
[Install]
......
......@@ -5,8 +5,8 @@ Wants=aura-engine.service
[Service]
Type=simple
User=david
ExecStart=/usr/bin/liquidsoap /home/david/Code/aura/engine/modules/liquidsoap/engine.liq
User=engineuser
ExecStart=/usr/bin/liquidsoap /opt/aura/engine/modules/liquidsoap/engine.liq
Restart=always
[Install]
......
#!/usr/bin/python3.7
#
# Aura Engine
#
# Copyright (C) 2020 David Trattnig <david.trattnig@subsquare.at>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import os, os.path
from datetime import datetime, date, timedelta
from flask import Flask, Response
from flask_caching import Cache
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields, post_dump
from flask_restful import Api, Resource, abort
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
# import werkzeug
from werkzeug.exceptions import HTTPException, default_exceptions, Aborter
from modules.base.logger import AuraLogger
from modules.base.config import AuraConfig
from libraries.database.broadcasts import AuraDatabaseModel, Schedule, Playlist, PlaylistEntry, PlaylistEntryMetaData, TrackService
#
# Initialize the Aura Web App and API.
#
config = AuraConfig()
app = Flask(__name__,
static_url_path='',
static_folder='web/')
# static_folder='contrib/aura-player/public/')
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SQLALCHEMY_DATABASE_URI"] = config.get_database_uri()
app.config["CACHE_TYPE"] = "simple"
app.config["CACHE_DEFAULT_TIMEOUT"] = 0
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
cache = Cache(app)
cors = CORS(app, resources={r"/*": {"origins": "*"}}) # FIXME Update CORS for production use
db = SQLAlchemy(app)
ma = Marshmallow(app)
api = Api(app)
#
# Werkzeug HTTP code mappings
#
class NoDataAvailable(HTTPException):
code = 204
description = "There is currently no content available."
default_exceptions[204] = NoDataAvailable
abort = Aborter()
class EngineApi:
"""
Provides the Aura Engine API services.
"""
config = None
api = None
logger = None
# trackservice_schema = None
def __init__(self, config, api):
"""
Initializes the API.
Args:
config (AuraConfig): The Engine configuration.
api (Api): The Flask restful API object.
"""
self.config = config
self.logger = AuraLogger(self.config, "engine-api")
self.logger = logging.getLogger("engine-api")
self.api = api
# Generate HTML files
self.generate_html("web/templates/clock.html", "web/clock.html")
self.generate_html("web/templates/trackservice.html", "web/trackservice.html")
# API Spec
spec.components.schema("TrackService", schema=TrackServiceSchema)
spec.components.schema("Report", schema=ReportSchema)
spec.components.schema("Schedule", schema=ScheduleSchema)
spec.components.schema("Clock", schema=ClockDataSchema)
# TODO Generates HTML for specification
#self.logger.info(spec.to_yaml())
# Schema instances
EngineApi.trackservice_schema = TrackServiceSchema(many=True)
EngineApi.track_schema = TrackServiceSchema()
EngineApi.report_schema = ReportSchema(many=True)
EngineApi.schedule_schema = ScheduleSchema(many=True)
EngineApi.clockdata_schema = ClockDataSchema()
# Define API routes
self.api.add_resource(TrackServiceResource, config.api_prefix + "/trackservice/")
self.api.add_resource(TrackResource, config.api_prefix + "/trackservice/<int:track_id>")
self.api.add_resource(CurrentTrackResource, config.api_prefix + "/trackservice/current")
self.api.add_resource(TracksByDayResource, config.api_prefix + "/trackservice/date/<string:date_string>")
self.api.add_resource(ReportResource, config.api_prefix + "/report/<string:year_month>")
self.api.add_resource(UpcomingSchedulesResource, config.api_prefix + "/schedule/upcoming")
self.api.add_resource(ClockDataResource, config.api_prefix + "/clock")
self.logger.info("Engine API routes successfully set!")
# Static resources
@app.route('/app/trackservice', methods=['GET'])
def trackservice():
content = open(os.path.join("web/", "trackservice.html"))
return Response(content, mimetype="text/html")
# Static resources
@app.route('/app/clock', methods=['GET'])
def clock():
content = open(os.path.join("web/", "clock.html"))
return Response(content, mimetype="text/html")
def generate_html(self, src_file, target_file):
"""
Generates HTML based on the configuration options and templates.
Args:
src_file (String): The template file
target_file (String): The HTML file to be generated
"""
src_file = open(src_file, "r")
target_file = open(target_file, "w")
content = src_file.read()
config_options = {
"CONFIG-STATION-NAME": config.station_name,
"CONFIG-STATION-LOGO-URL": config.station_logo_url,
"CONFIG-STATION-LOGO-SIZE": config.station_logo_size,
"CONFIG-API-URL": config.exposed_api_url
}
for key, value in config_options.items():
content = content.replace(":::"+key+":::", value)
target_file.write(content)
src_file.close()
target_file.close()
def run(self):
"""
Starts the API server.
"""
# Set debug=False if you want to use your native IDE debugger
self.api.app.run(port=self.config.api_port, debug=False)
#
# API SPEC
#
spec = APISpec(
title="Swagger API Specification for Aura Engine",
version="1.0.0",
openapi_version="3.0.2",
plugins=[FlaskPlugin(), MarshmallowPlugin()],
)
#
# API SCHEMA
#
class TrackServiceSchema(ma.Schema):
class Meta:
fields = (
"id",
"schedule.schedule_id",
"schedule.schedule_start",
"schedule.schedule_end",
"schedule.languages",
"schedule.type",
"schedule.category",
"schedule.topic",
"schedule.musicfocus",
"schedule.is_repetition",
"track",
"track_start",
"show"
)
class ClockDataSchema(ma.Schema):
class Meta:
fields = (
"current",
"next",
"track_id",
"track_start",
"track"
)
class ScheduleSchema(ma.Schema):
class Meta:
fields = (
"id",
"schedule_id",
"schedule_start",
"schedule",
"show_id",
"show_name",
"show_hosts",
"show_type"
)
class ReportSchema(ma.Schema):
class Meta:
fields = (
"id",
"schedule.schedule_id",
"schedule.schedule_start",
"schedule.schedule_end",
"schedule.languages",
"schedule.type",
"schedule.category",
"schedule.topic",
"schedule.musicfocus",
"schedule.is_repetition",
"schedule.show_id",
"schedule.show_name",
"schedule.show_hosts",
"schedule.show_type",
"schedule.show_funding_category",
"track",
"track_start",
"playlist_id",
"fallback_type",
"schedule_fallback_id",
"show_fallback_id",
"station_fallback_id"
)
#
# API RESOURCES
#
class TrackServiceResource(Resource):
logger = None
def __init__(self):
self.logger = logging.getLogger("engine-api")
def get(self):
today = date.today()
today = datetime(today.year, today.month, today.day)
tracks = TrackService.select_by_day(today)
return EngineApi.trackservice_schema.dump(tracks)
class TrackResource(Resource):
logger = None
def __init__(self):
self.logger = logging.getLogger("engine-api")
def get(self, track_id):
track = TrackService.select_one(track_id)
return EngineApi.track_schema.dump(track)
class ClockDataResource(Resource):
logger = None
def __init__(self):
self.logger = logging.getLogger("engine-api")
def get(self):
item = TrackService.select_current()
next_schedule = Schedule.select_upcoming(1)
if next_schedule:
next_schedule = next_schedule[0].as_dict()
next_schedule["playlist"] = None
else:
next_schedule = {}
clockdata = {
"track_id": item.id,
"track_start": item.track_start,
"track": item.track,
"current": {},
"next": next_schedule
}
if item.schedule:
clockdata["current"] = item.schedule.as_dict()
if item.schedule.playlist:
clockdata["current"]["playlist"] = item.schedule.playlist[0].as_dict()
clockdata["current"]["show"] = item.show
return EngineApi.clockdata_schema.dump(clockdata)
class CurrentTrackResource(Resource):
logger = None
def __init__(self):
self.logger = logging.getLogger("engine-api")
def get(self):
track = TrackService.select_current()
if not track:
return abort(204) # No content available
return EngineApi.track_schema.dump(track)
class TracksByDayResource(Resource):
logger = None
def __init__(self):
self.logger = logging.getLogger("engine-api")
def get(self, date_string):
date = datetime.strptime(date_string, "%Y-%m-%d")
self.logger.debug("Query track-service by day: %s" % str(date))
tracks = TrackService.select_by_day(date)
if not tracks:
return abort(204) # No content available
return EngineApi.trackservice_schema.dump(tracks)
class UpcomingSchedulesResource(Resource):
logger = None
def __init__(self):
self.logger = logging.getLogger("engine-api")
def get(self):
now = datetime.now()
self.logger.debug("Query upcoming schedules after %s" % str(now))
schedules = Schedule.select_upcoming(3)
if not schedules:
return abort(204) # No content available
return EngineApi.schedule_schema.dump(schedules)
class ReportResource(Resource):
logger = None
def __init__(self):
self.logger = logging.getLogger("engine-api")
def get(self, year_month):
year = int(year_month.split("-")[0])
month = int(year_month.split("-")[1])
first_day = datetime(year, month, 1)
next_month = first_day.replace(day=28) + timedelta(days=4)
next_month - timedelta(days=next_month.day)
self.logger.debug("Query report for month: %s - %s" % (str(first_day), str(next_month)))
report = TrackService.select_by_range(first_day, next_month)
if not report:
return abort(204) # No content available
return EngineApi.report_schema.dump(report)
#
# Initialization calls
#
engine_api = EngineApi(config, api)
if __name__ == "__main__":
engine_api.run()
#!/usr/bin/python3.7
#
# Aura Engine
#
# Copyright (C) 2017-2020
# David Trattnig <david.trattnig@subsquare.at>
# Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import signal
import logging
import unittest
import meta
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# from sqlalchemy.ext.declarative import declarative_base
from modules.base.logger import AuraLogger
from modules.base.config import AuraConfig
from modules.base.utils import EngineUtil
# from modules.monitoring.diskspace_watcher import DiskSpaceWatcher
config = AuraConfig()
def configure_flask():
app.config["SQLALCHEMY_DATABASE_URI"] = config.get_database_uri()
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# FIXME Instatiate SQLAlchemy without the need for Flask
app = Flask(__name__)
configure_flask()
DB = SQLAlchemy(app)
# Base = declarative_base()
class Aura:
"""
Aura Class
The core of Aura Engine.
"""
logger = None
config = None
server = None
messenger = None
controller = None
scheduler = None
def __init__(self):
"""
Initializes the Engine Core.
"""
self.config = config
AuraLogger(self.config)
self.logger = logging.getLogger("AuraEngine")
def startup(self):
"""
Starts the Engine Core.
"""
from modules.scheduling.scheduler import AuraScheduler
from modules.core.engine import SoundSystem
from modules.communication.redis.adapter import ServerRedisAdapter
# Check if the database has to be re-created
if self.config.get("recreate_db") is not None:
AuraScheduler(self.config, None)
# Create scheduler and Liquidsoap communicator
self.soundsystem = SoundSystem(self.config)
self.scheduler = AuraScheduler(self.config, self.soundsystem)
# Create the Redis adapter
self.messenger = ServerRedisAdapter(self.config)
self.messenger.scheduler = self.scheduler
self.messenger.soundsystem = self.soundsystem
# TODO Check if it's working / needed.
#self.diskspace_watcher = DiskSpaceWatcher(self.config, self.logger, self.soundsystem)
#self.diskspace_watcher.start()
# And finally wait for redis message / start listener thread
self.messenger.start()
#
# START THE ENGINE
#
if __name__ == "__main__":
aura = Aura()
if len(sys.argv) >= 2:
if "--use-test-data" in sys.argv:
aura.config.set("use_test_data", True)
if "--recreate-database" in sys.argv:
aura.config.set("recreate_db", True)
aura.startup()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment