Ejemplo n.º 1
0
 def __init__(self):
     super(MediathekViewPlugin, self).__init__()
     self.settings = Settings()
     self.notifier = Notifier()
     self.database = Store(self.get_new_logger('Store'), self.notifier,
                           self.settings)
     self.unicodePath = unicode(self.path, 'utf-8')
Ejemplo n.º 2
0
class MediathekViewPlugin(KodiPlugin):
    """ The main plugin class """
    def __init__(self):
        super(MediathekViewPlugin, self).__init__()
        self.settings = Settings()
        self.notifier = Notifier()
        self.database = Store(self.get_new_logger('Store'), self.notifier,
                              self.settings)

    def show_main_menu(self):
        """ Creates the main menu of the plugin """
        # Search
        self.add_folder_item(30901, {
            'mode': "search",
            'extendedsearch': False
        },
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'search-m.png'))
        # Search all
        self.add_folder_item(30902, {
            'mode': "search",
            'extendedsearch': True
        },
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'search-m.png'))
        # Browse livestreams
        self.add_folder_item(30903, {'mode': "livestreams"},
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'live2-m.png'))
        # Browse recently added
        self.add_folder_item(30904, {
            'mode': "recent",
            'channel': 0
        },
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'new-m.png'))
        # Browse recently added by channel
        self.add_folder_item(30905, {'mode': "recentchannels"},
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'new-m.png'))
        # Browse by Initial->Show
        self.add_folder_item(30906, {
            'mode': "initial",
            'channel': 0
        },
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'movie-m.png'))
        # Browse by Channel->Initial->Shows
        self.add_folder_item(30907, {'mode': "channels"},
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'movie-m.png'))
        # Database Information
        self.add_action_item(30908, {'mode': "action-dbinfo"},
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'dbinfo-m.png'))
        # Manual database update
        if self.settings.updmode == 1 or self.settings.updmode == 2:
            self.add_action_item(30909, {'mode': "action-dbupdate"})
        self.end_of_directory()
        self._check_outdate()

    def show_searches(self, extendedsearch=False):
        """
        Fill the search screen with "New Search..." and the
        list of recent searches

        Args:
            extendedsearch(bool, optionsl): If `True`, the searches
                are performed both in show title and description.
                Default is `False`
        """
        self.add_folder_item(30931, {
            'mode': "newsearch",
            'extendedsearch': extendedsearch
        },
                             icon=os.path.join(self.path, 'resources', 'icons',
                                               'search-m.png'))
        RecentSearches(self, extendedsearch).load().populate()
        self.end_of_directory()

    def new_search(self, extendedsearch=False):
        """
        Asks the user to enter his search terms and then
        performs the search and displays the results.

        Args:
            extendedsearch(bool, optionsl): If `True`, the searches
                are performed both in show title and description.
                Default is `False`
        """
        settingid = 'lastsearch2' if extendedsearch is True else 'lastsearch1'
        headingid = 30902 if extendedsearch is True else 30901
        # are we returning from playback ?
        search = self.get_setting(settingid)
        if search:
            # restore previous search
            self.database.search(search, FilmUI(self), extendedsearch)
        else:
            # enter search term
            (search, confirmed) = self.notifier.get_entered_text('', headingid)
            if len(search) > 2 and confirmed is True:
                RecentSearches(self, extendedsearch).load().add(search).save()
                if self.database.search(search, FilmUI(self),
                                        extendedsearch) > 0:
                    self.set_setting(settingid, search)
            else:
                # pylint: disable=line-too-long
                self.info(
                    'The following ERROR can be ignored. It is caused by the architecture of the Kodi Plugin Engine'
                )
                self.end_of_directory(False, cache_to_disc=True)

    def show_db_info(self):
        """ Displays current information about the database """
        info = self.database.get_status()
        heading = self.language(30908)
        infostr = self.language({
            'NONE': 30941,
            'UNINIT': 30942,
            'IDLE': 30943,
            'UPDATING': 30944,
            'ABORTED': 30945
        }.get(info['status'], 30941))
        infostr = self.language(30965) % infostr
        totinfo = self.language(30971) % (info['tot_chn'], info['tot_shw'],
                                          info['tot_mov'])
        updatetype = self.language(30972 if info['fullupdate'] > 0 else 30973)
        if info['status'] == 'UPDATING' and info['filmupdate'] > 0:
            updinfo = self.language(30967) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'])
        elif info['status'] == 'UPDATING':
            updinfo = self.language(30968) % (updatetype, info['add_chn'],
                                              info['add_shw'], info['add_mov'])
        elif info['lastupdate'] > 0 and info['filmupdate'] > 0:
            updinfo = self.language(30969) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                datetime.datetime.fromtimestamp(
                    info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'],
                info['del_chn'], info['del_shw'], info['del_mov'])
        elif info['lastupdate'] > 0:
            updinfo = self.language(30970) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'],
                info['del_chn'], info['del_shw'], info['del_mov'])
        else:
            updinfo = self.language(30966)

        xbmcgui.Dialog().textviewer(
            heading, infostr + '\n\n' + totinfo + '\n\n' + updinfo)

    def _check_outdate(self, maxage=172800):
        if self.settings.updmode != 1 and self.settings.updmode != 2:
            # no check with update disabled or update automatic
            return
        if self.database is None:
            # should never happen
            self.notifier.show_outdated_unknown()
            return
        status = self.database.get_status()
        if status['status'] == 'NONE' or status['status'] == 'UNINIT':
            # should never happen
            self.notifier.show_outdated_unknown()
            return
        elif status['status'] == 'UPDATING':
            # great... we are updating. nuthin to show
            return
        # lets check how old we are
        tsnow = int(time.time())
        tsold = int(status['lastupdate'])
        if tsnow - tsold > maxage:
            self.notifier.show_outdated_known(status)

    def init(self):
        """ Initialisation of the plugin """
        if self.database.init():
            if self.settings.handle_first_run():
                pass
            self.settings.handle_update_on_start()

    def run(self):
        """ Execution of the plugin """
        # save last activity timestamp
        self.settings.reset_user_activity()
        # process operation
        self.info("Plugin invoked with parameters {}", self.args)
        mode = self.get_arg('mode', None)
        if mode is None:
            self.show_main_menu()
        elif mode == 'search':
            extendedsearch = self.get_arg('extendedsearch', 'False') == 'True'
            self.show_searches(extendedsearch)
        elif mode == 'newsearch':
            self.new_search(self.get_arg('extendedsearch', 'False') == 'True')
        elif mode == 'research':
            search = self.get_arg('search', '')
            extendedsearch = self.get_arg('extendedsearch', 'False') == 'True'
            self.database.search(search, FilmUI(self), extendedsearch)
            RecentSearches(self, extendedsearch).load().add(search).save()
        elif mode == 'delsearch':
            search = self.get_arg('search', '')
            extendedsearch = self.get_arg('extendedsearch', 'False') == 'True'
            RecentSearches(
                self, extendedsearch).load().delete(search).save().populate()
            self.run_builtin('Container.Refresh')
        elif mode == 'livestreams':
            self.database.get_live_streams(
                FilmUI(self, [xbmcplugin.SORT_METHOD_LABEL]))
        elif mode == 'recent':
            channel = self.get_arg('channel', 0)
            self.database.get_recents(channel, FilmUI(self))
        elif mode == 'recentchannels':
            self.database.get_recent_channels(ChannelUI(self,
                                                        nextdir='recent'))
        elif mode == 'channels':
            self.database.get_channels(ChannelUI(self, nextdir='shows'))
        elif mode == 'action-dbinfo':
            self.show_db_info()
        elif mode == 'action-dbupdate':
            self.settings.trigger_update()
            self.notifier.show_notification(30963, 30964, time=10000)
        elif mode == 'initial':
            channel = self.get_arg('channel', 0)
            self.database.get_initials(channel, InitialUI(self))
        elif mode == 'shows':
            channel = self.get_arg('channel', 0)
            initial = self.get_arg('initial', None)
            self.database.get_shows(channel, initial, ShowUI(self))
        elif mode == 'films':
            show = self.get_arg('show', 0)
            self.database.get_films(show, FilmUI(self))
        elif mode == 'downloadmv':
            filmid = self.get_arg('id', 0)
            quality = self.get_arg('quality', 1)
            Downloader(self).download_movie(filmid, quality)
        elif mode == 'downloadep':
            filmid = self.get_arg('id', 0)
            quality = self.get_arg('quality', 1)
            Downloader(self).download_episode(filmid, quality)
        elif mode == 'playwithsrt':
            filmid = self.get_arg('id', 0)
            Downloader(self).play_movie_with_subs(filmid)

        # cleanup saved searches
        if mode is None or mode != 'newsearch':
            self.set_setting('lastsearch1', '')
            self.set_setting('lastsearch2', '')

    def exit(self):
        """ Shutdown of the application """
        self.database.exit()
Ejemplo n.º 3
0
	def Init( self ):
		if self.db is not None:
			self.Exit()
		self.db = Store( self.logger, self.notifier, self.settings )
		self.db.Init()
Ejemplo n.º 4
0
class MediathekViewUpdater( object ):
	def __init__( self, logger, notifier, settings, monitor = None ):
		self.logger		= logger
		self.notifier	= notifier
		self.settings	= settings
		self.monitor	= monitor
		self.db			= None
		self.use_xz     = mvutils.find_xz() is not None

	def Init( self ):
		if self.db is not None:
			self.Exit()
		self.db = Store( self.logger, self.notifier, self.settings )
		self.db.Init()

	def Exit( self ):
		if self.db is not None:
			self.db.Exit()
			del self.db
			self.db = None

	def IsEnabled( self ):
		return self.settings.updenabled

	def GetCurrentUpdateOperation( self ):
		if not self.IsEnabled() or self.db is None:
			# update disabled or not possible
			self.logger.info( 'update disabled or not possible' )
			return 0
		status = self.db.GetStatus()
		tsnow = int( time.time() )
		tsold = status['lastupdate']
		dtnow = datetime.datetime.fromtimestamp( tsnow ).date()
		dtold = datetime.datetime.fromtimestamp( tsold ).date()
		if status['status'] == 'UNINIT':
			# database not initialized
			self.logger.debug( 'database not initialized' )
			return 0
		elif status['status'] == "UPDATING" and tsnow - tsold > 10800:
			# process was probably killed during update
			self.logger.info( 'Stuck update pretending to run since epoch {} reset', tsold )
			self.db.UpdateStatus( 'ABORTED' )
			return 0
		elif status['status'] == "UPDATING":
			# already updating
			self.logger.debug( 'already updating' )
			return 0
		elif tsnow - tsold < self.settings.updinterval:
			# last update less than the configured update interval. do nothing
			self.logger.debug( 'last update less than the configured update interval. do nothing' )
			return 0
		elif dtnow != dtold:
			# last update was not today. do full update once a day
			self.logger.debug( 'last update was not today. do full update once a day' )
			return 1
		elif status['status'] == "ABORTED" and status['fullupdate'] == 1:
			# last full update was aborted - full update needed
			self.logger.debug( 'last full update was aborted - full update needed' )
			return 1
		else:
			# do differential update
			self.logger.debug( 'do differential update' )
			return 2

	def Update( self, full ):
		if self.db is None:
			return
		if self.db.SupportsUpdate():
			if self.GetNewestList( full ):
				self.Import( full )

	def Import( self, full ):
		( _, _, destfile, avgrecsize ) = self._get_update_info( full )
		if not mvutils.file_exists( destfile ):
			self.logger.error( 'File {} does not exists', destfile )
			return False
		# estimate number of records in update file
		records = int( mvutils.file_size( destfile ) / avgrecsize )
		if not self.db.ftInit():
			self.logger.warn( 'Failed to initialize update. Maybe a concurrency problem?' )
			return False
		try:
			self.logger.info( 'Starting import of approx. {} records from {}', records, destfile )
			with open( destfile, 'r' ) as file:
				parser = ijson.parse( file )
				flsm = 0
				flts = 0
				( self.tot_chn, self.tot_shw, self.tot_mov ) = self._update_start( full )
				self.notifier.ShowUpdateProgress()
				for prefix, event, value in parser:
					if ( prefix, event ) == ( "X", "start_array" ):
						self._init_record()
					elif ( prefix, event ) == ( "X", "end_array" ):
						self._end_record( records )
						if self.count % 100 == 0 and self.monitor.abortRequested():
							# kodi is shutting down. Close all
							self._update_end( full, 'ABORTED' )
							self.notifier.CloseUpdateProgress()
							return True
					elif ( prefix, event ) == ( "X.item", "string" ):
						if value is not None:
	#						self._add_value( value.strip().encode('utf-8') )
							self._add_value( value.strip() )
						else:
							self._add_value( "" )
					elif ( prefix, event ) == ( "Filmliste", "start_array" ):
						flsm += 1
					elif ( prefix, event ) == ( "Filmliste.item", "string" ):
						flsm += 1
						if flsm == 2 and value is not None:
							# this is the timestmap of this database update
							try:
								fldt = datetime.datetime.strptime( value.strip(), "%d.%m.%Y, %H:%M" )
								flts = int( time.mktime( fldt.timetuple() ) )
								self.db.UpdateStatus( filmupdate = flts )
								self.logger.info( 'Filmliste dated {}', value.strip() )
							except TypeError:
								# SEE: https://forum.kodi.tv/showthread.php?tid=112916&pid=1214507#pid1214507
								# Wonderful. His name is also Leopold
								try:
									flts = int( time.mktime( time.strptime( value.strip(), "%d.%m.%Y, %H:%M" ) ) )
									self.db.UpdateStatus( filmupdate = flts )
									self.logger.info( 'Filmliste dated {}', value.strip() )
								except Exception as err:
									# If the universe hates us...
									self.logger.debug( 'Could not determine date "{}" of filmliste: {}', value.strip(), err )
							except ValueError as err:
								pass

			self._update_end( full, 'IDLE' )
			self.logger.info( 'Import of {} finished', destfile )
			self.notifier.CloseUpdateProgress()
			return True
		except KeyboardInterrupt:
			self._update_end( full, 'ABORTED' )
			self.logger.info( 'Interrupted by user' )
			self.notifier.CloseUpdateProgress()
			return True
		except DatabaseCorrupted as err:
			self.logger.error( '{}', err )
			self.notifier.CloseUpdateProgress()
		except DatabaseLost as err:
			self.logger.error( '{}', err )
			self.notifier.CloseUpdateProgress()
		except Exception as err:
			self.logger.error( 'Error {} wile processing {}', err, destfile )
			self._update_end( full, 'ABORTED' )
			self.notifier.CloseUpdateProgress()
		return False

	def GetNewestList( self, full ):
		( url, compfile, destfile, _ ) = self._get_update_info( full )
		if url is None:
			self.logger.error( 'No suitable archive extractor available for this system' )
			self.notifier.ShowMissingExtractorError()
			return False

		# get mirrorlist
		self.logger.info( 'Opening {}', url )
		try:
			data = urllib2.urlopen( url ).read()
		except urllib2.URLError as err:
			self.logger.error( 'Failure opening {}', url )
			self.notifier.ShowDownloadError( url, err )
			return False
		root = etree.fromstring ( data )
		urls = []
		for server in root.findall( 'Server' ):
			try:
				URL = server.find( 'URL' ).text
				Prio = server.find( 'Prio' ).text
				urls.append( ( self._get_update_url( URL ), float( Prio ) + random.random() * 1.2 ) )
				self.logger.info( 'Found mirror {} (Priority {})', URL, Prio )
			except AttributeError:
				pass
		urls = sorted( urls, key = itemgetter( 1 ) )
		urls = [ url[0] for url in urls ]

		# cleanup downloads
		self.logger.info( 'Cleaning up old downloads...' )
		self._file_remove( compfile )
		self._file_remove( destfile )

		# download filmliste
		self.notifier.ShowDownloadProgress()
		lasturl = ''
		for url in urls:
			try:
				lasturl = url
				self.logger.info( 'Trying to download {} from {}...', os.path.basename( compfile ), url )
				self.notifier.UpdateDownloadProgress( 0, url )
				mvutils.url_retrieve( url, filename = compfile, reporthook = self.notifier.HookDownloadProgress, aborthook = self.monitor.abortRequested )
				break
			except urllib2.URLError as err:
				self.logger.error( 'Failure downloading {}', url )
				self.notifier.CloseDownloadProgress()
				self.notifier.ShowDownloadError( lasturl, err )
				return False
			except ExitRequested as err:
				self.logger.error( 'Immediate exit requested. Aborting download of {}', url )
				self.notifier.CloseDownloadProgress()
				self.notifier.ShowDownloadError( lasturl, err )
				return False
			except Exception as err:
				self.logger.error( 'Failure writng {}', url )
				self.notifier.CloseDownloadProgress()
				self.notifier.ShowDownloadError( lasturl, err )
				return False

		# decompress filmliste
		if self.use_xz is True:
			self.logger.info( 'Trying to decompress xz file...' )
			retval = subprocess.call( [ mvutils.find_xz(), '-d', compfile ] )
			self.logger.info( 'Return {}', retval )
		elif upd_can_bz2 is True:
			self.logger.info( 'Trying to decompress bz2 file...' )
			retval = self._decompress_bz2( compfile, destfile )
			self.logger.info( 'Return {}', retval )
		elif upd_can_gz is True:
			self.logger.info( 'Trying to decompress gz file...' )
			retval = self._decompress_gz( compfile, destfile )
			self.logger.info( 'Return {}', retval )
		else:
			# should nebver reach
			pass

		self.notifier.CloseDownloadProgress()
		return retval == 0 and mvutils.file_exists( destfile )

	def _get_update_info( self, full ):
		if self.use_xz is True:
			ext = 'xz'
		elif upd_can_bz2 is True:
			ext = 'bz2'
		elif upd_can_gz is True:
			ext = 'gz'
		else:
			return ( None, None, None, 0, )

		if full:
			return (
				FILMLISTE_AKT_URL,
				os.path.join( self.settings.datapath, 'Filmliste-akt.' + ext ),
				os.path.join( self.settings.datapath, 'Filmliste-akt' ),
				600,
			)
		else:
			return (
				FILMLISTE_DIF_URL,
				os.path.join( self.settings.datapath, 'Filmliste-diff.' + ext ),
				os.path.join( self.settings.datapath, 'Filmliste-diff' ),
				700,
			)

	def _get_update_url( self, url ):
		if self.use_xz is True:
			return url
		elif upd_can_bz2 is True:
			return os.path.splitext( url )[0] + '.bz2'
		elif upd_can_gz is True:
			return os.path.splitext( url )[0] + '.gz'
		else:
			# should never happen since it will not be called
			return None

	def _file_remove( self, name ):
		if mvutils.file_exists( name ):
			try:
				os.remove( name )
				return True
			except OSError as err:
				self.logger.error( 'Failed to remove {}: error {}', name, err )
		return False

	def _update_start( self, full ):
		self.logger.info( 'Initializing update...' )
		self.add_chn = 0
		self.add_shw = 0
		self.add_mov = 0
		self.add_chn = 0
		self.add_shw = 0
		self.add_mov = 0
		self.del_chn = 0
		self.del_shw = 0
		self.del_mov = 0
		self.index = 0
		self.count = 0
		self.film = {
			"channel": "",
			"show": "",
			"title": "",
			"aired": "1980-01-01 00:00:00",
			"duration": "00:00:00",
			"size": 0,
			"description": "",
			"website": "",
			"url_sub": "",
			"url_video": "",
			"url_video_sd": "",
			"url_video_hd": "",
			"airedepoch": 0,
			"geo": ""
		}
		return self.db.ftUpdateStart( full )

	def _update_end( self, full, status ):
		self.logger.info( 'Added: channels:%d, shows:%d, movies:%d ...' % ( self.add_chn, self.add_shw, self.add_mov ) )
		( self.del_chn, self.del_shw, self.del_mov, self.tot_chn, self.tot_shw, self.tot_mov ) = self.db.ftUpdateEnd( full and status == 'IDLE' )
		self.logger.info( 'Deleted: channels:%d, shows:%d, movies:%d' % ( self.del_chn, self.del_shw, self.del_mov ) )
		self.logger.info( 'Total: channels:%d, shows:%d, movies:%d' % ( self.tot_chn, self.tot_shw, self.tot_mov ) )
		self.db.UpdateStatus(
			status,
			int( time.time() ) if status != 'ABORTED' else None,
			None,
			1 if full else 0,
			self.add_chn, self.add_shw, self.add_mov,
			self.del_chn, self.del_shw, self.del_mov,
			self.tot_chn, self.tot_shw, self.tot_mov
		)

	def _init_record( self ):
		self.index = 0
		self.film["title"] = ""
		self.film["aired"] = "1980-01-01 00:00:00"
		self.film["duration"] = "00:00:00"
		self.film["size"] = 0
		self.film["description"] = ""
		self.film["website"] = ""
		self.film["url_sub"] = ""
		self.film["url_video"] = ""
		self.film["url_video_sd"] = ""
		self.film["url_video_hd"] = ""
		self.film["airedepoch"] = 0
		self.film["geo"] = ""

	def _end_record( self, records ):
		if self.count % 1000 == 0:
			percent = int( self.count * 100 / records )
			self.logger.info( 'In progress (%d%%): channels:%d, shows:%d, movies:%d ...' % ( percent, self.add_chn, self.add_shw, self.add_mov ) )
			self.notifier.UpdateUpdateProgress( percent if percent <= 100 else 100, self.count, self.add_chn, self.add_shw, self.add_mov )
			self.db.UpdateStatus(
				add_chn = self.add_chn,
				add_shw = self.add_shw,
				add_mov = self.add_mov,
				tot_chn = self.tot_chn + self.add_chn,
				tot_shw = self.tot_shw + self.add_shw,
				tot_mov = self.tot_mov + self.add_mov
			)
			self.count = self.count + 1
			( _, cnt_chn, cnt_shw, cnt_mov ) = self.db.ftInsertFilm( self.film, True )
		else:
			self.count = self.count + 1
			( _, cnt_chn, cnt_shw, cnt_mov ) = self.db.ftInsertFilm( self.film, False )
		self.add_chn += cnt_chn
		self.add_shw += cnt_shw
		self.add_mov += cnt_mov

	def _add_value( self, val ):
		if self.index == 0:
			if val != "":
				self.film["channel"] = val
		elif self.index == 1:
			if val != "":
				self.film["show"] = val[:255]
		elif self.index == 2:
			self.film["title"] = val[:255]
		elif self.index == 3:
			if len(val) == 10:
				self.film["aired"] = val[6:] + '-' + val[3:5] + '-' + val[:2]
		elif self.index == 4:
			if ( self.film["aired"] != "1980-01-01 00:00:00" ) and ( len(val) == 8 ):
				self.film["aired"] = self.film["aired"] + " " + val
		elif self.index == 5:
			if len(val) == 8:
				self.film["duration"] = val
		elif self.index == 6:
			if val != "":
				self.film["size"] = int(val)
		elif self.index == 7:
			self.film["description"] = val
		elif self.index == 8:
			self.film["url_video"] = val
		elif self.index == 9:
			self.film["website"] = val
		elif self.index == 10:
			self.film["url_sub"] = val
		elif self.index == 12:
			self.film["url_video_sd"] = self._make_url(val)
		elif self.index == 14:
			self.film["url_video_hd"] = self._make_url(val)
		elif self.index == 16:
			if val != "":
				self.film["airedepoch"] = int(val)
		elif self.index == 18:
			self.film["geo"] = val
		self.index = self.index + 1

	def _make_url( self, val ):
		x = val.split( '|' )
		if len( x ) == 2:
			cnt = int( x[0] )
			return self.film["url_video"][:cnt] + x[1]
		else:
			return val

	def _decompress_bz2( self, sourcefile, destfile ):
		blocksize = 8192
		try:
			with open( destfile, 'wb' ) as df, open( sourcefile, 'rb' ) as sf:
				decompressor = bz2.BZ2Decompressor()
				for data in iter( lambda : sf.read( blocksize ), b'' ):
					df.write( decompressor.decompress( data ) )
		except Exception as err:
			self.logger.error( 'bz2 decompression failed: {}'.format( err ) )
			return -1
		return 0

	def _decompress_gz( self, sourcefile, destfile ):
		blocksize = 8192
		try:
			with open( destfile, 'wb' ) as df, gzip.open( sourcefile ) as sf:
				for data in iter( lambda : sf.read( blocksize ), b'' ):
					df.write( data )
		except Exception as err:
			self.logger.error( 'gz decompression failed: {}'.format( err ) )
			return -1
		return 0
 def init(self, convert=False):
     """ Initializes the updater """
     if self.database is not None:
         self.exit()
     self.database = Store(self.logger, self.notifier, self.settings)
     self.database.init(convert=convert)
class MediathekViewUpdater(object):
    """ The database updator class """

    def __init__(self, logger, notifier, settings, monitor=None):
        self.logger = logger
        self.notifier = notifier
        self.settings = settings
        self.monitor = monitor
        self.database = None
        self.use_xz = mvutils.find_xz() is not None
        self.cycle = 0
        self.add_chn = 0
        self.add_shw = 0
        self.add_mov = 0
        self.del_chn = 0
        self.del_shw = 0
        self.del_mov = 0
        self.tot_chn = 0
        self.tot_shw = 0
        self.tot_mov = 0
        self.index = 0
        self.count = 0
        self.film = {}

    def init(self, convert=False):
        """ Initializes the updater """
        if self.database is not None:
            self.exit()
        self.database = Store(self.logger, self.notifier, self.settings)
        self.database.init(convert=convert)

    def exit(self):
        """ Resets the updater """
        if self.database is not None:
            self.database.exit()
            del self.database
            self.database = None

    def reload(self):
        """ Reloads the updater """
        self.exit()
        self.init()

    def is_enabled(self):
        """ Returns if the updater is enabled """
        return self.settings.updenabled

    def get_current_update_operation(self, force=False, full=False):
        """
        Determines which update operation should be done. Returns
        one of these values:

        0 - no update operation pending
        1 - full update
        2 - differential update

        Args:
            force(bool, optional): if `True` a full update
                is always returned. Default is `False`
            full(book, optional): if `True` a full update
                is always returned. Default is `False`
        """
        if self.database is None:
            # db not available - no update
            self.logger.info('Update disabled since database not available')
            return 0
        elif self.settings.updmode == 0:
            # update disabled - no update
            return 0
        elif self.settings.updmode == 1 or self.settings.updmode == 2:
            # manual update or update on first start
            if self.settings.is_update_triggered() is True:
                return self._get_next_update_operation(True, False)
            else:
                # no update on all subsequent calls
                return 0
        elif self.settings.updmode == 3:
            # automatic update
            if self.settings.is_user_alive():
                return self._get_next_update_operation(force, full)
            else:
                # no update if user is idle for more than 2 hours
                return 0
        elif self.settings.updmode == 4:
            # continous update
            return self._get_next_update_operation(force, full)

    def _get_next_update_operation(self, force=False, full=False):
        status = self.database.get_status()
        tsnow = int(time.time())
        tsold = status['lastupdate']
        dtnow = datetime.datetime.fromtimestamp(tsnow).date()
        dtold = datetime.datetime.fromtimestamp(tsold).date()
        if status['status'] == 'UNINIT':
            # database not initialized - no update
            self.logger.debug('database not initialized')
            return 0
        elif status['status'] == "UPDATING" and tsnow - tsold > 10800:
            # process was probably killed during update - no update
            self.logger.info(
                'Stuck update pretending to run since epoch {} reset', tsold)
            self.database.update_status('ABORTED')
            return 0
        elif status['status'] == "UPDATING":
            # already updating - no update
            self.logger.debug('Already updating')
            return 0
        elif not full and not force and tsnow - tsold < self.settings.updinterval:
            # last update less than the configured update interval - no update
            self.logger.debug(
                'Last update less than the configured update interval. do nothing')
            return 0
        elif dtnow != dtold:
            # last update was not today. do full update once a day
            self.logger.debug(
                'Last update was not today. do full update once a day')
            return 1
        elif status['status'] == "ABORTED" and status['fullupdate'] == 1:
            # last full update was aborted - full update needed
            self.logger.debug(
                'Last full update was aborted - full update needed')
            return 1
        elif full is True:
            # full update requested
            self.logger.info('Full update requested')
            return 1
        else:
            # do differential update
            self.logger.debug('Do differential update')
            return 2

    def update(self, full):
        """
        Downloads the database update file and
        then performs a database update

        Args:
            full(bool): Perform full update if `True`
        """
        if self.database is None:
            return
        elif self.database.supports_native_update(full):
            if self.get_newest_list(full):
                if self.database.native_update(full):
                    self.cycle += 1
            self.delete_list(full)
        elif self.database.supports_update():
            if self.get_newest_list(full):
                if self.import_database(full):
                    self.cycle += 1
            self.delete_list(full)

    def _object_pairs_hook(self, inputArg):
        """
        We need this because the default handler would convert everything to dict
        and the same key would be overwritten and lost (e.g. X) 
        """
        return inputArg

    def import_database(self, full):
        """
        Performs a database update when a
        downloaded update file is available

        Args:
            full(bool): Perform full update if `True`
        """
        (_, _, destfile, avgrecsize) = self._get_update_info(full)
        if not mvutils.file_exists(destfile):
            self.logger.error('File {} does not exists', destfile)
            return False
        # estimate number of records in update file
        records = int(mvutils.file_size(destfile) / avgrecsize)
        if not self.database.ft_init():
            self.logger.warn(
                'Failed to initialize update. Maybe a concurrency problem?')
            return False
        
        # pylint: disable=broad-except
        try:
            starttime = time.time()
            with closing( open(destfile, 'r', encoding="utf-8") ) as updatefile:
                jsonDoc = json.load( updatefile, object_pairs_hook=self._object_pairs_hook )
                self.logger.info( 'Starting import of {} records from {}', (len(jsonDoc)-2), destfile )
                flsm = 0
                flts = 0
                (self.tot_chn, self.tot_shw, self.tot_mov) = self._update_start(full)
                self.notifier.show_update_progress()
                
                ####
                flsm = 0
                sender = ""
                thema = ""
                ### ROOT LIST
                for atuple in jsonDoc:
                    if (atuple[0] == 'Filmliste' and flsm == 0):
                        ### META
                        ### "Filmliste":["23.04.2020, 18:23","23.04.2020, 16:23","3","MSearch [Vers.: 3.1.129]","3c90946f05eb1e2fa6cf2327cca4f1d4"],
                        flsm +=1
                        # this is the timestamp of this database update
                        value = atuple[1][0]
                        try:
                            fldt = datetime.datetime.strptime(
                                value.strip(), "%d.%m.%Y, %H:%M")
                            flts = int(time.mktime(fldt.timetuple()))
                            self.database.update_status(filmupdate=flts)
                            self.logger.info(
                                'Filmliste dated {}', value.strip())
                        except TypeError:
                            # pylint: disable=line-too-long
                            # SEE: https://forum.kodi.tv/showthread.php?tid=112916&pid=1214507#pid1214507
                            # Wonderful. His name is also Leopold
                            try:
                                flts = int(time.mktime(time.strptime(
                                    value.strip(), "%d.%m.%Y, %H:%M")))
                                self.database.update_status(
                                    filmupdate=flts)
                                self.logger.info(
                                    'Filmliste dated {}', value.strip())
                                # pylint: disable=broad-except
                            except Exception as err:
                                # If the universe hates us...
                                self.logger.debug(
                                    'Could not determine date "{}" of filmliste: {}', value.strip(), err)
                        except ValueError as err:
                            pass
                                        
                    elif (atuple[0] == 'filmliste' and flsm == 1):
                        flsm +=1
                        # VOID - we do not need column names
                        # "Filmliste":["Sender","Thema","Titel","Datum","Zeit","Dauer","Größe [MB]","Beschreibung","Url","Website","Url Untertitel","Url RTMP","Url Klein","Url RTMP Klein","Url HD","Url RTMP HD","DatumL","Url History","Geo","neu"],
                    elif (atuple[0] == 'X'):
                        self._init_record()
                        # behaviour of the update list
                        if (len(atuple[1][0]) > 0):
                            sender = atuple[1][0]
                        else:
                            atuple[1][0] = sender
                        # same for thema
                        if (len(atuple[1][1]) > 0):
                            thema = atuple[1][1]
                        else:
                            atuple[1][1] = thema
                        ##
                        self._add_value( atuple[1] )
                        self._end_record(records)
                        if self.count % 100 == 0 and self.monitor.abort_requested():
                            # kodi is shutting down. Close all
                            self._update_end(full, 'ABORTED')
                            self.notifier.close_update_progress()
                            return True
                    
            self._update_end(full, 'IDLE')
            self.logger.info('{} records processed',self.count)
            self.logger.info(
                'Import of {} in update cycle {} finished. Duration: {} seconds',
                destfile,
                self.cycle,
                int(time.time() - starttime)
            )
            self.notifier.close_update_progress()
            return True
        except KeyboardInterrupt:
            self._update_end(full, 'ABORTED')
            self.logger.info('Update cycle {} interrupted by user', self.cycle)
            self.notifier.close_update_progress()
            return False
        except DatabaseCorrupted as err:
            self.logger.error('{} on update cycle {}', err, self.cycle)
            self.notifier.close_update_progress()
        except DatabaseLost as err:
            self.logger.error('{} on update cycle {}', err, self.cycle)
            self.notifier.close_update_progress()
        except Exception as err:
            self.logger.error(
                'Error {} while processing {} on update cycle {}', err, destfile, self.cycle)
            self._update_end(full, 'ABORTED')
            self.notifier.close_update_progress()
        return False

    def get_newest_list(self, full):
        """
        Downloads the database update file

        Args:
            full(bool): Downloads the full list if `True`
        """
        (url, compfile, destfile, _) = self._get_update_info(full)
        if url is None:
            self.logger.error(
                'No suitable archive extractor available for this system')
            self.notifier.show_missing_extractor_error()
            return False

        # cleanup downloads
        self.logger.info('Cleaning up old downloads...')
        mvutils.file_remove(compfile)
        mvutils.file_remove(destfile)

        # download filmliste
        self.notifier.show_download_progress()

        # pylint: disable=broad-except
        try:
            self.logger.info('Trying to download {} from {}...',
                             os.path.basename(compfile), url)
            self.notifier.update_download_progress(0, url)
            mvutils.url_retrieve(
                url,
                filename=compfile,
                reporthook=self.notifier.hook_download_progress,
                aborthook=self.monitor.abort_requested
            )
        except URLError as err:
            self.logger.error('Failure downloading {} - {}', url, err)
            self.notifier.close_download_progress()
            self.notifier.show_download_error(url, err)
            return False
        except ExitRequested as err:
            self.logger.error(
                'Immediate exit requested. Aborting download of {}', url)
            self.notifier.close_download_progress()
            self.notifier.show_download_error(url, err)
            return False
        except Exception as err:
            self.logger.error('Failure writing {}', url)
            self.notifier.close_download_progress()
            self.notifier.show_download_error(url, err)
            return False

        # decompress filmliste
        if self.use_xz is True:
            self.logger.info('Trying to decompress xz file...')
            retval = subprocess.call([mvutils.find_xz(), '-d', compfile])
            self.logger.info('Return {}', retval)
        elif UPD_CAN_BZ2 is True:
            self.logger.info('Trying to decompress bz2 file...')
            retval = self._decompress_bz2(compfile, destfile)
            self.logger.info('Return {}', retval)
        elif UPD_CAN_GZ is True:
            self.logger.info('Trying to decompress gz file...')
            retval = self._decompress_gz(compfile, destfile)
            self.logger.info('Return {}', retval)
        else:
            # should never reach
            pass

        self.notifier.close_download_progress()
        return retval == 0 and mvutils.file_exists(destfile)

    def delete_list(self, full):
        """
        Deletes locally stored database update files

        Args:
            full(bool): Deletes the full lists if `True`
        """
        (_, compfile, destfile, _) = self._get_update_info(full)
        self.logger.info('Cleaning up downloads...')
        mvutils.file_remove(compfile)
        mvutils.file_remove(destfile)

    def _get_update_info(self, full):
        if self.use_xz is True:
            ext = '.xz'
        elif UPD_CAN_BZ2 is True:
            ext = '.bz2'
        elif UPD_CAN_GZ is True:
            ext = '.gz'
        else:
            return (None, None, None, 0, )

        info = self.database.get_native_info(full)
        if info is not None:
            return (
                self._get_update_url(info[0]),
                os.path.join(self.settings.datapath, info[1] + ext),
                os.path.join(self.settings.datapath, info[1]),
                500
            )

        if full:
            return (
                FILMLISTE_URL + FILMLISTE_AKT + ext,
                os.path.join(self.settings.datapath, FILMLISTE_AKT + ext),
                os.path.join(self.settings.datapath, FILMLISTE_AKT),
                600,
            )
        else:
            return (
                FILMLISTE_URL + FILMLISTE_DIF + ext,
                os.path.join(self.settings.datapath, FILMLISTE_DIF + ext),
                os.path.join(self.settings.datapath, FILMLISTE_DIF),
                700,
            )

    def _get_update_url(self, url):
        if self.use_xz is True:
            return url
        elif UPD_CAN_BZ2 is True:
            return os.path.splitext(url)[0] + '.bz2'
        elif UPD_CAN_GZ is True:
            return os.path.splitext(url)[0] + '.gz'
        else:
            # should never happen since it will not be called
            return None

    def _update_start(self, full):
        self.logger.info('Initializing update...')
        self.add_chn = 0
        self.add_shw = 0
        self.add_mov = 0
        self.del_chn = 0
        self.del_shw = 0
        self.del_mov = 0
        self.index = 0
        self.count = 0
        self.film = {
            "channel": "",
            "show": "",
            "title": "",
            "aired": "1980-01-01 00:00:00",
            "duration": "00:00:00",
            "size": 0,
            "description": "",
            "website": "",
            "url_sub": "",
            "url_video": "",
            "url_video_sd": "",
            "url_video_hd": "",
            "airedepoch": 0,
            "geo": ""
        }
        return self.database.ft_update_start(full)

    def _update_end(self, full, status):
        self.logger.info('Added: channels:%d, shows:%d, movies:%d ...' % (
            self.add_chn, self.add_shw, self.add_mov))
        (self.del_chn, self.del_shw, self.del_mov, self.tot_chn, self.tot_shw,
         self.tot_mov) = self.database.ft_update_end(full and status == 'IDLE')
        self.logger.info('Deleted: channels:%d, shows:%d, movies:%d' %
                         (self.del_chn, self.del_shw, self.del_mov))
        self.logger.info('Total: channels:%d, shows:%d, movies:%d' %
                         (self.tot_chn, self.tot_shw, self.tot_mov))
        self.database.update_status(
            status,
            int(time.time()) if status != 'ABORTED' else None,
            None,
            1 if full else 0,
            self.add_chn, self.add_shw, self.add_mov,
            self.del_chn, self.del_shw, self.del_mov,
            self.tot_chn, self.tot_shw, self.tot_mov
        )

    def _init_record(self):
        self.index = 0
        self.film["title"] = ""
        self.film["aired"] = "1980-01-01 00:00:00"
        self.film["duration"] = "00:00:00"
        self.film["size"] = 0
        self.film["description"] = ""
        self.film["website"] = ""
        self.film["url_sub"] = ""
        self.film["url_video"] = ""
        self.film["url_video_sd"] = ""
        self.film["url_video_hd"] = ""
        self.film["airedepoch"] = 0
        self.film["geo"] = ""

    def _end_record(self, records):
        if self.count % 1000 == 0:
            # pylint: disable=line-too-long
            percent = int(self.count * 100 / records)
            self.logger.info('In progress (%d%%): channels:%d, shows:%d, movies:%d ...' % (
                percent, self.add_chn, self.add_shw, self.add_mov))
            self.notifier.update_update_progress(
                percent if percent <= 100 else 100, self.count, self.add_chn, self.add_shw, self.add_mov)
            self.database.update_status(
                add_chn=self.add_chn,
                add_shw=self.add_shw,
                add_mov=self.add_mov,
                tot_chn=self.tot_chn + self.add_chn,
                tot_shw=self.tot_shw + self.add_shw,
                tot_mov=self.tot_mov + self.add_mov
            )
            self.count = self.count + 1
            (_, cnt_chn, cnt_shw, cnt_mov) = self.database.ft_insert_film(
                self.film,
                True
            )
        else:
            self.count = self.count + 1
            (_, cnt_chn, cnt_shw, cnt_mov) = self.database.ft_insert_film(
                self.film,
                False
            )
        self.add_chn += cnt_chn
        self.add_shw += cnt_shw
        self.add_mov += cnt_mov

    def _add_value(self, valueArray):
        self.film["channel"] = valueArray[0]
        self.film["show"] = valueArray[1][:255]
        self.film["title"] = valueArray[2][:255]
        ##
        if len(valueArray[3]) == 10:
            self.film["aired"] = valueArray[3][6:] + '-' + valueArray[3][3:5] + '-' + valueArray[3][:2]  
            if (len(valueArray[4]) == 8):
                self.film["aired"] = self.film["aired"] + " " + valueArray[4]
        ##
        if len(valueArray[5]) > 0:
            self.film["duration"] = valueArray[5]
        if len(valueArray[6]) > 0:
            self.film["size"] = int(valueArray[6])
        if len(valueArray[7]) > 0:
            self.film["description"] = valueArray[7]
        self.film["url_video"] = valueArray[8]
        self.film["website"] = valueArray[9]
        self.film["url_sub"] = valueArray[10]    
        self.film["url_video_sd"] = self._make_url(valueArray[12])
        self.film["url_video_hd"] = self._make_url(valueArray[14])
        if len(valueArray[16]) > 0:
            self.film["airedepoch"] = int(valueArray[16])
        self.film["geo"] = valueArray[18]
        
    def _make_url(self, val):
        parts = val.split('|')
        if len(parts) == 2:
            cnt = int(parts[0])
            return self.film["url_video"][:cnt] + parts[1]
        else:
            return val

    def _decompress_bz2(self, sourcefile, destfile):
        blocksize = 8192
        try:
            with open(destfile, 'wb') as dstfile, open(sourcefile, 'rb') as srcfile:
                decompressor = bz2.BZ2Decompressor()
                for data in iter(lambda: srcfile.read(blocksize), b''):
                    dstfile.write(decompressor.decompress(data))
                # pylint: disable=broad-except
        except Exception as err:
            self.logger.error('bz2 decompression failed: {}'.format(err))
            return -1
        return 0

    def _decompress_gz(self, sourcefile, destfile):
        blocksize = 8192
        # pylint: disable=broad-except

        try:
            with open(destfile, 'wb') as dstfile, gzip.open(sourcefile) as srcfile:
                for data in iter(lambda: srcfile.read(blocksize), b''):
                    dstfile.write(data)
        except Exception as err:
            self.logger.error(
                'gz decompression of "{}" to "{}" failed: {}', sourcefile, destfile, err)
            if mvutils.find_gzip() is not None:
                gzip_binary = mvutils.find_gzip()
                self.logger.info(
                    'Trying to decompress gzip file "{}" using {}...', sourcefile, gzip_binary)
                try:
                    mvutils.file_remove(destfile)
                    retval = subprocess.call([gzip_binary, '-d', sourcefile])
                    self.logger.info('Calling {} -d {} returned {}',
                                     gzip_binary, sourcefile, retval)
                    return retval
                except Exception as err:
                    self.logger.error(
                        'gz commandline decompression of "{}" to "{}" failed: {}',
                        sourcefile, destfile, err)
            return -1
        return 0

    # pylint: disable=pointless-string-statement
    """
 def Init(self, convert=False):
     if self.db is not None:
         self.Exit()
     self.db = Store(self.logger, self.notifier, self.settings)
     self.db.Init(convert=convert)
class MediathekViewUpdater(object):
    def __init__(self, logger, notifier, settings, monitor=None):
        self.logger = logger
        self.notifier = notifier
        self.settings = settings
        self.monitor = monitor
        self.db = None
        self.cycle = 0
        self.use_xz = mvutils.find_xz() is not None

    def Init(self, convert=False):
        if self.db is not None:
            self.Exit()
        self.db = Store(self.logger, self.notifier, self.settings)
        self.db.Init(convert=convert)

    def Exit(self):
        if self.db is not None:
            self.db.Exit()
            del self.db
            self.db = None

    def Reload(self):
        self.Exit()
        self.Init()

    def IsEnabled(self):
        return self.settings.updenabled

    def GetCurrentUpdateOperation(self, force=False):
        if self.db is None:
            # db not available - no update
            self.logger.info('Update disabled since database not available')
            return 0

        elif self.settings.updmode == 0:
            # update disabled - no update
            return 0

        elif self.settings.updmode == 1 or self.settings.updmode == 2:
            # manual update or update on first start
            if self.settings.IsUpdateTriggered() is True:
                return self._getNextUpdateOperation(True)
            else:
                # no update on all subsequent calls
                return 0

        elif self.settings.updmode == 3:
            # automatic update
            if self.settings.IsUserAlive():
                return self._getNextUpdateOperation(force)
            else:
                # no update of user is idle for more than 2 hours
                return 0

    def _getNextUpdateOperation(self, force=False):
        status = self.db.GetStatus()
        tsnow = int(time.time())
        tsold = status['lastupdate']
        dtnow = datetime.datetime.fromtimestamp(tsnow).date()
        dtold = datetime.datetime.fromtimestamp(tsold).date()
        if status['status'] == 'UNINIT':
            # database not initialized - no update
            self.logger.debug('database not initialized')
            return 0
        elif status['status'] == "UPDATING" and tsnow - tsold > 10800:
            # process was probably killed during update - no update
            self.logger.info(
                'Stuck update pretending to run since epoch {} reset', tsold)
            self.db.UpdateStatus('ABORTED')
            return 0
        elif status['status'] == "UPDATING":
            # already updating - no update
            self.logger.debug('Already updating')
            return 0
        elif not force and tsnow - tsold < self.settings.updinterval:
            # last update less than the configured update interval - no update
            self.logger.debug(
                'Last update less than the configured update interval. do nothing'
            )
            return 0
        elif dtnow != dtold:
            # last update was not today. do full update once a day
            self.logger.debug(
                'Last update was not today. do full update once a day')
            return 1
        elif status['status'] == "ABORTED" and status['fullupdate'] == 1:
            # last full update was aborted - full update needed
            self.logger.debug(
                'Last full update was aborted - full update needed')
            return 1
        else:
            # do differential update
            self.logger.debug('Do differential update')
            return 2

    def Update(self, full):
        if self.db is None:
            return
        if self.db.SupportsUpdate():
            if self.GetNewestList(full):
                if self.Import(full):
                    self.cycle += 1
            self.DeleteList(full)

    def Import(self, full):
        (_, _, destfile, avgrecsize) = self._get_update_info(full)
        if not mvutils.file_exists(destfile):
            self.logger.error('File {} does not exists', destfile)
            return False
        # estimate number of records in update file
        records = int(mvutils.file_size(destfile) / avgrecsize)
        if not self.db.ftInit():
            self.logger.warn(
                'Failed to initialize update. Maybe a concurrency problem?')
            return False
        try:
            self.logger.info('Starting import of approx. {} records from {}',
                             records, destfile)
            with open(destfile, 'r') as file:
                parser = ijson.parse(file)
                flsm = 0
                flts = 0
                (self.tot_chn, self.tot_shw,
                 self.tot_mov) = self._update_start(full)
                self.notifier.ShowUpdateProgress()
                for prefix, event, value in parser:
                    if (prefix, event) == ("X", "start_array"):
                        self._init_record()
                    elif (prefix, event) == ("X", "end_array"):
                        self._end_record(records)
                        if self.count % 100 == 0 and self.monitor.abortRequested(
                        ):
                            # kodi is shutting down. Close all
                            self._update_end(full, 'ABORTED')
                            self.notifier.CloseUpdateProgress()
                            return True
                    elif (prefix, event) == ("X.item", "string"):
                        if value is not None:
                            self._add_value(value.strip())
                        else:
                            self._add_value("")
                    elif (prefix, event) == ("Filmliste", "start_array"):
                        flsm += 1
                    elif (prefix, event) == ("Filmliste.item", "string"):
                        flsm += 1
                        if flsm == 2 and value is not None:
                            # this is the timestmap of this database update
                            try:
                                fldt = datetime.datetime.strptime(
                                    value.strip(), "%d.%m.%Y, %H:%M")
                                flts = int(time.mktime(fldt.timetuple()))
                                self.db.UpdateStatus(filmupdate=flts)
                                self.logger.info('Filmliste dated {}',
                                                 value.strip())
                            except TypeError:
                                # SEE: https://forum.kodi.tv/showthread.php?tid=112916&pid=1214507#pid1214507
                                # Wonderful. His name is also Leopold
                                try:
                                    flts = int(
                                        time.mktime(
                                            time.strptime(
                                                value.strip(),
                                                "%d.%m.%Y, %H:%M")))
                                    self.db.UpdateStatus(filmupdate=flts)
                                    self.logger.info('Filmliste dated {}',
                                                     value.strip())
                                except Exception as err:
                                    # If the universe hates us...
                                    self.logger.debug(
                                        'Could not determine date "{}" of filmliste: {}',
                                        value.strip(), err)
                            except ValueError as err:
                                pass

            self.db.ftFlushInsert()
            self._update_end(full, 'IDLE')
            self.logger.info('Import of {} in update cycle {} finished',
                             destfile, self.cycle)
            self.notifier.CloseUpdateProgress()
            return True
        except KeyboardInterrupt:
            self._update_end(full, 'ABORTED')
            self.logger.info('Update cycle {} interrupted by user', self.cycle)
            self.notifier.CloseUpdateProgress()
            return False
        except DatabaseCorrupted as err:
            self.logger.error('{} on update cycle {}', err, self.cycle)
            self.notifier.CloseUpdateProgress()
        except DatabaseLost as err:
            self.logger.error('{} on update cycle {}', err, self.cycle)
            self.notifier.CloseUpdateProgress()
        except Exception as err:
            self.logger.error(
                'Error {} while processing {} on update cycle {}', err,
                destfile, self.cycle)
            self._update_end(full, 'ABORTED')
            self.notifier.CloseUpdateProgress()
        return False

    def GetNewestList(self, full):
        (url, compfile, destfile, _) = self._get_update_info(full)
        if url is None:
            self.logger.error(
                'No suitable archive extractor available for this system')
            self.notifier.ShowMissingExtractorError()
            return False

        # get mirrorlist
        self.logger.info('Opening {}', url)
        try:
            data = urllib2.urlopen(url).read()
        except urllib2.URLError as err:
            self.logger.error('Failure opening {}', url)
            self.notifier.ShowDownloadError(url, err)
            return False
        root = etree.fromstring(data)
        urls = []
        for server in root.findall('Server'):
            try:
                URL = server.find('URL').text
                Prio = server.find('Prio').text
                urls.append((self._get_update_url(URL),
                             float(Prio) + random.random() * 1.2))
                self.logger.info('Found mirror {} (Priority {})', URL, Prio)
            except AttributeError:
                pass
        urls = sorted(urls, key=itemgetter(1))
        urls = [url[0] for url in urls]

        # cleanup downloads
        self.logger.info('Cleaning up old downloads...')
        mvutils.file_remove(compfile)
        mvutils.file_remove(destfile)

        # download filmliste
        self.notifier.ShowDownloadProgress()
        lasturl = ''
        for url in urls:
            try:
                lasturl = url
                self.logger.info('Trying to download {} from {}...',
                                 os.path.basename(compfile), url)
                self.notifier.UpdateDownloadProgress(0, url)
                mvutils.url_retrieve(
                    url,
                    filename=compfile,
                    reporthook=self.notifier.HookDownloadProgress,
                    aborthook=self.monitor.abortRequested)
                break
            except urllib2.URLError as err:
                self.logger.error('Failure downloading {}', url)
                self.notifier.CloseDownloadProgress()
                self.notifier.ShowDownloadError(lasturl, err)
                return False
            except ExitRequested as err:
                self.logger.error(
                    'Immediate exit requested. Aborting download of {}', url)
                self.notifier.CloseDownloadProgress()
                self.notifier.ShowDownloadError(lasturl, err)
                return False
            except Exception as err:
                self.logger.error('Failure writng {}', url)
                self.notifier.CloseDownloadProgress()
                self.notifier.ShowDownloadError(lasturl, err)
                return False

        # decompress filmliste
        if self.use_xz is True:
            self.logger.info('Trying to decompress xz file...')
            retval = subprocess.call([mvutils.find_xz(), '-d', compfile])
            self.logger.info('Return {}', retval)
        elif upd_can_bz2 is True:
            self.logger.info('Trying to decompress bz2 file...')
            retval = self._decompress_bz2(compfile, destfile)
            self.logger.info('Return {}', retval)
        elif upd_can_gz is True:
            self.logger.info('Trying to decompress gz file...')
            retval = self._decompress_gz(compfile, destfile)
            self.logger.info('Return {}', retval)
        else:
            # should nebver reach
            pass

        self.notifier.CloseDownloadProgress()
        return retval == 0 and mvutils.file_exists(destfile)

    def DeleteList(self, full):
        (_, compfile, destfile, _) = self._get_update_info(full)
        self.logger.info('Cleaning up downloads...')
        mvutils.file_remove(compfile)
        mvutils.file_remove(destfile)

    def _get_update_info(self, full):
        if self.use_xz is True:
            ext = 'xz'
        elif upd_can_bz2 is True:
            ext = 'bz2'
        elif upd_can_gz is True:
            ext = 'gz'
        else:
            return (
                None,
                None,
                None,
                0,
            )

        if full:
            return (
                FILMLISTE_AKT_URL,
                os.path.join(self.settings.datapath, 'Filmliste-akt.' + ext),
                os.path.join(self.settings.datapath, 'Filmliste-akt'),
                600,
            )
        else:
            return (
                FILMLISTE_DIF_URL,
                os.path.join(self.settings.datapath, 'Filmliste-diff.' + ext),
                os.path.join(self.settings.datapath, 'Filmliste-diff'),
                700,
            )

    def _get_update_url(self, url):
        if self.use_xz is True:
            return url
        elif upd_can_bz2 is True:
            return os.path.splitext(url)[0] + '.bz2'
        elif upd_can_gz is True:
            return os.path.splitext(url)[0] + '.gz'
        else:
            # should never happen since it will not be called
            return None

    def _update_start(self, full):
        self.logger.info('Initializing update...')
        self.add_chn = 0
        self.add_shw = 0
        self.add_mov = 0
        self.del_chn = 0
        self.del_shw = 0
        self.del_mov = 0
        self.index = 0
        self.count = 0
        self.film = {
            "channel": "",
            "show": "",
            "title": "",
            "aired": "1980-01-01 00:00:00",
            "duration": "00:00:00",
            "size": 0,
            "description": "",
            "website": "",
            "url_sub": "",
            "url_video": "",
            "url_video_sd": "",
            "url_video_hd": "",
            "airedepoch": 0,
            "geo": ""
        }
        return self.db.ftUpdateStart(full)

    def _update_end(self, full, status):
        self.logger.info('Added: channels:%d, shows:%d, movies:%d ...' %
                         (self.add_chn, self.add_shw, self.add_mov))
        (self.del_chn, self.del_shw, self.del_mov, self.tot_chn, self.tot_shw,
         self.tot_mov) = self.db.ftUpdateEnd(full and status == 'IDLE')
        self.logger.info('Deleted: channels:%d, shows:%d, movies:%d' %
                         (self.del_chn, self.del_shw, self.del_mov))
        self.logger.info('Total: channels:%d, shows:%d, movies:%d' %
                         (self.tot_chn, self.tot_shw, self.tot_mov))
        self.db.UpdateStatus(status,
                             int(time.time()) if status != 'ABORTED' else None,
                             None, 1 if full else 0, self.add_chn,
                             self.add_shw, self.add_mov, self.del_chn,
                             self.del_shw, self.del_mov, self.tot_chn,
                             self.tot_shw, self.tot_mov)

    def _init_record(self):
        self.index = 0
        self.film["title"] = ""
        self.film["aired"] = "1980-01-01 00:00:00"
        self.film["duration"] = "00:00:00"
        self.film["size"] = 0
        self.film["description"] = ""
        self.film["website"] = ""
        self.film["url_sub"] = ""
        self.film["url_video"] = ""
        self.film["url_video_sd"] = ""
        self.film["url_video_hd"] = ""
        self.film["airedepoch"] = 0
        self.film["geo"] = ""

    def _end_record(self, records):
        self.count = self.count + 1
        if self.count % self.db.flushBlockSize() == 0:
            #add 10% to record for final db update time in update_end
            percent = int(self.count * 100 / (records * 1.1))
            self.logger.info(
                'In progress (%d%%): channels:%d, shows:%d, movies:%d ...' %
                (percent, self.add_chn, self.add_shw, self.add_mov))
            self.notifier.UpdateUpdateProgress(
                percent if percent <= 100 else 100, self.count, self.add_chn,
                self.add_shw, self.add_mov)
            self.db.UpdateStatus(add_chn=self.add_chn,
                                 add_shw=self.add_shw,
                                 add_mov=self.add_mov,
                                 tot_chn=self.tot_chn + self.add_chn,
                                 tot_shw=self.tot_shw + self.add_shw,
                                 tot_mov=self.tot_mov + self.add_mov)
            (_, cnt_chn, cnt_shw,
             cnt_mov) = self.db.ftInsertFilm(self.film, True)
            self.db.ftFlushInsert()
        else:
            (_, cnt_chn, cnt_shw,
             cnt_mov) = self.db.ftInsertFilm(self.film, False)
        self.add_chn += cnt_chn
        self.add_shw += cnt_shw
        self.add_mov += cnt_mov

    def _add_value(self, val):
        if self.index == 0:
            if val != "":
                self.film["channel"] = val
        elif self.index == 1:
            if val != "":
                self.film["show"] = val[:255]
        elif self.index == 2:
            self.film["title"] = val[:255]
        elif self.index == 3:
            if len(val) == 10:
                self.film["aired"] = val[6:] + '-' + val[3:5] + '-' + val[:2]
        elif self.index == 4:
            if (self.film["aired"] != "1980-01-01 00:00:00") and (len(val)
                                                                  == 8):
                self.film["aired"] = self.film["aired"] + " " + val
        elif self.index == 5:
            if len(val) == 8:
                self.film["duration"] = val
        elif self.index == 6:
            if val != "":
                self.film["size"] = int(val)
        elif self.index == 7:
            self.film["description"] = val
        elif self.index == 8:
            self.film["url_video"] = val
        elif self.index == 9:
            self.film["website"] = val
        elif self.index == 10:
            self.film["url_sub"] = val
        elif self.index == 12:
            self.film["url_video_sd"] = self._make_url(val)
        elif self.index == 14:
            self.film["url_video_hd"] = self._make_url(val)
        elif self.index == 16:
            if val != "":
                self.film["airedepoch"] = int(val)
        elif self.index == 18:
            self.film["geo"] = val
        self.index = self.index + 1

    def _make_url(self, val):
        x = val.split('|')
        if len(x) == 2:
            cnt = int(x[0])
            return self.film["url_video"][:cnt] + x[1]
        else:
            return val

    def _decompress_bz2(self, sourcefile, destfile):
        blocksize = 8192
        try:
            with open(destfile, 'wb') as df, open(sourcefile, 'rb') as sf:
                decompressor = bz2.BZ2Decompressor()
                for data in iter(lambda: sf.read(blocksize), b''):
                    df.write(decompressor.decompress(data))
        except Exception as err:
            self.logger.error('bz2 decompression failed: {}'.format(err))
            return -1
        return 0

    def _decompress_gz(self, sourcefile, destfile):
        blocksize = 8192
        try:
            with open(destfile, 'wb') as df, gzip.open(sourcefile) as sf:
                for data in iter(lambda: sf.read(blocksize), b''):
                    df.write(data)
        except Exception as err:
            self.logger.error('gz decompression failed: {}'.format(err))
            return -1
        return 0
Ejemplo n.º 9
0
 def Init(self):
     if self.db is not None:
         self.Exit()
     self.db = Store(self.logger, self.notifier, self.settings)
     self.db.Init()
Ejemplo n.º 10
0
 def __init__(self):
     super(MediathekView, self).__init__()
     self.settings = Settings()
     self.notifier = Notifier()
     self.database = Store(self.getNewLogger('Store'), self.notifier,
                           self.settings)
Ejemplo n.º 11
0
class MediathekView(KodiPlugin):
    def __init__(self):
        super(MediathekView, self).__init__()
        self.settings = Settings()
        self.notifier = Notifier()
        self.database = Store(self.getNewLogger('Store'), self.notifier,
                              self.settings)

    def show_main_menu(self):
        # Search
        self.addFolderItem(30901, {'mode': "search", 'extendedsearch': False})
        # Search all
        self.addFolderItem(30902, {'mode': "search", 'extendedsearch': True})
        # Browse livestreams
        self.addFolderItem(30903, {'mode': "livestreams"})
        # Browse recently added
        self.addFolderItem(30904, {'mode': "recent", 'channel': 0})
        # Browse recently added by channel
        self.addFolderItem(30905, {'mode': "recentchannels"})
        # Browse by Initial->Show
        self.addFolderItem(30906, {'mode': "initial", 'channel': 0})
        # Browse by Channel->Initial->Shows
        self.addFolderItem(30907, {'mode': "channels"})
        # Database Information
        self.addActionItem(30908, {'mode': "action-dbinfo"})
        # Manual database update
        if self.settings.updmode == 1 or self.settings.updmode == 2:
            self.addActionItem(30909, {'mode': "action-dbupdate"})
        self.endOfDirectory()
        self._check_outdate()

    def show_searches(self, extendedsearch=False):
        self.addFolderItem(30931, {
            'mode': "newsearch",
            'extendedsearch': extendedsearch
        })
        RecentSearches(self, extendedsearch).load().populate()
        self.endOfDirectory()

    def new_search(self, extendedsearch=False):
        settingid = 'lastsearch2' if extendedsearch is True else 'lastsearch1'
        headingid = 30902 if extendedsearch is True else 30901
        # are we returning from playback ?
        search = self.addon.getSetting(settingid)
        if search:
            # restore previous search
            self.database.Search(search, FilmUI(self), extendedsearch)
        else:
            # enter search term
            (search, confirmed) = self.notifier.GetEnteredText('', headingid)
            if len(search) > 2 and confirmed is True:
                RecentSearches(self, extendedsearch).load().add(search).save()
                if self.database.Search(search, FilmUI(self),
                                        extendedsearch) > 0:
                    self.addon.setSetting(settingid, search)
            else:
                # pylint: disable=line-too-long
                self.info(
                    'The following ERROR can be ignored. It is caused by the architecture of the Kodi Plugin Engine'
                )
                self.endOfDirectory(False, cacheToDisc=True)
                # self.show_searches( extendedsearch )

    def show_db_info(self):
        info = self.database.GetStatus()
        heading = self.language(30907)
        infostr = self.language({
            'NONE': 30941,
            'UNINIT': 30942,
            'IDLE': 30943,
            'UPDATING': 30944,
            'ABORTED': 30945
        }.get(info['status'], 30941))
        infostr = self.language(30965) % infostr
        totinfo = self.language(30971) % (info['tot_chn'], info['tot_shw'],
                                          info['tot_mov'])
        updatetype = self.language(30972 if info['fullupdate'] > 0 else 30973)
        if info['status'] == 'UPDATING' and info['filmupdate'] > 0:
            updinfo = self.language(30967) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'])
        elif info['status'] == 'UPDATING':
            updinfo = self.language(30968) % (updatetype, info['add_chn'],
                                              info['add_shw'], info['add_mov'])
        elif info['lastupdate'] > 0 and info['filmupdate'] > 0:
            updinfo = self.language(30969) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                datetime.datetime.fromtimestamp(
                    info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'],
                info['del_chn'], info['del_shw'], info['del_mov'])
        elif info['lastupdate'] > 0:
            updinfo = self.language(30970) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'],
                info['del_chn'], info['del_shw'], info['del_mov'])
        else:
            updinfo = self.language(30966)

        xbmcgui.Dialog().textviewer(
            heading, infostr + '\n\n' + totinfo + '\n\n' + updinfo)

    def _check_outdate(self, maxage=172800):
        if self.settings.updmode != 1 and self.settings.updmode != 2:
            # no check with update disabled or update automatic
            return
        if self.database is None:
            # should never happen
            self.notifier.ShowOutdatedUnknown()
            return
        status = self.database.GetStatus()
        if status['status'] == 'NONE' or status['status'] == 'UNINIT':
            # should never happen
            self.notifier.ShowOutdatedUnknown()
            return
        elif status['status'] == 'UPDATING':
            # great... we are updating. nuthin to show
            return
        # lets check how old we are
        tsnow = int(time.time())
        tsold = int(status['lastupdate'])
        if tsnow - tsold > maxage:
            self.notifier.ShowOutdatedKnown(status)

    def init(self):
        if self.database.Init():
            if self.settings.HandleFirstRun():
                pass
            self.settings.HandleUpdateOnStart()

    def run(self):
        # save last activity timestamp
        self.settings.ResetUserActivity()
        # process operation
        mode = self.get_arg('mode', None)
        if mode is None:
            self.show_main_menu()
        elif mode == 'search':
            extendedsearch = self.get_arg('extendedsearch', 'False') == 'True'
            self.show_searches(extendedsearch)
        elif mode == 'newsearch':
            self.new_search(self.get_arg('extendedsearch', 'False') == 'True')
        elif mode == 'research':
            search = self.get_arg('search', '')
            extendedsearch = self.get_arg('extendedsearch', 'False') == 'True'
            self.database.Search(search, FilmUI(self), extendedsearch)
            RecentSearches(self, extendedsearch).load().add(search).save()
        elif mode == 'delsearch':
            search = self.get_arg('search', '')
            extendedsearch = self.get_arg('extendedsearch', 'False') == 'True'
            RecentSearches(
                self, extendedsearch).load().delete(search).save().populate()
            self.runBuiltin('Container.Refresh')
        elif mode == 'livestreams':
            self.database.GetLiveStreams(
                FilmUI(self, [xbmcplugin.SORT_METHOD_LABEL]))
        elif mode == 'recent':
            channel = self.get_arg('channel', 0)
            self.database.GetRecents(channel, FilmUI(self))
        elif mode == 'recentchannels':
            self.database.GetRecentChannels(ChannelUI(self, nextdir='recent'))
        elif mode == 'channels':
            self.database.GetChannels(ChannelUI(self, nextdir='shows'))
        elif mode == 'action-dbinfo':
            self.show_db_info()
        elif mode == 'action-dbupdate':
            self.settings.TriggerUpdate()
            self.notifier.ShowNotification(30963, 30964, time=10000)
        elif mode == 'initial':
            channel = self.get_arg('channel', 0)
            self.database.GetInitials(channel, InitialUI(self))
        elif mode == 'shows':
            channel = self.get_arg('channel', 0)
            initial = self.get_arg('initial', None)
            self.database.GetShows(channel, initial, ShowUI(self))
        elif mode == 'films':
            show = self.get_arg('show', 0)
            self.database.GetFilms(show, FilmUI(self))
        elif mode == 'downloadmv':
            filmid = self.get_arg('id', 0)
            quality = self.get_arg('quality', 1)
            Downloader(self).download_movie(filmid, quality)
        elif mode == 'downloadep':
            filmid = self.get_arg('id', 0)
            quality = self.get_arg('quality', 1)
            Downloader(self).download_episode(filmid, quality)
        elif mode == 'playwithsrt':
            filmid = self.get_arg('id', 0)
            only_sru = self.get_arg('only_set_resolved_url', 'False') == 'True'
            Downloader(self).play_movie_with_subs(filmid, only_sru)

        # cleanup saved searches
        if mode is None or mode != 'search':
            self.addon.setSetting('lastsearch1', '')
        if mode is None or mode != 'searchall':
            self.addon.setSetting('lastsearch2', '')

    def exit(self):
        self.database.Exit()
Ejemplo n.º 12
0
class MediathekViewUpdater(object):
    """ The database updator class """
    def __init__(self, logger, notifier, settings, monitor=None):
        self.logger = logger
        self.notifier = notifier
        self.settings = settings
        self.monitor = monitor
        self.database = None
        self.use_xz = mvutils.find_xz() is not None
        self.cycle = 0
        self.add_chn = 0
        self.add_shw = 0
        self.add_mov = 0
        self.del_chn = 0
        self.del_shw = 0
        self.del_mov = 0
        self.tot_chn = 0
        self.tot_shw = 0
        self.tot_mov = 0
        self.index = 0
        self.count = 0
        self.film = {}

    def init(self, convert=False):
        """ Initializes the updater """
        if self.database is not None:
            self.exit()
        self.database = Store(self.logger, self.notifier, self.settings)
        self.database.init(convert=convert)

    def exit(self):
        """ Resets the updater """
        if self.database is not None:
            self.database.exit()
            del self.database
            self.database = None

    def reload(self):
        """ Reloads the updater """
        self.exit()
        self.init()

    def is_enabled(self):
        """ Returns if the updater is enabled """
        return self.settings.updenabled

    def get_current_update_operation(self, force=False, full=False):
        """
        Determines which update operation should be done. Returns
        one of these values:

        0 - no update operation pending
        1 - full update
        2 - differential update

        Args:
            force(bool, optional): if `True` a full update
                is always returned. Default is `False`
            full(book, optional): if `True` a full update
                is always returned. Default is `False`
        """
        if self.database is None:
            # db not available - no update
            self.logger.info('Update disabled since database not available')
            return 0
        elif self.settings.updmode == 0:
            # update disabled - no update
            return 0
        elif self.settings.updmode == 1 or self.settings.updmode == 2:
            # manual update or update on first start
            if self.settings.is_update_triggered() is True:
                return self._get_next_update_operation(True, False)
            else:
                # no update on all subsequent calls
                return 0
        elif self.settings.updmode == 3:
            # automatic update
            if self.settings.is_user_alive():
                return self._get_next_update_operation(force, full)
            else:
                # no update if user is idle for more than 2 hours
                return 0
        elif self.settings.updmode == 4:
            # continous update
            return self._get_next_update_operation(force, full)

    def _get_next_update_operation(self, force=False, full=False):
        status = self.database.get_status()
        tsnow = int(time.time())
        tsold = status['lastupdate']
        dtnow = datetime.datetime.fromtimestamp(tsnow).date()
        dtold = datetime.datetime.fromtimestamp(tsold).date()
        if status['status'] == 'UNINIT':
            # database not initialized - no update
            self.logger.debug('database not initialized')
            return 0
        elif status['status'] == "UPDATING" and tsnow - tsold > 10800:
            # process was probably killed during update - no update
            self.logger.info(
                'Stuck update pretending to run since epoch {} reset', tsold)
            self.database.update_status('ABORTED')
            return 0
        elif status['status'] == "UPDATING":
            # already updating - no update
            self.logger.debug('Already updating')
            return 0
        elif not full and not force and tsnow - tsold < self.settings.updinterval:
            # last update less than the configured update interval - no update
            self.logger.debug(
                'Last update less than the configured update interval. do nothing'
            )
            return 0
        elif dtnow != dtold:
            # last update was not today. do full update once a day
            self.logger.debug(
                'Last update was not today. do full update once a day')
            return 1
        elif status['status'] == "ABORTED" and status['fullupdate'] == 1:
            # last full update was aborted - full update needed
            self.logger.debug(
                'Last full update was aborted - full update needed')
            return 1
        elif full is True:
            # full update requested
            self.logger.info('Full update requested')
            return 1
        else:
            # do differential update
            self.logger.debug('Do differential update')
            return 2

    def update(self, full):
        """
        Downloads the database update file and
        then performs a database update

        Args:
            full(bool): Perform full update if `True`
        """
        if self.database is None:
            return
        elif self.database.supports_native_update(full):
            if self.get_newest_list(full):
                if self.database.native_update(full):
                    self.cycle += 1
            self.delete_list(full)
        elif self.database.supports_update():
            if self.get_newest_list(full):
                if self.import_database(full):
                    self.cycle += 1
            self.delete_list(full)

    def import_database(self, full):
        """
        Performs a database update when a
        downloaded update file is available

        Args:
            full(bool): Perform full update if `True`
        """
        (_, _, destfile, avgrecsize) = self._get_update_info(full)
        if not mvutils.file_exists(destfile):
            self.logger.error('File {} does not exists', destfile)
            return False
        # estimate number of records in update file
        records = int(mvutils.file_size(destfile) / avgrecsize)
        if not self.database.ft_init():
            self.logger.warn(
                'Failed to initialize update. Maybe a concurrency problem?')
            return False
        # pylint: disable=broad-except
        try:
            starttime = time.time()
            self.logger.info('Starting import of approx. {} records from {}',
                             records, destfile)
            with closing(open(destfile, 'r')) as updatefile:
                parser = ijson.parse(updatefile)
                flsm = 0
                flts = 0
                (self.tot_chn, self.tot_shw,
                 self.tot_mov) = self._update_start(full)
                self.notifier.show_update_progress()
                for prefix, event, value in parser:
                    if (prefix, event) == ("X", "start_array"):
                        self._init_record()
                    elif (prefix, event) == ("X", "end_array"):
                        self._end_record(records)
                        if self.count % 100 == 0 and self.monitor.abort_requested(
                        ):
                            # kodi is shutting down. Close all
                            self._update_end(full, 'ABORTED')
                            self.notifier.close_update_progress()
                            return True
                    elif (prefix, event) == ("X.item", "string"):
                        if value is not None:
                            #						self._add_value( value.strip().encode('utf-8') )
                            self._add_value(value.strip())
                        else:
                            self._add_value("")
                    elif (prefix, event) == ("Filmliste", "start_array"):
                        flsm += 1
                    elif (prefix, event) == ("Filmliste.item", "string"):
                        flsm += 1
                        if flsm == 2 and value is not None:
                            # this is the timestmap of this database update
                            try:
                                fldt = datetime.datetime.strptime(
                                    value.strip(), "%d.%m.%Y, %H:%M")
                                flts = int(time.mktime(fldt.timetuple()))
                                self.database.update_status(filmupdate=flts)
                                self.logger.info('Filmliste dated {}',
                                                 value.strip())
                            except TypeError:
                                # pylint: disable=line-too-long
                                # SEE: https://forum.kodi.tv/showthread.php?tid=112916&pid=1214507#pid1214507
                                # Wonderful. His name is also Leopold
                                try:
                                    flts = int(
                                        time.mktime(
                                            time.strptime(
                                                value.strip(),
                                                "%d.%m.%Y, %H:%M")))
                                    self.database.update_status(
                                        filmupdate=flts)
                                    self.logger.info('Filmliste dated {}',
                                                     value.strip())
                                    # pylint: disable=broad-except
                                except Exception as err:
                                    # If the universe hates us...
                                    self.logger.debug(
                                        'Could not determine date "{}" of filmliste: {}',
                                        value.strip(), err)
                            except ValueError as err:
                                pass

            self._update_end(full, 'IDLE')
            self.logger.info(
                'Import of {} in update cycle {} finished. Duration: {} seconds',
                destfile, self.cycle, int(time.time() - starttime))
            self.notifier.close_update_progress()
            return True
        except KeyboardInterrupt:
            self._update_end(full, 'ABORTED')
            self.logger.info('Update cycle {} interrupted by user', self.cycle)
            self.notifier.close_update_progress()
            return False
        except DatabaseCorrupted as err:
            self.logger.error('{} on update cycle {}', err, self.cycle)
            self.notifier.close_update_progress()
        except DatabaseLost as err:
            self.logger.error('{} on update cycle {}', err, self.cycle)
            self.notifier.close_update_progress()
        except Exception as err:
            self.logger.error(
                'Error {} while processing {} on update cycle {}', err,
                destfile, self.cycle)
            self._update_end(full, 'ABORTED')
            self.notifier.close_update_progress()
        return False

    def get_newest_list(self, full):
        """
        Downloads the database update file

        Args:
            full(bool): Downloads the full list if `True`
        """
        (url, compfile, destfile, _) = self._get_update_info(full)
        if url is None:
            self.logger.error(
                'No suitable archive extractor available for this system')
            self.notifier.show_missing_extractor_error()
            return False

        # cleanup downloads
        self.logger.info('Cleaning up old downloads...')
        mvutils.file_remove(compfile)
        mvutils.file_remove(destfile)

        # download filmliste
        self.notifier.show_download_progress()

        # pylint: disable=broad-except
        try:
            self.logger.info('Trying to download {} from {}...',
                             os.path.basename(compfile), url)
            self.notifier.update_download_progress(0, url)
            mvutils.url_retrieve(
                url,
                filename=compfile,
                reporthook=self.notifier.hook_download_progress,
                aborthook=self.monitor.abort_requested)
        except urllib2.URLError as err:
            self.logger.error('Failure downloading {} - {}', url, err)
            self.notifier.close_download_progress()
            self.notifier.show_download_error(url, err)
            return False
        except ExitRequested as err:
            self.logger.error(
                'Immediate exit requested. Aborting download of {}', url)
            self.notifier.close_download_progress()
            self.notifier.show_download_error(url, err)
            return False
        except Exception as err:
            self.logger.error('Failure writng {}', url)
            self.notifier.close_download_progress()
            self.notifier.show_download_error(url, err)
            return False

        # decompress filmliste
        if self.use_xz is True:
            self.logger.info('Trying to decompress xz file...')
            retval = subprocess.call([mvutils.find_xz(), '-d', compfile])
            self.logger.info('Return {}', retval)
        elif UPD_CAN_BZ2 is True:
            self.logger.info('Trying to decompress bz2 file...')
            retval = self._decompress_bz2(compfile, destfile)
            self.logger.info('Return {}', retval)
        elif UPD_CAN_GZ is True:
            self.logger.info('Trying to decompress gz file...')
            retval = self._decompress_gz(compfile, destfile)
            self.logger.info('Return {}', retval)
        else:
            # should nebver reach
            pass

        self.notifier.close_download_progress()
        return retval == 0 and mvutils.file_exists(destfile)

    def delete_list(self, full):
        """
        Deletes locally stored database update files

        Args:
            full(bool): Deletes the full lists if `True`
        """
        (_, compfile, destfile, _) = self._get_update_info(full)
        self.logger.info('Cleaning up downloads...')
        mvutils.file_remove(compfile)
        mvutils.file_remove(destfile)

    def _get_update_info(self, full):
        if self.use_xz is True:
            ext = '.xz'
        elif UPD_CAN_BZ2 is True:
            ext = '.bz2'
        elif UPD_CAN_GZ is True:
            ext = '.gz'
        else:
            return (
                None,
                None,
                None,
                0,
            )

        info = self.database.get_native_info(full)
        if info is not None:
            return (self._get_update_url(info[0]),
                    os.path.join(self.settings.datapath, info[1] + ext),
                    os.path.join(self.settings.datapath, info[1]), 500)

        if full:
            return (
                FILMLISTE_URL + FILMLISTE_AKT + ext,
                os.path.join(self.settings.datapath, FILMLISTE_AKT + ext),
                os.path.join(self.settings.datapath, FILMLISTE_AKT),
                600,
            )
        else:
            return (
                FILMLISTE_URL + FILMLISTE_DIF + ext,
                os.path.join(self.settings.datapath, FILMLISTE_DIF + ext),
                os.path.join(self.settings.datapath, FILMLISTE_DIF),
                700,
            )

    def _get_update_url(self, url):
        if self.use_xz is True:
            return url
        elif UPD_CAN_BZ2 is True:
            return os.path.splitext(url)[0] + '.bz2'
        elif UPD_CAN_GZ is True:
            return os.path.splitext(url)[0] + '.gz'
        else:
            # should never happen since it will not be called
            return None

    def _update_start(self, full):
        self.logger.info('Initializing update...')
        self.add_chn = 0
        self.add_shw = 0
        self.add_mov = 0
        self.del_chn = 0
        self.del_shw = 0
        self.del_mov = 0
        self.index = 0
        self.count = 0
        self.film = {
            "channel": "",
            "show": "",
            "title": "",
            "aired": "1980-01-01 00:00:00",
            "duration": "00:00:00",
            "size": 0,
            "description": "",
            "website": "",
            "url_sub": "",
            "url_video": "",
            "url_video_sd": "",
            "url_video_hd": "",
            "airedepoch": 0,
            "geo": ""
        }
        return self.database.ft_update_start(full)

    def _update_end(self, full, status):
        self.logger.info('Added: channels:%d, shows:%d, movies:%d ...' %
                         (self.add_chn, self.add_shw, self.add_mov))
        (self.del_chn, self.del_shw, self.del_mov, self.tot_chn, self.tot_shw,
         self.tot_mov) = self.database.ft_update_end(full and status == 'IDLE')
        self.logger.info('Deleted: channels:%d, shows:%d, movies:%d' %
                         (self.del_chn, self.del_shw, self.del_mov))
        self.logger.info('Total: channels:%d, shows:%d, movies:%d' %
                         (self.tot_chn, self.tot_shw, self.tot_mov))
        self.database.update_status(
            status,
            int(time.time()) if status != 'ABORTED' else None, None,
            1 if full else 0, self.add_chn, self.add_shw, self.add_mov,
            self.del_chn, self.del_shw, self.del_mov, self.tot_chn,
            self.tot_shw, self.tot_mov)

    def _init_record(self):
        self.index = 0
        self.film["title"] = ""
        self.film["aired"] = "1980-01-01 00:00:00"
        self.film["duration"] = "00:00:00"
        self.film["size"] = 0
        self.film["description"] = ""
        self.film["website"] = ""
        self.film["url_sub"] = ""
        self.film["url_video"] = ""
        self.film["url_video_sd"] = ""
        self.film["url_video_hd"] = ""
        self.film["airedepoch"] = 0
        self.film["geo"] = ""

    def _end_record(self, records):
        if self.count % 1000 == 0:
            # pylint: disable=line-too-long
            percent = int(self.count * 100 / records)
            self.logger.info(
                'In progress (%d%%): channels:%d, shows:%d, movies:%d ...' %
                (percent, self.add_chn, self.add_shw, self.add_mov))
            self.notifier.update_update_progress(
                percent if percent <= 100 else 100, self.count, self.add_chn,
                self.add_shw, self.add_mov)
            self.database.update_status(add_chn=self.add_chn,
                                        add_shw=self.add_shw,
                                        add_mov=self.add_mov,
                                        tot_chn=self.tot_chn + self.add_chn,
                                        tot_shw=self.tot_shw + self.add_shw,
                                        tot_mov=self.tot_mov + self.add_mov)
            self.count = self.count + 1
            (_, cnt_chn, cnt_shw,
             cnt_mov) = self.database.ft_insert_film(self.film, True)
        else:
            self.count = self.count + 1
            (_, cnt_chn, cnt_shw,
             cnt_mov) = self.database.ft_insert_film(self.film, False)
        self.add_chn += cnt_chn
        self.add_shw += cnt_shw
        self.add_mov += cnt_mov

    def _add_value(self, val):
        if self.index == 0:
            if val != "":
                self.film["channel"] = val
        elif self.index == 1:
            if val != "":
                self.film["show"] = val[:255]
        elif self.index == 2:
            self.film["title"] = val[:255]
        elif self.index == 3:
            if len(val) == 10:
                self.film["aired"] = val[6:] + '-' + val[3:5] + '-' + val[:2]
        elif self.index == 4:
            if (self.film["aired"] != "1980-01-01 00:00:00") and (len(val)
                                                                  == 8):
                self.film["aired"] = self.film["aired"] + " " + val
        elif self.index == 5:
            if len(val) == 8:
                self.film["duration"] = val
        elif self.index == 6:
            if val != "":
                self.film["size"] = int(val)
        elif self.index == 7:
            self.film["description"] = val
        elif self.index == 8:
            self.film["url_video"] = val
        elif self.index == 9:
            self.film["website"] = val
        elif self.index == 10:
            self.film["url_sub"] = val
        elif self.index == 12:
            self.film["url_video_sd"] = self._make_url(val)
        elif self.index == 14:
            self.film["url_video_hd"] = self._make_url(val)
        elif self.index == 16:
            if val != "":
                self.film["airedepoch"] = int(val)
        elif self.index == 18:
            self.film["geo"] = val
        self.index = self.index + 1

    def _make_url(self, val):
        parts = val.split('|')
        if len(parts) == 2:
            cnt = int(parts[0])
            return self.film["url_video"][:cnt] + parts[1]
        else:
            return val

    def _decompress_bz2(self, sourcefile, destfile):
        blocksize = 8192
        try:
            with open(destfile, 'wb') as dstfile, open(sourcefile,
                                                       'rb') as srcfile:
                decompressor = bz2.BZ2Decompressor()
                for data in iter(lambda: srcfile.read(blocksize), b''):
                    dstfile.write(decompressor.decompress(data))
                # pylint: disable=broad-except
        except Exception as err:
            self.logger.error('bz2 decompression failed: {}'.format(err))
            return -1
        return 0

    def _decompress_gz(self, sourcefile, destfile):
        """
        blocksize = 8192

        try:
            with open(destfile, 'wb') as dstfile, gzip.open(sourcefile) as srcfile:
                for data in iter(lambda: srcfile.read(blocksize), b''):
                    dstfile.write(data)
                # pylint: disable=broad-except
        except Exception as err:
            self.logger.error('gz decompression of "{}" to "{}" failed: {}'.format(
                sourcefile, destfile, err))
            return -1
        return 0
        """
        blocksize = 8192
        # pylint: disable=broad-except,line-too-long

        try:
            srcfile = gzip.open(sourcefile)
        except Exception as err:
            self.logger.error(
                'gz decompression of "{}" to "{}" failed on opening gz file: {}'
                .format(sourcefile, destfile, err))
            return -1

        try:
            dstfile = open(destfile, 'wb')
        except Exception as err:
            self.logger.error(
                'gz decompression of "{}" to "{}" failed on opening destination file: {}'
                .format(sourcefile, destfile, err))
            return -1

        try:
            for data in iter(lambda: srcfile.read(blocksize), b''):
                try:
                    dstfile.write(data)
                except Exception as err:
                    self.logger.error(
                        'gz decompression of "{}" to "{}" failed on writing destination file: {}'
                        .format(sourcefile, destfile, err))
                    return -1
        except Exception as err:
            self.logger.error(
                'gz decompression of "{}" to "{}" failed on reading gz file: {}'
                .format(sourcefile, destfile, err))
            return -1
        return 0
Ejemplo n.º 13
0
class MediathekView(KodiPlugin):
    def __init__(self):
        super(MediathekView, self).__init__()
        self.settings = Settings()
        self.notifier = Notifier()
        self.db = Store(self.getNewLogger('Store'), self.notifier,
                        self.settings)

    def showMainMenu(self):
        # Search
        self.addFolderItem(30901, {'mode': "search"})
        # Search all
        self.addFolderItem(30902, {'mode': "searchall"})
        # Browse livestreams
        self.addFolderItem(30903, {'mode': "livestreams"})
        # Browse recently added
        self.addFolderItem(30904, {'mode': "recent", 'channel': 0})
        # Browse recently added by channel
        self.addFolderItem(30905, {'mode': "recentchannels"})
        # Browse by Initial->Show
        self.addFolderItem(30906, {'mode': "initial", 'channel': 0})
        # Browse by Channel->Initial->Shows
        self.addFolderItem(30907, {'mode': "channels"})
        # Database Information
        self.addActionItem(30908, {'mode': "action-dbinfo"})
        # Manual database update
        if self.settings.updmode == 1 or self.settings.updmode == 2:
            self.addActionItem(30909, {'mode': "action-dbupdate"})
        self.endOfDirectory()
        self._check_outdate()

    def showSearch(self, extendedsearch=False):
        settingid = 'lastsearch2' if extendedsearch is True else 'lastsearch1'
        headingid = 30902 if extendedsearch is True else 30901
        # are we returning from playback ?
        searchText = self.addon.getSetting(settingid)
        if len(searchText) > 0:
            # restore previous search
            self.db.Search(searchText, FilmUI(self), extendedsearch)
        else:
            # enter search term
            searchText = self.notifier.GetEnteredText('', headingid)
            if len(searchText) > 2:
                if self.db.Search(searchText, FilmUI(self),
                                  extendedsearch) > 0:
                    self.addon.setSetting(settingid, searchText)
            else:
                self.info(
                    'The following ERROR can be ignored. It is caused by the architecture of the Kodi Plugin Engine'
                )
                self.endOfDirectory(False, cacheToDisc=True)
                # self.showMainMenu()

    def showDbInfo(self):
        info = self.db.GetStatus()
        heading = self.language(30907)
        infostr = self.language({
            'NONE': 30941,
            'UNINIT': 30942,
            'IDLE': 30943,
            'UPDATING': 30944,
            'ABORTED': 30945
        }.get(info['status'], 30941))
        infostr = self.language(30965) % infostr
        totinfo = self.language(30971) % (info['tot_chn'], info['tot_shw'],
                                          info['tot_mov'])
        updatetype = self.language(30972 if info['fullupdate'] > 0 else 30973)
        if info['status'] == 'UPDATING' and info['filmupdate'] > 0:
            updinfo = self.language(30967) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'])
        elif info['status'] == 'UPDATING':
            updinfo = self.language(30968) % (updatetype, info['add_chn'],
                                              info['add_shw'], info['add_mov'])
        elif info['lastupdate'] > 0 and info['filmupdate'] > 0:
            updinfo = self.language(30969) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                datetime.datetime.fromtimestamp(
                    info['filmupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'],
                info['del_chn'], info['del_shw'], info['del_mov'])
        elif info['lastupdate'] > 0:
            updinfo = self.language(30970) % (
                updatetype, datetime.datetime.fromtimestamp(
                    info['lastupdate']).strftime('%Y-%m-%d %H:%M:%S'),
                info['add_chn'], info['add_shw'], info['add_mov'],
                info['del_chn'], info['del_shw'], info['del_mov'])
        else:
            updinfo = self.language(30966)

        xbmcgui.Dialog().textviewer(
            heading, infostr + '\n\n' + totinfo + '\n\n' + updinfo)

    def doDownloadFilm(self, filmid, quality):
        if self.settings.downloadpath:
            film = self.db.RetrieveFilmInfo(filmid)
            if film is None:
                # film not found - should never happen
                return

            # check if the download path is reachable
            if not xbmcvfs.exists(self.settings.downloadpath):
                self.notifier.ShowError(self.language(30952),
                                        self.language(30979))
                return

            # get the best url
            if quality == '0' and film.url_video_sd:
                videourl = film.url_video_sd
            elif quality == '2' and film.url_video_hd:
                videourl = film.url_video_hd
            else:
                videourl = film.url_video

            # prepare names
            showname = mvutils.cleanup_filename(film.show)[:64]
            filestem = mvutils.cleanup_filename(film.title)[:64]
            extension = os.path.splitext(videourl)[1]
            if not extension:
                extension = u'.mp4'
            if not filestem:
                filestem = u'Film-{}'.format(film.id)
            if not showname:
                showname = filestem

            # prepare download directory and determine episode number
            dirname = self.settings.downloadpath + showname + '/'
            episode = 1
            if xbmcvfs.exists(dirname):
                (
                    _,
                    epfiles,
                ) = xbmcvfs.listdir(dirname)
                for epfile in epfiles:
                    match = re.search('^.* [eE][pP]([0-9]*)\.[^/]*$', epfile)
                    if match and len(match.groups()) > 0:
                        if episode <= int(match.group(1)):
                            episode = int(match.group(1)) + 1
            else:
                xbmcvfs.mkdir(dirname)

            # prepare resulting filenames
            fileepi = filestem + u' - EP%04d' % episode
            movname = dirname + fileepi + extension
            srtname = dirname + fileepi + u'.srt'
            ttmname = dirname + fileepi + u'.ttml'
            nfoname = dirname + fileepi + u'.nfo'

            # download video
            bgd = KodiBGDialog()
            bgd.Create(self.language(30974), fileepi + extension)
            try:
                bgd.Update(0)
                mvutils.url_retrieve_vfs(videourl, movname,
                                         bgd.UrlRetrieveHook)
                bgd.Close()
                self.notifier.ShowNotification(
                    30960,
                    self.language(30976).format(videourl))
            except Exception as err:
                bgd.Close()
                self.error('Failure downloading {}: {}', videourl, err)
                self.notifier.ShowError(
                    30952,
                    self.language(30975).format(videourl, err))

            # download subtitles
            if film.url_sub:
                bgd = KodiBGDialog()
                bgd.Create(30978, fileepi + u'.ttml')
                try:
                    bgd.Update(0)
                    mvutils.url_retrieve_vfs(film.url_sub, ttmname,
                                             bgd.UrlRetrieveHook)
                    try:
                        ttml2srt(xbmcvfs.File(ttmname, 'r'),
                                 xbmcvfs.File(srtname, 'w'))
                    except Exception as err:
                        self.info('Failed to convert to srt: {}', err)
                    bgd.Close()
                except Exception as err:
                    bgd.Close()
                    self.error('Failure downloading {}: {}', film.url_sub, err)

            # create NFO Files
            self._make_nfo_files(film, episode, dirname, nfoname, videourl)
        else:
            self.notifier.ShowError(30952, 30958)

    def doEnqueueFilm(self, filmid):
        self.info('Enqueue {}', filmid)

    def _check_outdate(self, maxage=172800):
        if self.settings.updmode != 1 and self.settings.updmode != 2:
            # no check with update disabled or update automatic
            return
        if self.db is None:
            # should never happen
            self.notifier.ShowOutdatedUnknown()
            return
        status = self.db.GetStatus()
        if status['status'] == 'NONE' or status['status'] == 'UNINIT':
            # should never happen
            self.notifier.ShowOutdatedUnknown()
            return
        elif status['status'] == 'UPDATING':
            # great... we are updating. nuthin to show
            return
        # lets check how old we are
        tsnow = int(time.time())
        tsold = int(status['lastupdate'])
        if tsnow - tsold > maxage:
            self.notifier.ShowOutdatedKnown(status)

    def _make_nfo_files(self, film, episode, dirname, filename, videourl):
        # create NFO files
        if not xbmcvfs.exists(dirname + 'tvshow.nfo'):
            try:
                with closing(xbmcvfs.File(dirname + 'tvshow.nfo',
                                          'w')) as file:
                    file.write(b'<tvshow>\n')
                    file.write(b'<id></id>\n')
                    file.write(
                        bytearray('\t<title>{}</title>\n'.format(film.show),
                                  'utf-8'))
                    file.write(
                        bytearray(
                            '\t<sorttitle>{}</sorttitle>\n'.format(film.show),
                            'utf-8'))
                    # TODO:				file.write( bytearray( '\t<year>{}</year>\n'.format( 2018 ), 'utf-8' ) )
                    file.write(
                        bytearray(
                            '\t<studio>{}</studio>\n'.format(film.channel),
                            'utf-8'))
                    file.write(b'</tvshow>\n')
            except Exception as err:
                self.error('Failure creating show NFO file for {}: {}',
                           videourl, err)

        try:
            with closing(xbmcvfs.File(filename, 'w')) as file:
                file.write(b'<episodedetails>\n')
                file.write(
                    bytearray('\t<title>{}</title>\n'.format(film.title),
                              'utf-8'))
                file.write(b'\t<season>1</season>\n')
                file.write(b'\t<autonumber>1</autonumber>\n')
                file.write(
                    bytearray('\t<episode>{}</episode>\n'.format(episode),
                              'utf-8'))
                file.write(
                    bytearray(
                        '\t<showtitle>{}</showtitle>\n'.format(film.show),
                        'utf-8'))
                file.write(
                    bytearray('\t<plot>{}</plot>\n'.format(film.description),
                              'utf-8'))
                file.write(
                    bytearray('\t<aired>{}</aired>\n'.format(film.aired),
                              'utf-8'))
                if film.seconds > 60:
                    file.write(
                        bytearray(
                            '\t<runtime>{}</runtime>\n'.format(
                                int(film.seconds / 60)), 'utf-8'))
                file.write(
                    bytearray('\t<studio>{}</studio\n'.format(film.channel),
                              'utf-8'))
                file.write(b'</episodedetails>\n')
        except Exception as err:
            self.error('Failure creating episode NFO file for {}: {}',
                       videourl, err)

    def Init(self):
        self.args = urlparse.parse_qs(sys.argv[2][1:])
        self.db.Init()
        if self.settings.HandleFirstRun():
            # TODO: Implement Issue #16
            pass

    def Do(self):
        # save last activity timestamp
        self.settings.ResetUserActivity()
        # process operation
        mode = self.args.get('mode', None)
        if mode is None:
            self.showMainMenu()
        elif mode[0] == 'search':
            self.showSearch()
        elif mode[0] == 'searchall':
            self.showSearch(extendedsearch=True)
        elif mode[0] == 'livestreams':
            self.db.GetLiveStreams(FilmUI(self,
                                          [xbmcplugin.SORT_METHOD_LABEL]))
        elif mode[0] == 'recent':
            channel = self.args.get('channel', [0])
            self.db.GetRecents(channel[0], FilmUI(self))
        elif mode[0] == 'recentchannels':
            self.db.GetRecentChannels(ChannelUI(self, nextdir='recent'))
        elif mode[0] == 'channels':
            self.db.GetChannels(ChannelUI(self, nextdir='shows'))
        elif mode[0] == 'action-dbinfo':
            self.showDbInfo()
        elif mode[0] == 'action-dbupdate':
            self.settings.TriggerUpdate()
            self.notifier.ShowNotification(30963, 30964, time=10000)
        elif mode[0] == 'initial':
            channel = self.args.get('channel', [0])
            self.db.GetInitials(channel[0], InitialUI(self))
        elif mode[0] == 'shows':
            channel = self.args.get('channel', [0])
            initial = self.args.get('initial', [None])
            self.db.GetShows(channel[0], initial[0], ShowUI(self))
        elif mode[0] == 'films':
            show = self.args.get('show', [0])
            self.db.GetFilms(show[0], FilmUI(self))
        elif mode[0] == 'download':
            filmid = self.args.get('id', [0])
            quality = self.args.get('quality', [1])
            self.doDownloadFilm(filmid[0], quality[0])
        elif mode[0] == 'enqueue':
            self.doEnqueueFilm(self.args.get('id', [0])[0])

        # cleanup saved searches
        if mode is None or mode[0] != 'search':
            self.addon.setSetting('lastsearch1', '')
        if mode is None or mode[0] != 'searchall':
            self.addon.setSetting('lastsearch2', '')

    def Exit(self):
        self.db.Exit()