Exemple #1
0
	def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, timerdict, moviedict, simulateOnly=False):
		new = 0
		modified = 0

		# Precompute timer destination dir
		dest = timer.destination or config.usage.default_path.value

		# Workaround to allow search for umlauts if we know the encoding
		#match = timer.match
		match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '')
		if timer.encoding != 'UTF-8':
			try:
				match = match.decode('UTF-8').encode(timer.encoding)
			except UnicodeDecodeError:
				pass

		if timer.searchType == "description":
			epgmatches = []
			mask = (eServiceReference.isMarker | eServiceReference.isDirectory)

			casesensitive = timer.searchCase == "sensitive"
			if not casesensitive:
				match = match.lower()

			# Service filter defined
			# Search only using the specified services
			test = [(service, 0, -1, -1) for service in timer.services]

			for bouquet in timer.bouquets:
				services = serviceHandler.list(eServiceReference(bouquet))
				if not services is None:
					while True:
						service = services.getNext()
						if not service.valid(): #check end of list
							break
						if not (service.flags & mask):
							test.append( (service.toString(), 0, -1, -1 ) )

			if not test:
				# No service filter defined
				# Search within all services - could be very slow

				# Get all bouquets
				bouquetlist = []
				if config.usage.multibouquet.value:
					refstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet'
					bouquetroot = eServiceReference(refstr)
					bouquets = serviceHandler.list(bouquetroot)
					if bouquets:
						while True:
							s = bouquets.getNext()
							if not s.valid():
								break
							if s.flags & eServiceReference.isDirectory:
								info = serviceHandler.info(s)
								if info:
									bouquetlist.append((info.getName(s), s))
					mask = (eServiceReference.isMarker | eServiceReference.isDirectory)
					for name, bouquet in bouquetlist:
						if not bouquet.valid(): #check end of list
							break
						if bouquet.flags & eServiceReference.isDirectory:
							services = serviceHandler.list(bouquet)
							if not services is None:
								while True:
									service = services.getNext()
									if not service.valid(): #check end of list
										break
									if not (service.flags & mask):
										test.append( (service.toString(), 0, -1, -1 ) )
				else:
					service_types_tv = '1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 22) || (type == 25) || (type == 134) || (type == 195)'
					refstr = '%s FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet'%(service_types_tv)
					bouquetroot = eServiceReference(refstr)
					info = serviceHandler.info(bouquetroot)
					if info:
						bouquetlist.append((info.getName(bouquetroot), bouquetroot))
					mask = (eServiceReference.isMarker | eServiceReference.isDirectory)
					for name, bouquet in bouquetlist:
						if bouquet.flags & eServiceReference.isDirectory:
							services = serviceHandler.list(bouquet)
							if not services is None:
								while True:
									service = services.getNext()
									if not service.valid(): #check end of list
										break
									if not (service.flags & mask):
										test.append( (service.toString(), 0, -1, -1 ) )

			if test:
				# Get all events
				#  eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] )
				test.insert(0, 'RITBDSE')
				allevents = epgcache.lookupEvent(test) or []

				# Filter events
				for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents:
					if match in (shortdesc if casesensitive else shortdesc.lower()) \
						or match in (extdesc if casesensitive else extdesc.lower()):
						epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) )

		else:
			# Search EPG, default to empty list
			epgmatches = epgcache.search( ('RITBDSE', 2000, typeMap[timer.searchType], match, caseMap[timer.searchCase]) ) or []

		# Sort list of tuples by begin time 'B'
		epgmatches.sort(key=itemgetter(3))

		# Contains the the marked similar eits and the conflicting strings
		similardict = defaultdict(list)

		# Loop over all EPG matches
		for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ):

			print("[AutoTimer] possible epgmatch %s" % (name))
			eserviceref = eServiceReference(serviceref)
			evt = epgcache.lookupEventId(eserviceref, eit)
			if not evt:
				print("[AutoTimer] Could not create Event!")
				continue
			# Try to determine real service (we always choose the last one)
			n = evt.getNumOfLinkageServices()
			if n > 0:
				i = evt.getLinkageService(eserviceref, n-1)
				serviceref = i.toString()

			evtBegin = begin
			evtEnd = end = begin + duration

			# If event starts in less than 60 seconds skip it
			if begin < time() + 60:
				print("[AutoTimer] Skipping an event because it starts in less than 60 seconds")
				continue

			# Set short description to equal extended description if it is empty.
			if not shortdesc:
				shortdesc = extdesc

			# Convert begin time
			timestamp = localtime(begin)
			# Update timer
			timer.update(begin, timestamp)

			# Check if eit is in similar matches list
			# NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive
			similarTimer = False
			if eit in similardict:
				similarTimer = True
				dayofweek = None # NOTE: ignore day on similar timer
			else:
				# If maximum days in future is set then check time
				if checkEvtLimit:
					if begin > evtLimit:
						continue

				dayofweek = str(timestamp.tm_wday)

			# Check timer conditions
			# NOTE: similar matches do not care about the day/time they are on, so ignore them
			if timer.checkServices(serviceref) \
				or timer.checkDuration(duration) \
				or (not similarTimer and (\
					timer.checkTimespan(timestamp) \
					or timer.checkTimeframe(begin) \
				)): #or timer.checkFilter(name, shortdesc, extdesc, dayofweek):
				continue

			if timer.hasOffset():
				# Apply custom Offset
				begin, end = timer.applyOffset(begin, end)
			else:
				# Apply E2 Offset
				begin -= config.recording.margin_before.value * 60
				end += config.recording.margin_after.value * 60

			# Overwrite endtime if requested
			if timer.justplay and not timer.setEndtime:
				end = begin

			# Eventually change service to alternative
			if timer.overrideAlternatives:
				serviceref = timer.getAlternative(serviceref)

			# Check for existing recordings in directory
			if timer.avoidDuplicateDescription == 3:
				# Reset movie Exists
				movieExists = False

				if dest and dest not in moviedict:
					self.addDirectoryToMovieDict(moviedict, dest, serviceHandler)
				for movieinfo in moviedict.get(dest, ()):
					if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ):
						print("[AutoTimer] We found a matching recorded movie, skipping event:", name)
						movieExists = True
						break
				if movieExists:
					continue

			# Initialize
			newEntry = None
			oldExists = False

			# Check for double Timers
			# We first check eit and if user wants us to guess event based on time
			# we try this as backup. The allowed diff should be configurable though.
			for rtimer in timerdict.get(serviceref, ()):
				if rtimer.eit == eit:
					oldExists = True
					print("[AutoTimer] We found a timer based on eit")
					newEntry = rtimer
					break
				elif config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, evtBegin, evtEnd) > ((duration/10)*8):
					oldExists = True
					print("[AutoTimer] We found a timer based on time guessing")
					newEntry = rtimer
					break
				elif timer.avoidDuplicateDescription >= 1 \
					and not rtimer.disabled:
						if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ):
						# if searchForDuplicateDescription > 1 then check short description
							oldExists = True
							print("[AutoTimer] We found a timer (similar service) with same description, skipping event")
							break

			# We found no timer we want to edit
			if newEntry is None:
				# But there is a match
				if oldExists:
					continue

				# We want to search for possible doubles
				if timer.avoidDuplicateDescription >= 2:
					for rtimer in chain.from_iterable( itervalues(timerdict) ):
						if not rtimer.disabled:
							if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ):
								oldExists = True
								print("[AutoTimer] We found a timer (any service) with same description, skipping event")
								break
					if oldExists:
						continue

				if timer.checkCounter(timestamp):
					print("[AutoTimer] Not adding new timer because counter is depleted.")
					continue
			# Append to timerlist and abort if simulating
			timers.append((name, begin, end, serviceref, timer.name))
			if simulateOnly:
				continue

			if newEntry is not None:
				# Abort if we don't want to modify timers or timer is repeated
				if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated:
					print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer")
					continue

				if hasattr(newEntry, "isAutoTimer"):
					newEntry.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name))
				else:
					if config.plugins.autotimer.refresh.value != "all":
						print("[AutoTimer] Won't modify existing timer because it's no timer set by us")
						continue

					newEntry.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name))

				modified += 1

				self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit)
				newEntry.log(501, "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name))
			else:
				newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit)
				newEntry.log(500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name))

				# Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
				# It is only temporarily, after a restart it will be lost,
				# because it won't be stored in the timer xml file
				newEntry.isAutoTimer = True

			if getSeasonAndEpisode is not None and timer.series_labeling:
				sp_timer = getSeasonAndEpisode(newEntry, name, evtBegin, evtEnd)
				if sp_timer:
					newEntry = sp_timer
				name = newEntry.name
				print("[AutoTimer SeriesPlugin] Returned name %s" % (name))
				shortdesc = newEntry.description
				print("[AutoTimer SeriesPlugin] Returned description %s" % (shortdesc))
			
			if timer.checkFilter(name, shortdesc, extdesc, dayofweek):
				continue

			# Apply afterEvent
			if timer.hasAfterEvent():
				afterEvent = timer.getAfterEventTimespan(localtime(end))
				if afterEvent is None:
					afterEvent = timer.getAfterEvent()
				if afterEvent is not None:
					newEntry.afterEvent = afterEvent

			newEntry.dirname = timer.destination
			newEntry.justplay = timer.justplay
			newEntry.vpsplugin_enabled = timer.vps_enabled
			newEntry.vpsplugin_overwrite = timer.vps_overwrite
			tags = timer.tags[:]
			if config.plugins.autotimer.add_autotimer_to_tags.value:
				tags.append('AutoTimer')
			if config.plugins.autotimer.add_name_to_tags.value:
				tagname = timer.name.strip()
				if tagname:
					tagname = tagname[0].upper() + tagname[1:].replace(" ", "_")
					tags.append(tagname)
			newEntry.tags = tags

			if oldExists:
				# XXX: this won't perform a sanity check, but do we actually want to do so?
				recordHandler.timeChanged(newEntry)

				#if renameTimer is not None and timer.series_labeling:
				#	renameTimer(newEntry, name, evtBegin, evtEnd)

			else:
				conflictString = ""
				if similarTimer:
					conflictString = similardict[eit].conflictString
					newEntry.log(504, "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString))

				# Try to add timer
				conflicts = recordHandler.record(newEntry)

				if conflicts:
					# Maybe use newEntry.log
					conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts])
					print("[AutoTimer] conflict with %s detected" % (conflictString))

					if config.plugins.autotimer.addsimilar_on_conflict.value:
						# We start our search right after our actual index
						# Attention we have to use a copy of the list, because we have to append the previous older matches
						lepgm = len(epgmatches)
						for i in xrange(lepgm):
							servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ]
							if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ):
								# Check if the similar is already known
								if eitS not in similardict:
									print("[AutoTimer] Found similar Timer: " + name)

									# Store the actual and similar eit and conflictString, so it can be handled later
									newEntry.conflictString = conflictString
									similardict[eit] = newEntry
									similardict[eitS] = newEntry
									similarTimer = True
									if beginS <= evtBegin:
										# Event is before our actual epgmatch so we have to append it to the epgmatches list
										epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS))
									# If we need a second similar it will be found the next time
								else:
									similarTimer = False
									newEntry = similardict[eitS]
								break

				if conflicts is None:
					timer.decrementCounter()
					new += 1
					newEntry.extdesc = extdesc
					timerdict[serviceref].append(newEntry)

					#if renameTimer is not None and timer.series_labeling:
					#	renameTimer(newEntry, name, evtBegin, evtEnd)

					# Similar timers are in new timers list and additionally in similar timers list
					if similarTimer:
						similars.append((name, begin, end, serviceref, timer.name))
						similardict.clear()

				# Don't care about similar timers
				elif not similarTimer:
					conflicting.append((name, begin, end, serviceref, timer.name))

					if config.plugins.autotimer.disabled_on_conflict.value:
						newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString))
						newEntry.disabled = True
						# We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
						conflicts = recordHandler.record(newEntry)
		return (new, modified)
	def parseTimer(self, timer, epgcache, serviceHandler, recordHandler, checkEvtLimit, evtLimit, timers, conflicting, similars, skipped, timerdict, moviedict, simulateOnly=False):
		new = 0
		modified = 0

		# Search EPG, default to empty list
		epgmatches = epgcache.search( ('RITBDSE', 1000, typeMap[timer.searchType], timer.match, caseMap[timer.searchCase]) ) or []

		# Sort list of tuples by begin time 'B'
		epgmatches.sort(key=itemgetter(3))

		# Contains the the marked similar eits and the conflicting strings
		similardict = defaultdict(list)		

		# Loop over all EPG matches
		for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ):
			
			startLog()
			
			# timer destination dir
			dest = timer.destination or config.usage.default_path.value
			
			evtBegin = begin
			evtEnd = end = begin + duration

			doLog("possible epgmatch %s" % (name))
			doLog("Serviceref %s" % (str(serviceref)))
			eserviceref = eServiceReference(serviceref)
			evt = epgcache.lookupEventId(eserviceref, eit)
			if not evt:
				doLog("Could not create Event!")
				skipped.append((name, begin, end, str(serviceref), timer.name, getLog()))
				continue
			# Try to determine real service (we always choose the last one)
			n = evt.getNumOfLinkageServices()
			if n > 0:
				i = evt.getLinkageService(eserviceref, n-1)
				serviceref = i.toString()
				doLog("Serviceref2 %s" % (str(serviceref)))

			# If event starts in less than 60 seconds skip it
			if begin < time() + 60:
				doLog("Skipping an event because it starts in less than 60 seconds")
				skipped.append((name, begin, end, serviceref, timer.name, getLog()))
				continue

			# Convert begin time
			timestamp = localtime(begin)
			# Update timer
			timer.update(begin, timestamp)

			# Check if eit is in similar matches list
			# NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive
			similarTimer = False
			if eit in similardict:
				similarTimer = True
				dayofweek = None # NOTE: ignore day on similar timer
			else:
				# If maximum days in future is set then check time
				if checkEvtLimit:
					if begin > evtLimit:
						doLog("Skipping an event because of maximum days in future is reached")
						skipped.append((name, begin, end, serviceref, timer.name, getLog()))
						continue

				dayofweek = str(timestamp.tm_wday)

			# Check timer conditions
			# NOTE: similar matches do not care about the day/time they are on, so ignore them
			if timer.checkServices(serviceref):
				doLog("Skipping an event because of check services")
				skipped.append((name, begin, end, serviceref, timer.name, getLog()))
				continue
			if timer.checkDuration(duration):
				doLog("Skipping an event because of duration check")
				skipped.append((name, begin, end, serviceref, timer.name, getLog()))
				continue
			if not similarTimer:
				if timer.checkTimespan(timestamp):
					doLog("Skipping an event because of timestamp check")
					skipped.append((name, begin, end, serviceref, timer.name, getLog()))
					continue
				if timer.checkTimeframe(begin):
					doLog("Skipping an event because of timeframe check")
					skipped.append((name, begin, end, serviceref, timer.name, getLog()))
					continue

			# Initialize
			newEntry = None
			oldExists = False
			allow_modify = True
			
			# Eventually change service to alternative
			if timer.overrideAlternatives:
				serviceref = timer.getAlternative(serviceref)

			if timer.series_labeling and sp_getSeasonEpisode is not None:
				allow_modify = False
				#doLog("Request name, desc, path %s %s %s" % (name,shortdesc,dest))
				sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd, shortdesc, dest)
				if sp and type(sp) in (tuple, list) and len(sp) == 4:
					name = sp[0] or name
					shortdesc = sp[1] or shortdesc
					dest = sp[2] or dest
					doLog(str(sp[3]))
					#doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest))
					allow_modify = True
				else:
					# Nothing found
					doLog(str(sp))
					
					# If AutoTimer name not equal match, do a second lookup with the name
					if timer.name.lower() != timer.match.lower():
						#doLog("Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest))
						sp = sp_getSeasonEpisode(serviceref, timer.name, evtBegin, evtEnd, shortdesc, dest)
						if sp and type(sp) in (tuple, list) and len(sp) == 4:
							name = sp[0] or name
							shortdesc = sp[1] or shortdesc
							dest = sp[2] or dest
							doLog(str(sp[3]))
							#doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest))
							allow_modify = True
						else:
							doLog(str(sp))

			if timer.checkFilter(name, shortdesc, extdesc, dayofweek):
				doLog("Skipping an event because of filter check")
				skipped.append((name, begin, end, serviceref, timer.name, getLog()))
				continue
			
			if timer.hasOffset():
				# Apply custom Offset
				begin, end = timer.applyOffset(begin, end)
			else:
				# Apply E2 Offset
				if ServiceRecordingSettings:
					begin -= ServiceRecordingSettings.instance.getMarginBefore(eserviceref)
					end += ServiceRecordingSettings.instance.getMarginAfter(eserviceref)
				else:
					begin -= config.recording.margin_before.value * 60
					end += config.recording.margin_after.value * 60

			# Overwrite endtime if requested
			if timer.justplay and not timer.setEndtime:
				end = begin

			# Check for existing recordings in directory
			if timer.avoidDuplicateDescription == 3:
				# Reset movie Exists
				movieExists = False

				if dest and dest not in moviedict:
					self.addDirectoryToMovieDict(moviedict, dest, serviceHandler)
				for movieinfo in moviedict.get(dest, ()):
					if self.checkDuplicates(timer, name, movieinfo.get("name"), shortdesc, movieinfo.get("shortdesc"), extdesc, movieinfo.get("extdesc") ):
						doLog("We found a matching recorded movie, skipping event:", name)
						movieExists = True
						break
				if movieExists:
					doLog("Skipping an event because movie already exists")
					skipped.append((name, begin, end, serviceref, timer.name, getLog()))
					continue

			# Check for double Timers
			# We first check eit and if user wants us to guess event based on time
			# we try this as backup. The allowed diff should be configurable though.
			for rtimer in timerdict.get(serviceref, ()):
				if rtimer.eit == eit:
					oldExists = True
					doLog("We found a timer based on eit")
					newEntry = rtimer
					break
				elif config.plugins.autotimer.try_guessing.value:
					if timer.hasOffset():
						# Remove custom Offset
						rbegin = rtimer.begin + timer.offset[0] * 60
						rend = rtimer.end - timer.offset[1] * 60
					else:
						# Remove E2 Offset
						rbegin = rtimer.begin + config.recording.margin_before.value * 60
						rend = rtimer.end - config.recording.margin_after.value * 60
					# As alternative we could also do a epg lookup
					#revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit)
					#rbegin = revent.getBeginTime() or 0
					#rduration = revent.getDuration() or 0
					#rend = rbegin + rduration or 0
					if getTimeDiff(rbegin, rend, evtBegin, evtEnd) > ((duration/10)*8):
						oldExists = True
						doLog("We found a timer based on time guessing")
						newEntry = rtimer
						break
				if timer.avoidDuplicateDescription >= 1 \
					and not rtimer.disabled:
						if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ):
						# if searchForDuplicateDescription > 1 then check short description
							oldExists = True
							doLog("We found a timer (similar service) with same description, skipping event")
							break

			# We found no timer we want to edit
			if newEntry is None:
				# But there is a match
				if oldExists:
					doLog("Skipping an event because a timer on same service exists")
					skipped.append((name, begin, end, serviceref, timer.name, getLog()))
					continue

				# We want to search for possible doubles
				if timer.avoidDuplicateDescription >= 2:
					for rtimer in chain.from_iterable( itervalues(timerdict) ):
						if not rtimer.disabled:
							if self.checkDuplicates(timer, name, rtimer.name, shortdesc, rtimer.description, extdesc, rtimer.extdesc ):
								oldExists = True
								doLog("We found a timer (any service) with same description, skipping event")
								break
					if oldExists:
						doLog("Skipping an event because a timer on any service exists")
						skipped.append((name, begin, end, serviceref, timer.name, getLog()))
						continue

				if timer.checkCounter(timestamp):
					doLog("Not adding new timer because counter is depleted.")
					skipped.append((name, begin, end, serviceref, timer.name, getLog()))
					continue

			# Append to timerlist and abort if simulating
			timers.append((name, begin, end, serviceref, timer.name, getLog()))
			if simulateOnly:
				continue

			if newEntry is not None:
				# Abort if we don't want to modify timers or timer is repeated
				if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated:
					doLog("Won't modify existing timer because either no modification allowed or repeated timer")
					continue

				if hasattr(newEntry, "isAutoTimer"):
					msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)
					doLog(msg)
					newEntry.log(501, msg)
				elif config.plugins.autotimer.add_autotimer_to_tags.value and TAG in newEntry.tags:
					msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name)
					doLog(msg)
					newEntry.log(501, msg)
				else:
					if config.plugins.autotimer.refresh.value != "all":
						doLog("Won't modify existing timer because it's no timer set by us")
						continue

					msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (timer.name, newEntry.name)
					doLog(msg)
					newEntry.log(501, msg)

				modified += 1

				if allow_modify:
					self.modifyTimer(newEntry, name, shortdesc, begin, end, serviceref, eit)
					msg = "[AutoTimer] AutoTimer modified timer: %s ." % (newEntry.name)
					doLog(msg)
					newEntry.log(501, msg)
				else:
					msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (newEntry.name)
					doLog(msg)
			else:
				newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit)
				msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name)
				doLog(msg)
				newEntry.log(500, msg)
				
				# Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
				# It is only temporarily, after a restart it will be lost,
				# because it won't be stored in the timer xml file
				newEntry.isAutoTimer = True

			# Apply afterEvent
			if timer.hasAfterEvent():
				afterEvent = timer.getAfterEventTimespan(localtime(end))
				if afterEvent is None:
					afterEvent = timer.getAfterEvent()
				if afterEvent is not None:
					newEntry.afterEvent = afterEvent

			newEntry.dirname = dest
			newEntry.calculateFilename()

			newEntry.justplay = timer.justplay
			newEntry.vpsplugin_enabled = timer.vps_enabled
			newEntry.vpsplugin_overwrite = timer.vps_overwrite
			tags = timer.tags[:]
			if config.plugins.autotimer.add_autotimer_to_tags.value:
				if TAG not in tags:
					tags.append(TAG)
			if config.plugins.autotimer.add_name_to_tags.value:
				tagname = timer.name.strip()
				if tagname:
					tagname = tagname[0].upper() + tagname[1:].replace(" ", "_")
					if tagname not in tags:
						tags.append(tagname)
			newEntry.tags = tags

			if oldExists:
				# XXX: this won't perform a sanity check, but do we actually want to do so?
				recordHandler.timeChanged(newEntry)

			else:
				conflictString = ""
				if similarTimer:
					conflictString = similardict[eit].conflictString
					msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString)
					doLog(msg)
					newEntry.log(504, msg)

				# Try to add timer
				conflicts = recordHandler.record(newEntry)

				if conflicts:
					# Maybe use newEntry.log
					conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts])
					doLog("conflict with %s detected" % (conflictString))

					if config.plugins.autotimer.addsimilar_on_conflict.value:
						# We start our search right after our actual index
						# Attention we have to use a copy of the list, because we have to append the previous older matches
						lepgm = len(epgmatches)
						for i in xrange(lepgm):
							servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ]
							if self.checkDuplicates(timer, name, nameS, shortdesc, shortdescS, extdesc, extdescS, force=True ):
								# Check if the similar is already known
								if eitS not in similardict:
									doLog("Found similar Timer: " + name)

									# Store the actual and similar eit and conflictString, so it can be handled later
									newEntry.conflictString = conflictString
									similardict[eit] = newEntry
									similardict[eitS] = newEntry
									similarTimer = True
									if beginS <= evtBegin:
										# Event is before our actual epgmatch so we have to append it to the epgmatches list
										epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS))
									# If we need a second similar it will be found the next time
								else:
									similarTimer = False
									newEntry = similardict[eitS]
								break

				if conflicts is None:
					timer.decrementCounter()
					new += 1
					newEntry.extdesc = extdesc
					timerdict[serviceref].append(newEntry)

					# Similar timers are in new timers list and additionally in similar timers list
					if similarTimer:
						similars.append((name, begin, end, serviceref, timer.name))
						similardict.clear()

				# Don't care about similar timers
				elif not similarTimer:
					conflicting.append((name, begin, end, serviceref, timer.name))

					if config.plugins.autotimer.disabled_on_conflict.value:
						msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString)
						doLog(msg)
						newEntry.log(503, msg)
						newEntry.disabled = True
						# We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
						conflicts = recordHandler.record(newEntry)
		
		return (new, modified)
Exemple #3
0
    def parseTimer(self,
                   timer,
                   epgcache,
                   serviceHandler,
                   recordHandler,
                   checkEvtLimit,
                   evtLimit,
                   timers,
                   conflicting,
                   similars,
                   timerdict,
                   moviedict,
                   simulateOnly=False):
        new = 0
        modified = 0
        skipped = 0

        # Precompute timer destination dir
        dest = timer.destination or config.usage.default_path.value

        # Workaround to allow search for umlauts if we know the encoding
        match = timer.match.replace('\xc2\x86', '').replace('\xc2\x87', '')
        if timer.encoding != 'UTF-8':
            try:
                match = match.decode('UTF-8').encode(timer.encoding)
            except UnicodeDecodeError:
                pass

        if timer.searchType == "description":
            epgmatches = []
            mask = (eServiceReference.isMarker | eServiceReference.isDirectory)

            casesensitive = timer.searchCase == "sensitive"
            if not casesensitive:
                match = match.lower()

            # Service filter defined
            # Search only using the specified services
            test = [(service, 0, -1, -1) for service in timer.services]

            for bouquet in timer.bouquets:
                services = serviceHandler.list(eServiceReference(bouquet))
                if not services is None:
                    while True:
                        service = services.getNext()
                        if not service.valid():  #check end of list
                            break
                        if not (service.flags & mask):
                            test.append((service.toString(), 0, -1, -1))

            if not test:
                # No service filter defined
                # Search within all services - could be very slow

                # Get all bouquets
                bouquetlist = []
                refstr = '1:134:1:0:0:0:0:0:0:0:FROM BOUQUET \"bouquets.tv\" ORDER BY bouquet'
                bouquetroot = eServiceReference(refstr)
                mask = eServiceReference.isDirectory
                if config.usage.multibouquet.value:
                    bouquets = serviceHandler.list(bouquetroot)
                    if bouquets:
                        while True:
                            s = bouquets.getNext()
                            if not s.valid():
                                break
                            if s.flags & mask:
                                info = serviceHandler.info(s)
                                if info:
                                    bouquetlist.append((info.getName(s), s))
                else:
                    info = serviceHandler.info(bouquetroot)
                    if info:
                        bouquetlist.append(
                            (info.getName(bouquetroot), bouquetroot))

                # Get all services
                mask = (eServiceReference.isMarker
                        | eServiceReference.isDirectory)
                for name, bouquet in bouquetlist:
                    if not bouquet.valid():  #check end of list
                        break
                    if bouquet.flags & eServiceReference.isDirectory:
                        services = serviceHandler.list(bouquet)
                        if not services is None:
                            while True:
                                service = services.getNext()
                                if not service.valid():  #check end of list
                                    break
                                if not (service.flags & mask):
                                    test.append(
                                        (service.toString(), 0, -1, -1))

            if test:
                # Get all events
                #  eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] )
                test.insert(0, 'RITBDSE')
                allevents = epgcache.lookupEvent(test) or []

                # Filter events
                for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents:
                    if match in (shortdesc if casesensitive else shortdesc.lower()) \
                     or match in (extdesc if casesensitive else extdesc.lower()):
                        epgmatches.append((serviceref, eit, name, begin,
                                           duration, shortdesc, extdesc))

        else:
            # Search EPG, default to empty list
            epgmatches = epgcache.search(
                ('RITBDSE', 3000, typeMap[timer.searchType], match,
                 caseMap[timer.searchCase])) or []

        # Sort list of tuples by begin time 'B'
        epgmatches.sort(key=itemgetter(3))

        # Contains the the marked similar eits and the conflicting strings
        similardict = defaultdict(list)

        # Loop over all EPG matches
        preveit = False
        for idx, (serviceref, eit, name, begin, duration, shortdesc,
                  extdesc) in enumerate(epgmatches):

            eserviceref = eServiceReference(serviceref)
            evt = epgcache.lookupEventId(eserviceref, eit)
            if not evt:
                print("[AutoTimer] Could not create Event!")
                continue
            # Try to determine real service (we always choose the last one)
            n = evt.getNumOfLinkageServices()
            if n > 0:
                i = evt.getLinkageService(eserviceref, n - 1)
                serviceref = i.toString()

            evtBegin = begin
            evtEnd = end = begin + duration

            # If event starts in less than 60 seconds skip it
            # if begin < time() + 60:
            # 	print ("[AutoTimer] Skipping " + name + " because it starts in less than 60 seconds")
            # 	skipped += 1
            # 	continue

            # Set short description to equal extended description if it is empty.
            if not shortdesc:
                shortdesc = extdesc

            # Convert begin time
            timestamp = localtime(begin)
            # Update timer
            timer.update(begin, timestamp)

            # Check if eit is in similar matches list
            # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive
            similarTimer = False
            if eit in similardict:
                similarTimer = True
                dayofweek = None  # NOTE: ignore day on similar timer
            else:
                # If maximum days in future is set then check time
                if checkEvtLimit:
                    if begin > evtLimit:
                        continue

                dayofweek = str(timestamp.tm_wday)

            # Check timer conditions
            # NOTE: similar matches do not care about the day/time they are on, so ignore them
            if timer.checkServices(serviceref) \
             or timer.checkDuration(duration) \
             or (not similarTimer and (\
              timer.checkTimespan(timestamp) \
              or timer.checkTimeframe(begin) \
             )) or timer.checkFilter(name, shortdesc, extdesc, dayofweek):
                continue

            if timer.hasOffset():
                # Apply custom Offset
                begin, end = timer.applyOffset(begin, end)
            else:
                # Apply E2 Offset
                begin -= config.recording.margin_before.value * 60
                end += config.recording.margin_after.value * 60

            # Overwrite endtime if requested
            if timer.justplay and not timer.setEndtime:
                end = begin

            # Eventually change service to alternative
            if timer.overrideAlternatives:
                serviceref = timer.getAlternative(serviceref)

            # Append to timerlist and abort if simulating
            timers.append((name, begin, end, serviceref, timer.name))
            if simulateOnly:
                continue

            # Check for existing recordings in directory
            if timer.avoidDuplicateDescription == 3:
                # Reset movie Exists
                movieExists = False

                if dest and dest not in moviedict:
                    self.addDirectoryToMovieDict(moviedict, dest,
                                                 serviceHandler)
                for movieinfo in moviedict.get(dest, ()):
                    if self.checkSimilarity(timer, name, movieinfo.get("name"),
                                            shortdesc,
                                            movieinfo.get("shortdesc"),
                                            extdesc, movieinfo.get("extdesc")):
                        print(
                            "[AutoTimer] We found a matching recorded movie, skipping event:",
                            name)
                        movieExists = True
                        break
                if movieExists:
                    continue

            # Initialize
            newEntry = None
            oldExists = False

            # Check for double Timers
            # We first check eit and if user wants us to guess event based on time
            # we try this as backup. The allowed diff should be configurable though.
            for rtimer in timerdict.get(serviceref, ()):
                if (rtimer.eit == eit
                        or config.plugins.autotimer.try_guessing.getValue()
                    ) and getTimeDiff(rtimer, evtBegin, evtEnd) > (
                        (duration / 10) * 8):
                    oldExists = True

                    # Abort if we don't want to modify timers or timer is repeated
                    if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated:
                        print(
                            "[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer"
                        )
                        break

                    if eit == preveit:
                        break

                    if (evtBegin -
                        (config.recording.margin_before.getValue() *
                         60) != rtimer.begin) or (
                             evtEnd +
                             (config.recording.margin_after.getValue() * 60) !=
                             rtimer.end) or (shortdesc != rtimer.description):
                        if rtimer.isAutoTimer and eit == rtimer.eit:
                            print(
                                "[AutoTimer] AutoTimer %s modified this automatically generated timer."
                                % (timer.name))
                            rtimer.log(
                                501,
                                "[AutoTimer] AutoTimer %s modified this automatically generated timer."
                                % (timer.name))
                            preveit = eit
                        else:
                            if config.plugins.autotimer.refresh.getValue(
                            ) != "all":
                                print(
                                    "[AutoTimer] Won't modify existing timer because it's no timer set by us"
                                )
                                break
                            rtimer.log(
                                501,
                                "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ."
                                % (timer.name, rtimer.name))
                        newEntry = rtimer
                        modified += 1
                        self.modifyTimer(rtimer, name, shortdesc, begin, end,
                                         serviceref, eit)
                        rtimer.log(
                            501, "[AutoTimer] AutoTimer modified timer: %s ." %
                            (rtimer.name))
                        break
                    else:
                        print(
                            "[AutoTimer] Skipping timer because it has not changed."
                        )
                        skipped += 1
                        break
                elif timer.avoidDuplicateDescription >= 1 and not rtimer.disabled:
                    if self.checkSimilarity(timer, name, rtimer.name,
                                            shortdesc, rtimer.description,
                                            extdesc, rtimer.extdesc):
                        print(
                            "[AutoTimer] We found a timer with similar description, skipping event"
                        )
                        oldExists = True
                        break

            # We found no timer we want to edit
            if newEntry is None:
                # But there is a match
                if oldExists:
                    continue

                # We want to search for possible doubles
                for rtimer in chain.from_iterable(itervalues(timerdict)):
                    if not rtimer.disabled:
                        if self.checkDoubleTimers(timer, name, rtimer.name,
                                                  begin, rtimer.begin, end,
                                                  rtimer.end):
                            oldExists = True
                            # print("[AutoTimer] We found a timer with same StartTime, skipping event")
                            break
                        if timer.avoidDuplicateDescription >= 2:
                            if self.checkSimilarity(timer, name, rtimer.name,
                                                    shortdesc,
                                                    rtimer.description,
                                                    extdesc, rtimer.extdesc):
                                oldExists = True
                                print(
                                    "[AutoTimer] We found a timer (any service) with same description, skipping event"
                                )
                                break
                if oldExists:
                    continue

                if timer.checkCounter(timestamp):
                    print(
                        "[AutoTimer] Not adding new timer because counter is depleted."
                    )
                    continue

                newEntry = RecordTimerEntry(ServiceReference(serviceref),
                                            begin, end, name, shortdesc, eit)
                newEntry.log(
                    500,
                    "[AutoTimer] Try to add new timer based on AutoTimer %s." %
                    (timer.name))

                # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
                # It is only temporarily, after a restart it will be lost,
                # because it won't be stored in the timer xml file
                newEntry.isAutoTimer = True

            # Apply afterEvent
            if timer.hasAfterEvent():
                afterEvent = timer.getAfterEventTimespan(localtime(end))
                if afterEvent is None:
                    afterEvent = timer.getAfterEvent()
                if afterEvent is not None:
                    newEntry.afterEvent = afterEvent

            newEntry.dirname = timer.destination
            newEntry.justplay = timer.justplay
            newEntry.vpsplugin_enabled = timer.vps_enabled
            newEntry.vpsplugin_overwrite = timer.vps_overwrite
            tags = timer.tags[:]
            if config.plugins.autotimer.add_autotimer_to_tags.value:
                tags.append('AutoTimer')
            if config.plugins.autotimer.add_name_to_tags.value:
                tagname = timer.name.strip()
                if tagname:
                    tagname = tagname[0].upper() + tagname[1:].replace(
                        " ", "_")
                    tags.append(tagname)
            newEntry.tags = tags

            if oldExists:
                # XXX: this won't perform a sanity check, but do we actually want to do so?
                recordHandler.timeChanged(newEntry)

                if renameTimer is not None and timer.series_labeling:
                    renameTimer(newEntry, name, evtBegin, evtEnd)

            else:
                conflictString = ""
                if similarTimer:
                    conflictString = similardict[eit].conflictString
                    newEntry.log(
                        504,
                        "[AutoTimer] Try to add similar Timer because of conflicts with %s."
                        % (conflictString))

                # Try to add timer
                conflicts = recordHandler.record(newEntry)

                if conflicts:
                    # Maybe use newEntry.log
                    conflictString += ' / '.join([
                        "%s (%s)" %
                        (x.name, strftime("%Y%m%d %H%M", localtime(x.begin)))
                        for x in conflicts
                    ])
                    print("[AutoTimer] conflict with %s detected" %
                          (conflictString))

                    if config.plugins.autotimer.addsimilar_on_conflict.value:
                        # We start our search right after our actual index
                        # Attention we have to use a copy of the list, because we have to append the previous older matches
                        lepgm = len(epgmatches)
                        for i in xrange(lepgm):
                            servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[
                                (i + idx + 1) % lepgm]
                            if self.checkSimilarity(timer,
                                                    name,
                                                    nameS,
                                                    shortdesc,
                                                    shortdescS,
                                                    extdesc,
                                                    extdescS,
                                                    force=True):
                                # Check if the similar is already known
                                if eitS not in similardict:
                                    print("[AutoTimer] Found similar Timer: " +
                                          name)

                                    # Store the actual and similar eit and conflictString, so it can be handled later
                                    newEntry.conflictString = conflictString
                                    similardict[eit] = newEntry
                                    similardict[eitS] = newEntry
                                    similarTimer = True
                                    if beginS <= evtBegin:
                                        # Event is before our actual epgmatch so we have to append it to the epgmatches list
                                        epgmatches.append(
                                            (servicerefS, eitS, nameS, beginS,
                                             durationS, shortdescS, extdescS))
                                    # If we need a second similar it will be found the next time
                                else:
                                    similarTimer = False
                                    newEntry = similardict[eitS]
                                break

                if conflicts is None:
                    timer.decrementCounter()
                    new += 1
                    newEntry.extdesc = extdesc
                    timerdict[serviceref].append(newEntry)

                    if renameTimer is not None and timer.series_labeling:
                        renameTimer(newEntry, name, evtBegin, evtEnd)

                    # Similar timers are in new timers list and additionally in similar timers list
                    if similarTimer:
                        similars.append(
                            (name, begin, end, serviceref, timer.name))
                        similardict.clear()

                # Don't care about similar timers
                elif not similarTimer:
                    conflicting.append(
                        (name, begin, end, serviceref, timer.name))

                    if config.plugins.autotimer.disabled_on_conflict.value:
                        newEntry.log(
                            503,
                            "[AutoTimer] Timer disabled because of conflicts with %s."
                            % (conflictString))
                        newEntry.disabled = True
                        # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
                        conflicts = recordHandler.record(newEntry)
        self.result = (new, modified, skipped)
        self.completed.append(timer.name)
        sleep(0.5)
	def parseEPG(self, simulateOnly = False):
		if NavigationInstance.instance is None:
			print "[AutoTimer] Navigation is not available, can't parse EPG"
			return (0, 0, 0, [], [])

		total = 0
		new = 0
		modified = 0
		timers = []
		conflicting = []

		self.readXml()

		# Save Recordings in a dict to speed things up a little
		# We include processed timers as we might search for duplicate descriptions
		recorddict = {}
		for timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers:
			recorddict.setdefault(str(timer.service_ref), []).append(timer)

		# Iterate Timer
		for timer in self.getEnabledTimerList():
			# Workaround to allow search for umlauts if we know the encoding
			match = timer.match
			if timer.encoding != 'UTF-8':
				try:
					match = match.decode('UTF-8').encode(timer.encoding)
				except UnicodeDecodeError:
					pass

			# Search EPG, default to empty list
			epgcache = eEPGCache.getInstance()
			ret = epgcache.search(('RI', 500, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or ()

			for serviceref, eit in ret:
				eserviceref = eServiceReference(serviceref)

				evt = epgcache.lookupEventId(eserviceref, eit)
				if not evt:
					print "[AutoTimer] Could not create Event!"
					continue

				# Try to determine real service (we always choose the last one)
				n = evt.getNumOfLinkageServices()
				if n > 0:
					i = evt.getLinkageService(eserviceref, n-1)
					serviceref = i.toString()

				# Gather Information
				name = evt.getEventName()
				description = evt.getShortDescription()
				begin = evt.getBeginTime()
				duration = evt.getDuration()
				end = begin + duration

				# If event starts in less than 60 seconds skip it
				if begin < time() + 60:
					continue

				# Convert begin time
				timestamp = localtime(begin)

				# Update timer
				timer.update(begin, timestamp)

				# Check Duration, Timespan and Excludes
				if timer.checkServices(serviceref) \
					or timer.checkDuration(duration) \
					or timer.checkTimespan(timestamp) \
					or timer.checkFilter(name, description,
						evt.getExtendedDescription(), str(timestamp.tm_wday)):
					continue

				if timer.hasOffset():
					# Apply custom Offset
					begin, end = timer.applyOffset(begin, end)
				else:
					# Apply E2 Offset
					begin -= config.recording.margin_before.value * 60
					end += config.recording.margin_after.value * 60

				# Eventually change service to alternative
				if timer.overrideAlternatives:
					serviceref = timer.getAlternative(serviceref)

				total += 1

				# Append to timerlist and abort if simulating
				timers.append((name, begin, end, serviceref, timer.name))
				if simulateOnly:
					continue

				# Initialize
				newEntry = None
				oldExists = False

				# Check for double Timers
				# We first check eit and if user wants us to guess event based on time
				# we try this as backup. The allowed diff should be configurable though.
				for rtimer in recorddict.get(serviceref, ()):
					if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8):
						oldExists = True

						# Abort if we don't want to modify timers or timer is repeated
						if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated:
							print "[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer"
							break

						if hasattr(rtimer, "isAutoTimer"):
								print "[AutoTimer] Modifying existing AutoTimer!"
						else:
							if config.plugins.autotimer.refresh.value != "all":
								print "[AutoTimer] Won't modify existing timer because it's no timer set by us"
								break

							print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us"

						newEntry = rtimer
						modified += 1

						# Modify values saved in timer
						newEntry.name = name
						newEntry.description = description
						newEntry.begin = int(begin)
						newEntry.end = int(end)
						newEntry.service_ref = ServiceReference(serviceref)

						break
					elif timer.avoidDuplicateDescription == 1 and not rtimer.disabled and rtimer.name == name and rtimer.description == description:
						oldExists = True
						print "[AutoTimer] We found a timer with same description, skipping event"
						break

				# We found no timer we want to edit
				if newEntry is None:
					# But there is a match
					if oldExists:
						continue

					# We want to search for possible doubles
					if timer.avoidDuplicateDescription == 2:
						# I thinks thats the fastest way to do this, though it's a little ugly
						try:
							for list in recorddict.values():
								for rtimer in list:
									if not rtimer.disabled and rtimer.name == name and rtimer.description == description:
										raise AutoTimerIgnoreTimerException("We found a timer with same description, skipping event")
						except AutoTimerIgnoreTimerException, etite:
							print etite
							continue

					if timer.checkCounter(timestamp):
						continue

					print "[AutoTimer] Adding an event."
					newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, description, eit)

					# Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
					newEntry.isAutoTimer = True

				# Apply afterEvent
 				if timer.hasAfterEvent():
 					afterEvent = timer.getAfterEventTimespan(localtime(end))
 					if afterEvent is None:
 						afterEvent = timer.getAfterEvent()
 					if afterEvent is not None:
 						newEntry.afterEvent = afterEvent

				newEntry.dirname = timer.destination
				newEntry.justplay = timer.justplay
				newEntry.tags = timer.tags

				if oldExists:
					# XXX: this won't perform a sanity check, but do we actually want to do so?
					NavigationInstance.instance.RecordTimer.timeChanged(newEntry)
				else:
					conflicts = NavigationInstance.instance.RecordTimer.record(newEntry)
					if conflicts and config.plugins.autotimer.disabled_on_conflict.value:
						newEntry.disabled = True
						# We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
						conflicts = NavigationInstance.instance.RecordTimer.record(newEntry)
						conflicting.append((name, begin, end, serviceref, timer.name))
					if conflicts is None:
						timer.decrementCounter()
						new += 1
						recorddict.setdefault(serviceref, []).append(newEntry)
					else:
						conflicting.append((name, begin, end, serviceref, timer.name))
Exemple #5
0
    def parseTimer(self,
                   timer,
                   epgcache,
                   serviceHandler,
                   recordHandler,
                   checkEvtLimit,
                   evtLimit,
                   timers,
                   conflicting,
                   similars,
                   skipped,
                   timerdict,
                   moviedict,
                   simulateOnly=False):
        new = 0
        modified = 0

        # Search EPG, default to empty list
        epgmatches = epgcache.search(
            ('RITBDSE', 1000, typeMap[timer.searchType], timer.match,
             caseMap[timer.searchCase])) or []

        # Sort list of tuples by begin time 'B'
        epgmatches.sort(key=itemgetter(3))

        # Contains the the marked similar eits and the conflicting strings
        similardict = defaultdict(list)

        # Loop over all EPG matches
        for idx, (serviceref, eit, name, begin, duration, shortdesc,
                  extdesc) in enumerate(epgmatches):

            startLog()

            # timer destination dir
            dest = timer.destination or config.usage.default_path.value

            evtBegin = begin
            evtEnd = end = begin + duration

            doLog("possible epgmatch %s" % (name))
            doLog("Serviceref %s" % (str(serviceref)))
            eserviceref = eServiceReference(serviceref)
            evt = epgcache.lookupEventId(eserviceref, eit)
            if not evt:
                doLog("Could not create Event!")
                skipped.append(
                    (name, begin, end, str(serviceref), timer.name, getLog()))
                continue
            # Try to determine real service (we always choose the last one)
            n = evt.getNumOfLinkageServices()
            if n > 0:
                i = evt.getLinkageService(eserviceref, n - 1)
                serviceref = i.toString()
                doLog("Serviceref2 %s" % (str(serviceref)))

            # If event starts in less than 60 seconds skip it
            if begin < time() + 60:
                doLog(
                    "Skipping an event because it starts in less than 60 seconds"
                )
                skipped.append(
                    (name, begin, end, serviceref, timer.name, getLog()))
                continue

            # Convert begin time
            timestamp = localtime(begin)
            # Update timer
            timer.update(begin, timestamp)

            # Check if eit is in similar matches list
            # NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive
            similarTimer = False
            if eit in similardict:
                similarTimer = True
                dayofweek = None  # NOTE: ignore day on similar timer
            else:
                # If maximum days in future is set then check time
                if checkEvtLimit:
                    if begin > evtLimit:
                        doLog(
                            "Skipping an event because of maximum days in future is reached"
                        )
                        skipped.append((name, begin, end, serviceref,
                                        timer.name, getLog()))
                        continue

                dayofweek = str(timestamp.tm_wday)

            # Check timer conditions
            # NOTE: similar matches do not care about the day/time they are on, so ignore them
            if timer.checkServices(serviceref):
                doLog("Skipping an event because of check services")
                skipped.append(
                    (name, begin, end, serviceref, timer.name, getLog()))
                continue
            if timer.checkDuration(duration):
                doLog("Skipping an event because of duration check")
                skipped.append(
                    (name, begin, end, serviceref, timer.name, getLog()))
                continue
            if not similarTimer:
                if timer.checkTimespan(timestamp):
                    doLog("Skipping an event because of timestamp check")
                    skipped.append(
                        (name, begin, end, serviceref, timer.name, getLog()))
                    continue
                if timer.checkTimeframe(begin):
                    doLog("Skipping an event because of timeframe check")
                    skipped.append(
                        (name, begin, end, serviceref, timer.name, getLog()))
                    continue

            # Initialize
            newEntry = None
            oldExists = False
            allow_modify = True

            # Eventually change service to alternative
            if timer.overrideAlternatives:
                serviceref = timer.getAlternative(serviceref)

            if timer.series_labeling and sp_getSeasonEpisode is not None:
                allow_modify = False
                #doLog("Request name, desc, path %s %s %s" % (name,shortdesc,dest))
                sp = sp_getSeasonEpisode(serviceref, name, evtBegin, evtEnd,
                                         shortdesc, dest)
                if sp and type(sp) in (tuple, list) and len(sp) == 4:
                    name = sp[0] or name
                    shortdesc = sp[1] or shortdesc
                    dest = sp[2] or dest
                    doLog(str(sp[3]))
                    #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest))
                    allow_modify = True
                else:
                    # Nothing found
                    doLog(str(sp))

                    # If AutoTimer name not equal match, do a second lookup with the name
                    if timer.name.lower() != timer.match.lower():
                        #doLog("Request name, desc, path %s %s %s" % (timer.name,shortdesc,dest))
                        sp = sp_getSeasonEpisode(serviceref, timer.name,
                                                 evtBegin, evtEnd, shortdesc,
                                                 dest)
                        if sp and type(sp) in (tuple, list) and len(sp) == 4:
                            name = sp[0] or name
                            shortdesc = sp[1] or shortdesc
                            dest = sp[2] or dest
                            doLog(str(sp[3]))
                            #doLog("Returned name, desc, path %s %s %s" % (name,shortdesc,dest))
                            allow_modify = True
                        else:
                            doLog(str(sp))

            if timer.checkFilter(name, shortdesc, extdesc, dayofweek):
                doLog("Skipping an event because of filter check")
                skipped.append(
                    (name, begin, end, serviceref, timer.name, getLog()))
                continue

            if timer.hasOffset():
                # Apply custom Offset
                begin, end = timer.applyOffset(begin, end)
            else:
                # Apply E2 Offset
                if ServiceRecordingSettings:
                    begin -= ServiceRecordingSettings.instance.getMarginBefore(
                        eserviceref)
                    end += ServiceRecordingSettings.instance.getMarginAfter(
                        eserviceref)
                else:
                    begin -= config.recording.margin_before.value * 60
                    end += config.recording.margin_after.value * 60

            # Overwrite endtime if requested
            if timer.justplay and not timer.setEndtime:
                end = begin

            # Check for existing recordings in directory
            if timer.avoidDuplicateDescription == 3:
                # Reset movie Exists
                movieExists = False

                if dest and dest not in moviedict:
                    self.addDirectoryToMovieDict(moviedict, dest,
                                                 serviceHandler)
                for movieinfo in moviedict.get(dest, ()):
                    if self.checkDuplicates(timer, name, movieinfo.get("name"),
                                            shortdesc,
                                            movieinfo.get("shortdesc"),
                                            extdesc, movieinfo.get("extdesc")):
                        doLog(
                            "We found a matching recorded movie, skipping event:",
                            name)
                        movieExists = True
                        break
                if movieExists:
                    doLog("Skipping an event because movie already exists")
                    skipped.append(
                        (name, begin, end, serviceref, timer.name, getLog()))
                    continue

            # Check for double Timers
            # We first check eit and if user wants us to guess event based on time
            # we try this as backup. The allowed diff should be configurable though.
            for rtimer in timerdict.get(serviceref, ()):
                if rtimer.eit == eit:
                    oldExists = True
                    doLog("We found a timer based on eit")
                    newEntry = rtimer
                    break
                elif config.plugins.autotimer.try_guessing.value:
                    if timer.hasOffset():
                        # Remove custom Offset
                        rbegin = rtimer.begin + timer.offset[0] * 60
                        rend = rtimer.end - timer.offset[1] * 60
                    else:
                        # Remove E2 Offset
                        rbegin = rtimer.begin + config.recording.margin_before.value * 60
                        rend = rtimer.end - config.recording.margin_after.value * 60
                    # As alternative we could also do a epg lookup
                    #revent = epgcache.lookupEventId(rtimer.service_ref.ref, rtimer.eit)
                    #rbegin = revent.getBeginTime() or 0
                    #rduration = revent.getDuration() or 0
                    #rend = rbegin + rduration or 0
                    if getTimeDiff(rbegin, rend, evtBegin,
                                   evtEnd) > ((duration / 10) * 8):
                        oldExists = True
                        doLog("We found a timer based on time guessing")
                        newEntry = rtimer
                        break
                if timer.avoidDuplicateDescription >= 1 \
                 and not rtimer.disabled:
                    if self.checkDuplicates(timer, name, rtimer.name,
                                            shortdesc, rtimer.description,
                                            extdesc, rtimer.extdesc):
                        # if searchForDuplicateDescription > 1 then check short description
                        oldExists = True
                        doLog(
                            "We found a timer (similar service) with same description, skipping event"
                        )
                        break

            # We found no timer we want to edit
            if newEntry is None:
                # But there is a match
                if oldExists:
                    doLog(
                        "Skipping an event because a timer on same service exists"
                    )
                    skipped.append(
                        (name, begin, end, serviceref, timer.name, getLog()))
                    continue

                # We want to search for possible doubles
                if timer.avoidDuplicateDescription >= 2:
                    for rtimer in chain.from_iterable(itervalues(timerdict)):
                        if not rtimer.disabled:
                            if self.checkDuplicates(timer, name, rtimer.name,
                                                    shortdesc,
                                                    rtimer.description,
                                                    extdesc, rtimer.extdesc):
                                oldExists = True
                                doLog(
                                    "We found a timer (any service) with same description, skipping event"
                                )
                                break
                    if oldExists:
                        doLog(
                            "Skipping an event because a timer on any service exists"
                        )
                        skipped.append((name, begin, end, serviceref,
                                        timer.name, getLog()))
                        continue

                if timer.checkCounter(timestamp):
                    doLog("Not adding new timer because counter is depleted.")
                    skipped.append(
                        (name, begin, end, serviceref, timer.name, getLog()))
                    continue

            # Append to timerlist and abort if simulating
            timers.append((name, begin, end, serviceref, timer.name, getLog()))
            if simulateOnly:
                continue

            if newEntry is not None:
                # Abort if we don't want to modify timers or timer is repeated
                if config.plugins.autotimer.refresh.value == "none" or newEntry.repeated:
                    doLog(
                        "Won't modify existing timer because either no modification allowed or repeated timer"
                    )
                    continue

                if hasattr(newEntry, "isAutoTimer"):
                    msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (
                        timer.name)
                    doLog(msg)
                    newEntry.log(501, msg)
                elif config.plugins.autotimer.add_autotimer_to_tags.value and TAG in newEntry.tags:
                    msg = "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (
                        timer.name)
                    doLog(msg)
                    newEntry.log(501, msg)
                else:
                    if config.plugins.autotimer.refresh.value != "all":
                        doLog(
                            "Won't modify existing timer because it's no timer set by us"
                        )
                        continue

                    msg = "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it: %s ." % (
                        timer.name, newEntry.name)
                    doLog(msg)
                    newEntry.log(501, msg)

                modified += 1

                if allow_modify:
                    self.modifyTimer(newEntry, name, shortdesc, begin, end,
                                     serviceref, eit)
                    msg = "[AutoTimer] AutoTimer modified timer: %s ." % (
                        newEntry.name)
                    doLog(msg)
                    newEntry.log(501, msg)
                else:
                    msg = "[AutoTimer] AutoTimer modification not allowed for timer: %s ." % (
                        newEntry.name)
                    doLog(msg)
            else:
                newEntry = RecordTimerEntry(ServiceReference(serviceref),
                                            begin, end, name, shortdesc, eit)
                msg = "[AutoTimer] Try to add new timer based on AutoTimer %s." % (
                    timer.name)
                doLog(msg)
                newEntry.log(500, msg)

                # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
                # It is only temporarily, after a restart it will be lost,
                # because it won't be stored in the timer xml file
                newEntry.isAutoTimer = True

            # Apply afterEvent
            if timer.hasAfterEvent():
                afterEvent = timer.getAfterEventTimespan(localtime(end))
                if afterEvent is None:
                    afterEvent = timer.getAfterEvent()
                if afterEvent is not None:
                    newEntry.afterEvent = afterEvent

            newEntry.dirname = dest
            newEntry.calculateFilename()

            newEntry.justplay = timer.justplay
            newEntry.vpsplugin_enabled = timer.vps_enabled
            newEntry.vpsplugin_overwrite = timer.vps_overwrite
            tags = timer.tags[:]
            if config.plugins.autotimer.add_autotimer_to_tags.value:
                if TAG not in tags:
                    tags.append(TAG)
            if config.plugins.autotimer.add_name_to_tags.value:
                tagname = timer.name.strip()
                if tagname:
                    tagname = tagname[0].upper() + tagname[1:].replace(
                        " ", "_")
                    if tagname not in tags:
                        tags.append(tagname)
            newEntry.tags = tags

            if oldExists:
                # XXX: this won't perform a sanity check, but do we actually want to do so?
                recordHandler.timeChanged(newEntry)

            else:
                conflictString = ""
                if similarTimer:
                    conflictString = similardict[eit].conflictString
                    msg = "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (
                        conflictString)
                    doLog(msg)
                    newEntry.log(504, msg)

                # Try to add timer
                conflicts = recordHandler.record(newEntry)

                if conflicts:
                    # Maybe use newEntry.log
                    conflictString += ' / '.join([
                        "%s (%s)" %
                        (x.name, strftime("%Y%m%d %H%M", localtime(x.begin)))
                        for x in conflicts
                    ])
                    doLog("conflict with %s detected" % (conflictString))

                    if config.plugins.autotimer.addsimilar_on_conflict.value:
                        # We start our search right after our actual index
                        # Attention we have to use a copy of the list, because we have to append the previous older matches
                        lepgm = len(epgmatches)
                        for i in xrange(lepgm):
                            servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[
                                (i + idx + 1) % lepgm]
                            if self.checkDuplicates(timer,
                                                    name,
                                                    nameS,
                                                    shortdesc,
                                                    shortdescS,
                                                    extdesc,
                                                    extdescS,
                                                    force=True):
                                # Check if the similar is already known
                                if eitS not in similardict:
                                    doLog("Found similar Timer: " + name)

                                    # Store the actual and similar eit and conflictString, so it can be handled later
                                    newEntry.conflictString = conflictString
                                    similardict[eit] = newEntry
                                    similardict[eitS] = newEntry
                                    similarTimer = True
                                    if beginS <= evtBegin:
                                        # Event is before our actual epgmatch so we have to append it to the epgmatches list
                                        epgmatches.append(
                                            (servicerefS, eitS, nameS, beginS,
                                             durationS, shortdescS, extdescS))
                                    # If we need a second similar it will be found the next time
                                else:
                                    similarTimer = False
                                    newEntry = similardict[eitS]
                                break

                if conflicts is None:
                    timer.decrementCounter()
                    new += 1
                    newEntry.extdesc = extdesc
                    timerdict[serviceref].append(newEntry)

                    # Similar timers are in new timers list and additionally in similar timers list
                    if similarTimer:
                        similars.append(
                            (name, begin, end, serviceref, timer.name))
                        similardict.clear()

                # Don't care about similar timers
                elif not similarTimer:
                    conflicting.append(
                        (name, begin, end, serviceref, timer.name))

                    if config.plugins.autotimer.disabled_on_conflict.value:
                        msg = "[AutoTimer] Timer disabled because of conflicts with %s." % (
                            conflictString)
                        doLog(msg)
                        newEntry.log(503, msg)
                        newEntry.disabled = True
                        # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
                        conflicts = recordHandler.record(newEntry)

        return (new, modified)
	def parseEPG(self, simulateOnly = False):
		if NavigationInstance.instance is None:
			print("[AutoTimer] Navigation is not available, can't parse EPG")
			return (0, 0, 0, [], [], [])

		total = 0
		new = 0
		modified = 0
		timers = []
		conflicting = []
		similar = defaultdict(list)			# Contains the the marked similar eits and the conflicting strings
		similars = []										# Contains the added similar timers

		# NOTE: the config option specifies "the next X days" which means today (== 1) + X
		delta = timedelta(days = config.plugins.autotimer.maxdaysinfuture.value + 1)
		evtLimit = mktime((date.today() + delta).timetuple())
		checkEvtLimit = delta.days > 1
		del delta

		# Read AutoTimer configuration
		self.readXml()

		# Get E2 instances
		epgcache = eEPGCache.getInstance()
		serviceHandler = eServiceCenter.getInstance()
		recordHandler = NavigationInstance.instance.RecordTimer

		# Save Recordings in a dict to speed things up a little
		# We include processed timers as we might search for duplicate descriptions
		# The recordict is always filled
		#Question: It might be better to name it timerdict
		#Question: Move to a separate function getTimerDict()
		#Note: It is also possible to use RecordTimer isInTimer(), but we won't get the timer itself on a match
		recorddict = defaultdict(list)
		for timer in chain(recordHandler.timer_list, recordHandler.processed_timers):
			if timer and timer.service_ref:
				if timer.eit is not None:
					event = epgcache.lookupEventId(timer.service_ref.ref, timer.eit)
					extdesc = event and event.getExtendedDescription() or ''
					timer.extdesc = extdesc
				elif not hasattr(timer, 'extdesc'):
					timer.extdesc = ''
				recorddict[str(timer.service_ref)].append(timer)

		# Create dict of all movies in all folders used by an autotimer to compare with recordings
		# The moviedict will be filled only if one AutoTimer is configured to avoid duplicate description for any recordings
		#Question: It might be better to name it recorddict
		moviedict = defaultdict(list)

		# Iterate Timer
		for timer in self.getEnabledTimerList():
			# Precompute timer destination dir
			dest = timer.destination or config.usage.default_path.value

			# Workaround to allow search for umlauts if we know the encoding
			match = timer.match
			if timer.encoding != 'UTF-8':
				try:
					match = match.decode('UTF-8').encode(timer.encoding)
				except UnicodeDecodeError:
					pass

			if timer.searchType == "description":
				test = []
				epgmatches = []

				casesensitive = timer.searchCase == "sensitive"
				if not casesensitive:
					match = match.lower()

				#if timer.services or timer.bouquets:
				# Service filter defined
				# Search only using the specified services
				for service in timer.services:
					test.append( (service, 0, -1, -1 ) )
				mask = (eServiceReference.isMarker | eServiceReference.isDirectory)
				for bouquet in timer.bouquets:
					services = serviceHandler.list(eServiceReference(bouquet))
					if not services is None:
						while True:
							service = services.getNext()
							if not service.valid(): #check end of list
								break
							if not (service.flags & mask):
								test.append( (service.toString(), 0, -1, -1 ) )

				if not test:
				#else:
					# No service filter defined
					# Search within all services - could be very slow

					# Get all bouquets
					bouquetlist = []
					refstr = '1:134:1:0:0:0:0:0:0:0:FROM BOUQUET \"bouquets.tv\" ORDER BY bouquet'
					bouquetroot = eServiceReference(refstr)
					mask = eServiceReference.isDirectory
					if config.usage.multibouquet.value:
						bouquets = serviceHandler.list(bouquetroot)
						if bouquets:
							while True:
								s = bouquets.getNext()
								if not s.valid():
									break
								if s.flags & mask:
									info = serviceHandler.info(s)
									if info:
										bouquetlist.append((info.getName(s), s))
					else:
						info = serviceHandler.info(bouquetroot)
						if info:
							bouquetlist.append((info.getName(bouquetroot), bouquetroot))

					# Get all services
					mask = (eServiceReference.isMarker | eServiceReference.isDirectory)
					for name, bouquet in bouquetlist:
						if not bouquet.valid(): #check end of list
							break
						if bouquet.flags & eServiceReference.isDirectory:
							services = serviceHandler.list(bouquet)
							if not services is None:
								while True:
									service = services.getNext()
									if not service.valid(): #check end of list
										break
									if not (service.flags & mask):
										test.append( (service.toString(), 0, -1, -1 ) )

				if test:
					# Get all events
					#  eEPGCache.lookupEvent( [ format of the returned tuples, ( service, 0 = event intersects given start_time, start_time -1 for now_time), ] )
					test.insert(0, 'RITBDSE')
					allevents = epgcache.lookupEvent( test ) or []

					# Filter events
					for serviceref, eit, name, begin, duration, shortdesc, extdesc in allevents:
						if match in (shortdesc if casesensitive else shortdesc.lower()) \
							or match in (extdesc if casesensitive else extdesc.lower()):
							epgmatches.append( (serviceref, eit, name, begin, duration, shortdesc, extdesc) )

			else:
				# Search EPG, default to empty list
			epgmatches = epgcache.search(('RITBDSE', 1000, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or []
			# Sort list of tuples by begin time 'B'
			epgmatches.sort(key=itemgetter(3))

			# Reset the the marked similar servicerefs
			similar.clear()

			# Loop over all EPG matches
			for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ):

			# Loop over all EPG matches
			for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ):

				#Question: Do we need this?
				#Question: Move to separate function getRealService()
				eserviceref = eServiceReference(serviceref)
				evt = epgcache.lookupEventId(eserviceref, eit)
				if not evt:
					print("[AutoTimer] Could not create Event!")
					continue
				# Try to determine real service (we always choose the last one)
				n = evt.getNumOfLinkageServices()
				if n > 0:
					i = evt.getLinkageService(eserviceref, n-1)
					serviceref = i.toString()

				evtBegin = begin
				evtEnd = end = begin + duration

				# If event starts in less than 60 seconds skip it
				if begin < time() + 60:
					print("[AutoTimer] Skipping an event because it starts in less than 60 seconds")
					continue

				# Convert begin time
				timestamp = localtime(begin)
				# Update timer
				timer.update(begin, timestamp)

				# Check if eit is in similar matches list
				# NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive
				similarTimer = False
				if eit in similar:
					similarTimer = True
					dayofweek = None # NOTE: ignore day on similar timer
				else:
					# If maximum days in future is set then check time
					if checkEvtLimit:
						if begin > evtLimit:
							continue

					dayofweek = str(timestamp.tm_wday)

				# Check timer conditions
				# NOTE: similar matches to not care about the day/time they are on, so ignore them
				if timer.checkServices(serviceref) \
					or timer.checkDuration(duration) \
					or (not similarTimer and (\
						timer.checkTimespan(timestamp) \
						or timer.checkTimeframe(begin) \
					)) or timer.checkFilter(name, shortdesc, extdesc, dayofweek):
					continue

				if timer.hasOffset():
					# Apply custom Offset
					begin, end = timer.applyOffset(begin, end)
				else:
					# Apply E2 Offset
					begin -= config.recording.margin_before.value * 60
					end += config.recording.margin_after.value * 60

				# Overwrite endtime if requested
				if timer.justplay and not timer.setEndtime:
					end = begin

				# Eventually change service to alternative
				if timer.overrideAlternatives:
					serviceref = timer.getAlternative(serviceref)

				total += 1

				# Append to timerlist and abort if simulating
				timers.append((name, begin, end, serviceref, timer.name))
				if simulateOnly:
					continue

				# Check for existing recordings in directory
				if timer.avoidDuplicateDescription == 3:
					# Reset movie Exists
					movieExists = False

					if dest and dest not in moviedict:
						#Question: Move to a separate function getRecordDict()
							print("[AutoTimer] listing of movies in " + dest + " failed")
								event = info.getEvent(movieref)
								if event is None:
									continue
									"shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription),
									"extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string?
					for movieinfo in moviedict.get(dest, ()):
						if movieinfo.get("name") == name \
							and movieinfo.get("shortdesc") == shortdesc:
							# Some channels indicate replays in the extended descriptions
							# If the similarity percent is higher then 0.8 it is a very close match
							extdescM = movieinfo.get("extdesc")
							if ( len(extdesc) == len(extdescM) and extdesc == extdescM ) \
								or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescM).ratio() ):
								print("[AutoTimer] We found a matching recorded movie, skipping event:", name)
								movieExists = True
								break
					if movieExists:
						continue

				# Initialize
				newEntry = None
				oldExists = False

				# Check for double Timers
				# We first check eit and if user wants us to guess event based on time
				# we try this as backup. The allowed diff should be configurable though.
				for rtimer in recorddict.get(serviceref, ()):
					if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, evtBegin, evtEnd) > ((duration/10)*8):
						oldExists = True

						# Abort if we don't want to modify timers or timer is repeated
						if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated:
							print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer")
							break

						if hasattr(rtimer, "isAutoTimer"):
							rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name))
						else:
							if config.plugins.autotimer.refresh.value != "all":
								print("[AutoTimer] Won't modify existing timer because it's no timer set by us")
								break

							rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it." % (timer.name))

						newEntry = rtimer
						modified += 1

						# Modify values saved in timer
						newEntry.name = name
						newEntry.description = shortdesc
						newEntry.begin = int(begin)
						newEntry.end = int(end)
						newEntry.service_ref = ServiceReference(serviceref)

						break
					elif timer.avoidDuplicateDescription >= 1 \
						and not rtimer.disabled \
						and rtimer.name == name \
						and rtimer.description == shortdesc:
							# Some channels indicate replays in the extended descriptions
							# If the similarity percent is higher then 0.8 it is a very close match
							if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \
								or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ):
								oldExists = True
								print("[AutoTimer] We found a timer (similar service) with same description, skipping event")
								break

				# We found no timer we want to edit
				if newEntry is None:
					# But there is a match
					if oldExists:
						continue

					# We want to search for possible doubles
					if timer.avoidDuplicateDescription >= 2:
						for rtimer in chain.from_iterable( itervalues(recorddict) ):
							if not rtimer.disabled \
								and rtimer.name == name \
								and rtimer.description == shortdesc:
									# Some channels indicate replays in the extended descriptions
									# If the similarity percent is higher then 0.8 it is a very close match
									if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \
										or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ):
										oldExists = True
										print("[AutoTimer] We found a timer (any service) with same description, skipping event")
										break
						if oldExists:
							continue

					if timer.checkCounter(timestamp):
						print("[AutoTimer] Not adding new timer because counter is depleted.")
						continue

					newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit)
					newEntry.log(500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name))

					# Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
					# It is only temporarily, after a restart it will be lost,
					# because it won't be stored in the timer xml file
					newEntry.isAutoTimer = True

				# Apply afterEvent
				if timer.hasAfterEvent():
					afterEvent = timer.getAfterEventTimespan(localtime(end))
					if afterEvent is None:
						afterEvent = timer.getAfterEvent()
					if afterEvent is not None:
						newEntry.afterEvent = afterEvent

				newEntry.dirname = timer.destination
				newEntry.justplay = timer.justplay
				newEntry.tags = timer.tags
				newEntry.vpsplugin_enabled = timer.vps_enabled
				newEntry.vpsplugin_overwrite = timer.vps_overwrite

				if oldExists:
					# XXX: this won't perform a sanity check, but do we actually want to do so?
					recordHandler.timeChanged(newEntry)
				else:
					conflictString = ""
					if similarTimer:
						conflictString = similar[eit].conflictString
						newEntry.log(504, "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString))

					# Try to add timer
					conflicts = recordHandler.record(newEntry)

					if conflicts:
						conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts])
						print("[AutoTimer] conflict with %s detected" % (conflictString))

					if conflicts and config.plugins.autotimer.addsimilar_on_conflict.value:
						# We start our search right after our actual index
						# Attention we have to use a copy of the list, because we have to append the previous older matches
						lepgm = len(epgmatches)
						for i in xrange(lepgm):
							servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ]
							if shortdesc == shortdescS:
								# Some channels indicate replays in the extended descriptions
								# If the similarity percent is higher then 0.8 it is a very close match
								if ( len(extdesc) == len(extdescS) and extdesc == extdescS ) \
									or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescS).ratio() ):
									# Check if the similar is already known
									if eitS not in similar:
										print("[AutoTimer] Found similar Timer: " + name)

										# Store the actual and similar eit and conflictString, so it can be handled later
										newEntry.conflictString = conflictString
										similar[eit] = newEntry
										similar[eitS] = newEntry
										similarTimer = True
										if beginS <= evtBegin:
											# Event is before our actual epgmatch so we have to append it to the epgmatches list
											epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS))
										# If we need a second similar it will be found the next time
										break
									else:
										similarTimer = False
										newEntry = similar[eitS]
										break

					if conflicts is None:
						timer.decrementCounter()
						new += 1
						newEntry.extdesc = extdesc
						recorddict[serviceref].append(newEntry)

						# Similar timers are in new timers list and additionally in similar timers list
						if similarTimer:
							similars.append((name, begin, end, serviceref, timer.name))
							similar.clear()

					# Don't care about similar timers
					elif not similarTimer:
						conflicting.append((name, begin, end, serviceref, timer.name))

						if config.plugins.autotimer.disabled_on_conflict.value:
							newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString))
							newEntry.disabled = True
							# We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
							conflicts = recordHandler.record(newEntry)

		return (total, new, modified, timers, conflicting, similars)

# Supporting functions

	def addDirectoryToMovieDict(self, moviedict, dest, serviceHandler):
		movielist = serviceHandler.list(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + dest))
		if movielist is None:
			print("[AutoTimer] listing of movies in " + dest + " failed")
		else:
			append = moviedict[dest].append
			while 1:
				movieref = movielist.getNext()
				if not movieref.valid():
					break
				if movieref.flags & eServiceReference.mustDescent:
					continue
				info = serviceHandler.info(movieref)
				if info is None:
					continue
				event = info.getEvent(movieref)
				if event is None:
					continue
				append({
					"name": info.getName(movieref),
					"shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription),
					"extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string?
				})

	def checkSimilarity(self, timer, name1, name2, shortdesc1, shortdesc2, extdesc1, extdesc2):
		foundTitle = (name1 == name2)
		foundShort = (shortdesc1 == shortdesc2) if timer.searchForDuplicateDescription > 0 else True
		foundExt = True
		# NOTE: only check extended if short description already is a match because otherwise
		# it won't evaluate to True anyway
		if timer.searchForDuplicateDescription == 2 and foundShort:
			# Some channels indicate replays in the extended descriptions
			# If the similarity percent is higher then 0.8 it is a very close match
			foundExt = ( len(extdesc1) == len(extdesc2) and extdesc1 == extdesc2 ) \
			 or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc1, extdesc2).ratio() )

		return (total, new, modified, timers, conflicting, similars)
    def parseEPG(self, simulateOnly=False):
        if NavigationInstance.instance is None:
            print "[AutoTimer] Navigation is not available, can't parse EPG"
            return (0, 0, 0, [], [])

        total = 0
        new = 0
        modified = 0
        timers = []
        conflicting = []

        self.readXml()

        # Save Recordings in a dict to speed things up a little
        # We include processed timers as we might search for duplicate descriptions
        recorddict = {}
        for timer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers:
            recorddict.setdefault(str(timer.service_ref), []).append(timer)

        # Iterate Timer
        for timer in self.getEnabledTimerList():
            # Workaround to allow search for umlauts if we know the encoding
            match = timer.match
            if timer.encoding != 'UTF-8':
                try:
                    match = match.decode('UTF-8').encode(timer.encoding)
                except UnicodeDecodeError:
                    pass

            # Search EPG, default to empty list
            epgcache = eEPGCache.getInstance()
            ret = epgcache.search(('RI', 500, typeMap[timer.searchType], match,
                                   caseMap[timer.searchCase])) or ()

            for serviceref, eit in ret:
                eserviceref = eServiceReference(serviceref)

                evt = epgcache.lookupEventId(eserviceref, eit)
                if not evt:
                    print "[AutoTimer] Could not create Event!"
                    continue

                # Try to determine real service (we always choose the last one)
                n = evt.getNumOfLinkageServices()
                if n > 0:
                    i = evt.getLinkageService(eserviceref, n - 1)
                    serviceref = i.toString()

                # Gather Information
                name = evt.getEventName()
                description = evt.getShortDescription()
                begin = evt.getBeginTime()
                duration = evt.getDuration()
                end = begin + duration

                # If event starts in less than 60 seconds skip it
                if begin < time() + 60:
                    continue

                # Convert begin time
                timestamp = localtime(begin)

                # Update timer
                timer.update(begin, timestamp)

                # Check Duration, Timespan and Excludes
                if timer.checkServices(serviceref) \
                 or timer.checkDuration(duration) \
                 or timer.checkTimespan(timestamp) \
                 or timer.checkFilter(name, description,
                  evt.getExtendedDescription(), str(timestamp.tm_wday)):
                    continue

                if timer.hasOffset():
                    # Apply custom Offset
                    begin, end = timer.applyOffset(begin, end)
                else:
                    # Apply E2 Offset
                    begin -= config.recording.margin_before.value * 60
                    end += config.recording.margin_after.value * 60

                # Eventually change service to alternative
                if timer.overrideAlternatives:
                    serviceref = timer.getAlternative(serviceref)

                total += 1

                # Append to timerlist and abort if simulating
                timers.append((name, begin, end, serviceref, timer.name))
                if simulateOnly:
                    continue

                # Initialize
                newEntry = None
                oldExists = False

                # Check for double Timers
                # We first check eit and if user wants us to guess event based on time
                # we try this as backup. The allowed diff should be configurable though.
                for rtimer in recorddict.get(serviceref, ()):
                    if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(
                            rtimer, begin, end) > ((duration / 10) * 8):
                        oldExists = True

                        # Abort if we don't want to modify timers or timer is repeated
                        if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated:
                            print "[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer"
                            break

                        if hasattr(rtimer, "isAutoTimer"):
                            print "[AutoTimer] Modifying existing AutoTimer!"
                        else:
                            if config.plugins.autotimer.refresh.value != "all":
                                print "[AutoTimer] Won't modify existing timer because it's no timer set by us"
                                break

                            print "[AutoTimer] Warning, we're messing with a timer which might not have been set by us"

                        newEntry = rtimer
                        modified += 1

                        # Modify values saved in timer
                        newEntry.name = name
                        newEntry.description = description
                        newEntry.begin = int(begin)
                        newEntry.end = int(end)
                        newEntry.service_ref = ServiceReference(serviceref)

                        break
                    elif timer.avoidDuplicateDescription == 1 and not rtimer.disabled and rtimer.name == name and rtimer.description == description:
                        oldExists = True
                        print "[AutoTimer] We found a timer with same description, skipping event"
                        break

                # We found no timer we want to edit
                if newEntry is None:
                    # But there is a match
                    if oldExists:
                        continue

                    # We want to search for possible doubles
                    if timer.avoidDuplicateDescription == 2:
                        # I thinks thats the fastest way to do this, though it's a little ugly
                        try:
                            for list in recorddict.values():
                                for rtimer in list:
                                    if not rtimer.disabled and rtimer.name == name and rtimer.description == description:
                                        raise AutoTimerIgnoreTimerException(
                                            "We found a timer with same description, skipping event"
                                        )
                        except AutoTimerIgnoreTimerException, etite:
                            print etite
                            continue

                    if timer.checkCounter(timestamp):
                        continue

                    print "[AutoTimer] Adding an event."
                    newEntry = RecordTimerEntry(ServiceReference(serviceref),
                                                begin, end, name, description,
                                                eit)

                    # Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
                    newEntry.isAutoTimer = True

                # Apply afterEvent
                if timer.hasAfterEvent():
                    afterEvent = timer.getAfterEventTimespan(localtime(end))
                    if afterEvent is None:
                        afterEvent = timer.getAfterEvent()
                    if afterEvent is not None:
                        newEntry.afterEvent = afterEvent

                newEntry.dirname = timer.destination
                newEntry.justplay = timer.justplay
                newEntry.tags = timer.tags

                if oldExists:
                    # XXX: this won't perform a sanity check, but do we actually want to do so?
                    NavigationInstance.instance.RecordTimer.timeChanged(
                        newEntry)
                else:
                    conflicts = NavigationInstance.instance.RecordTimer.record(
                        newEntry)
                    if conflicts and config.plugins.autotimer.disabled_on_conflict.value:
                        newEntry.disabled = True
                        # We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
                        conflicts = NavigationInstance.instance.RecordTimer.record(
                            newEntry)
                        conflicting.append(
                            (name, begin, end, serviceref, timer.name))
                    if conflicts is None:
                        timer.decrementCounter()
                        new += 1
                        recorddict.setdefault(serviceref, []).append(newEntry)
                    else:
                        conflicting.append(
                            (name, begin, end, serviceref, timer.name))
	def parseEPG(self, simulateOnly = False):
		if NavigationInstance.instance is None:
			print "[AutoTimer] Navigation is not available, can't parse EPG"
			return (0, 0, 0, [], [])

		total = 0
		new = 0
		modified = 0
		timers = []
		conflicting = []

		# NOTE: the config option specifies "the next X days" which means today (== 1) + X
		delta = timedelta(days = config.plugins.autotimer.maxdaysinfuture.value + 1)
		evtLimit = mktime((date.today() + delta).timetuple())
		checkEvtLimit = delta.days > 1
		del delta

		self.readXml()

		# Save Recordings in a dict to speed things up a little
		# We include processed timers as we might search for duplicate descriptions
		recorddict = {}
		for rtimer in NavigationInstance.instance.RecordTimer.timer_list + NavigationInstance.instance.RecordTimer.processed_timers:
			if not rtimer.disabled:
				recorddict.setdefault(str(rtimer.service_ref), []).append(rtimer)

		# Iterate Timer
		for timer in self.getEnabledTimerList():
			# Precompute timer destination dir
			dest = timer.destination or config.usage.default_path.value

			# Workaround to allow search for umlauts if we know the encoding
			match = timer.match
			if timer.encoding != 'UTF-8':
				try:
					match = match.decode('UTF-8').encode(timer.encoding)
				except UnicodeDecodeError:
					pass

			# Search EPG, default to empty list
			ret = self.epgcache.search(('RI', 500, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or ()

			for serviceref, eit in ret:
				eserviceref = eServiceReference(serviceref)

				evt = self.epgcache.lookupEventId(eserviceref, eit)
				if not evt:
					print "[AutoTimer] Could not create Event!"
					continue

				# Try to determine real service (we always choose the last one)
				n = evt.getNumOfLinkageServices()
				if n > 0:
					i = evt.getLinkageService(eserviceref, n-1)
					serviceref = i.toString()

				# Gather Information
				evtInfo = self.normalizeEvent(evt)
				evtBegin = begin = evt.getBeginTime()
				duration = evt.getDuration()
				evtEnd = end = begin + duration

				# If event starts in less than 60 seconds skip it
				if begin < time() + 60:
					print "[AutoTimer] Skipping an event because it starts in less than 60 seconds"
					continue

				# If maximum days in future is set then check time
				if checkEvtLimit:
					if begin > evtLimit:
						continue

				# Convert begin time
				timestamp = localtime(begin)

				# Update timer
				timer.update(begin, timestamp)

				# Check Duration, Timespan, Timeframe and Excludes
				if timer.checkServices(serviceref) \
					or timer.checkDuration(duration) \
					or timer.checkTimespan(timestamp) \
					or timer.checkTimeframe(begin) \
					or timer.checkFilter(
							evtInfo.name,
							evtInfo.shortDescription,
							evtInfo.extendedDescription,
							str(timestamp.tm_wday)
						):
					continue

				if timer.hasOffset():
					# Apply custom Offset
					begin, end = timer.applyOffset(begin, end)
				else:
					# Apply E2 Offset
					begin -= config.recording.margin_before.value * 60
					end += config.recording.margin_after.value * 60

				# Eventually change service to alternative
				if timer.overrideAlternatives:
					serviceref = timer.getAlternative(serviceref)

				total += 1

				# Append to timerlist and abort if simulating
				timers.append((evtInfo.name, begin, end, serviceref, timer.name))
				if simulateOnly:
					continue


                # Check for existing recordings in directory
				if timer.avoidDuplicateDescription == 3 and self.checkMovies(evtInfo, dest):
					continue

				# Initialize
				newEntry = None
				oldExists = False

				# Check for double Timers
				# We first check eit and if user wants us to guess event based on time
				# we try this as backup. The allowed diff should be configurable though.
				for rtimer in recorddict.get(serviceref, ()):
					if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, begin, end) > ((duration/10)*8):
						oldExists = True

						# Abort if we don't want to modify timers or timer is repeated
						if config.plugins.autotimer.refresh.value == "none":
							print "[AutoTimer] Won't modify existing timer because no modification allowed"
							break
						if rtimer.repeated:
							print "[AutoTimer] Won't modify existing timer because repeated timer"
							break

						if hasattr(rtimer, "isAutoTimer"):
								rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name,))
						else:
							if config.plugins.autotimer.refresh.value != "all":
								print "[AutoTimer] Won't modify existing timer because it's no timer set by us"
								break

							rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it." % (timer.name,))

						newEntry = rtimer
						modified += 1

						# Modify values saved in timer
						newEntry.name = evtInfo.name
						newEntry.description = evtInfo.shortDescription
						newEntry.begin = int(begin)
						newEntry.end = int(end)
						newEntry.service_ref = ServiceReference(serviceref)

						break
					elif timer.avoidDuplicateDescription >= 1 and self.normalizeRecordTimer(rtimer) == evtInfo:
						oldExists = True
						print "[AutoTimer] We found a timer with same service and description, skipping event"
						break

				# We found no timer we want to edit
				if newEntry is None:
					# But there is a match
					if oldExists:
						continue

					# We want to search for possible doubles
					if timer.avoidDuplicateDescription >= 2 and self.isEventInList(evtInfo, recorddict, self.normalizeRecordTimer):
						print "[AutoTimer] We found a timer with same description, skipping event"
						continue

					if timer.checkCounter(timestamp):
						print "[AutoTimer] Not adding new timer because counter is depleted."
						continue

					newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, evtInfo.name, evtInfo.shortDescription, eit)
					newEntry.log(500, "[AutoTimer] Adding new timer based on AutoTimer %s." % (timer.name,))

					# Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
					newEntry.isAutoTimer = True

				# Apply afterEvent
				if timer.hasAfterEvent():
					afterEvent = timer.getAfterEventTimespan(localtime(end))
					if afterEvent is None:
						afterEvent = timer.getAfterEvent()
					if afterEvent is not None:
						newEntry.afterEvent = afterEvent

				newEntry.dirname = timer.destination
				newEntry.justplay = timer.justplay
				newEntry.tags = timer.tags
				newEntry.vpsplugin_enabled = timer.vps_enabled
				newEntry.vpsplugin_overwrite = timer.vps_overwrite

				if oldExists:
					# XXX: this won't perform a sanity check, but do we actually want to do so?
					NavigationInstance.instance.RecordTimer.timeChanged(newEntry)
				else:
					conflicts = NavigationInstance.instance.RecordTimer.record(newEntry)
					if conflicts and config.plugins.autotimer.disabled_on_conflict.value:
						conflictString = ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts])
						newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString,))
						del conflictString
						newEntry.disabled = True
						# We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
						conflicts = NavigationInstance.instance.RecordTimer.record(newEntry)
						conflicting.append((evtInfo.name, begin, end, serviceref, timer.name))
					if conflicts is None:
						timer.decrementCounter()
						new += 1
						recorddict.setdefault(serviceref, []).append(newEntry)
					else:
						conflicting.append((evtInfo.name, begin, end, serviceref, timer.name))

		return (total, new, modified, timers, conflicting)
Exemple #9
0
	def parseEPG(self, simulateOnly = False):
		if NavigationInstance.instance is None:
			print("[AutoTimer] Navigation is not available, can't parse EPG")
			return (0, 0, 0, [], [], [])

		total = 0
		new = 0
		modified = 0
		timers = []
		conflicting = []
		similar = defaultdict(list)			# Contains the the marked similar eits and the conflicting strings
		similars = []										# Contains the added similar timers

		# NOTE: the config option specifies "the next X days" which means today (== 1) + X
		delta = timedelta(days = config.plugins.autotimer.maxdaysinfuture.value + 1)
		evtLimit = mktime((date.today() + delta).timetuple())
		checkEvtLimit = delta.days > 1
		del delta

		# Read AutoTimer configuration
		self.readXml()

		# Get E2 instances
		epgcache = eEPGCache.getInstance()
		serviceHandler = eServiceCenter.getInstance()
		recordHandler = NavigationInstance.instance.RecordTimer

		# Save Recordings in a dict to speed things up a little
		# We include processed timers as we might search for duplicate descriptions
		# The recordict is always filled
		#Question: It might be better to name it timerdict
		#Question: Move to a separate function getTimerDict()
		#Note: It is also possible to use RecordTimer isInTimer(), but we won't get the timer itself on a match
		recorddict = defaultdict(list)
		for timer in chain(recordHandler.timer_list, recordHandler.processed_timers):
			if timer and timer.service_ref:
				if timer.eit is not None:
					event = epgcache.lookupEventId(timer.service_ref.ref, timer.eit)
					extdesc = event and event.getExtendedDescription() or ''
					timer.extdesc = extdesc
				elif not hasattr(timer, 'extdesc'):
					timer.extdesc = ''
				recorddict[str(timer.service_ref)].append(timer)

		# Create dict of all movies in all folders used by an autotimer to compare with recordings
		# The moviedict will be filled only if one AutoTimer is configured to avoid duplicate description for any recordings
		#Question: It might be better to name it recorddict
		moviedict = defaultdict(list)

		# Iterate Timer
		for timer in self.getEnabledTimerList():
			# Precompute timer destination dir
			dest = timer.destination or config.usage.default_path.value

			# Workaround to allow search for umlauts if we know the encoding
			match = timer.match
			if timer.encoding != 'UTF-8':
				try:
					match = match.decode('UTF-8').encode(timer.encoding)
				except UnicodeDecodeError:
					pass

			# Search EPG, default to empty list
			epgmatches = epgcache.search(('RITBDSE', 1000, typeMap[timer.searchType], match, caseMap[timer.searchCase])) or []
			# Sort list of tuples by begin time 'B'
			epgmatches.sort(key=itemgetter(3))

			# Reset the the marked similar servicerefs
			similar.clear()

			# Loop over all EPG matches
			for idx, ( serviceref, eit, name, begin, duration, shortdesc, extdesc ) in enumerate( epgmatches ):

				#Question: Do we need this?
				#Question: Move to separate function getRealService()
				eserviceref = eServiceReference(serviceref)
				evt = epgcache.lookupEventId(eserviceref, eit)
				if not evt:
					print("[AutoTimer] Could not create Event!")
					continue
				# Try to determine real service (we always choose the last one)
				n = evt.getNumOfLinkageServices()
				if n > 0:
					i = evt.getLinkageService(eserviceref, n-1)
					serviceref = i.toString()

				evtBegin = begin
				evtEnd = end = begin + duration

				# If event starts in less than 60 seconds skip it
				if begin < time() + 60:
					print("[AutoTimer] Skipping an event because it starts in less than 60 seconds")
					continue

				# Convert begin time
				timestamp = localtime(begin)
				# Update timer
				timer.update(begin, timestamp)

				# Check if eit is in similar matches list
				# NOTE: ignore evtLimit for similar timers as I feel this makes the feature unintuitive
				similarTimer = False
				if eit in similar:
					similarTimer = True
					dayofweek = None # NOTE: ignore day on similar timer
				else:
					# If maximum days in future is set then check time
					if checkEvtLimit:
						if begin > evtLimit:
							continue

					dayofweek = str(timestamp.tm_wday)

				# Check timer conditions
				# NOTE: similar matches to not care about the day/time they are on, so ignore them
				if timer.checkServices(serviceref) \
					or timer.checkDuration(duration) \
					or (not similarTimer and (\
						timer.checkTimespan(timestamp) \
						or timer.checkTimeframe(begin) \
					)) or timer.checkFilter(name, shortdesc, extdesc, dayofweek):
					continue

				if timer.hasOffset():
					# Apply custom Offset
					begin, end = timer.applyOffset(begin, end)
				else:
					# Apply E2 Offset
					begin -= config.recording.margin_before.value * 60
					end += config.recording.margin_after.value * 60

				# Eventually change service to alternative
				if timer.overrideAlternatives:
					serviceref = timer.getAlternative(serviceref)

				total += 1

				# Append to timerlist and abort if simulating
				timers.append((name, begin, end, serviceref, timer.name))
				if simulateOnly:
					continue

				# Check for existing recordings in directory
				if timer.avoidDuplicateDescription == 3:
					# Reset movie Exists
					movieExists = False

					# Eventually create cache
					if dest and dest not in moviedict:
						#Question: Move to a separate function getRecordDict()
						movielist = serviceHandler.list(eServiceReference("2:0:1:0:0:0:0:0:0:0:" + dest))
						if movielist is None:
							print("[AutoTimer] listing of movies in " + dest + " failed")
						else:
							append = moviedict[dest].append
							while 1:
								movieref = movielist.getNext()
								if not movieref.valid():
									break
								if movieref.flags & eServiceReference.mustDescent:
									continue
								info = serviceHandler.info(movieref)
								if info is None:
									continue
								event = info.getEvent(movieref)
								if event is None:
									continue
								append({
									"name": info.getName(movieref),
									"shortdesc": info.getInfoString(movieref, iServiceInformation.sDescription),
									"extdesc": event.getExtendedDescription() or '' # XXX: does event.getExtendedDescription() actually return None on no description or an empty string?
								})
							del append

					for movieinfo in moviedict.get(dest, ()):
						if movieinfo.get("name") == name \
							and movieinfo.get("shortdesc") == shortdesc:
							# Some channels indicate replays in the extended descriptions
							# If the similarity percent is higher then 0.8 it is a very close match
							extdescM = movieinfo.get("extdesc")
							if ( len(extdesc) == len(extdescM) and extdesc == extdescM ) \
								or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescM).ratio() ):
								print("[AutoTimer] We found a matching recorded movie, skipping event:", name)
								movieExists = True
								break

					if movieExists:
						continue

				# Initialize
				newEntry = None
				oldExists = False

				# Check for double Timers
				# We first check eit and if user wants us to guess event based on time
				# we try this as backup. The allowed diff should be configurable though.
				for rtimer in recorddict.get(serviceref, ()):
					if rtimer.eit == eit or config.plugins.autotimer.try_guessing.value and getTimeDiff(rtimer, evtBegin, evtEnd) > ((duration/10)*8):
						oldExists = True

						# Abort if we don't want to modify timers or timer is repeated
						if config.plugins.autotimer.refresh.value == "none" or rtimer.repeated:
							print("[AutoTimer] Won't modify existing timer because either no modification allowed or repeated timer")
							break

						if hasattr(rtimer, "isAutoTimer"):
							rtimer.log(501, "[AutoTimer] AutoTimer %s modified this automatically generated timer." % (timer.name))
						else:
							if config.plugins.autotimer.refresh.value != "all":
								print("[AutoTimer] Won't modify existing timer because it's no timer set by us")
								break

							rtimer.log(501, "[AutoTimer] Warning, AutoTimer %s messed with a timer which might not belong to it." % (timer.name))

						newEntry = rtimer
						modified += 1

						# Modify values saved in timer
						newEntry.name = name
						newEntry.description = shortdesc
						newEntry.begin = int(begin)
						newEntry.end = int(end)
						newEntry.service_ref = ServiceReference(serviceref)

						break
					elif timer.avoidDuplicateDescription >= 1 \
						and not rtimer.disabled \
						and rtimer.name == name \
						and rtimer.description == shortdesc:
							# Some channels indicate replays in the extended descriptions
							# If the similarity percent is higher then 0.8 it is a very close match
							if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \
								or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ):
								oldExists = True
								print("[AutoTimer] We found a timer (similar service) with same description, skipping event")
								break

				# We found no timer we want to edit
				if newEntry is None:
					# But there is a match
					if oldExists:
						continue

					# We want to search for possible doubles
					if timer.avoidDuplicateDescription >= 2:
						for rtimer in chain.from_iterable( itervalues(recorddict) ):
							if not rtimer.disabled \
								and rtimer.name == name \
								and rtimer.description == shortdesc:
									# Some channels indicate replays in the extended descriptions
									# If the similarity percent is higher then 0.8 it is a very close match
									if ( len(extdesc) == len(rtimer.extdesc) and extdesc == rtimer.extdesc ) \
										or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, rtimer.extdesc).ratio() ):
										oldExists = True
										print("[AutoTimer] We found a timer (any service) with same description, skipping event")
										break
						if oldExists:
							continue

					if timer.checkCounter(timestamp):
						print("[AutoTimer] Not adding new timer because counter is depleted.")
						continue

					newEntry = RecordTimerEntry(ServiceReference(serviceref), begin, end, name, shortdesc, eit)
					newEntry.log(500, "[AutoTimer] Try to add new timer based on AutoTimer %s." % (timer.name))

					# Mark this entry as AutoTimer (only AutoTimers will have this Attribute set)
					# It is only temporarily, after a restart it will be lost,
					# because it won't be stored in the timer xml file
					newEntry.isAutoTimer = True

				# Apply afterEvent
				if timer.hasAfterEvent():
					afterEvent = timer.getAfterEventTimespan(localtime(end))
					if afterEvent is None:
						afterEvent = timer.getAfterEvent()
					if afterEvent is not None:
						newEntry.afterEvent = afterEvent

				newEntry.dirname = timer.destination
				newEntry.justplay = timer.justplay
				newEntry.tags = timer.tags
				newEntry.vpsplugin_enabled = timer.vps_enabled
				newEntry.vpsplugin_overwrite = timer.vps_overwrite

				if oldExists:
					# XXX: this won't perform a sanity check, but do we actually want to do so?
					recordHandler.timeChanged(newEntry)
				else:
					conflictString = ""
					if similarTimer:
						conflictString = similar[eit].conflictString
						newEntry.log(504, "[AutoTimer] Try to add similar Timer because of conflicts with %s." % (conflictString))

					# Try to add timer
					conflicts = recordHandler.record(newEntry)

					if conflicts:
						conflictString += ' / '.join(["%s (%s)" % (x.name, strftime("%Y%m%d %H%M", localtime(x.begin))) for x in conflicts])
						print("[AutoTimer] conflict with %s detected" % (conflictString))

					if conflicts and config.plugins.autotimer.addsimilar_on_conflict.value:
						# We start our search right after our actual index
						# Attention we have to use a copy of the list, because we have to append the previous older matches
						lepgm = len(epgmatches)
						for i in xrange(lepgm):
							servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS = epgmatches[ (i+idx+1)%lepgm ]
							if shortdesc == shortdescS:
								# Some channels indicate replays in the extended descriptions
								# If the similarity percent is higher then 0.8 it is a very close match
								if ( len(extdesc) == len(extdescS) and extdesc == extdescS ) \
									or ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc, extdescS).ratio() ):
									# Check if the similar is already known
									if eitS not in similar:
										print("[AutoTimer] Found similar Timer: " + name)

										# Store the actual and similar eit and conflictString, so it can be handled later
										newEntry.conflictString = conflictString
										similar[eit] = newEntry
										similar[eitS] = newEntry
										similarTimer = True
										if beginS <= evtBegin:
											# Event is before our actual epgmatch so we have to append it to the epgmatches list
											epgmatches.append((servicerefS, eitS, nameS, beginS, durationS, shortdescS, extdescS))
										# If we need a second similar it will be found the next time
										break
									else:
										similarTimer = False
										newEntry = similar[eitS]
										break

					if conflicts is None:
						timer.decrementCounter()
						new += 1
						newEntry.extdesc = extdesc
						recorddict[serviceref].append(newEntry)

						# Similar timers are in new timers list and additionally in similar timers list
						if similarTimer:
							similars.append((name, begin, end, serviceref, timer.name))
							similar.clear()

					# Don't care about similar timers
					elif not similarTimer:
						conflicting.append((name, begin, end, serviceref, timer.name))

						if config.plugins.autotimer.disabled_on_conflict.value:
							newEntry.log(503, "[AutoTimer] Timer disabled because of conflicts with %s." % (conflictString))
							newEntry.disabled = True
							# We might want to do the sanity check locally so we don't run it twice - but I consider this workaround a hack anyway
							conflicts = recordHandler.record(newEntry)

		return (total, new, modified, timers, conflicting, similars)