broadcasts.py 16.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#
#  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/>.
#
24

25
import sys
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
26
import time
27
28
import logging
import datetime
29

30
from sqlalchemy import orm, func, Boolean, Column, DateTime, Integer, String, ForeignKey, ForeignKeyConstraint
31
from sqlalchemy.orm import relationship
32
from sqlalchemy.sql.expression import false, true
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
33
from libraries.database.database import DB
34
from libraries.enum.auraenumerations import ScheduleEntryType
35

36

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
37
class AuraDatabaseModel:
38
39
40
41
42
    logger = None

    def __init__(self):
        self.logger = logging.getLogger("AuraEngine")

43
44
    def store(self, add=False, commit=False):
        if add:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
45
            DB.session.add(self)
46
        if commit:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
47
            DB.session.commit()
48
49

    def delete(self, commit=False):
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
50
        DB.session.delete(self)
51
52
53
54
#        current_db_sessions = DB.session.object_session(self)
#        current_db_sessions.delete(self)
#        return
#        DB.session.delete(self)
55
        if commit:
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
56
            DB.session.commit()
57

58
59
    def _asdict(self):
        return self.__dict__
60

61
62
    @staticmethod
    def recreate_db(systemexit = False):
63
64
        manualschedule = Schedule()
        manualschedule.schedule_id = 0
65
        manualschedule.show_name = "Manual Show"
66

67
#        self.logger.debug("Recreating Database...")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
68
        DB.drop_all()
69
#        self.logger.debug("all dropped. creating...")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
70
        DB.create_all()
71
#        self.logger.debug("inserting manual scheduling possibility and fallback trackservice schedule")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
72
        DB.session.add(manualschedule)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
73
#        db.session.add(fallback_trackservice_schedule)
74
#        self.logger.debug("all created. commiting...")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
75
        DB.session.commit()
76
#        self.logger.debug("Database recreated!")
77

78
79
        if systemexit:
            sys.exit(0)
80

81

82
# ------------------------------------------------------------------------------------------ #
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
83
class Schedule(DB.Model, AuraDatabaseModel):
84
    """
85
    One specific Schedule for a show on a timeslot
86
    """
87
    __tablename__ = 'schedule'
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
88
89

    # primary and foreign keys
90
    schedule_id = Column(Integer, primary_key=True, autoincrement=False)
91
    show_id = Column(Integer) # well, in fact not needed..
92

93
94
    schedule_start = Column(DateTime) # can be null due to manual entries
    schedule_end = Column(DateTime) # can be null due to manual entries
95
96
    show_name = Column(String(256))
    show_hosts = Column(String(256))
97
    funding_category = Column(String(256))
98
99
100
101
102
103
    comment = Column(String(512))
    languages = Column(String(256))
    type = Column(String(256))
    category = Column(String(256))
    topic = Column(String(256))
    musicfocus = Column(String(256))
104
105
106

    is_repetition = Column(Boolean())

107
108
109
110
111
    playlist_id = Column(Integer)
    timeslot_fallback_id = Column(Integer)
    show_fallback_id = Column(Integer)
    station_fallback_id = Column(Integer)

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
112
113
114
115
116
117
118
119
120
121
122
    @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

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    @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()

146
147
148
149
150
151
    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

152
153
154
155
    # ------------------------------------------------------------------------------------------ #
    def __str__(self):
        return "ScheduleID: #" + str(self.schedule_id) + " Showname: " + self.show_name + " starts @ " + str(self.schedule_start)

156

157
# ------------------------------------------------------------------------------------------ #
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
158
class ScheduleEntry(DB.Model, AuraDatabaseModel):
159
    """
160
    One schedule can have multiple entries
161
    """
162
163
    __tablename__ = 'schedule_entry'

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
164
    # primary and foreign keys
165
166
167
    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"))
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
168

169
    entry_start = Column(DateTime)
170
    source = Column(String(256))
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
171
    volume = Column(Integer, default=100)
172
    fallback_type = Column(Integer, default=0)
173
    cleansource = ""
174
    cleanprotocol = ""
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
175
    entry_start_unix = 0
176
    programme_index = -1
177
    type = None
178
179
180
    fadeintimer = None
    fadeouttimer = None
    switchtimer = None
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
181

182
    # schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
183

184
    # normal constructor
185
186
    def __init__(self, **kwargs):
        super(ScheduleEntry, self).__init__(**kwargs)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
187
        self.calc_unix_times()
188
        self.define_clean_source()
189

190
    # constructor like - called from sqlalchemy
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
191
    @orm.reconstructor
192
193
    def reconstructor(self):
        self.calc_unix_times()
194
        self.define_clean_source()
195
196
        self.set_entry_type()

197
    def define_clean_source(self):
198
199
200
        if self.source is None:
            return None

201
202
        if self.source.startswith("http"):
            self.cleanprotocol = self.source[:7]
203
            self.cleansource = self.source
204
205
206
207
208
209
210

        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]
211
            self.cleansource = self.source[7:]
212
213
214

        elif self.source.startswith("playlist"):
            self.cleanprotocol = self.source[:11]
215
216
            self.cleansource = self.source[11:]

217
218
219
        else:
            self.logger.error("Unknown source protocol")

220
    def calc_unix_times(self):
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
221
222
        if self.entry_start is not None:
            self.entry_start_unix = time.mktime(self.entry_start.timetuple())
223

224
225
    def set_entry_type(self):
        if self.source.startswith("http"):
226
227
228
229
            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"):
230
231
232
233
234
235
236
237
238
239
            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
240

241
242
243
    # ------------------------------------------------------------------------------------------ #
    @staticmethod
    def select_all():
244
        # fetching all entries
245
        all_entries = DB.session.query(ScheduleEntry).filter(ScheduleEntry.fallback_type == 0).order_by(ScheduleEntry.entry_start).all()
246
247
248
249
250
251
252
253

        cnt = 0
        for entry in all_entries:
            entry.programme_index = cnt
            cnt = cnt + 1

        return all_entries

254
255
256
257
258
259
260
261
262
263
264
265
266
    @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

267
268
269
270
271
272
273
274
275
276
    # ------------------------------------------------------------------------------------------ #
    @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()

    # ------------------------------------------------------------------------------------------ #
277
278
279
    @staticmethod
    def select_next_manual_entry_num():

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
280
        max_manual_entry_num = DB.session.query(func.max(ScheduleEntry.entry_num)).filter(ScheduleEntry.schedule_id == 0).first()
281
282
283
284
285
286
287
288

        if max_manual_entry_num[0] is None:
            return 0
        else:
            return int(max_manual_entry_num[0])+1

    # ------------------------------------------------------------------------------------------ #
    @staticmethod
289
290
    def select_upcoming(datefrom=datetime.datetime.now()):
        upcomingtracks = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start > datefrom).order_by(ScheduleEntry.entry_start).all()
291
292
293
294
295
        return upcomingtracks

    # ------------------------------------------------------------------------------------------ #
    @staticmethod
    def select_one(playlist_id, entry_num):
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
296
        return DB.session.query(ScheduleEntry).filter(ScheduleEntry.playlist_id == playlist_id, ScheduleEntry.entry_num == entry_num).first()
297

298
299
300
301
302
    # ------------------------------------------------------------------------------------------ #
    @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()

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
303
304
305
    # ------------------------------------------------------------------------------------------ #
    @staticmethod
    def select_playlist(playlist_id):
306
307
308
309
310
311
312
313
314
315
316
317
        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()
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
318

319
320
321
322
    def getChannel(self):
        if self.type == self.type.FILESYSTEM:
            return "fs"

323
        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:
324
325
326
327
328
329
            return "aura_linein_"+self.cleansource # .cleanprotocol[8]

        if self.type == self.type.STREAM:
            return "http"


330
331
    # ------------------------------------------------------------------------------------------ #
    def __str__(self):
332
        return "Showentry starts @ " + str(self.entry_start) + " and plays " + self.source
333

334

335
# ------------------------------------------------------------------------------------------ #
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
336
class TrackService(DB.Model, AuraDatabaseModel):
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
337
    __tablename__ = 'trackservice'
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
338

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
339
340
341
    trackservice_id = Column(Integer, primary_key=True, autoincrement=True)
    playlist_id = Column(Integer, nullable=False)
    entry_num = Column(Integer, nullable=False)
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
342

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
343
    source = Column(String(255), nullable=False)
344
    start = Column(DateTime, nullable=False, default=func.now())
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
345

346
347
348
    __table_args__ = (
        ForeignKeyConstraint(['playlist_id', 'entry_num'], ['schedule_entry.playlist_id', 'schedule_entry.entry_num']),
    )
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
349

350
    #schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
351
352
    # 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")
353

354
    @staticmethod
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
355
    # ------------------------------------------------------------------------------------------ #
356
    def select_one(trackservice_id):
Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
357
        return DB.session.query(TrackService).filter(TrackService.trackservice_id == trackservice_id).first()
358

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
359
360
361
362
363
364
365
    @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

366
367
368
369
370
371
372
    @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

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
373
374
375
376
    # ------------------------------------------------------------------------------------------ #
    def __str__(self):
        return "TrackServiceID: #" + str(self.trackservice_id) + " playlist_id: " + str(self.playlist_id) + " started @ " + str(self.start) + " and played " + self.source

Gottfried Gaisbauer's avatar
Gottfried Gaisbauer committed
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# ------------------------------------------------------------------------------------------ #
# 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()

428
#AuraDatabaseModel.recreate_db(systemexit=True)