Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • aura/engine
  • hermannschwaerzler/engine
  • sumpfralle/aura-engine
3 results
Show changes
Showing
with 4291 additions and 0 deletions
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from libraries.base.config import AuraConfig
class AuraLogger(AuraConfig):
logger = None
def __init__(self):
super(AuraLogger, self).__init__()
self.__create_logger("AuraEngine")
def __create_logger(self, name):
"""
Creates the logger instance for AuraEngine
:param name: LoggerName
:return:
"""
lvl = self.config.get("loglevel")
# create logger
self.logger = logging.getLogger(name)
self.logger.setLevel(lvl)
if not self.logger.hasHandlers():
# create file handler for logger
file_handler = logging.FileHandler(self.config.get("logdir") + "/engine.log")
file_handler.setLevel(lvl)
# create stream handler for logger
stream_handler = logging.StreamHandler()
stream_handler.setLevel(lvl)
# set format of log
datepart = "%(asctime)s:%(name)s:%(levelname)s"
message = " - %(message)s - "
filepart = "[%(filename)s:%(lineno)s-%(funcName)s()]"
formatter = logging.Formatter(datepart + message + filepart)
# set log of handlers
file_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)
# add handlers to the logger
self.logger.addHandler(file_handler)
self.logger.addHandler(stream_handler)
self.logger.critical("ADDED HANDLERS")
else:
self.logger.critical("REUSED LOGGER")
\ No newline at end of file
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import time
import logging
import datetime
from sqlalchemy import orm, func, Boolean, Column, DateTime, Integer, String, ForeignKey, ForeignKeyConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import false, true
from libraries.database.database import DB
from libraries.enum.auraenumerations import ScheduleEntryType
from libraries.enum.auraenumerations import LqsIDs, LiquidSoapIDs
class AuraDatabaseModel:
logger = None
def __init__(self):
self.logger = logging.getLogger("AuraEngine")
def store(self, add=False, commit=False):
if add:
DB.session.add(self)
if commit:
DB.session.commit()
def delete(self, commit=False):
DB.session.delete(self)
# current_db_sessions = DB.session.object_session(self)
# current_db_sessions.delete(self)
# return
# DB.session.delete(self)
if commit:
DB.session.commit()
def _asdict(self):
return self.__dict__
@staticmethod
def recreate_db(systemexit = False):
manualschedule = Schedule()
manualschedule.schedule_id = 0
manualschedule.show_name = "Manual Show"
# self.logger.debug("Recreating Database...")
DB.drop_all()
# self.logger.debug("all dropped. creating...")
DB.create_all()
# self.logger.debug("inserting manual scheduling possibility and fallback trackservice schedule")
# DB.session.add(manualschedule)
# db.session.add(fallback_trackservice_schedule)
# self.logger.debug("all created. commiting...")
# DB.session.commit()
# self.logger.debug("Database recreated!")
if systemexit:
sys.exit(0)
# ------------------------------------------------------------------------------------------ #
class Schedule(): #DB.Model, AuraDatabaseModel):
"""
One specific Schedule for a show on a timeslot
"""
__tablename__ = 'schedule'
# primary and foreign keys
schedule_id = Column(Integer, primary_key=True, autoincrement=False)
show_id = Column(Integer) # well, in fact not needed..
schedule_start = Column(DateTime) # can be null due to manual entries
schedule_end = Column(DateTime) # can be null due to manual entries
show_name = Column(String(256))
show_hosts = Column(String(256))
funding_category = Column(String(256))
comment = Column(String(512))
languages = Column(String(256))
type = Column(String(256))
category = Column(String(256))
topic = Column(String(256))
musicfocus = Column(String(256))
is_repetition = Column(Boolean())
playlist_id = Column(Integer)
timeslot_fallback_id = Column(Integer)
show_fallback_id = Column(Integer)
station_fallback_id = Column(Integer)
@staticmethod
def select_all():
# fetching all entries
all_entries = DB.session.query(Schedule).filter().order_by(Schedule.schedule_start).all()
return all_entries
@staticmethod
def select_by_id(id):
entry = DB.session.query(Schedule).filter(Schedule.schedule_id == id).first()
return entry
@staticmethod
def select_act_programme():
#DB.session.query(Schedule).filter
# fetching all from today to ..
today = datetime.date.today()
all_entries = DB.session.query(Schedule).filter(Schedule.schedule_start >= today).order_by(Schedule.schedule_start).all()
return all_entries
@staticmethod
def select_show_on_datetime(datetime):
return DB.session.query(Schedule).filter(Schedule.schedule_start == datetime).first()
@staticmethod
def drop_the_future(timedelta):
then = datetime.datetime.now() + timedelta
# is this really necessary?
future_entries = DB.session.query(Schedule).filter(Schedule.schedule_start > then)
for e in future_entries:
e.delete()
DB.session.commit()
def get_length(self):
sec1 = int(datetime.datetime.strptime(self.start[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
sec2 = int(datetime.datetime.strptime(self.end[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
len = sec2 - sec1
return len
# ------------------------------------------------------------------------------------------ #
def __str__(self):
return "ScheduleID: #" + str(self.schedule_id) + " Showname: " + self.show_name + " starts @ " + str(self.schedule_start)
# ------------------------------------------------------------------------------------------ #
class ScheduleEntry(): #DB.Model, AuraDatabaseModel):
"""
One schedule can have multiple entries
"""
__tablename__ = 'schedule_entry'
# schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
# normal constructor
def __init__(self, **kwargs):
super(ScheduleEntry, self).__init__(**kwargs)
self.calc_unix_times()
self.define_clean_source()
# constructor like - called from sqlalchemy
@orm.reconstructor
def reconstructor(self):
self.calc_unix_times()
self.define_clean_source()
self.set_entry_type()
def define_clean_source(self):
if self.source is None:
return None
if self.source.startswith("http"):
self.cleanprotocol = self.source[:7]
self.cleansource = self.source
elif self.source.startswith("linein"):
self.cleanprotocol = self.source[:9]
self.cleansource = self.source[9:]
elif self.source.startswith("pool") or self.source.startswith("file") or self.source.startswith("live"):
self.cleanprotocol = self.source[:7]
self.cleansource = self.source[7:]
elif self.source.startswith("playlist"):
self.cleanprotocol = self.source[:11]
self.cleansource = self.source[11:]
else:
self.logger.error("Unknown source protocol")
def calc_unix_times(self):
if self.entry_start is not None:
self.entry_start_unix = time.mktime(self.entry_start.timetuple())
def set_entry_type(self):
if self.source.startswith("http"):
self.type = ScheduleEntryType.STREAM
if self.source.startswith("pool") or self.source.startswith("playlist") or self.source.startswith("file"):
self.type = ScheduleEntryType.FILESYSTEM
if self.source.startswith("live") or self.source.startswith("linein"):
if self.cleansource == "0":
self.type = ScheduleEntryType.LIVE_0
elif self.cleansource == "1":
self.type = ScheduleEntryType.LIVE_1
elif self.cleansource == "2":
self.type = ScheduleEntryType.LIVE_2
elif self.cleansource == "3":
self.type = ScheduleEntryType.LIVE_3
elif self.cleansource == "4":
self.type = ScheduleEntryType.LIVE_4
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_all():
# fetching all entries
all_entries = DB.session.query(ScheduleEntry).filter(ScheduleEntry.fallback_type == 0).order_by(ScheduleEntry.entry_start).all()
cnt = 0
for entry in all_entries:
entry.programme_index = cnt
cnt = cnt + 1
return all_entries
@staticmethod
def select_act_programme(include_act_playing = True):
# fetching all from today to ..
today = datetime.date.today()
all_entries = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start >= today, ScheduleEntry.fallback_type == 0).order_by(ScheduleEntry.entry_start).all()
cnt = 0
for entry in all_entries:
entry.programme_index = cnt
cnt = cnt + 1
return all_entries
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_upcoming(datefrom=datetime.datetime.now()):
upcomingtracks = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start > datefrom).order_by(ScheduleEntry.entry_start).all()
return upcomingtracks
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_playlist(playlist_id):
return DB.session.query(ScheduleEntry).filter(ScheduleEntry.playlist_id == playlist_id).order_by(ScheduleEntry.entry_start).all()
@staticmethod
def drop_the_future(timedelta):
then = datetime.datetime.now() + timedelta
#DB.session.delete(ScheduleEntry).filter(ScheduleEntry.entry_start >= then)
# is this really necessary?
future_entries = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start > then)
for e in future_entries:
e.delete()
DB.session.commit()
# ------------------------------------------------------------------------------------------ #
def __str__(self):
return "Showentry starts @ " + str(self.entry_start) + " and plays " + self.source
# ------------------------------------------------------------------------------------------ #
class TrackService(DB.Model, AuraDatabaseModel):
__tablename__ = 'trackservice'
trackservice_id = Column(Integer, primary_key=True, autoincrement=True)
playlist_id = Column(Integer, nullable=False)
entry_num = Column(Integer, nullable=False)
schedule_start = Column(DateTime, nullable=False)
schedule_end = Column(DateTime, nullable=False)
show_name = Column(String(256))
show_hosts = Column(String(256))
funding_category = Column(String(256))
comment = Column(String(512))
languages = Column(String(256))
type = Column(String(256))
category = Column(String(256))
topic = Column(String(256))
musicfocus = Column(String(256))
is_repetition = Column(Boolean())
playlist_id = Column(Integer)
timeslot_fallback_id = Column(Integer)
show_fallback_id = Column(Integer)
station_fallback_id = Column(Integer)
entry_start = Column(DateTime)
source = Column(String(256))
volume = Column(Integer, default=100)
fallback_type = Column(Integer, default=0)
cleansource = ""
cleanprotocol = ""
entry_start_unix = 0
programme_index = -1
type = None
fadeintimer = None
fadeouttimer = None
switchtimer = None
source = Column(String(255), nullable=False)
start = Column(DateTime, nullable=False, default=func.now())
# __table_args__ = (
# ForeignKeyConstraint(['playlist_id', 'entry_num'], ['schedule_entry.playlist_id', 'schedule_entry.entry_num']),
# )
#schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
# trackservice_entry = relationship("ScheduleEntry", foreign_keys=[playlist_id, entry_num], lazy="joined")
#schedule_entry = relationship("ScheduleEntry", primaryjoin="and_(TrackService.playlist_id==ScheduleEntry.playlist_id, TrackService.entry_num==ScheduleEntry.entry_num)", lazy="joined")
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_one(trackservice_id):
return DB.session.query(TrackService).filter(TrackService.trackservice_id == trackservice_id).first()
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_all():
# fetching all entries
all_entries = DB.session.query(TrackService).filter().all()
return all_entries
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_by_day(day):
day_plus_one = day + datetime.timedelta(days=1)
tracks = DB.session.query(TrackService).filter(TrackService.start >= str(day), TrackService.start < str(day_plus_one)).all()
return tracks
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_by_range(from_day, to_day):
tracks = DB.session.query(TrackService).filter(TrackService.start >= str(from_day),
TrackService.start < str(to_day)).all()
return tracks
# ------------------------------------------------------------------------------------------ #
def __str__(self):
return "TrackServiceID: #" + str(self.trackservice_id) + " playlist_id: " + str(self.playlist_id) + " started @ " + str(self.start) + " and played " + self.source
# ------------------------------------------------------------------------------------------ #
# class TrackServiceSchedule(db.Model, AuraDatabaseModel):
# """
# Trackservice is tracking every schedule.
# """
# __tablename__ = 'trackservice_schedule'
#
# # primary and foreign keys
# ts_schedule_id = Column(Integer, primary_key=True, autoincrement=True)
# schedule_id = Column(Integer, ForeignKey("schedule.schedule_id"))
#
# schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
#
# # ------------------------------------------------------------------------------------------ #
# @staticmethod
# def select_one(schedule_id):
# # damn BAND-AID
# # db.session.commit()
#
# return db.session.query(ScheduleEntry).filter(TrackServiceSchedule.schedule_id == schedule_id).first()
#
# # ------------------------------------------------------------------------------------------ #
# class TrackServiceScheduleEntry(db.Model, AuraDatabaseModel):
# """
# And a schedule can have multiple entries
# """
# __tablename__ = 'trackservice_entry'
#
# # primary and foreign keys. the foreign keys here can be null, because of fallback stuff
# ts_entry_id = Column(Integer, primary_key=True, autoincrement=True)
# ts_schedule_id = Column(Integer, ForeignKey("trackservice_schedule.ts_schedule_id"), nullable=True)
# playlist_id = Column(Integer, nullable=True)
# entry_num = Column(Integer, nullable=True)
#
# fallback = Column(Boolean, default=False)
# fallback_start = Column(DateTime, nullable=True, default=None)
# source = Column(String(256), nullable=True, default=None)
#
# # foreign key definitions
# __table_args__ = (
# ForeignKeyConstraint(['playlist_id', 'entry_num'], ['schedule_entry.playlist_id', 'schedule_entry.entry_num']),
# )
#
# trackservice_schedule = relationship("TrackServiceSchedule", foreign_keys=[ts_schedule_id], lazy="joined")
# #trackservice_entry = relationship("ScheduleEntry", foreign_keys=[playlist_id, entry_num], lazy="joined")
# trackservice_entry = relationship("ScheduleEntry", primaryjoin="and_(TrackServiceScheduleEntry.playlist_id==ScheduleEntry.playlist_id, TrackServiceScheduleEntry.entry_num==ScheduleEntry.entry_num)" , lazy="joined")
#
# @staticmethod
# def select_all():
# return db.session.query(TrackServiceScheduleEntry).filter().all()
#AuraDatabaseModel.recreate_db(systemexit=True)
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import time
import logging
import datetime
from sqlalchemy import orm, func, Boolean, Column, DateTime, Integer, String, ForeignKey, ForeignKeyConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import false, true
from libraries.database.database import DB
from libraries.enum.auraenumerations import ScheduleEntryType
from libraries.enum.auraenumerations import LqsIDs, LiquidSoapIDs
class AuraDatabaseModel:
logger = None
def __init__(self):
self.logger = logging.getLogger("AuraEngine")
def store(self, add=False, commit=False):
if add:
DB.session.add(self)
if commit:
DB.session.commit()
def delete(self, commit=False):
DB.session.delete(self)
# current_db_sessions = DB.session.object_session(self)
# current_db_sessions.delete(self)
# return
# DB.session.delete(self)
if commit:
DB.session.commit()
def _asdict(self):
return self.__dict__
@staticmethod
def recreate_db(systemexit = False):
manualschedule = Schedule()
manualschedule.schedule_id = 0
manualschedule.show_name = "Manual Show"
# self.logger.debug("Recreating Database...")
DB.drop_all()
# self.logger.debug("all dropped. creating...")
DB.create_all()
# self.logger.debug("inserting manual scheduling possibility and fallback trackservice schedule")
DB.session.add(manualschedule)
# db.session.add(fallback_trackservice_schedule)
# self.logger.debug("all created. commiting...")
DB.session.commit()
# self.logger.debug("Database recreated!")
if systemexit:
sys.exit(0)
# ------------------------------------------------------------------------------------------ #
class Schedule(DB.Model, AuraDatabaseModel):
"""
One specific Schedule for a show on a timeslot
"""
__tablename__ = 'schedule'
# primary and foreign keys
schedule_id = Column(Integer, primary_key=True, autoincrement=False)
show_id = Column(Integer) # well, in fact not needed..
schedule_start = Column(DateTime) # can be null due to manual entries
schedule_end = Column(DateTime) # can be null due to manual entries
show_name = Column(String(256))
show_hosts = Column(String(256))
funding_category = Column(String(256))
comment = Column(String(512))
languages = Column(String(256))
type = Column(String(256))
category = Column(String(256))
topic = Column(String(256))
musicfocus = Column(String(256))
is_repetition = Column(Boolean())
playlist_id = Column(Integer)
timeslot_fallback_id = Column(Integer)
show_fallback_id = Column(Integer)
station_fallback_id = Column(Integer)
@staticmethod
def select_all():
# fetching all entries
all_entries = DB.session.query(Schedule).filter().order_by(Schedule.schedule_start).all()
return all_entries
@staticmethod
def select_by_id(id):
entry = DB.session.query(Schedule).filter(Schedule.schedule_id == id).first()
return entry
@staticmethod
def select_act_programme():
#DB.session.query(Schedule).filter
# fetching all from today to ..
today = datetime.date.today()
all_entries = DB.session.query(Schedule).filter(Schedule.schedule_start >= today).order_by(Schedule.schedule_start).all()
return all_entries
@staticmethod
def select_show_on_datetime(datetime):
return DB.session.query(Schedule).filter(Schedule.schedule_start == datetime).first()
@staticmethod
def drop_the_future(timedelta):
then = datetime.datetime.now() + timedelta
# is this really necessary?
future_entries = DB.session.query(Schedule).filter(Schedule.schedule_start > then)
for e in future_entries:
e.delete()
DB.session.commit()
def get_length(self):
sec1 = int(datetime.datetime.strptime(self.start[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
sec2 = int(datetime.datetime.strptime(self.end[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
len = sec2 - sec1
return len
# ------------------------------------------------------------------------------------------ #
def __str__(self):
return "ScheduleID: #" + str(self.schedule_id) + " Showname: " + self.show_name + " starts @ " + str(self.schedule_start)
# ------------------------------------------------------------------------------------------ #
class ScheduleEntry(DB.Model, AuraDatabaseModel):
"""
One schedule can have multiple entries
"""
__tablename__ = 'schedule_entry'
# primary and foreign keys
playlist_id = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
entry_num = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
schedule_id = Column(Integer, ForeignKey("schedule.schedule_id"))
entry_start = Column(DateTime)
source = Column(String(256))
volume = Column(Integer, default=100)
fallback_type = Column(Integer, default=0)
cleansource = ""
cleanprotocol = ""
entry_start_unix = 0
programme_index = -1
type = None
fadeintimer = None
fadeouttimer = None
switchtimer = None
# schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
# normal constructor
def __init__(self, **kwargs):
super(ScheduleEntry, self).__init__(**kwargs)
self.calc_unix_times()
self.define_clean_source()
# constructor like - called from sqlalchemy
@orm.reconstructor
def reconstructor(self):
self.calc_unix_times()
self.define_clean_source()
self.set_entry_type()
def define_clean_source(self):
if self.source is None:
return None
if self.source.startswith("http"):
self.cleanprotocol = self.source[:7]
self.cleansource = self.source
elif self.source.startswith("linein"):
self.cleanprotocol = self.source[:9]
self.cleansource = self.source[9:]
elif self.source.startswith("pool") or self.source.startswith("file") or self.source.startswith("live"):
self.cleanprotocol = self.source[:7]
self.cleansource = self.source[7:]
elif self.source.startswith("playlist"):
self.cleanprotocol = self.source[:11]
self.cleansource = self.source[11:]
else:
self.logger.error("Unknown source protocol")
def calc_unix_times(self):
if self.entry_start is not None:
self.entry_start_unix = time.mktime(self.entry_start.timetuple())
def set_entry_type(self):
if self.source.startswith("http"):
self.type = ScheduleEntryType.STREAM
if self.source.startswith("pool") or self.source.startswith("playlist") or self.source.startswith("file"):
self.type = ScheduleEntryType.FILESYSTEM
if self.source.startswith("live") or self.source.startswith("linein"):
if self.cleansource == "0":
self.type = ScheduleEntryType.LIVE_0
elif self.cleansource == "1":
self.type = ScheduleEntryType.LIVE_1
elif self.cleansource == "2":
self.type = ScheduleEntryType.LIVE_2
elif self.cleansource == "3":
self.type = ScheduleEntryType.LIVE_3
elif self.cleansource == "4":
self.type = ScheduleEntryType.LIVE_4
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_all():
# fetching all entries
all_entries = DB.session.query(ScheduleEntry).filter(ScheduleEntry.fallback_type == 0).order_by(ScheduleEntry.entry_start).all()
cnt = 0
for entry in all_entries:
entry.programme_index = cnt
cnt = cnt + 1
return all_entries
@staticmethod
def select_act_programme(include_act_playing = True):
# fetching all from today to ..
today = datetime.date.today()
all_entries = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start >= today, ScheduleEntry.fallback_type == 0).order_by(ScheduleEntry.entry_start).all()
cnt = 0
for entry in all_entries:
entry.programme_index = cnt
cnt = cnt + 1
return all_entries
# ------------------------------------------------------------------------------------------ #
@staticmethod
def truncate():
all_entries = DB.session.query(ScheduleEntry).filter().order_by(ScheduleEntry.entry_start).all()
for a in all_entries:
a.delete()
DB.session.commit()
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_next_manual_entry_num():
max_manual_entry_num = DB.session.query(func.max(ScheduleEntry.entry_num)).filter(ScheduleEntry.schedule_id == 0).first()
if max_manual_entry_num[0] is None:
return 0
else:
return int(max_manual_entry_num[0])+1
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_upcoming(datefrom=datetime.datetime.now()):
upcomingtracks = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start > datefrom).order_by(ScheduleEntry.entry_start).all()
return upcomingtracks
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_one(playlist_id, entry_num):
return DB.session.query(ScheduleEntry).filter(ScheduleEntry.playlist_id == playlist_id, ScheduleEntry.entry_num == entry_num).first()
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_one_playlist_entry_for_show(schedule_id, playlist_type, entry_num):
return DB.session.query(ScheduleEntry).filter(ScheduleEntry.schedule_id == schedule_id, ScheduleEntry.fallback_type == playlist_type, ScheduleEntry.entry_num == entry_num).first()
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_playlist(playlist_id):
return DB.session.query(ScheduleEntry).filter(ScheduleEntry.playlist_id == playlist_id).order_by(ScheduleEntry.entry_start).all()
@staticmethod
def drop_the_future(timedelta):
then = datetime.datetime.now() + timedelta
#DB.session.delete(ScheduleEntry).filter(ScheduleEntry.entry_start >= then)
# is this really necessary?
future_entries = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start > then)
for e in future_entries:
e.delete()
DB.session.commit()
def getChannel(self):
if self.type == self.type.FILESYSTEM:
return LiquidSoapIDs.IN_FILE
if self.type == self.type.LIVE_0 or self.type == self.type.LIVE_1 or self.type == self.type.LIVE_2 or self.type == self.type.LIVE_3 or self.type == self.type.LIVE_4:
print("LIST INDICES MUST BE INTEGERS OR SLICES: " + str(self.cleansource))
return LqsIDs.in_linein[self.cleansource]
# "input_linein_"+self.cleansource # .cleanprotocol[8]
if self.type == self.type.STREAM:
return LiquidSoapIDs.IN_STREAM.value
# ------------------------------------------------------------------------------------------ #
def __str__(self):
return "Showentry starts @ " + str(self.entry_start) + " and plays " + self.source
# ------------------------------------------------------------------------------------------ #
class TrackService(DB.Model, AuraDatabaseModel):
__tablename__ = 'trackservice'
trackservice_id = Column(Integer, primary_key=True, autoincrement=True)
playlist_id = Column(Integer, nullable=False)
entry_num = Column(Integer, nullable=False)
source = Column(String(255), nullable=False)
start = Column(DateTime, nullable=False, default=func.now())
__table_args__ = (
ForeignKeyConstraint(['playlist_id', 'entry_num'], ['schedule_entry.playlist_id', 'schedule_entry.entry_num']),
)
#schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
# trackservice_entry = relationship("ScheduleEntry", foreign_keys=[playlist_id, entry_num], lazy="joined")
schedule_entry = relationship("ScheduleEntry", primaryjoin="and_(TrackService.playlist_id==ScheduleEntry.playlist_id, TrackService.entry_num==ScheduleEntry.entry_num)", lazy="joined")
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_one(trackservice_id):
return DB.session.query(TrackService).filter(TrackService.trackservice_id == trackservice_id).first()
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_all():
# fetching all entries
all_entries = DB.session.query(TrackService).filter().all()
return all_entries
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_by_day(day):
day_plus_one = day + datetime.timedelta(days=1)
tracks = DB.session.query(TrackService).filter(TrackService.start >= str(day), TrackService.start < str(day_plus_one)).all()
return tracks
@staticmethod
# ------------------------------------------------------------------------------------------ #
def select_by_range(from_day, to_day):
tracks = DB.session.query(TrackService).filter(TrackService.start >= str(from_day),
TrackService.start < str(to_day)).all()
return tracks
# ------------------------------------------------------------------------------------------ #
def __str__(self):
return "TrackServiceID: #" + str(self.trackservice_id) + " playlist_id: " + str(self.playlist_id) + " started @ " + str(self.start) + " and played " + self.source
# ------------------------------------------------------------------------------------------ #
# class TrackServiceSchedule(db.Model, AuraDatabaseModel):
# """
# Trackservice is tracking every schedule.
# """
# __tablename__ = 'trackservice_schedule'
#
# # primary and foreign keys
# ts_schedule_id = Column(Integer, primary_key=True, autoincrement=True)
# schedule_id = Column(Integer, ForeignKey("schedule.schedule_id"))
#
# schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
#
# # ------------------------------------------------------------------------------------------ #
# @staticmethod
# def select_one(schedule_id):
# # damn BAND-AID
# # db.session.commit()
#
# return db.session.query(ScheduleEntry).filter(TrackServiceSchedule.schedule_id == schedule_id).first()
#
# # ------------------------------------------------------------------------------------------ #
# class TrackServiceScheduleEntry(db.Model, AuraDatabaseModel):
# """
# And a schedule can have multiple entries
# """
# __tablename__ = 'trackservice_entry'
#
# # primary and foreign keys. the foreign keys here can be null, because of fallback stuff
# ts_entry_id = Column(Integer, primary_key=True, autoincrement=True)
# ts_schedule_id = Column(Integer, ForeignKey("trackservice_schedule.ts_schedule_id"), nullable=True)
# playlist_id = Column(Integer, nullable=True)
# entry_num = Column(Integer, nullable=True)
#
# fallback = Column(Boolean, default=False)
# fallback_start = Column(DateTime, nullable=True, default=None)
# source = Column(String(256), nullable=True, default=None)
#
# # foreign key definitions
# __table_args__ = (
# ForeignKeyConstraint(['playlist_id', 'entry_num'], ['schedule_entry.playlist_id', 'schedule_entry.entry_num']),
# )
#
# trackservice_schedule = relationship("TrackServiceSchedule", foreign_keys=[ts_schedule_id], lazy="joined")
# #trackservice_entry = relationship("ScheduleEntry", foreign_keys=[playlist_id, entry_num], lazy="joined")
# trackservice_entry = relationship("ScheduleEntry", primaryjoin="and_(TrackServiceScheduleEntry.playlist_id==ScheduleEntry.playlist_id, TrackServiceScheduleEntry.entry_num==ScheduleEntry.entry_num)" , lazy="joined")
#
# @staticmethod
# def select_all():
# return db.session.query(TrackServiceScheduleEntry).filter().all()
#AuraDatabaseModel.recreate_db(systemexit=True)
from sqlalchemy.ext.declarative import declarative_base
from flask_sqlalchemy import SQLAlchemy
# from flask_babel import Babel
from flask import Flask
from modules.base.config import ConfigReader # pylint: disable=import-error
def create_app(install_dir, uri):
"""
creates flask app context
:param install_dir: Installdir of Aura
:param uri: Database connection uri
:return: Flask object
"""
app = Flask(__name__, template_folder=install_dir + '/modules/web/templates')
app.config["SQLALCHEMY_DATABASE_URI"] = uri
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
return app
def create_database():
"""
creates sqlalchemy database connection
:return: SQLAlchemy object
"""
#### load config ####
config = ConfigReader()
config.load_config()
#### read config ####
install_dir = config.get(str("install_dir"))
db_name = config.get(str("db_name"))
db_user = config.get(str("db_user"))
db_pass = config.get(str("db_pass"))
db_host = config.get(str("db_host"))
#### create database conn ####
uri = "mysql://"+db_user+":"+db_pass+"@"+db_host+"/"+db_name+"?charset=utf8"
app = create_app(install_dir, uri)
database = SQLAlchemy(app)
return app, database
Base = declarative_base()
APP, DB = create_database()
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import redis
import time
import datetime
import json
import re
import uuid
class RedisStateStore(object):
"""Store and get Reports from redis"""
def __init__(self, config, **redis_kwargs):
"""The default connection parameters are: host='localhost', port=6379, db=0"""
self.db = redis.Redis(host=config.get("redis_host"), port=config.get("redis_port"), db=config.get("redis_db"))
self.channel = '*'
self.section = '*'
self.separator = '_'
self.daily = False
# ------------------------------------------------------------------------------------------ #
def set_channel(self, channel):
"""
Kanal setzen
@type channel: string
@param channel: Kanal
"""
self.channel = channel
# ------------------------------------------------------------------------------------------ #
def set_section(self, section):
"""
Sektion setzen
@type section: string
@param section: Sektion
"""
self.section = section
# ------------------------------------------------------------------------------------------ #
def set_alive_state(self):
"""
Alive Funktion - alle 20 Sekunden melden, dass man noch am Leben ist
"""
self.set_state('alive', 'Hi', 21)
# ------------------------------------------------------------------------------------------ #
def get_alive_state(self, channel):
"""
Alive Status eines Channels ermitteln
@type channel: string
@param channel: der Channel
@rtype: string/None
@return: Ein String, oder None, bei negativem Ergebnis
"""
return self.get_state('alive', channel)
# ------------------------------------------------------------------------------------------ #
def set_state(self, name, value, expires=None, channel=None):
"""
Setzt einen Status
@type name: string
@param name: Name des state
@type value: string
@param value: Wert
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
key = self.__create_key__(channel + 'State', name)
if value == "":
self.db.delete(key)
else:
# publish on channel
message = json.dumps({'eventname':name, 'value': value})
self.db.publish(channel + 'Publish', message)
# store in database
self.db.set(key, value)
if(expires):
self.db.expire(key, 21)
# ------------------------------------------------------------------------------------------ #
def get_state(self, name, channel):
"""
Holt einen Status
@type name: string
@param name: Name des state
@type channel: string
@param channel: Kanal (optional)
"""
key = self.__create_key__(channel + 'State', name)
return self.db.get(key)
# ------------------------------------------------------------------------------------------ #
def queue_add_event(self, eventtime, name, value, channel=None):
"""
Kündigt einen Event an
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
expire = int(time.mktime(timeevent.timetuple()) - time.time()) + 60
self.__set_event__(name, eventtime, value, 'Evqueue', 'evqueue', expire, channel)
# ------------------------------------------------------------------------------------------ #
def queue_remove_events(self, name=None, channel=None):
"""
Löscht Events
@type name: string
@param name: Name des Events
@type channel: string
@param channel: Kanal (optional)
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
for delkey in keys:
self.db.delete(delkey)
# ------------------------------------------------------------------------------------------ #
def fire_event(self, name, value, channel=None):
"""
Feuert einen Event
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
eventtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M")
self.__set_event__(name, eventtime, value, 'Event', 'events', 60, channel)
# ------------------------------------------------------------------------------------------ #
def __set_event__(self, name, eventtime, value, type, namespace, expire, channel=None):
"""
Feuert einen Event
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
key = self.__create_key__(channel + type, eventtime, name)
value['starts'] = eventtime[0:16]
value['eventchannel'] = channel
value['eventname'] = name
self.db.hset(key, namespace, value)
self.db.expire(key, expire)
# ------------------------------------------------------------------------------------------ #
def get_event_queue(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self.__get_entries__(keys, 'evqueue')
return entries
# ------------------------------------------------------------------------------------------ #
def get_events(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Event_' if channel else '*Event_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self.__get_entries__(keys, 'events')
return entries
# ------------------------------------------------------------------------------------------ #
def get_next_event(self, name=None, channel=None):
"""
Holt den aktuellsten Event
@type channel: string
@param channel: Kanal (optional)
@rtype: dict/boolean
@return: ein Event oder False
"""
events = self.get_event_queue(name, channel)
if len(events) > 0:
result = events.pop(0)
else:
result = False
return result
# ------------------------------------------------------------------------------------------ #
def store(self, level, value):
"""
Hash speichern
@type level: string
@param level: der errorlevel
@type value: dict
@param value: Werte als dict
"""
microtime = str(time.time())
value['microtime'] = microtime
value['level'] = level
key = self.__create_key__(self.channel, self.section, level, microtime, str(uuid.uuid1()))
self.db.hset(key, self.channel, value)
self.db.expire(key, 864000)
# ------------------------------------------------------------------------------------------ #
def __get_keys__(self, level ='*'):
"""
Redis-Keys nach Suchkriterium ermitteln
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Die Keys auf die das Suchkriterium zutrifft
"""
key = self.__create_key__(self.channel, self.section, level)
microtime = str(time.time())
search = microtime[0:4] + '*' if self.daily else '*'
return self.db.keys(key + self.separator + '*')
# ------------------------------------------------------------------------------------------ #
def __create_key__(self, *args):
"""
Key erschaffen - beliebig viele Argumente
@rtype: string
@return: Der key
"""
return self.separator.join(args)
def get_entries(self, level ='*'):
"""
Liste von Hashs nach Suchkriterium erhalten
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Redis Hashs
"""
def tsort(x,y):
if float(x.split('_',4)[3]) > float(y.split('_',4)[3]):
return 1
elif float(x.split('_',4)[3]) < float(y.split('_',4)[3]):
return -1
else:
return 0
keys = self.__get_keys__(level)
keys.sort(tsort)
entries = self.__get_entries__(keys, self.channel)
entries = sorted(entries, key=lambda k: k['microtime'], reverse=True)
return entries
# ------------------------------------------------------------------------------------------ #
def __get_entries__(self, keys, channel):
entries = []
for key in keys:
entry = self.db.hget(key,channel)
entry = json.dumps(entry.decode('utf-8'))
if not (entry is None):
try:
entry = entry.decode('utf-8').replace('None','"None"')
entry = re.sub("########[^]]*########", lambda x:x.group(0).replace('\"','').replace('\'',''),entry.replace("\\\"","########").replace("\\'","++++++++").replace("'",'"').replace('u"','"').replace('"{','{').replace('}"','}')).replace("########","\"")
entry = json.loads(entry)
entry['key'] = key
entries.append(entry)
except:
pass
return entries
# ------------------------------------------------------------------------------------------ #
def publish(self, channel, message):
subscriber_count = self.db.execute_command('PUBSUB', 'NUMSUB', channel)
if channel.lower().find("reply") < 0 and subscriber_count[1] == 0:
raise Exception("No subscriber! Is Aura daemon running?")
self.db.publish(channel, message)
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
from enum import Enum
class TerminalColors(Enum):
HEADER = "\033[95m"
RED = "\033[31m"
GREEN = "\033[32m"
ORANGE = "\033[33m"
BLUE = "\033[34m"
PINK = "\033[35m"
CYAN = "\033[36m"
WARNING = "\033[31m"
FAIL = "\033[41m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
ENDC = "\033[0m"
class RedisChannel(Enum):
STANDARD = "aura"
DPE_REPLY = "delete_playlist_entry_reply"
FNP_REPLY = "fetch_new_programme_reply"
GAP_REPLY = "get_act_programme_reply"
GCS_REPLY = "get_connection_status_reply"
GNF_REPLY = "get_next_file_reply"
IPE_REPLY = "insert_playlist_entry_reply"
IP_REPLY = "init_player_reply"
MPE_REPLY = "move_playlist_entry_reply"
PMQ_REPLY = "print_message_queue_reply"
RDB_REPLY = "recreate_database_reply"
SNF_REPLY = "get_next_file_reply"
class ScheduleEntryType(Enum):
# enumeration with names of liquidsoap inputs
FILESYSTEM = "input_fs"
STREAM = "input_stream"
LIVE_0 = "input_linein_0"
LIVE_1 = "input_linein_1"
LIVE_2 = "input_linein_2"
LIVE_3 = "input_linein_3"
LIVE_4 = "input_linein_4"
class LiquidSoapIDs(Enum):
IN_LINEIN_0 = "input_linein_0"
IN_LINEIN_1 = "input_linein_1"
IN_LINEIN_2 = "input_linein_2"
IN_LINEIN_3 = "input_linein_3"
IN_LINEIN_4 = "input_linein_4"
IN_STREAM = "input_stream"
IN_FILE = "input_fs"
OUT_STREAM_0 = "output_stream_0"
OUT_STREAM_1 = "output_stream_1"
OUT_STREAM_2 = "output_stream_2"
OUT_STREAM_3 = "output_stream_3"
OUT_STREAM_4 = "output_stream_4"
OUT_LINEOUT_0 = "output_lineout_0"
OUT_LINEOUT_1 = "output_lineout_1"
OUT_LINEOUT_2 = "output_lineout_2"
OUT_LINEOUT_3 = "output_lineout_3"
OUT_LINEOUT_4 = "output_lineout_4"
OUT_RECORDER_0 = "output_recorder_0"
OUT_RECORDER_1 = "output_recorder_1"
OUT_RECORDER_2 = "output_recorder_2"
OUT_RECORDER_3 = "output_recorder_3"
OUT_RECORDER_4 = "output_recorder_4"
IN_MIXER = "input_mixer"
class LqsIDs():
in_linein = [ LiquidSoapIDs.IN_LINEIN_0, LiquidSoapIDs.IN_LINEIN_1, LiquidSoapIDs.IN_LINEIN_2, LiquidSoapIDs.IN_LINEIN_3, LiquidSoapIDs.IN_LINEIN_4 ]
in_stream = [ LiquidSoapIDs.IN_STREAM ]
in_file = [ LiquidSoapIDs.IN_FILE ]
out_lineout = [ LiquidSoapIDs.OUT_LINEOUT_0, LiquidSoapIDs.OUT_LINEOUT_1, LiquidSoapIDs.OUT_LINEOUT_2, LiquidSoapIDs.OUT_LINEOUT_3, LiquidSoapIDs.OUT_LINEOUT_4 ]
out_streams = [ LiquidSoapIDs.OUT_STREAM_0, LiquidSoapIDs.OUT_STREAM_1, LiquidSoapIDs.OUT_STREAM_2, LiquidSoapIDs.OUT_STREAM_3, LiquidSoapIDs.OUT_STREAM_4 ]
out_recorder = [ LiquidSoapIDs.OUT_RECORDER_0, LiquidSoapIDs.OUT_RECORDER_1, LiquidSoapIDs.OUT_RECORDER_2, LiquidSoapIDs.OUT_RECORDER_3, LiquidSoapIDs.OUT_RECORDER_4 ]
class FallbackType(Enum):
SHOW = "show" # the first played when the show playlist fails
STATION = "station" # the last played when everything else fails
TIMESLOT = "timeslot" # the second played when show fallback fails
class TimerType(Enum):
SWITCH = "switch"
FADEIN = "fadein"
FADEOUT = "fadeout"
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
class NoProgrammeLoadedException(Exception):
pass
class LQConnectionError(Exception):
pass
class RedisConnectionException(Exception):
pass
class PlaylistException(Exception):
pass
class MailingException(Exception):
pass
class DiskSpaceException(Exception):
pass
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import json
import logging
class ExceptionLogger:
logger = None
error_data = None
job_result = None
def __init__(self):
self.logger = logging.getLogger("AuraEngine")
# ------------------------------------------------------------------------------------------ #
def __get_error__(self, job, errornumber, data):
"""
Privat: Ermittelt Fehlermeldung, Job-Name (Klassenmethode) und Fehlercode für den Job aus error/controller_error.js
@type errornumber: string
@param errornumber: Die interne Fehlernummer der aufrufenden Methode
"""
if data is None:
data = {}
if type(data) == type(str()):
data = json.loads(data)
has_data = isinstance(data, (dict)) and len(data) > 0
if job in self.error_data:
err_msg = self.error_data[job][errornumber]
err_id = self.error_data[job]['id'] + str(errornumber)
if has_data:
for key in data.keys():
err_msg = err_msg.replace('::' + key + '::', str(data[key]))
data['message'] = err_msg
data['job'] = job
data['code'] = err_id
return data
# ------------------------------------------------------------------------------------------ #
def success(self, job, data=None, errnum='00', value=''):
"""
Erfolgsmeldung loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
@type section: string
@param section: Gültigkeitsbereich
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'success',
'job': error['job'],
'value': value}
self.logger.debug(job + " successfully done " + str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def info(self, job, data=None, errnum='01', value=''):
"""
Info loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
@type section: string
@param section: Gültigkeitsbereich
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'info',
'job': error['job'],
'value': value}
self.logger.info(str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def warning(self, job, data=None, errnum='01', value=''):
"""
Warnung loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'warning',
'job': error['job'],
'value': value}
self.logger.warning(str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def error(self, job, data=None, errnum='01', value=''):
"""
Error loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'error',
'job': error['job'],
'value': value}
self.logger.error(str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def fatal(self, job, data=None, errnum='01', value='', section='execjob'):
"""
Fatal error loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
@type section: string
@param section: Gültigkeitsbereich
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'fatal',
'job': error['job'],
'value': value,
'section': section}
self.logger.critical(str(self.job_result))
\ No newline at end of file
#
# engine
#
# Playout Daemon for autoradio project
#
# Copyright 2014 BFR <info@freie-radios.de>
# Copyright (C) 2017-2018 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 General Public License as published by
# the Free Software Foundation; Version 3 of the License
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, the license can be downloaded here:
#
# http://www.gnu.org/licenses/gpl.html
# Meta
__version__ = '0.1.1'
__license__ = "GNU General Public License (GPL) Version 3"
__version_info__ = (0, 1, 1)
__author__ = 'Michael Liebler <michael-liebler@radio-z.net>'
"""
Aura Config Reader
"""
import os
import sys
import socket
import logging
from configparser import ConfigParser
class ConfigReader(object):
ini_path = ""
logger = None
def __init__(self):
self.logger = logging.getLogger("AuraEngine")
def set(self, key, value):
"""
Set a property
@type key: string
@param key: The Key
@type value: mixed
@param value: Beliebiger Wert
"""
try:
self.__dict__[key] = int(value)
except:
self.__dict__[key] = str(value)
# ------------------------------------------------------------------------------------------ #
def get(self, key, default=None):
"""
get a loaded property
@type key: string
@param key: Der Key
@type default: mixed
@param default: Beliebiger Wert
"""
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
if key == "loglevel":
loglvl = self.__dict__[key]
if loglvl == "debug":
return logging.DEBUG
elif loglvl == "info":
return logging.INFO
elif loglvl == "warning":
return logging.WARNING
elif loglvl == "error":
return logging.ERROR
else:
return logging.CRITICAL
if key == "debug":
return self.__dict__[key].count("y")
return self.__dict__[key]
# ------------------------------------------------------------------------------------------ #
def load_config(self):
"""
Set config defaults and load settings from file
:return:
"""
self.ini_path = self.get('configpath', '/etc/aura/engine.ini')
if not os.path.isfile(self.ini_path):
self.logger.critical(self.ini_path + " not found :(")
sys.exit(1)
# INI einlesen
f = open(self.ini_path, 'r')
ini_str = f.read()
f.close()
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.set("install_dir", os.path.realpath(__file__ + "../../../.."))
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import json
from libraries.enum.auraenumerations import RedisChannel, TerminalColors
from modules.communication.redis.adapter import ClientRedisAdapter, ServerRedisAdapter
from modules.communication.redis.messenger import RedisMessenger
from libraries.database.broadcasts import AuraDatabaseModel
class Padavan:
args = None
config = None
lsc = None
zmqclient = None
redisclient = None
stringreply = ""
# ------------------------------------------------------------------------------------------ #
def __init__(self, args, config):
self.args = args
self.config = config
# ------------------------------------------------------------------------------------------ #
def meditate(self):
if self.args.fetch_new_programme:
self.fetch_new_programme()
elif self.args.get_active_mixer:
self.get_active_mixer()
elif self.args.get_mixer_status:
self.get_mixer_status()
elif self.args.get_act_programme:
self.get_act_programme()
elif self.args.get_connection_status:
self.get_connection_status()
elif self.args.shutdown:
self.shutdown()
elif self.args.redis_message:
self.redis_message(self.args.redis_message[0], self.args.redis_message[1])
elif self.args.select_mixer != -1:
self.select_mixer(self.args.select_mixer)
elif self.args.deselect_mixer != -1:
self.select_mixer(self.args.deselect_mixer, False)
elif self.args.set_volume:
self.set_volume(self.args.set_volume[0], self.args.set_volume[1])
elif self.args.print_message_queue:
self.print_message_queue()
elif self.args.get_file_for:
self.get_next_file(self.args.get_file_for)
elif self.args.set_file_for:
self.set_next_file(self.args.set_file_for[0], self.args.set_file_for[1])
elif self.args.now_playing:
print("")
elif self.args.init_player:
self.init_player()
elif self.args.recreatedb:
self.recreatedb()
# else:
# raise Exception("")
# init liquid => faster exec time, when loading at runtime just what is needed
# ------------------------------------------------------------------------------------------ #
def init_liquidsoap_communication(self):
# import
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
# init liquidsoap communication
self.lsc = LiquidSoapCommunicator(self.config)
# enable connection
self.lsc.enable_transaction()
# ------------------------------------------------------------------------------------------ #
def destroy_liquidsoap_communication(self):
# enable connection
self.lsc.disable_transaction()
# ------------------------------------------------------------------------------------------ #
def init_redis_communication(self, with_server=False):
self.redisclient = ClientRedisAdapter(self.config)
if with_server:
self.redisserver = ServerRedisAdapter(self.config)
# ------------------------------------------------------------------------------------------ #
def send_redis(self, channel, message):
self.init_redis_communication()
self.redisclient.publish(channel, message)
# ------------------------------------------------------------------------------------------ #
def send_and_wait_redis(self, channel, message, reply_channel):
self.init_redis_communication(True)
self.redisclient.publish(channel, message)
return self.redisserver.listen_for_one_message(reply_channel.value)
# ------------------------------------------------------------------------------------------ #
def shutdown(self):
self.send_redis("aura", "shutdown")
self.stringreply = "Shutdown message sent!"
# ------------------------------------------------------------------------------------------ #
def fetch_new_programme(self):
json_reply = self.send_and_wait_redis("aura", "fetch_new_programme", RedisChannel.FNP_REPLY)
if json_reply != "":
actprogramme = json.loads(json_reply)
self.print_programme(actprogramme)
else:
print("No programme fetched")
# ------------------------------------------------------------------------------------------ #
def get_act_programme(self):
json_reply = self.send_and_wait_redis("aura", "get_act_programme", RedisChannel.GAP_REPLY)
actprogramme = json.loads(json_reply)
self.print_programme(actprogramme)
# ------------------------------------------------------------------------------------------ #
def get_connection_status(self):
json_reply = self.send_and_wait_redis("aura", "get_connection_status", RedisChannel.GCS_REPLY)
connection_status = json.loads(json_reply)
self.print_connection_status(connection_status)
# ------------------------------------------------------------------------------------------ #
def print_programme(self, programme):
cnt = 1
for show in programme:
for entry in show["playlist"]:
self.stringreply += str(cnt) + \
" --- schedule id #" + str(show["schedule_id"]) + "." + str(entry["entry_num"]) + \
" - show: " + show["show_name"] + \
" - starts @ " + entry["entry_start"] + \
" - plays " + str(entry["source"]) + "\n"
cnt = cnt + 1
# ------------------------------------------------------------------------------------------ #
def print_connection_status(self, connection_status):
if connection_status["pv"]:
self.stringreply = "Connection to pv: " + TerminalColors.GREEN.value + " " + str(connection_status["pv"]) + TerminalColors.ENDC.value
else:
self.stringreply = "Connection to pv: " + TerminalColors.RED.value + " " + str(connection_status["pv"]) + TerminalColors.ENDC.value
if connection_status["db"]:
self.stringreply += "\nConnection to db: " + TerminalColors.GREEN.value + " " + str(connection_status["db"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to db: " + TerminalColors.RED.value + " " + str(connection_status["db"]) + TerminalColors.ENDC.value
if connection_status["lqs"]:
self.stringreply += "\nConnection to lqs: " + TerminalColors.GREEN.value + " " + str(connection_status["lqs"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to lqs: " + TerminalColors.RED.value + " " + str(connection_status["lqs"]) + TerminalColors.ENDC.value
if connection_status["lqsr"]:
self.stringreply += "\nConnection to lqsr: " + TerminalColors.GREEN.value + " " + str(connection_status["lqsr"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to lqsr: " + TerminalColors.RED.value + " " + str(connection_status["lqsr"]) + TerminalColors.ENDC.value
if connection_status["tank"]:
self.stringreply += "\nConnection to tank: " + TerminalColors.GREEN.value + " " + str(connection_status["tank"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to tank: " + TerminalColors.RED.value + " " + str(connection_status["tank"]) + TerminalColors.ENDC.value
if connection_status["redis"]:
self.stringreply += "\nConnection to redis: " + TerminalColors.GREEN.value + " " + str(connection_status["redis"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to redis: " + TerminalColors.RED.value + " " + str(connection_status["redis"]) + TerminalColors.ENDC.value
# ------------------------------------------------------------------------------------------ #
def init_player(self):
self.stringreply = self.send_and_wait_redis("aura", "init_player", RedisChannel.IP_REPLY)
# ------------------------------------------------------------------------------------------ #
def recreatedb(self):
print("YOU WILL GET PROBLEMS DUE TO DATABASE BLOCKING IF aura.py IS RUNNING! NO CHECKS IMPLEMENTED SO FAR!")
x = AuraDatabaseModel()
x.recreate_db()
self.stringreply = "Database recreated!"
# ------------------------------------------------------------------------------------------ #
def redis_message(self, channel, message):
self.send_redis(channel, message)
self.stringreply = "Message '"+message+"' sent to channel '"+channel+"'"
# ------------------------------------------------------------------------------------------ #
def print_message_queue(self):
self.stringreply = self.send_and_wait_redis("aura", "print_message_queue", RedisChannel.PMQ_REPLY)
# LIQUIDSOAP #
# ------------------------------------------------------------------------------------------ #
def select_mixer(self, mixername, activate=True):
# init lqs
self.init_liquidsoap_communication()
# select mixer and return the feedback
self.stringreply = self.lsc.channel_activate(mixername, activate)
# disable connection
self.destroy_liquidsoap_communication()
# ------------------------------------------------------------------------------------------ #
def set_volume(self, mixernumber, volume):
# init lqs and enable comm
self.init_liquidsoap_communication()
self.stringreply = self.lsc.set_volume(mixernumber, volume)
# disable connection
self.destroy_liquidsoap_communication()
# ------------------------------------------------------------------------------------------ #
def get_active_mixer(self):
self.init_liquidsoap_communication()
am = self.lsc.get_active_mixer()
if len(am) == 0:
self.destroy_liquidsoap_communication()
raise Exception("Guru recognized a problem: No active source!!!")
self.stringreply = str(am)
# disable connection
self.destroy_liquidsoap_communication()
# ------------------------------------------------------------------------------------------ #
def get_mixer_status(self):
self.init_liquidsoap_communication()
status = self.lsc.get_mixer_status()
for k, v in status.items():
self.stringreply += "source: " + k + "\t status: " + v + "\n"
# disable connection
self.destroy_liquidsoap_communication()
# REDIS #
# ------------------------------------------------------------------------------------------ #
def get_next_file(self, type):
# redis = RedisMessenger()
# next_file = redis.get_next_file_for(type)
# if next_file == "":
# next_file = "/var/audio/blank.flac"
# self.stringreply = next_file
#self.send_redis("aura", "set_next_file " + type)
next_file = self.send_and_wait_redis("aura", "get_next_file " + type, RedisChannel.GNF_REPLY)
self.stringreply = next_file
# ------------------------------------------------------------------------------------------ #
def set_next_file(self, type, file):
#from modules.communication.redis.messenger import RedisMessenger
#redis = RedisMessenger()
#redis.set_next_file_for(type, file)
self.send_redis("aura", "set_next_file " + type + " " + file)
self.stringreply = "Set "+file+" for fallback '"+type+"'"
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import urllib
import logging
import json
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
from libraries.database.broadcasts import ScheduleEntry
from libraries.base.config import AuraConfig
# ------------------------------------------------------------------------------------------ #
class ConnectionTester(AuraConfig):
# ------------------------------------------------------------------------------------------ #
def __init__(self):
super(ConnectionTester, self).__init__()
# ------------------------------------------------------------------------------------------ #
def get_connection_status(self):
status = dict()
status["db"] = self.test_db_conn()
status["pv"] = self.test_pv_conn()
status["lqs"] = self.test_lqs_conn()
status["lqsr"] = False # self.test_lqsr_conn()
status["tank"] = self.test_tank_conn()
status["redis"] = self.test_redis_conn()
return json.dumps(status)
# ------------------------------------------------------------------------------------------ #
def test_db_conn(self):
try:
ScheduleEntry.select_all()
except:
return False
return True
# ------------------------------------------------------------------------------------------ #
def test_lqs_conn(self):
try:
lsc = LiquidSoapCommunicator(self.config)
lsc.get_mixer_status()
return True
except Exception as e:
return False
# ------------------------------------------------------------------------------------------ #
def test_lqsr_conn(self):
try:
lsc = LiquidSoapCommunicator(self.config)
lsc.get_recorder_status()
return True
except Exception as e:
return False
# ------------------------------------------------------------------------------------------ #
def test_pv_conn(self):
return self.test_url_connection(self.config.get("calendarurl"))
# ------------------------------------------------------------------------------------------ #
def test_tank_conn(self):
# test load of playlist 1
return self.test_url_connection(self.config.get("importerurl")+"1")
# ------------------------------------------------------------------------------------------ #
def test_redis_conn(self):
from modules.communication.redis.adapter import ClientRedisAdapter
try:
cra = ClientRedisAdapter()
cra.publish("aura", "status")
except:
return False
return True
def test_url_connection(self, url):
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
response.read()
except Exception as e:
return False
return True
\ No newline at end of file
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import socket
import urllib.parse
import configparser
import logging
from multiprocessing import Lock
from libraries.exceptions.auraexceptions import LQConnectionError
"""
LiquidSoapClient Class
Connects to a LiquidSoap instance over a socket and sends commands to it
"""
class LiquidSoapClient:
mutex = None
logger = None
debug = False
socket_path = ""
disable_logging = True
def __init__(self, config, socket_filename):
"""
Constructor
@type socket_path: string
@param socket_path: Der Pfad zum Socket des Liquidsoap-Scripts
"""
self.logger = logging.getLogger("AuraEngine")
self.socket_path = config.get('socketdir') + '/' + socket_filename
self.logger.debug("LiquidSoapClient using socketpath: " + self.socket_path)
# init
self.mutex = Lock()
self.connected = False
self.can_connect = True
self.message = ''
self.socket = None
self.metareader = configparser.ConfigParser()
# ------------------------------------------------------------------------------------------ #
def connect(self):
"""
Verbindung herstellen
"""
try:
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.connect(self.socket_path)
except socket.error as e:
msg = "Cannot connect to socketpath " + self.socket_path + ". Reason: "+str(e)
self.logger.critical(msg)
self.can_connect = False
self.connected = False
# raise e
else:
self.can_connect = True
self.connected = True
return True
# ------------------------------------------------------------------------------------------ #
def is_connected(self):
return self.connected
# ------------------------------------------------------------------------------------------ #
def write(self, data):
"""
Auf den Socket schreiben
@type data: string
@param data: Der String der gesendet wird
"""
if self.connected:
self.socket.sendall(data.decode("UTF-8"))
# ------------------------------------------------------------------------------------------ #
def read_all(self, timeout=2):
"""
Vom Socket lesen, bis dieser "END" sendet
@type timeout: int
@param timeout: Ein optionales Timeout
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
# make socket non blocking
# self.client.setblocking(0)
data = '';
try:
# set timeout
self.socket.settimeout(timeout)
# acquire the lock
self.mutex.acquire()
while True:
data += self.socket.recv(1).decode("utf-8")
# receive as long as we are not at the END or recv a Bye! from liquidsoap
if data.find("END\r\n") != -1 or data.find("Bye!\r\n") != -1:
data.replace("END\r\n", "")
break
# release the lock
self.mutex.release()
except Exception as e:
self.logger.error(str(e))
self.mutex.release()
return data
# ------------------------------------------------------------------------------------------ #
def read(self):
"""
read from socket and store return value in self.message
@rtype: string
@return: The answer of liquidsoap server
"""
if self.connected:
ret = self.read_all().splitlines()
try:
last = ret.pop() # pop out end
if len(ret) > 1:
self.message = str.join(" - ", ret)
elif len(ret) == 1:
self.message = ret[0]
if last == "Bye!":
self.message = last
except Exception as e:
self.logger.error(str(e))
return self.message
# ------------------------------------------------------------------------------------------ #
def close(self):
"""
Quit senden und Verbindung schließen
"""
if self.connected:
message = "quit\r"
self.socket.sendall(message.decode("UTF-8"))
self.socket.close()
self.connected = False
# ------------------------------------------------------------------------------------------ #
def command(self, namespace, command, param=""):
"""
Kommando an Liquidosap senden
@type command: string
@param command: Kommando
@type namespace: string
@param namespace: Namespace/Kanal der angesprochen wird
@type param: mixed
@param param: ein optionaler Parameter
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
param = (param.strip() if param.strip() == "" else " " + urllib.parse.unquote(param.strip()))
if self.connected:
# print namespace + '.' + command + param + "\n"
if namespace is "":
message = str(command) + str(param) + str("\n")
else:
message = str(namespace) + str(".") + str(command) + str(param) + str("\n")
try:
if not self.disable_logging:
self.logger.info("LiquidSoapClient sending to LiquidSoap Server: " + message[0:len(message)-1])
# send all the stuff over the socket to liquidsoap server
self.socket.sendall(message.encode())
if not self.disable_logging:
self.logger.debug("LiquidSoapClient waiting for reply from LiquidSoap Server")
# wait for reply
self.read()
if not self.disable_logging:
self.logger.info("LiquidSoapClient got reply: " + self.message)
except BrokenPipeError as e:
self.logger.error("Detected a problem with liquidsoap connection while sending: " + message + ". Reason: " + str(e) + "! Trying to reconnect.")
self.connect()
raise
except Exception as e:
self.logger.error("Unexpected error: " + str(e))
raise
return self.message
else:
msg = "LiquidsoapClient not connected to LiquidSoap Server"
self.logger.error(msg)
raise LQConnectionError(msg)
# ------------------------------------------------------------------------------------------ #
def help(self):
"""
get liquidsoap server help
@rtype: string
@return: the response of the liquidsoap server
"""
if self.connected:
self.command('help', '')
return self.message
# ------------------------------------------------------------------------------------------ #
def version(self):
"""
Liquidsoap get version
@rtype: string
@return: the response of the liquidsoap server
"""
if self.connected:
message = 'version'
self.command(message, '')
return self.message
# ------------------------------------------------------------------------------------------ #
def uptime(self):
"""
Liquidsoap get uptime
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
self.command('uptime', '')
return self.message
# ------------------------------------------------------------------------------------------ #
def byebye(self):
"""
Liquidsoap say byebye
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
self.command("", "quit")
return self.message
\ No newline at end of file
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import time
import logging
import json
from modules.communication.liquidsoap.playerclient import LiquidSoapPlayerClient
# from modules.communication.liquidsoap.recorderclient import LiquidSoapRecorderClient
from modules.communication.liquidsoap.initthread import LiquidSoapInitThread
from modules.communication.mail.mail import AuraMailer
from libraries.enum.auraenumerations import TerminalColors, ScheduleEntryType
from libraries.exceptions.auraexceptions import LQConnectionError
from libraries.database.broadcasts import TrackService
from libraries.exceptions.exception_logger import ExceptionLogger
from libraries.enum.auraenumerations import LqsIDs, LiquidSoapIDs
"""
LiquidSoapCommunicator Class
Uses LiquidSoapClient, but introduces more complex commands, transactions and error handling
"""
class LiquidSoapCommunicator(ExceptionLogger):
client = None
logger = None
transaction = 0
channels = None
scheduler = None
error_data = None
auramailer = None
is_liquidsoap_running = False
connection_attempts = 0
active_channel = None
disable_logging = False
fade_in_active = False
fade_out_active = False
# ------------------------------------------------------------------------------------------ #
def __init__(self, config):
"""
Constructor
"""
self.config = config
self.logger = logging.getLogger("AuraEngine")
self.client = LiquidSoapPlayerClient(config, "engine.sock")
# self.lqcr = LiquidSoapRecorderClient(config, "record.sock")
errors_file = self.config.get("install_dir") + "/errormessages/controller_error.js"
f = open(errors_file)
self.error_data = json.load(f)
f.close()
self.auramailer = AuraMailer(self.config)
self.is_liquidsoap_up_and_running()
# ------------------------------------------------------------------------------------------ #
def is_liquidsoap_up_and_running(self):
try:
self.uptime()
self.is_liquidsoap_running = True
except LQConnectionError as e:
self.logger.info("Liquidsoap is not running so far")
self.is_liquidsoap_running = False
except Exception as e:
self.logger.error("Cannot check if Liquidsoap is running. Reason: " + str(e))
self.is_liquidsoap_running = False
# ------------------------------------------------------------------------------------------ #
def set_volume(self, mixernumber, volume):
return self.__send_lqc_command__(self.client, "mixer", "volume", mixernumber, volume)
# ------------------------------------------------------------------------------------------ #
def get_active_mixer(self):
"""
get active mixer in liquidsoap server
:return:
"""
activeinputs = []
# enable more control over the connection
self.enable_transaction()
inputs = self.get_all_channels()
cnt = 0
for input in inputs:
status = self.__get_mixer_status__(cnt)
if "selected=true" in status:
activeinputs.append(input)
cnt = cnt + 1
self.disable_transaction()
return activeinputs
# ------------------------------------------------------------------------------------------ #
def get_active_channel(self):
"""
gets active channel from programme
:return:
"""
(show, active_entry) = self.scheduler.get_active_entry()
if active_entry is None:
return ""
return active_entry.type
# ------------------------------------------------------------------------------------------ #
def get_mixer_status(self):
inputstate = {}
self.enable_transaction()
inputs = self.get_all_channels()
cnt = 0
for input in inputs:
inputstate[input] = self.__get_mixer_status__(cnt)
cnt = cnt + 1
self.disable_transaction()
return inputstate
# ------------------------------------------------------------------------------------------ #
def get_mixer_volume(self, channel):
return False
# ------------------------------------------------------------------------------------------ #
def get_recorder_status(self):
self.enable_transaction(self.client)
recorder_state = self.__send_lqc_command__(self.client, "record", "status")
self.disable_transaction(self.client)
return recorder_state
# ------------------------------------------------------------------------------------------ #
def http_start_stop(self, start):
if start:
cmd = "start"
else:
cmd = "stop"
try:
self.enable_transaction()
self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_STREAM.value, cmd)
self.disable_transaction()
except LQConnectionError as e:
# we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further
pass
# ------------------------------------------------------------------------------------------ #
def recorder_stop(self):
self.enable_transaction()
for i in range(5):
if self.config.get("rec_" + str(i)) == "y":
self.__send_lqc_command__(self.client, "recorder", "stop", LqsIDs.out_recorder[i].value)
self.disable_transaction()
# ------------------------------------------------------------------------------------------ #
def recorder_start(self, num=-1):
if not self.is_liquidsoap_running:
if num==-1:
msg = "Want to start recorder, but LiquidSoap is not running"
else:
msg = "Want to start recorder " + str(num) + ", but LiquidSoap is not running"
self.logger.warning(msg)
return False
self.enable_transaction()
if num == -1:
self.recorder_start_all()
else:
self.recorder_start_one(num)
self.disable_transaction()
# ------------------------------------------------------------------------------------------ #
def recorder_start_all(self):
if not self.is_liquidsoap_running:
self.logger.warning("Want to start all recorder, but LiquidSoap is not running")
return False
self.enable_transaction()
for i in range(5):
self.recorder_start_one(i)
self.disable_transaction()
# ------------------------------------------------------------------------------------------ #
def recorder_start_one(self, num):
if not self.is_liquidsoap_running:
return False
if self.config.get("rec_" + str(num)) == "y":
returnvalue = self.__send_lqc_command__(self.client, LqsIDs.out_recorder[num].value, "status")
if returnvalue == "off":
self.__send_lqc_command__(self.client, LqsIDs.out_recorder[num].value, "start")
# ------------------------------------------------------------------------------------------ #
def fade_in(self, new_entry):
try:
fade_in_time = float(self.config.get("fade_in_time"))
if fade_in_time > 0:
self.fade_in_active = True
target_volume = new_entry.volume
step = fade_in_time / target_volume
self.logger.info("Starting to fading " + new_entry.type.value + " in. step is " + str(step) + "s. target volume is " + str(target_volume))
self.disable_logging = True
self.client.disable_logging = True
for i in range(target_volume):
self.channel_volume(new_entry.type.value, i + 1)
time.sleep(step)
self.logger.info("Finished with fading " + new_entry.type.value + " in.")
self.fade_in_active = False
if not self.fade_out_active:
self.disable_logging = False
self.client.disable_logging = False
except LQConnectionError as e:
self.logger.critical(str(e))
return True
# ------------------------------------------------------------------------------------------ #
def fade_out(self, old_entry):
try:
fade_out_time = float(self.config.get("fade_out_time"))
if fade_out_time > 0:
step = abs(fade_out_time) / old_entry.volume
self.logger.info("Starting to fading " + old_entry.type.value + " out. step is " + str(step) + "s")
# disable logging... it is going to be enabled again after fadein and -out is finished
self.disable_logging = True
self.client.disable_logging = True
for i in range(old_entry.volume):
self.channel_volume(old_entry.type.value, old_entry.volume-i-1)
time.sleep(step)
self.logger.info("Finished with fading " + old_entry.type.value + " out.")
# enable logging again
self.fade_out_active = False
if not self.fade_in_active:
self.disable_logging = False
self.client.disable_logging = False
except LQConnectionError as e:
self.logger.critical(str(e))
return True
# ------------------------------------------------------------------------------------------ #
def activate(self, new_entry):
# grab the actual active entry
(show, old_entry) = self.scheduler.get_active_entry()
# determine its type
old_type = old_entry.type
try:
# enable transaction
self.enable_transaction()
if old_type == new_entry.type:
# push something to active channel
self.activate_same_channel(new_entry)
else:
# switch to another channel
self.activate_different_channel(new_entry, old_type)
# disable conn
self.disable_transaction()
# insert playlist entry
self.insert_track_service_entry(new_entry)
except LQConnectionError as e:
# we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further and pass the exception
pass
# ------------------------------------------------------------------------------------------ #
def activate_same_channel(self, entry, activate_different_channel=False):
if not activate_different_channel:
self.logger.info(TerminalColors.PINK.value + entry.type.value + " already active!" + TerminalColors.ENDC.value)
# push to fs or stream
if entry.type == ScheduleEntryType.FILESYSTEM:
self.playlist_push(entry.source)
self.active_channel = entry.type
elif entry.type == ScheduleEntryType.STREAM:
self.set_http_url(entry.source)
self.http_start_stop(True)
self.active_channel = entry.type
# else: # live
# nothing to do when we are live => just leave it as is
self.active_channel = entry.type
# set active channel to wanted volume
if not activate_different_channel:
self.channel_volume(entry.type.value, entry.volume)
# ------------------------------------------------------------------------------------------ #
def activate_different_channel(self, entry, active_type):
self.logger.info(TerminalColors.PINK.value + "LiquidSoapCommunicator is activating " + entry.type.value + " & deactivating " + active_type.value + "!" + TerminalColors.ENDC.value)
# reuse of this function, because activate_same_channel and activate_different_channel are doing pretty the same except setting of the volume to zero
self.activate_same_channel(entry, True)
# set other channels to zero volume
others = self.all_inputs_but(entry.getChannel().value)
for o in others:
self.channel_volume(o, 0)
# set active channel to wanted volume
self.channel_volume(entry.type.value, entry.volume)
# ------------------------------------------------------------------------------------------ #
def insert_track_service_entry(self, schedule_entry):
self.logger.info(TerminalColors.GREEN.value + "Inserting TrackService Entry " + schedule_entry.source + "!" + TerminalColors.ENDC.value)
# create trackservice entry
trackservice_entry = TrackService()
# set foreign keys
trackservice_entry.playlist_id = schedule_entry.playlist_id
trackservice_entry.entry_num = schedule_entry.entry_num
trackservice_entry.source = schedule_entry.source
# store
trackservice_entry.store(add=True, commit=True)
# ------------------------------------------------------------------------------------------ #
def all_inputs_but(self, input_type):
try:
activemixer_copy = self.get_all_channels().copy()
activemixer_copy.remove(input_type)
except ValueError as e:
self.logger.error("Requested channel (" + input_type + ") not in channellist. Reason: " + str(e))
except AttributeError:
self.logger.critical("Channellist is None")
return activemixer_copy
# ------------------------------------------------------------------------------------------ #
def get_all_channels(self):
if self.channels is None or len(self.channels) == 0:
self.channels = self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_MIXER.value, "inputs")
return self.channels
# ------------------------------------------------------------------------------------------ #
def reload_channels(self):
self.channels = None
return self.get_all_channels()
# ------------------------------------------------------------------------------------------ #
def __get_mixer_status__(self, mixernumber):
return self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_MIXER.value, "status", mixernumber)
# ------------------------------------------------------------------------------------------ #
def init_player(self):
(_, active_entry) = self.scheduler.get_active_entry()
t = LiquidSoapInitThread(self, active_entry)
t.start()
return "LiquidSoapInitThread started!"
# ------------------------------------------------------------------------------------------ #
def channel_activate(self, channel, activate):
channels = self.get_all_channels()
try:
index = channels.index(channel)
if len(channel) < 1:
self.logger.critical("Cannot activate channel. There are no channels!")
else:
message = self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_MIXER.value, "select", index, activate)
return message
except Exception as e:
self.logger.critical("Ran into exception when activating channel. Reason: " + str(e))
# ------------------------------------------------------------------------------------------ #
def channel_volume(self, channel, volume):
"""
set volume of a channel
@type channel: string
@param channel: Channel
@type volume: int
@param volume: Volume between 0 and 100
"""
try:
channels = self.get_all_channels()
index = channels.index(channel)
except ValueError as e:
self.logger.error("Cannot set volume of channel " + channel + " to " + str(volume) + "!. Reason: " + str(e))
return
try:
if len(channel) < 1:
self.logger.warning("Cannot set volume of channel " + channel + " to " + str(volume) + "! There are no channels.")
else:
message = self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_MIXER.value, "volume", str(index), str(int(volume)))
if not self.disable_logging:
if message.find('volume=' + str(volume) + '%'):
self.logger.debug("Set volume of channel " + channel + " to " + str(volume))
else:
self.logger.warning("Setting volume of channel " + channel + " gone wrong! Liquidsoap message: " + message)
return message
except AttributeError as e: #(LQConnectionError, AttributeError):
self.disable_transaction(force=True)
self.logger.error("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e))
# ------------------------------------------------------------------------------------------ #
def auraengine_state(self):
state = self.__send_lqc_command__(self.client, "auraengine", "state")
return state
# ------------------------------------------------------------------------------------------ #
def liquidsoap_help(self):
data = self.__send_lqc_command__(self.client, 'help')
if not data:
self.logger.warning("Could not get Liquidsoap's help")
else:
self.logger.debug("Got Liquidsoap's help")
return data
# ------------------------------------------------------------------------------------------ #
def set_http_url(self, uri):
return self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_STREAM.value, "url", uri)
# ------------------------------------------------------------------------------------------ #
def playlist_push(self, uri):
"""
Eine Uri in die Playlist einfügen
@type uri: str
@param uri: Die Uri
"""
return self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_FILE.value, "push", uri)
# ------------------------------------------------------------------------------------------ #
def playlist_seek(self, seconds_to_seek):
return self.__send_lqc_command__(self.client, LiquidSoapIDs.IN_FILE.value, "seek", seconds_to_seek)
# ------------------------------------------------------------------------------------------ #
def version(self):
"""
get version
"""
data = self.__send_lqc_command__(self.client, "version", "")
self.logger.debug("Got Liquidsoap's version")
return data
# ------------------------------------------------------------------------------------------ #
def uptime(self):
"""
get uptime
"""
data = self.__send_lqc_command__(self.client, "uptime", "")
self.logger.debug("Got Liquidsoap's uptime")
return data
# ------------------------------------------------------------------------------------------ #
def __send_lqc_command__(self, lqs_instance, namespace, command, *args):
"""
Ein Kommando an Liquidsoap senden
@type lqs_instance: object
@param lqs_instance: Instance of LiquidSoap Client
@type namespace: string
@param namespace: Namespace of function
@type command: string
@param command: Function name
@type args: list
@param args: List of parameters
@rtype: string
@return: Response from LiquidSoap
"""
try:
if not self.disable_logging:
if namespace.find("recorder") >= 0:
self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + "." + str(command) + str(args))
else:
if command == "":
self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + str(args))
else:
self.logger.info("LiquidSoapCommunicator is calling " + str(namespace) + "." + str(command) + str(args))
# call wanted function ...
func = getattr(lqs_instance, namespace.split("_")[0])
# ... and fetch the result
if(namespace == "uptime"):
result = func(namespace)
else:
result = func(namespace, command, *args)
if not self.disable_logging:
self.logger.info("LiquidSoapCommunicator got response " + str(result))
self.connection_attempts = 0
return result
except LQConnectionError as e:
self.logger.error("Connection Error when sending " + str(namespace) + "." + str(command) + str(args))
if self.try_to_reconnect():
time.sleep(0.2)
self.connection_attempts += 1
if self.connection_attempts < 5:
# reconnect
self.__open_conn(self.client)
self.logger.info("Trying to resend " + str(namespace) + "." + str(command) + str(args))
# grab return value
retval = self.__send_lqc_command__(lqs_instance, namespace, command, *args)
# disconnect
self.__close_conn(self.client)
# return the val
return retval
else:
if command == "":
msg = "Rethrowing Exception while trying to send " + str(namespace) + str(args)
else:
msg = "Rethrowing Exception while trying to send " + str(namespace) + "." + str(command) + str(args)
self.logger.info(msg)
self.disable_transaction(socket=self.client, force=True)
raise e
else:
# also store when was last admin mail sent with which content...
self.logger.critical("SEND ADMIN MAIL AT THIS POINT")
raise e
# ------------------------------------------------------------------------------------------ #
def try_to_reconnect(self):
self.enable_transaction()
return self.transaction > 0
# ------------------------------------------------------------------------------------------ #
def enable_transaction(self, socket=None):
# set socket to playout if nothing else is given
if socket is None:
socket = self.client
self.transaction = self.transaction + 1
self.logger.debug(TerminalColors.WARNING.value + "ENabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)
if self.transaction > 1:
return
try:
self.__open_conn(socket)
except FileNotFoundError:
self.disable_transaction(socket=socket, force=True)
msg = "socket file " + socket.socket_path + " not found. Is liquidsoap running?"
self.logger.critical(TerminalColors.RED.value + msg + TerminalColors.ENDC.value)
self.auramailer.send_admin_mail("CRITICAL Exception when connecting to Liquidsoap", msg)
# ------------------------------------------------------------------------------------------ #
def disable_transaction(self, socket=None, force=False):
if not force:
# nothing to disable
if self.transaction == 0:
return
# decrease transaction counter
self.transaction = self.transaction - 1
# debug msg
self.logger.debug(TerminalColors.WARNING.value + "DISabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value)
# return if connection is still needed
if self.transaction > 0:
return
else:
self.logger.debug(TerminalColors.WARNING.value + "Forcefully DISabling transaction! " + TerminalColors.ENDC.value)
# close conn and set transactioncounter to 0
self.__close_conn(socket)
self.transaction = 0
# ------------------------------------------------------------------------------------------ #
def __open_conn(self, socket):
# already connected
if self.transaction > 1:
return
self.logger.debug(TerminalColors.GREEN.value + "LiquidSoapCommunicator opening conn" + TerminalColors.ENDC.value)
# try to connect
socket.connect()
# ------------------------------------------------------------------------------------------ #
def __close_conn(self, socket):
# set socket to playout
if socket is None:
socket = self.client
# do not disconnect if a transaction is going on
if self.transaction > 0:
return
# say bye
socket.byebye()
# debug msg
self.logger.debug(TerminalColors.BLUE.value + "LiquidSoapCommunicator closed conn" + TerminalColors.ENDC.value)
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import time
import logging
import datetime
import threading
from libraries.enum.auraenumerations import ScheduleEntryType
class LiquidSoapInitThread(threading.Thread):
logger = None
active_entry = None
liquidsoapcommunicator = None
# ------------------------------------------------------------------------------------------ #
def __init__(self, liquidsoapcommunicator, active_entry):
threading.Thread.__init__(self)
self.logger = logging.getLogger("AuraEngine")
self.liquidsoapcommunicator = liquidsoapcommunicator
self.active_entry = active_entry
# ------------------------------------------------------------------------------------------ #
def run(self):
try:
# sleep needed, because the socket is created too slow by liquidsoap
time.sleep(1)
self.logger.info("Waited 1s for liquidsoap. Jez soit a si gspian")
# enable lqs transaction
self.liquidsoapcommunicator.enable_transaction()
# wait another second. lqs really starts slow.. be prepared you liquidsoap you!
time.sleep(1)
# set some parameters
self.set_start_parameters()
# set active
self.set_active_show()
# disable lqs transaction again
self.liquidsoapcommunicator.disable_transaction()
# the rest of the system now can use liquidsoap connection
self.liquidsoapcommunicator.is_liquidsoap_running = True
except Exception as e:
self.logger.critical("Liquidsoap connection ERROR! Restart LQ Server! Reason: "+str(e))
self.logger.info("InitThread finished")
def set_active_show(self):
if self.active_entry is not None:
self.logger.info("LiquidSoapInitThread sets activechannel: " + str(self.active_entry))
channel = self.active_entry.type
# have to seek?
if channel == ScheduleEntryType.FILESYSTEM:
# calc how many seconds were missed
now_unix = time.mktime(datetime.datetime.now().timetuple())
seconds_to_seek = now_unix - self.active_entry.entry_start_unix
# and seek these seconds forward
if seconds_to_seek > 0:
self.liquidsoapcommunicator.playlist_seek(seconds_to_seek)
# finally make something hearable :-)
if channel != "" and channel is not None:
# activate http stream if needed
self.liquidsoapcommunicator.http_start_stop(channel == ScheduleEntryType.STREAM)
# finally set the volume up
self.liquidsoapcommunicator.channel_volume(channel.value, self.active_entry.volume)
else:
self.logger.error("Channel is NULL or empty! Cannot set ")
else:
self.logger.warning("No active entry in the scheduler! Is a programme loaded?")
def set_start_parameters(self):
# reset channels and reload them
channels = self.liquidsoapcommunicator.reload_channels()
# for all available channels
for c in channels:
# set volume to zero
self.liquidsoapcommunicator.channel_volume(c, "0")
# and activate this channel
self.liquidsoapcommunicator.channel_activate(c, True)
# setting init params like a blank file..
install_dir = self.liquidsoapcommunicator.config.get("install_dir")
self.liquidsoapcommunicator.playlist_push(install_dir + "/configuration/blank.flac")
# .. or the radio fro stream (it is overwritten as soon as one http overtake is planned)
self.liquidsoapcommunicator.set_http_url("http://stream.fro.at/fro-128.ogg")
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
from modules.communication.liquidsoap.client import LiquidSoapClient
from libraries.enum.auraenumerations import LiquidSoapIDs
class LiquidSoapPlayerClient(LiquidSoapClient):
# ------------------------------------------------------------------------------------------ #
def output(self, namespace, command, *args):
if namespace.find("recorder") >= 0:
if command == "status":
return self.recorderstatus(namespace)
if command == "start":
return self.recorderstart(namespace)
if command == "stop":
return self.recorderstop(namespace)
return "LiquidSoapPlayerClient does not understand " + namespace + "." + command + str(args)
# ------------------------------------------------------------------------------------------ #
def input(self, namespace, command, *args):
if namespace == LiquidSoapIDs.IN_FILE.value:
if command == "push":
return self.fs_push(*args)
if command == "seek":
return self.fs_seek(*args)
if namespace == LiquidSoapIDs.IN_STREAM.value:
if command == "url":
return self.set_http_url(*args)
if namespace == LiquidSoapIDs.IN_MIXER.value:
if command == "status":
return self.mixerstatus(*args)
if command == "inputs":
return self.mixerinputs()
if command == "volume":
return self.mixervolume(*args)
if command == "select":
if len(args) == 2:
return self.mixerselect(args[0], args[1])
return "LiquidSoapPlayerClient does not understand " + namespace + "." + command + str(args)
# ------------------------------------------------------------------------------------------ #
def uptime(self, command=""): # no command will come
return self.command("uptime", "")
# ------------------------------------------------------------------------------------------ #
def auraengine(self, command, *args):
if command == "state":
return self.auraengine_state()
return "LiquidSoapPlayerClient does not understand auraengine." + command + str(args)
# ------------------------------------------------------------------------------------------ #
def auraengine_state(self):
self.command('auraengine', 'state')
return self.message
# ------------------------------------------------------------------------------------------ #
def fs_push(self, uri):
self.command(LiquidSoapIDs.IN_FILE.value, 'push', uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def fs_seek(self, uri):
self.command(LiquidSoapIDs.IN_FILE.value, 'seek', uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def set_http_url(self, uri):
self.command(LiquidSoapIDs.IN_STREAM.value, 'url', uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def mixerinputs(self):
# send command
self.command(LiquidSoapIDs.IN_MIXER.value, "inputs")
# convert to list and return it
return self.message.strip().split(' ')
# ------------------------------------------------------------------------------------------ #
def mixerstatus(self, pos=""):
"""
Get state of a source in the mixer
@type pos: string
@param pos: Mixerposition
@rtype: string
@return: Response from LiquidSoap
"""
self.command(LiquidSoapIDs.IN_MIXER.value, "status", str(pos))
return self.message
# ------------------------------------------------------------------------------------------ #
def mixerselect(self, pos, activate):
"""
Kanal/Source aktivieren
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command(LiquidSoapIDs.IN_MIXER.value, "select", str(pos) + " " + str(activate).lower())
return self.message
# ------------------------------------------------------------------------------------------ #
def mixervolume(self, pos, volume):
"""
set channel volume
:param pos:
:param volume:
:return:
"""
self.command(LiquidSoapIDs.IN_MIXER.value, "volume", str(pos) + " " + str(volume))
return self.message
# ------------------------------------------------------------------------------------------ #
def recorderstatus(self, id):
"""
get status of a recorder
:return:
"""
self.command(id, "status")
return self.message
# ------------------------------------------------------------------------------------------ #
def recorderstart(self, id):
"""
get status of a recorder
:return:
"""
self.command(id, "start")
return self.message
# ------------------------------------------------------------------------------------------ #
def recorderstop(self, id):
"""
get status of a recorder
:return:
"""
self.command(id, "stop")
return self.message
# ------------------------------------------------------------------------------------------ #
def skip(self, namespace="playlist", pos=""):
"""
Source skippen
@type namespace: string
@param namespace: Namespace der Source
@type pos: string
@param pos: Die Position - optional - Position des Channels vom Mixer benötigt
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('skip', namespace, pos)
return self.message
# ------------------------------------------------------------------------------------------ #
def remove(self, pos, namespace="playlist"):
"""
Track aus der secondary_queue oder der Playlist entfernen
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('remove', namespace, str(pos))
return self.message
# ------------------------------------------------------------------------------------------ #
def insert(self, uri, pos='0', namespace="playlist"):
"""
Track einfügen
@type uri: string
@param uri: Uri einer Audiodatei
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('insert', namespace, str(pos) + ' ' + uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def move(self, fromPos, toPos, namespace="playlist"):
"""
Track von Position fromPos nach Position toPos verschieben
@type fromPos: string/int
@param fromPos: Position des zu verschiebenden Tracks
@type toPos: string
@param toPos: Die Position zu der verschoben werden soll
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('move', namespace, str(fromPos) + ' ' + str(toPos))
return self.message
# ------------------------------------------------------------------------------------------ #
def play(self, namespace="playlist"):
"""
Source abspielen - funktioniert nur bei Playlist
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('play', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def pause(self, namespace="playlist"):
"""
Source pausieren/stoppen - funktioniert nur bei Playlist
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('pause', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def flush(self, namespace="playlist"):
"""
Playlist leeren
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('flush', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def playlistData(self):
"""
Metadaten der Playlist ausgeben
@rtype: string
@return: Ein Json-String
"""
self.command('data', 'playlist')
return self.message
# ------------------------------------------------------------------------------------------ #
def seek(self, duration, namespace="playlist"):
"""
Aktuell laufenen Track des Kanals vorspulen
@type duration: string/int
@param duration: Dauer in Sekunden
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('seek', namespace, str(duration))
return self.message
# ------------------------------------------------------------------------------------------ #
def get_queue(self, namespace="ch1", queue='queue'):
"""
Queue eines Kanals ausgeben
@type namespace: string
@param namespace: Namespace der Source
@type queue: string
@param queue: Name des queues (queue, primary_queue, secondary_queue)
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command(queue, namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def loadPlaylist(self, uri, params="", namespace="playlist"):
"""
Playlist laden
@type uri: string
@param uri: Uri einer Playlist im XSPF-Format
@type params: string
@param params: obsolete
@type namespace: string
@param namespace: Namespace der Source - hier nur playlist
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('load', namespace, uri + params)
return self.message
# ------------------------------------------------------------------------------------------ #
def currentTrack(self, namespace="request"):
"""
Das oder die ID(s) der gerade abgespielten requests erhalten
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers (als String)
"""
self.command('on_air', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def volume(self, pos, volume, namespace="mixer"):
"""
Lautstärke eines Kanals setzen
@type pos: int/string
@param pos: Die Position/ Nummer des Kanals (playlist=0)
@type volume: int/string
@param volume: Zahl von 1 -100
@type namespace: string
@param namespace: Namespace der Source (immer mixer)
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('volume', namespace, str(pos) + ' ' + str(volume))
return self.message
# ------------------------------------------------------------------------------------------ #
def playlist_remaining(self):
"""
Wie lange läuft der aktuelle Track der Playlist noch
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('remaining', 'playlist')
return self.message
# ------------------------------------------------------------------------------------------ #
def list_channels(self):
"""
Channels auflisten (Simple JSON)
"""
# Liquidsoap Kommando
channels = self.sendLqcCommand(self.lqc, LiquidSoapIDs.IN_MIXER.value, 'inputs')
if not isinstance(channels, list):
self.error('02')
elif len(channels) < 1:
self.warning('01')
else:
self.success('00', channels)
self.notifyClient()
\ No newline at end of file
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import smtplib
from email.message import EmailMessage
from libraries.exceptions.auraexceptions import MailingException
class AuraMailer():
config = None
# ------------------------------------------------------------------------------------------ #
def __init__(self, config):
self.config = config
self.admin_mails = config.get("admin_mail")
# ------------------------------------------------------------------------------------------ #
def send_admin_mail(self, subject, body):
admin_mails = self.admin_mails.split()
for mail_to in admin_mails:
self.__send(mail_to, subject, body)
# ------------------------------------------------------------------------------------------ #
def __send(self, mail_to, subject, body):
# read config
mail_server = self.config.get("mail_server")
mail_port = self.config.get("mail_server_port")
mail_user = self.config.get("mail_user")
mail_pass = self.config.get("mail_pass")
from_mail = self.config.get("from_mail")
# check settings
if mail_server == "":
raise MailingException("Mail Server not set")
if mail_port == "":
raise MailingException("Mailserver Port not set")
if mail_user == "":
raise MailingException("Mail user not set")
if mail_pass == "":
raise MailingException("No Password for mailing set")
if from_mail == "":
raise MailingException("From Mail not set")
# stuff the message together and ...
msg = EmailMessage()
msg.set_content(body)
mailsubject_prefix = self.config.get("mailsubject_prefix")
if mailsubject_prefix == "":
msg["Subject"] = subject
else:
msg["Subject"] = mailsubject_prefix + " " + subject
msg["From"] = from_mail
msg["To"] = mail_to
# ... send the mail
try:
server = smtplib.SMTP(mail_server, int(mail_port))
server.starttls()
server.login(mail_user, mail_pass)
server.send_message(msg)
server.quit()
except Exception as e:
raise MailingException(str(e))
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import time
import redis
import logging
import threading
from datetime import datetime
from threading import Event
from modules.communication.redis.messenger import RedisMessenger
from modules.communication.connection_tester import ConnectionTester
from libraries.database.statestore import RedisStateStore
from libraries.exceptions.auraexceptions import RedisConnectionException
from libraries.enum.auraenumerations import RedisChannel, TerminalColors, FallbackType
# ------------------------------------------------------------------------------------------ #
class ServerRedisAdapter(threading.Thread, RedisMessenger):
debug = False
pubsub = None
config = None
redisdb = None
channel = ""
scheduler = None
redisclient = None
connection_tester = None
liquidsoapcommunicator = None
# ------------------------------------------------------------------------------------------ #
def __init__(self, config):
threading.Thread.__init__(self)
RedisMessenger.__init__(self, config)
# init
#threading.Thread.__init__ (self)
self.config = config
self.shutdown_event = Event()
self.channel = RedisChannel.STANDARD.value
self.section = ''
self.rstore = RedisStateStore(config)
self.errnr = '00'
self.components = {'controller':'01', 'scheduling':'02', 'playd':'03', 'recorder':'04', 'helpers':'09'}
self.fromMail = ''
self.adminMails = ''
self.redisclient = ClientRedisAdapter(config)
self.connection_tester = ConnectionTester()
# ------------------------------------------------------------------------------------------ #
def run(self):
self.redisdb = redis.Redis(host=self.config.get("redis_host"), port=self.config.get("redis_port"), db=self.config.get("redis_db"))
self.pubsub = self.redisdb.pubsub()
self.pubsub.subscribe(self.channel)
self.logger.info(TerminalColors.ORANGE.value + "waiting for REDIS message on channel '" + self.channel + TerminalColors.ENDC.value + "'")
# listener loop
for item in self.pubsub.listen():
if item["type"] == "subscribe":
continue
self.logger.info(TerminalColors.ORANGE.value + "received REDIS message: " + TerminalColors.ENDC.value + str(item))
item["channel"] = self.decode_if_needed(item["channel"])
item["data"] = self.decode_if_needed(item["data"])
try:
self.work(item)
except RedisConnectionException as rce:
self.logger.error(str(rce))
if not self.shutdown_event.is_set():
self.logger.info(TerminalColors.ORANGE.value + "waiting for REDIS message on channel " + self.channel + TerminalColors.ENDC.value)
self.pubsub.unsubscribe()
if not self.shutdown_event.is_set():
self.logger.warning("unsubscribed from " + self.channel + " and finished")
# ------------------------------------------------------------------------------------------ #
def decode_if_needed(self, val):
if isinstance(val, bytes):
return val.decode("utf-8")
return val
# ------------------------------------------------------------------------------------------ #
def listen_for_one_message(self, channel, socket_timeout=2):
self.redisdb = redis.Redis(host=self.config.get("redis_host"), port=self.config.get("redis_port"), db=self.config.get("redis_db"), socket_timeout=socket_timeout)
self.pubsub = self.redisdb.pubsub()
self.pubsub.subscribe(channel)
try:
self.logger.info("I am listening on channel '"+channel+"' for "+str(socket_timeout)+" seconds")
for item in self.pubsub.listen():
it = self.receive_message(item)
if it is not None:
break
except redis.exceptions.TimeoutError as te:
raise te
return item["data"]
# ------------------------------------------------------------------------------------------ #
def receive_message(self, item):
if item["type"] == "subscribe":
self.logger.info("i am subscribed to channel " + item["channel"].decode("utf-8"))
return None
item["channel"] = item["channel"].decode("utf-8")
if isinstance(item["data"], bytes):
item["data"] = item["data"].decode("utf-8")
self.pubsub.unsubscribe()
return item
# ------------------------------------------------------------------------------------------ #
def work(self, item):
if item["data"] == "fetch_new_programme":
#self.execute(RedisChannel.FNP_REPLY.value, self.scheduler.fetch_new_programme)
self.execute(RedisChannel.FNP_REPLY.value, self.scheduler.get_act_programme_as_string)
elif item["data"] == "shutdown":
self.shutdown_event.set()
self.scheduler.stop()
self.pubsub.close()
self.logger.info("shutdown event received. Bye bye...")
elif item["data"] == "init_player":
self.execute(RedisChannel.IP_REPLY.value, self.liquidsoapcommunicator.init_player)
elif item["data"] == "get_act_programme":
self.execute(RedisChannel.GAP_REPLY.value, self.scheduler.get_act_programme_as_string)
elif item["data"] == "get_connection_status":
self.execute(RedisChannel.GCS_REPLY.value, self.connection_tester.get_connection_status)
elif item["data"] == "print_message_queue":
self.execute(RedisChannel.PMQ_REPLY.value, self.scheduler.print_message_queue)
elif item["data"].find("delete_playlist_entry") >= 0:
entrynum = item["data"].split()[1]
self.logger.info("entry to del: " + str(entrynum))
self.execute(RedisChannel.DPE_REPLY.value, self.scheduler.delete_playlist_entry, entrynum)
elif item["data"].find("set_next_file") >= 0:
playlist = item["data"].split()[1]
playlist = playlist[0:len(playlist)-8]
self.execute(RedisChannel.SNF_REPLY.value, self.scheduler.set_next_file_for, playlist)
elif item["data"].find("get_next_file") >= 0:
playlist = item["data"].split()[1]
#playlist = playlist[0:len(playlist)-8]
self.execute(RedisChannel.GNF_REPLY.value, self.scheduler.get_next_file_for, playlist)
elif item["data"] == "recreate_db":
self.execute(RedisChannel.RDB_REPLY.value, self.scheduler.recreate_database)
elif item["data"] == "status":
return True
else:
raise RedisConnectionException("ServerRedisAdapter Cannot understand command: " + item["data"])
# ------------------------------------------------------------------------------------------ #
def execute(self, channel, f, param=None):
if param:
reply = f(param)
else:
reply = f()
if reply is None:
reply = ""
# sometimes the sender is faster than the receiver. redis messages would be lost
time.sleep(0.1)
self.logger.info(TerminalColors.ORANGE.value + "replying REDIS message " + TerminalColors.ENDC.value + reply + TerminalColors.ORANGE.value + " on channel " + channel + TerminalColors.ENDC.value)
# publish
self.redisclient.publish(channel, reply)
# ------------------------------------------------------------------------------------------ #
def join_comm(self):
try:
while self.is_alive():
self.logger.info(str(datetime.now())+" joining")
self.join()
self.logger.warning("join out")
except (KeyboardInterrupt, SystemExit):
# Dem Server den Shutdown event setzen
# server.shutdown_event.set()
# Der Server wartet auf Eingabe
# Daher einen Client initiieren, der eine Nachricht schickt
self.halt()
sys.exit('Terminated')
# ------------------------------------------------------------------------------------------ #
def halt(self):
"""
Stop the server
"""
if self.shutdown_event.is_set():
return
try:
del self.auracontroller
except:
pass
self.shutdown_event.set()
result = 'failed'
try:
result = self.socket.unbind("tcp://"+self.ip+":"+self.port)
except:
pass
# self.socket.close()
# ------------------------------------------------------------------------------------------ #
def send(self, message):
"""
Send a message to the client
:param message: string
"""
if not self.can_send:
self.logger.info("sending a "+str(len(message))+" long message via REDIS.")
self.socket.send(message.encode("utf-8"))
self.can_send = False
else:
self.logger.warning("cannot send message via REDIS: "+str(message))
# ------------------------------------------------------------------------------------------ #
class ClientRedisAdapter(RedisMessenger):
def __init__(self, config):
RedisMessenger.__init__(self, config)
# ------------------------------------------------------------------------------------------ #
def publish(self, channel, message):
if type(channel) == RedisChannel:
channel = channel.value
self.rstore.publish(channel, message)
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import time
import logging
import datetime
from libraries.database.statestore import RedisStateStore
from modules.communication.mail.mail import AuraMailer
from libraries.exceptions.auraexceptions import PlaylistException
from libraries.enum.auraenumerations import RedisChannel
from libraries.base.logger import AuraLogger
"""
Send and receive redis messages
"""
# ------------------------------------------------------------------------------------------ #
class RedisMessenger():
logger = None
rstore = None
# ------------------------------------------------------------------------------------------ #
def __init__(self, config):
super(RedisMessenger, self).__init__()
"""
Constructor
"""
self.logger = logging.getLogger("AuraEngine")
self.channel = RedisChannel.STANDARD
self.section = ''
self.rstore = RedisStateStore(config)
self.errnr = '00'
self.components = {'controller':'01', 'scheduling':'02', 'playd':'03', 'recorder':'04', 'helpers':'09'}
self.fromMail = ''
self.adminMails = ''
# ------------------------------------------------------------------------------------------ #
def set_channel(self, channel):
"""
Einen "Kanal" setzen - zb scheduling
@type channel: string
@param channel: Kanal/Name der Komponente
"""
self.channel = channel
if channel in self.components:
self.errnr = self.components[channel]
self.rstore.set_channel(channel)
# ------------------------------------------------------------------------------------------ #
def set_section(self, section):
"""
Einen Sektion / Gültigkeitsbereich der Meldung setzen - zb internal
@type section: string
@param section: Gültigkeitsbereich
"""
self.section = section
# ------------------------------------------------------------------------------------------ #
def set_mail_addresses(self, fromMail, adminMails):
"""
Einen Sektion / Gültigkeitsbereich der Meldung setzen - zb internal
@type section: string
@param section: Gültigkeitsbereich
"""
self.fromMail = fromMail
self.adminMails = adminMails
# ------------------------------------------------------------------------------------------ #
def send(self, message, code, level, job, value='', section=''):
"""
Eine Message senden
@type message: string
@param message: menschenverständliche Nachricht
@type code: string
@param code: Fehlercode - endet mit 00 bei Erfolg
@type level: string
@param level: Error-Level - info, warning, error, fatal
@type job: string
@param job: Name der ausgeführten Funktion
@type value: string
@param value: Ein Wert
@type section: string
@param section: Globale Sektion überschreiben
"""
section = self.section if section == '' else section
self.time = str(datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S:%f'))
self.utime = time.time()
state = {'message':message.strip().replace("'","\\'"), 'code':self.errnr + str(code),'job':job,'value':value}
self.rstore.set_section(section)
self.rstore.store(level, state)
if level == 'info' or level == 'success':
self.logger.info(message)
elif level == 'warning':
self.logger.warning(message)
elif level == 'error':
self.logger.error(message)
self.send_admin_mail(level, message, state)
elif level == 'fatal':
self.logger.critical(message)
self.send_admin_mail(level, message, state)
# ------------------------------------------------------------------------------------------ #
def say_alive(self):
"""
Soll alle 20 Sekunden von den Komponenten ausgeführt werden,
um zu melden, dass sie am Leben sind
"""
self.rstore.set_alive_state()
# ------------------------------------------------------------------------------------------ #
def get_alive_state(self, channel):
"""
Live State abfragen
@type channel: string
@param channel: Channel/Komponente
"""
return self.rstore.get_alive_state(channel)
# ------------------------------------------------------------------------------------------ #
def set_state(self, name, value, expires=None, channel=None):
"""
Kündigt einen Event an
@type name: string
@param name: Name des state
@type value: string
@param value: Wert
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
self.rstore.set_state(name, value, expires, channel)
# ------------------------------------------------------------------------------------------ #
def queue_add_event(self, name, eventtime, value, channel=None):
"""
Kündigt einen Event an
@type name: string
@param name: der Name des Events
@type eventtime: string|datetime.datetime
@param eventtime: Datum und Zeit des events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
if type(eventtime) == type(str()):
eventtime_str = datetime.datetime.strptime(eventtime[0:16].replace(' ','T'), "%Y-%m-%dT%H:%M").strftime("%Y-%m-%dT%H:%M")
elif type(eventtime) is datetime.datetime:
eventtime_str = eventtime.strftime("%Y-%m-%dT%H:%M")
else:
raise TypeError('eventtime must be a datetime.date or a string, not a %s' % type(eventtime))
self.rstore.queue_add_event(eventtime_str, name, value, channel)
# ------------------------------------------------------------------------------------------ #
def queue_remove_events(self, name, channel=None):
"""
Löscht Events
@type name: string
@param name: der Name des Events
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
self.rstore.queue_remove_events(name, channel)
# ------------------------------------------------------------------------------------------ #
def fire_event(self, name, value, channel=None):
"""
Feuert einen Event
@type name: string
@param name: der Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
self.rstore.fire_event(name, value, channel)
# ------------------------------------------------------------------------------------------ #
def get_event_queue(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
queue = self.rstore.get_event_queue(name, channel)
return queue
# ------------------------------------------------------------------------------------------ #
def get_events(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
events = self.rstore.get_events(name, channel)
return events
# ------------------------------------------------------------------------------------------ #
def get_event(self, name=None, channel=None):
"""
Holt event eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: dict
@return: Event
"""
events = self.rstore.get_events(name, channel)
result = False
if events:
result = events.pop(0)
return result
# ------------------------------------------------------------------------------------------ #
def send_admin_mail(self, level, message, state):
"""
Sendent mail an Admin(s),
@type message: string
@param message: Die Message
@type state: dict
@param state: Der State
@return result
"""
if self.fromMail and self.adminMails:
subject = "Possible comba problem on job " + state['job'] + " - " + level
mailmessage = "Hi Admin,\n comba reports a possible problem\n\n"
mailmessage = mailmessage + level + "!\n"
mailmessage = mailmessage + message + "\n\n"
mailmessage = mailmessage + "Additional information:\n"
mailmessage = mailmessage + "##################################################\n"
mailmessage = mailmessage + "Job:\t" + state['job'] + "\n"
mailmessage = mailmessage + "Code:\t" + state['code'] + "\n"
mailmessage = mailmessage + "Value:\t" + str(state['value']) + "\n"
mailer = AuraMailer(self.adminMails, self.fromMail)
mailer.send_admin_mail(subject, mailmessage)
else:
return False
# ------------------------------------------------------------------------------------------ #
def receive(self):
"""
Bisher wird nichts empfangen
"""
return ""
# ------------------------------------------------------------------------------------------ #
def get_next_file_for(self, playlisttype):
next = self.rstore.db.get('next_'+playlisttype+'file')
if next is None:
next = b""
return next.decode('utf-8')
# ------------------------------------------------------------------------------------------ #
def set_next_file_for(self, playlisttype, file):
self.rstore.db.set("next_" + playlisttype + "file", file)
__author__ = 'gg'
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
set("log.file.path", "./<script>.log")
set("server.telnet", true)
set("server.telnet.bind_addr", "0.0.0.0")
set("server.telnet.port", 1234)
# ALSA / pulse settings
# durch ausprobieren herausgefunden für asus xonar dgx 5.1
# chip: CMI8788
# driver: snd_oxygen
set("frame.duration", 0.30)
set("alsa.alsa_buffer", 8192) # 7168) # 6144) # 8192) # 10240) #15876
set("alsa.buffer_length", 25)
set("alsa.periods", 0) # assertion error when setting periods other than 0 => alsa default
input_linein = input.alsa(id="linein", bufferize = false)
input_fs = single(id="fs", "/var/audio/fallback/output.flac")
input_http = input.http(id="http", "http://stream.fro.at/fro-128.ogg")
mixer = mix(id="mixer", [input_fs, input_http, input_linein])
output.alsa(id="lineout", bufferize = false, mixer)