diff options
Diffstat (limited to 'plugin.video.mediathekview/classes/storemysql.py')
-rw-r--r-- | plugin.video.mediathekview/classes/storemysql.py | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/plugin.video.mediathekview/classes/storemysql.py b/plugin.video.mediathekview/classes/storemysql.py new file mode 100644 index 0000000..a272277 --- /dev/null +++ b/plugin.video.mediathekview/classes/storemysql.py @@ -0,0 +1,534 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Leo Moll +# + +# -- Imports ------------------------------------------------ +import string, time +import mysql.connector + +from classes.film import Film +from classes.exceptions import DatabaseCorrupted + +# -- Classes ------------------------------------------------ +class StoreMySQL( object ): + def __init__( self, logger, notifier, settings ): + self.conn = None + self.logger = logger + self.notifier = notifier + self.settings = settings + # useful query fragments + self.sql_query_films = "SELECT film.id,`title`,`show`,`channel`,`description`,TIME_TO_SEC(`duration`) AS `seconds`,`size`,`aired`,`url_sub`,`url_video`,`url_video_sd`,`url_video_hd` FROM `film` LEFT JOIN `show` ON show.id=film.showid LEFT JOIN `channel` ON channel.id=film.channelid" + self.sql_cond_recent = "( TIMESTAMPDIFF(HOUR,`aired`,CURRENT_TIMESTAMP()) < 24 )" + self.sql_cond_nofuture = " AND ( ( `aired` IS NULL ) OR ( TIMESTAMPDIFF(HOUR,`aired`,CURRENT_TIMESTAMP()) > 0 ) )" if settings.nofuture else "" + self.sql_cond_minlength = " AND ( ( `duration` IS NULL ) OR ( TIME_TO_SEC(`duration`) >= %d ) )" % settings.minlength if settings.minlength > 0 else "" + + def Init( self, reset = False ): + self.logger.info( 'Using MySQL connector version {}', mysql.connector.__version__ ) + try: + self.conn = mysql.connector.connect( + host = self.settings.host, + user = self.settings.user, + password = self.settings.password, + database = self.settings.database + ) + except mysql.connector.Error as err: + self.conn = None + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + + def Exit( self ): + if self.conn is not None: + self.conn.close() + + def Search( self, search, filmui ): + self._Search_Condition( '( ( `title` LIKE "%%%s%%" ) OR ( `show` LIKE "%%%s%%" ) )' % ( search, search, ), filmui, True, True ) + + def SearchFull( self, search, filmui ): + self._Search_Condition( '( ( `title` LIKE "%%%s%%" ) OR ( `show` LIKE "%%%s%%" ) ) OR ( `description` LIKE "%%%s%%") )' % ( search, search, search ), filmui, True, True ) + + def GetRecents( self, channelid, filmui ): + sql_cond_channel = ' AND ( film.channelid=' + str( channelid ) + ' ) ' if channelid != '0' else '' + self._Search_Condition( self.sql_cond_recent + sql_cond_channel, filmui, True, False ) + + def GetLiveStreams( self, filmui ): + self._Search_Condition( '( show.search="LIVESTREAM" )', filmui, False, False ) + + def GetChannels( self, channelui ): + self._Channels_Condition( None, channelui ) + + def GetRecentChannels( self, channelui ): + self._Channels_Condition( self.sql_cond_recent, channelui ) + + def _Channels_Condition( self, condition, channelui): + if self.conn is None: + return + try: + if condition is None: + query = 'SELECT `id`,`channel`,0 AS `count` FROM `channel`' + else: + query = 'SELECT channel.id AS `id`,`channel`,COUNT(*) AS `count` FROM `film` LEFT JOIN `channel` ON channel.id=film.channelid WHERE ' + condition + ' GROUP BY channel.id' + self.logger.info( 'MySQL Query: {}', query ) + cursor = self.conn.cursor() + cursor.execute( query ) + channelui.Begin() + for ( channelui.id, channelui.channel, channelui.count ) in cursor: + channelui.Add() + channelui.End() + cursor.close() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + + def GetInitials( self, channelid, initialui ): + if self.conn is None: + return + try: + condition = 'WHERE ( `channelid`=' + str( channelid ) + ' ) ' if channelid != '0' else '' + self.logger.info( 'MySQL Query: {}', + 'SELECT LEFT(`search`,1) AS letter,COUNT(*) AS `count` FROM `show` ' + + condition + + 'GROUP BY LEFT(search,1)' + ) + cursor = self.conn.cursor() + cursor.execute( + 'SELECT LEFT(`search`,1) AS letter,COUNT(*) AS `count` FROM `show` ' + + condition + + 'GROUP BY LEFT(`search`,1)' + ) + initialui.Begin( channelid ) + for ( initialui.initial, initialui.count ) in cursor: + initialui.Add() + initialui.End() + cursor.close() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + + def GetShows( self, channelid, initial, showui ): + if self.conn is None: + return + try: + if channelid == '0' and self.settings.groupshows: + query = 'SELECT GROUP_CONCAT(show.id),GROUP_CONCAT(`channelid`),`show`,GROUP_CONCAT(`channel`) FROM `show` LEFT JOIN `channel` ON channel.id=show.channelid WHERE ( `show` LIKE "%s%%" ) GROUP BY `show`' % initial + elif channelid == '0': + query = 'SELECT show.id,show.channelid,show.show,channel.channel FROM `show` LEFT JOIN channel ON channel.id=show.channelid WHERE ( `show` LIKE "%s%%" )' % initial + else: + query = 'SELECT show.id,show.channelid,show.show,channel.channel FROM `show` LEFT JOIN channel ON channel.id=show.channelid WHERE ( `channelid`=%s ) AND ( `show` LIKE "%s%%" )' % ( channelid, initial ) + self.logger.info( 'MySQL Query: {}', query ) + cursor = self.conn.cursor() + cursor.execute( query ) + showui.Begin( channelid ) + for ( showui.id, showui.channelid, showui.show, showui.channel ) in cursor: + showui.Add() + showui.End() + cursor.close() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + + def GetFilms( self, showid, filmui ): + if self.conn is None: + return + if showid.find( ',' ) == -1: + # only one channel id + condition = '( `showid=`%s )' % showid + showchannels = False + else: + # multiple channel ids + condition = '( `showid` IN ( %s ) )' % showid + showchannels = True + self._Search_Condition( condition, filmui, False, showchannels ) + + def _Search_Condition( self, condition, filmui, showshows, showchannels ): + if self.conn is None: + return + try: + self.logger.info( 'MySQL Query: {}', + self.sql_query_films + + ' WHERE ' + + condition + + self.sql_cond_nofuture + + self.sql_cond_minlength + ) + cursor = self.conn.cursor() + cursor.execute( + self.sql_query_films + + ' WHERE ' + + condition + + self.sql_cond_nofuture + + self.sql_cond_minlength + ) + filmui.Begin( showshows, showchannels ) + for ( filmui.id, filmui.title, filmui.show, filmui.channel, filmui.description, filmui.seconds, filmui.size, filmui.aired, filmui.url_sub, filmui.url_video, filmui.url_video_sd, filmui.url_video_hd ) in cursor: + filmui.Add() + filmui.End() + cursor.close() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + + def RetrieveFilmInfo( self, filmid ): + if self.conn is None: + return None + try: + condition = '( film.id={} )'.format( filmid ) + self.logger.info( 'MySQL Query: {}', + self.sql_query_films + + ' WHERE ' + + condition + ) + cursor = self.conn.cursor() + cursor.execute( + self.sql_query_films + + ' WHERE ' + + condition + ) + film = Film() + for ( film.id, film.title, film.show, film.channel, film.description, film.seconds, film.size, film.aired, film.url_sub, film.url_video, film.url_video_sd, film.url_video_hd ) in cursor: + cursor.close() + return film + cursor.close() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + return None + + def GetStatus( self ): + status = { + 'modified': int( time.time() ), + 'status': '', + 'lastupdate': 0, + 'filmupdate': 0, + 'fullupdate': 0, + 'add_chn': 0, + 'add_shw': 0, + 'add_mov': 0, + 'del_chn': 0, + 'del_shw': 0, + 'del_mov': 0, + 'tot_chn': 0, + 'tot_shw': 0, + 'tot_mov': 0 + } + if self.conn is None: + status['status'] = "UNINIT" + return status + try: + cursor = self.conn.cursor() + cursor.execute( 'SELECT * FROM `status` LIMIT 1' ) + r = cursor.fetchall() + cursor.close() + self.conn.commit() + if len( r ) == 0: + status['status'] = "NONE" + return status + status['modified'] = r[0][0] + status['status'] = r[0][1] + status['lastupdate'] = r[0][2] + status['filmupdate'] = r[0][3] + status['fullupdate'] = r[0][4] + status['add_chn'] = r[0][5] + status['add_shw'] = r[0][6] + status['add_mov'] = r[0][7] + status['del_chn'] = r[0][8] + status['del_shw'] = r[0][9] + status['del_mov'] = r[0][10] + status['tot_chn'] = r[0][11] + status['tot_shw'] = r[0][12] + status['tot_mov'] = r[0][13] + return status + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + status['status'] = "UNINIT" + return status + + def UpdateStatus( self, status = None, lastupdate = None, filmupdate = None, fullupdate = None, add_chn = None, add_shw = None, add_mov = None, del_chn = None, del_shw = None, del_mov = None, tot_chn = None, tot_shw = None, tot_mov = None ): + if self.conn is None: + return + new = self.GetStatus() + old = new['status'] + if status is not None: + new['status'] = status + if lastupdate is not None: + new['lastupdate'] = lastupdate + if filmupdate is not None: + new['filmupdate'] = filmupdate + if fullupdate is not None: + new['fullupdate'] = fullupdate + if add_chn is not None: + new['add_chn'] = add_chn + if add_shw is not None: + new['add_shw'] = add_shw + if add_mov is not None: + new['add_mov'] = add_mov + if del_chn is not None: + new['del_chn'] = del_chn + if del_shw is not None: + new['del_shw'] = del_shw + if del_mov is not None: + new['del_mov'] = del_mov + if tot_chn is not None: + new['tot_chn'] = tot_chn + if tot_shw is not None: + new['tot_shw'] = tot_shw + if tot_mov is not None: + new['tot_mov'] = tot_mov + # TODO: we should only write, if we have changed something... + new['modified'] = int( time.time() ) + try: + cursor = self.conn.cursor() + if old == "NONE": + # insert status + cursor.execute( + """ + INSERT INTO `status` ( + `modified`, + `status`, + `lastupdate`, + `filmupdate`, + `fullupdate`, + `add_chn`, + `add_shw`, + `add_mov`, + `del_chm`, + `del_shw`, + `del_mov`, + `tot_chn`, + `tot_shw`, + `tot_mov` + ) + VALUES ( + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s + ) + """, ( + new['modified'], + new['status'], + new['lastupdate'], + new['filmupdate'], + new['fullupdate'], + new['add_chn'], + new['add_shw'], + new['add_mov'], + new['del_chn'], + new['del_shw'], + new['del_mov'], + new['tot_chn'], + new['tot_shw'], + new['tot_mov'], + ) + ) + else: + # update status + cursor.execute( + """ + UPDATE `status` + SET `modified` = %s, + `status` = %s, + `lastupdate` = %s, + `filmupdate` = %s, + `fullupdate` = %s, + `add_chn` = %s, + `add_shw` = %s, + `add_mov` = %s, + `del_chm` = %s, + `del_shw` = %s, + `del_mov` = %s, + `tot_chn` = %s, + `tot_shw` = %s, + `tot_mov` = %s + """, ( + new['modified'], + new['status'], + new['lastupdate'], + new['filmupdate'], + new['fullupdate'], + new['add_chn'], + new['add_shw'], + new['add_mov'], + new['del_chn'], + new['del_shw'], + new['del_mov'], + new['tot_chn'], + new['tot_shw'], + new['tot_mov'], + ) + ) + cursor.close() + self.conn.commit() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + + def SupportsUpdate( self ): + return True + + def ftInit( self ): + # prevent concurrent updating + cursor = self.conn.cursor() + cursor.execute( + """ + UPDATE `status` + SET `modified` = %s, + `status` = 'UPDATING' + WHERE ( `status` != 'UPDATING' ) + OR + ( `modified` < %s ) + """, ( + int( time.time() ), + int( time.time() ) - 86400 + ) + ) + retval = cursor.rowcount > 0 + self.conn.commit() + cursor.close() + self.ft_channel = None + self.ft_channelid = None + self.ft_show = None + self.ft_showid = None + return retval + + def ftUpdateStart( self, full ): + param = ( 1, ) if full else ( 0, ) + try: + cursor = self.conn.cursor() + cursor.callproc( 'ftUpdateStart', param ) + for result in cursor.stored_results(): + for ( cnt_chn, cnt_shw, cnt_mov ) in result: + cursor.close() + self.conn.commit() + return ( cnt_chn, cnt_shw, cnt_mov ) + # should never happen + cursor.close() + self.conn.commit() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + return ( 0, 0, 0, ) + + def ftUpdateEnd( self, delete ): + param = ( 1, ) if delete else ( 0, ) + try: + cursor = self.conn.cursor() + cursor.callproc( 'ftUpdateEnd', param ) + for result in cursor.stored_results(): + for ( del_chn, del_shw, del_mov, cnt_chn, cnt_shw, cnt_mov ) in result: + cursor.close() + self.conn.commit() + return ( del_chn, del_shw, del_mov, cnt_chn, cnt_shw, cnt_mov ) + # should never happen + cursor.close() + self.conn.commit() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + return ( 0, 0, 0, 0, 0, 0, ) + + def ftInsertFilm( self, film, commit ): + newchn = False + inschn = 0 + insshw = 0 + insmov = 0 + + # handle channel + if self.ft_channel != film['channel']: + # process changed channel + newchn = True + self.ft_channel = film['channel'] + ( self.ft_channelid, inschn ) = self._insert_channel( self.ft_channel ) + if self.ft_channelid == 0: + self.logger.info( 'Undefined error adding channel "{}"', self.ft_channel ) + return ( 0, 0, 0, 0, ) + + if newchn or self.ft_show != film['show']: + # process changed show + self.ft_show = film['show'] + ( self.ft_showid, insshw ) = self._insert_show( self.ft_channelid, self.ft_show, self._make_search( self.ft_show ) ) + if self.ft_showid == 0: + self.logger.info( 'Undefined error adding show "{}"', self.ft_show ) + return ( 0, 0, 0, 0, ) + + try: + cursor = self.conn.cursor() + cursor.callproc( 'ftInsertFilm', ( + self.ft_channelid, + self.ft_showid, + film["title"], + self._make_search( film['title'] ), + film["aired"], + film["duration"], + film["size"], + film["description"], + film["website"], + film["url_sub"], + film["url_video"], + film["url_video_sd"], + film["url_video_hd"], + film["airedepoch"], + ) ) + for result in cursor.stored_results(): + for ( filmid, insmov ) in result: + cursor.close() + if commit: + self.conn.commit() + return ( filmid, inschn, insshw, insmov ) + # should never happen + cursor.close() + if commit: + self.conn.commit() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + return ( 0, 0, 0, 0, ) + + def _insert_channel( self, channel ): + try: + cursor = self.conn.cursor() + cursor.callproc( 'ftInsertChannel', ( channel, ) ) + for result in cursor.stored_results(): + for ( id, added ) in result: + cursor.close() + self.conn.commit() + return ( id, added ) + # should never happen + cursor.close() + self.conn.commit() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + return ( 0, 0, ) + + def _insert_show( self, channelid, show, search ): + try: + cursor = self.conn.cursor() + cursor.callproc( 'ftInsertShow', ( channelid, show, search, ) ) + for result in cursor.stored_results(): + for ( id, added ) in result: + cursor.close() + self.conn.commit() + return ( id, added ) + # should never happen + cursor.close() + self.conn.commit() + except mysql.connector.Error as err: + self.logger.error( 'Database error: {}', err ) + self.notifier.ShowDatabaseError( err ) + return ( 0, 0, ) + + def _make_search( self, val ): + cset = string.letters + string.digits + ' _-#' + search = ''.join( [ c for c in val if c in cset ] ) + return search.upper().strip() |