#!/usr/bin/python
# epgdb.py for OE 2.2 epg.db using sqlite
# written by gutemine <gutemine@oozoon.de> http://www.oozoon.de
# 20150102 adapted by msatter for version 2.1
# 20150106 r12 enhance saving check of epg.db
# 20150114 r13 pass provider and priority as arguments
# 20150122 r14 use load_finished and save_finished
# 20150211 r15 dvb event id checksum fix
# 20150212 r16 dvb event id performance enhancement
# 20150213 r17 dvb event id with only begin time
# 20150301 r18 updates for latest plugin version
# 20150629 r19 prevent duration=0
# 20150629 r20 add hacks
# 20160325 r21 add processing delay

import os
import sys
import time
from datetime import datetime
from enigma import eEPGCache, cachestate
from ServiceReference import ServiceReference
from sqlite3 import dbapi2 as sqlite
from Components.config import config
from enigma import eTimer

class epgdb_class:

	EPG_HEADER1_channel_count=0
	EPG_TOTAL_EVENTS=0
	EXCLUDED_SID=[]

	# initialize an empty dictionary (Python array)
	# as channel events container before preprocessing
	events=[]

	def __init__(self,provider_name,provider_priority,epgdb_path=None):
		self.source_name=provider_name
		self.priority=provider_priority
		# get timespan time from system settings defined in days
		# get outdated time from system settings defined in hours
		self.epg_outdated = int(config.misc.epgcache_outdated_timespan.value)
		# subtract the outdated time from current time                    
		self.epoch_time = int(time.time())-(self.epg_outdated*3600)
		# limit (timespan) the number of day to import
		self.epg_timespan = int(config.misc.epgcache_timespan.value)
		self.epg_cutoff_time=int(time.time())+(self.epg_timespan*86400)
		self.event_counter_journal = 0
		self.events_in_past_journal = 0
		self.events_in_import_range_journal = 0
		self.epgdb_path=config.misc.epgcache_filename.value
		if epgdb_path is None:
			self.epgdb_path=config.misc.epgcache_filename.value
		else:
			self.epgdb_path=epgdb_path
		self.connection = None
		self.epginstance = eEPGCache.getInstance()
		# epg saving will notify when finished ...
		self.cacheState_conn = self.epginstance.cacheState.connect(self.cacheStateChanged)
		print "[EPGDB] saving EPG to %s" % self.epgdb_path
		eEPGCache.save(self.epginstance)

	def cacheStateChanged(self, state):
		if state.state == cachestate.save_finished:
        		print "[EPGDB] epgcache save finished"
  			self.ProcessingTimer = eTimer()
        		if os.path.exists("/var/lib/dpkg/status"):
                		self.ProcessingTimer_conn = self.ProcessingTimer.timeout.connect(self.start_process)
  			else:                        
                		self.ProcessingTimer.callback.append(self.start_process)
        		self.ProcessingTimer.start(10000, True)
#			self.start_process()

	def start_process(self):
		if os.path.exists(self.epgdb_path):
			size=os.path.getsize(self.epgdb_path)/1024
			# even empty epg.db has at least 23k size
	       		min_size = 23
			if size < min_size:
				print "[EPGDB] %s too small" % self.epgdb_path
				return False
		else:
			print "[EPGDB] %s not found" % self.epgdb_path
			return False
		print "[EPGDB] %s exists" % self.epgdb_path
		if self.connection is not None:
			print "[EPGDB] %s already connected" % self.epgdb_path
			return True

		# get outdated time from system settings defined in hours
		self.epg_outdated = int(config.misc.epgcache_outdated_timespan.value)
		# subtract the outdated time from current time                    
		self.epoch_time = int(time.time())-(self.epg_outdated*3600)
		# get timespan time from system settings defined in days
		self.epg_timespan = int(config.misc.epgcache_timespan.value)
		# limit (timespan) is the number of days to import
		self.epg_cutoff_time=int(time.time())+(self.epg_timespan*86400)
		self.events_in_past_journal = 0
		self.events_in_import_range_journal = 0
		try:
			self.connection = sqlite.connect(self.epgdb_path, timeout=20, isolation_level=None, check_same_thread=False)
			self.connection.text_factory = str
			self.cursor = self.connection.cursor()
	                # is it really wise for the small performance gain ?
		        cmd="PRAGMA synchronous = OFF"
        		self.cursor.execute(cmd)
       	        	cmd="PRAGMA journal_mode = OFF"
			self.cursor.execute(cmd)
			# check if it is already a valid T_source
			cmd ="SELECT id from T_Source WHERE source_name=? and priority=?"
			self.cursor.execute(cmd, (self.source_name,self.priority))
			row = self.cursor.fetchone()
			if row is not None:
				self.source_id=int(row[0])
				print "[EPGDB] FOUND %s EPG with source_id %d" % (self.source_name, self.source_id)
			else:	# looks like we have to add it 
				cmd = "insert into T_Source (source_name, priority) values (?, ?)" 
				self.cursor.execute(cmd, (self.source_name,self.priority))
				self.source_id=self.cursor.lastrowid
				self.connection.commit()
				print "[EPGDB] ADDED %s EPG with source_id %d" % (self.source_name,self.source_id)
			# begin transaction  ....
			self.cursor.execute('BEGIN')
			print "[EPGDB] connect to %s finished" % self.epgdb_path
			return True
		except:
			print "[EPGDB] connect to %s failed" % self.epgdb_path
			return False

	def set_excludedsid(self,exsidlist):
		self.EXCLUDED_SID=exsidlist

	def add_event(self, starttime, duration, title, description, language):
		self.events.append((starttime, duration, title[:240], description, language))

	def preprocess_events_channel(self, services):
		if self.connection is None:
			print "[EPGDB] not connected, retrying"
			self.start_process()
		# one local cursor per table seems to perform slightly better ...
		cursor_service = self.connection.cursor()
		cursor_event = self.connection.cursor()
		cursor_title = self.connection.cursor()
		cursor_short_desc = self.connection.cursor()
		cursor_extended_desc = self.connection.cursor()
		cursor_data = self.connection.cursor()

		EPG_EVENT_DATA_id = 0
		events=[]

		# now we go through all the channels we got
		for service in services:
			# prepare and write CHANNEL INFO record
	        	channel = ServiceReference(str(service)).getServiceName()                   
			ssid = service.split(":")
			number_of_events=len(self.events)
			# only add channels where we have events
			if number_of_events > 0:
				# convert hex stuff to integer as epg.db likes it to have
				self.sid=int(ssid[3],16)
				self.tsid=int(ssid[4],16)
				self.onid=int(ssid[5],16)
				self.dvbnamespace=int(ssid[6],16)
				if self.dvbnamespace > 2147483647:           
					self.dvbnamespace -= 4294967296 

				self.EPG_HEADER1_channel_count += 1

				cmd = "SELECT id from T_Service WHERE sid=? and tsid=? and onid=? and dvbnamespace=?"
				cursor_service.execute(cmd, (self.sid,self.tsid,self.onid,self.dvbnamespace))
				row = cursor_service.fetchone()
				if row is not None:
					self.service_id=int(row[0])
				else:
					cmd = "INSERT INTO T_Service (sid,tsid,onid,dvbnamespace) VALUES(?,?,?,?)"
					cursor_service.execute(cmd, (self.sid,self.tsid,self.onid,self.dvbnamespace))
					self.service_id=cursor_service.lastrowid

				# triggers will clean up the rest ... hopefully ...
                                cmd = "DELETE FROM T_Event where service_id=%d" % self.service_id
                                cursor_event.execute(cmd)
				# now we go through all the events for this channel/service_id and add them ...
				self.event_counter_journal = 0
				events = self.events
				for event in events:
					# short description (title)
					self.short_d = event[2]
					# extended description 
					if len(event[3]) > 0:
						self.long_d = event[3]
					else:
						self.long_d = event[2]
					# extract date and time 
					self.begin_time=int(event[0])
					self.duration=int(event[1])
					if self.duration < 1:
						self.duration=1
					self.language=event[4]
					# we need hash values for descriptions, hash is provided by enigma 
					self.short_hash=eEPGCache.getStringHash(self.short_d) 
					self.long_hash=eEPGCache.getStringHash(self.long_d) 
					# generate an unique dvb event id < 65536
					self.dvb_event_id=(self.begin_time-(self.begin_time/3932160)*3932160)/60
#					print "[EPGDB] dvb event id: %d" % self.dvb_event_id
					if self.short_hash > 2147483647:           
						self.short_hash -= 4294967296 
					if self.long_hash > 2147483647:           
						self.long_hash -= 4294967296 
					# now insert into epg.db what we have
					self.end_time = self.begin_time + self.duration
					if self.end_time > self.epoch_time and self.begin_time < self.epg_cutoff_time:
                                                cmd = "INSERT INTO T_Event (service_id, begin_time, duration, source_id, dvb_event_id) VALUES(?,?,?,?,?)"
                                                cursor_event.execute(cmd, (self.service_id, self.begin_time, self.duration, self.source_id, self.dvb_event_id))
                                                self.event_id=cursor_event.lastrowid
                                                # check if hash already exists on Title
                                                cmd ="SELECT id from T_Title WHERE hash=%d" % self.short_hash
                                                cursor_title.execute(cmd)
                                                row = cursor_title.fetchone()
                                                if row is None:
                                                        cmd = "INSERT INTO T_Title (hash, title) VALUES(?,?)"
                                                        cursor_title.execute(cmd, (self.short_hash,self.short_d))
                                                        self.title_id=cursor_title.lastrowid
                                                else:
                                                        self.title_id=int(row[0])
                                                cmd ="SELECT id from T_Short_Description WHERE hash=%d" % self.short_hash
                                                cursor_short_desc.execute(cmd)
                                                row = cursor_short_desc.fetchone()
                                                if row is None:
                                                        cmd = "INSERT INTO T_Short_Description (hash, short_description) VALUES(?,?)"
                                                        cursor_short_desc.execute(cmd, (self.short_hash,self.short_d))
                                                        self.short_description_id=cursor_short_desc.lastrowid
                                                else:
                                                        self.short_description_id=int(row[0])
                                                # check if hash already exists for Extended Description
                                                cmd ="SELECT id from T_Extended_Description WHERE hash=%d" % self.long_hash
                                                cursor_extended_desc.execute(cmd)
                                                row = cursor_extended_desc.fetchone()
                                                if row is None:
                                                        cmd = "INSERT INTO T_Extended_Description (hash, extended_description) VALUES(?,?)"
                                                        cursor_extended_desc.execute(cmd, (self.long_hash,self.long_d))
                                                        self.extended_description_id=cursor_extended_desc.lastrowid
                                                else:
                                                        self.extended_description_id=int(row[0])
                                                cmd = "INSERT INTO T_Data (event_id, title_id, short_description_id, extended_description_id, iso_639_language_code) VALUES(?,?,?,?,?)"
                                                cursor_data.execute(cmd, (self.event_id,self.title_id,self.short_description_id,self.extended_description_id,self.language))
						# increase journaling counters 
                                                self.events_in_import_range_journal += 1
						self.event_counter_journal += 1	
                                        else:
                                                self.events_in_past_journal += 1

			print "[EPGDB] added %d from %d events for channel %s" % (self.event_counter_journal, number_of_events, channel)
			self.EPG_TOTAL_EVENTS += number_of_events

		# reset event container
		self.events=[]
		cursor_service.close()
		cursor_event.close()
		cursor_title.close()
		cursor_short_desc.close()
		cursor_extended_desc.close()
		cursor_data.close()

	def cancel_process(self):
		if self.connection is None:
			print "[EPGDB] still not connected, sorry"
			return
		print "[EPGDB] Importing cancelled"
		self.cursor.execute('END')
		self.cursor.close()
		self.connection.close()
		self.connection = None

	def final_process(self):
		if self.connection is None:
			print "[EPGDB] still not connected, sorry"
			return
		print "[EPGDB] Importing finished. From the total available %d events %d events were imported." % (self.EPG_TOTAL_EVENTS, self.events_in_import_range_journal)
		print "[EPGDB] %d Events were outside of the defined timespan(-%d hours outdated and timespan %d days)." % (self.events_in_past_journal, self.epg_outdated, self.epg_timespan)
		self.cursor.execute('END')
		self.cursor.close()
		self.connection.close()
		self.connection = None
		print "[EPGDB] now writes the epg database ..."
		epginstance = eEPGCache.getInstance()
		eEPGCache.load(epginstance)
