#!/bin/sh

# pr-auto-timer - automatic TV timer creation for neutrino

# Copyright (C) 2012-2013  Patrick Reinhardt, pr-cs <at> reinhardtweb.de
#               2013-2014  nadine
#                    2014  LtCmdrLuke
#               2013-2020  NI-Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA

# Related files:
#	Configuration:		/var/tuxbox/config/pr-auto-timer.conf
#	Timer rules:		/var/tuxbox/config/pr-auto-timer.rules
#	Neutrino plugin:	/usr/share/tuxbox/neutrino/plugins/pr-auto-timer.{sh,cfg}

VERSION=0.44

# Changelog:
#
# 0.44 by tewsbo (NI-Team)
#	-fix parsing moviepilot website
#
# 0.43 by tewsbo (NI-Team)
#	-fix displaylog output
#
# 0.42 by tewsbo (NI-Team)
#	-align to new sky channels
#	-more sky exclude channels
#	-fix parsing moviepilot website
#
# 0.41b by tewsbo (NI-Team)
#	-more sky exclude channels
#
# 0.41a by vanhofen (NI-Team)
#	-fix "minor format changes in logging" from previous version
#
# 0.41 by vanhofen (NI-Team)
#	-fix search-code in find_show_by_regex()
#	-minor format changes in logging
#	-fix new movies on Sky Cinema; align to new format on website again
#	-fix another arithmetic syntax error
#
# 0.40b by tewsbo (NI-Team)
#	-add Sky Stars to sky_exclude_channels
#	-fix arithmetic syntax error
#
# 0.40a by tewsbo (NI-Team)
#	-fix msgbox-calls for larger OSD-resolutions
#
# 0.40 by vanhofen (NI-Team)
#	-whitespace cleanup
#	-reformatting awk-blocks (just for my readability)
#	-change some german words in code
#	-search-code in find_show_by_regex() fundamentally revised
#	-find_show_by_start_sec() now returns same format as find_show_by_regex()
#
# 0.39 by vanhofen (NI-Team)
#	-allow to search for every show; just filter by include/exclude expressions
#	 To search for every show that contains "Mikrokosmos" in EPG-data use:
#		Das Erste HD;*;*,+Mikrokosmos;R
#
# 0.38b by tewsbo (NI-Team)
#	-find correct Sky Cinema channel
#	 add exclude channels: Action Comedy Emotion Family Hits Nostalgie
#
# 0.38a by tewsbo (NI-Team)
#	-Bugfix: New movies on Sky Cinema; align to new format on website again
#
# 0.38 by tewsbo (NI-Team)
#	-Bugfix: New movies on Sky Cinema; align to new format on website
#
# 0.37b by vanhofen (NI-Team)
#	-align to new controlapi calls
#
# 0.37a by vanhofen (NI-Team)
#	-bugfix for: find correct Sky Cinema channel
#
# 0.37 by vanhofen (NI-Team)
#	-find correct Sky Cinema channel
#	-add more Sky Cinema channels
#		(in conf-file: SKY_CINEMA="SD|HD|1|1HD|24|24HD[;DOW[,TIMESPAN]][;RECDIR]")
#
# 0.36a by nadine
#	-Fixed IIClude Filter One Word (~)
#
# 0.36 by LtCmdrLuke
#	-Bugfix: Empty timestamp in some rare cases causes and error on the console (just cosmetic)
#	-Bugfix: Fixed log message in dry-run mode about amount of broken records (just cosmetic)
#	-Bugfix: Detecting the EXTRA_TIME Before recording is broken in 2.7 (maybe also in 2.6?) due to the introduction of a new value
#	 ZAPTO_EXTRA_TIME in timerd.conf. This is fixed now.
#
# 0.35 by LtCmdrLuke
#	-Bugfix: The path in $ME.show_history is now correctly updated when the index is re-generated and old files have been moved
#	-Bugfix/Feature: Should now also work when box is in radio mode. When starting, pr-auto-timer switches the box to tv and afterwards back.
#	-Bugfix/workaround: Added a simple function to convert the most common html-entities occurring in titles (&quot; &amp; and &apos;).
#	 This function uses only shell replacement and is kind of inefficient. Furthermore for full conversion we would need 'recode' or even
#	 better, neutrino[HD] should be consistent when delivering data in EPG and record-xml.
#	-New feature: Added possibility to execute arbitrary pre- and post-actions. This feature can be activated by giving arbitrary commands
#	 in the new settings in $ME.conf: PRE_ACTION and POST_ACTION. "PRE_ACTION" is executed after initializations, but before processing
#	 any rules and "POST_ACTION" is executed after processing all rules, before exiting. Updated also $ME.conf.template to reflect changes.
#
# 0.34 by LtCmdrLuke
#	-Added command line option "-p|--post-action [x]" to override any configured post-actions with the optional argument x.
#	 This is helpful, when you configured the box to perform e.g. a reboot after running pr-auto-timer and you need it to
#	 avoid doing exactly that when you're testing the script.
#	-Using command line option -d|--dry-run also prevents any post-action
#	-New option BROKEN_RECORD_HANDLING which controls the behavior when detecting broken records while creating the history index;
#	 see $ME.conf.template for full explanation and possible settings
#	-Bugfix when cleaning the history file (if there where more events starting in exactly the same second, only one of them survived the cleaning)
#	-Automatically detect and upgrade .show_history prior to 0.33 - no need for any manual intervention.
#	-Restructured code, i.e. grouped functions into sections - code is getting bigger and therefore harder to find things. Sections help..
#
# 0.33 by LtCmdrLuke
#	-Added possibility to limit the size of $ME.show_history by setting MAX_HISTORY_ENTRIES in the conf file. Default is 1000.
#	-Due to the above change, the format of $ME.show_history has changed, there it is necessary to delete the $ME.show_history and let
#	 pr-auto-timer generate a new one from your files on the disk. If you have manual entries, they must be added again in the new file.
#	 Use '1500000000' for unknown timestamps (somewhere in the future) and 'x' for unknown filenames.
#	-Updated pr-auto-timer.conf.template to reflect this change
#	-Improved log-readability by printing human-readable time strings in the most important messages.
#
# 0.32 by LtCmdrLuke
#	-Added general handling logic for command-line options, call pr-auto-timer -h for an overview
#	-Major changes in the logic for duplicate prevention
#	 * The additional script gen_show_history is no longer needed - the functionality is fully integrated in pr-auto-timer
#	 * The gen_show_history.sh-functionality is now accessible with the following command line options:
#	   * -g|--gen-show-history   Generate and print the index from your previous recordings
#	   * -t|--print-timer-index  Generate and print the future timer index.
#	   * -d|--dry-run            Perform a dry-run, i.e. do not add/remove any timers. (provide first!)
#	 * New config settings (updated also $ME.conf.template to reflect changes)
#	   * AUTOGEN_SHOW_HISTORY=1 - automatically searches for previously recorded files, when the D-flag is used
#	   * MYRECORDPATHS - a semicolon separated list of paths to search for previously recorded files
#	 * Future timers and already existing records are separated now: When using the D-flag and a new timer is added, it is also added to a
#	   internal future timer index. Therefore manually deleted timers or failed recordings will no longer be detected as duplicates. This
#	   feature works by reading all timers from the box upon first usage of the D-flag. For each timer, than the EPg is searched for the
#	   necessary data about the show to compare with new timers.
#	-Bugfix: Now the entire info2 is searched for a given regular expression - until now lines between newlines not starting or ending
#	 with info2 have been omitted
#	-Minor
#	 * Added some quotation to protect special characters from shell in some special situations
#	 * Re-ordered some code, for better readability and logical reasons
#
# 0.31 by LtCmdrLuke
#	-Improved handling of .show_history for duplicate detection:
#	  * show are stored in $ME.show_history if and only if the D-flag is present
#	  * removed the N-flag, since the above change makes it obsolete
#	  * This feature is still not final; handling the size of .show_history and automatic integration of already recorded stuff (i.e. obsoleting gen_show_history) is still missing
#	-Bugfix: Added check if $HISTORY_FILE exists before trying to read it.
#	-Improved duplicate detection (hopefully) as follows:
#	  Until now, only the EPG-title and info1 was used to detect the same show again. However, some channels do not set info1 to anything meaningful.
#	  In this case, pr-auto-timer now uses the first 30 characters (INFO2_CHARACTERS) of info2 to differentiate between shows. Obviously this fails, if the channel EPG
#	  is changed by the provider for the repetition.
#	-Minor improvement of the log-readability
#	-Updated the .rules.template files to reflect the newly added flags since 0.30 (W & M)
#	-Minor cleanup:
#	  * Renamed ignore_empty_info --> allow_empty_info (reflects better its purpose)
#	  * Rewrote parse_flags in a cleaner way (now allows for a log message if unknown flag was used)
#
# 0.30 by nadine
#	-Maximale Anzahl an parallelen Aufnahmen parametrierbar zwischen 1 bis 8. Standard:	MAX_RECORDS=8
#	-History wird bei leerer Kurzbeschreibung (info1) ignoriert. Abschaltbar für z.B. Filme durch Flag 'M'
#	-Neues Flag 'N' hinzugefügt. Ist dieses angegeben, werden Duplicate und Flag 'D' ignoriert. Die Aufnahme kommt dann auch nicht ins History-File
#	-Neues Flag 'W' hinzugefügt. Da werden ungeachtet der Einstellung von DEL_TIMER_REFRESH Timer wiederholt hinzugefügt.
#
# 0.29 by LtCmdrLuke
#	-Extended check for max tuners with transponder information, i.e. now max tuners allows currently for an unlimited number
#	 of recordings, as long as only at most MAX_TUNERS transponders are needed. The number of concurrent recordings could also be limited
#	 in future versions - the necessary logic for counting the concurrent recordings is now implicitly available.
#	 Before adding a timer, pr-auto-timer calculates the needed number of transponders and also the max number of concurrent recordings
#	 as if the new timer would have been added. Check the logfile to see if timers got rejected because there aren't enough free tuners.
#	-Bugfix: define another "ret" (in prevent_timer_by_timespan) as local to prevent side-effects (same effect as the bug wich was fixed in 027)
#	-Bugfix: Quotation added to the log-message in parse_dowgroup, to prevent ugly log message
#	-Commented unnecessary log-message in parse_dowgroup
#	-Added a function to get the transponder-id (tsid) from the service id (sid)
#
# 0.28 by nadine
#	-Möglichkeit für die Angabe mehrerer Wochentage
#	 Um die Tagesschau nur Montags, Mittwochs und Freitags um 20:00 Uhr aufzuzeichnen
#		Das Erste HD;MonWedFri,20:00-21:00;Tagesschau
#
# 0.27 by LtCmdrLuke
#	-added check if there are enough tuners available before setting a new timer. The number of available tuners can be set
#	 in the pr-auto-timer.conf with the settings MAX_TUNERS. It defaults to 2.
#	 If you set this value to a 1 lower, than actually available, your timer recordings will never interfere with your normal
#	 TV watching. Obviously, if there are to few tuners available, or too many shows at the same time, those cannot be recorded.
#	 However, if the show is repeated, auto-timer should find the repetition and record it then.
#	-code cleanup:
#		-renamed some functions to better reflect their purpose for better readability):
#			* show_already_recorded --> find_already_recorded
#			* add_timer2 --> add_historic_timer
#			* find_timer2 --> find_historic_timer
#			* find_timer --> find_neutrino_timer
#		-declared some function variables local to prevent nasty side-effects (all shell variables are global unless explicitly
#		 defined local) - this fixes a small bug which lead to adding the show to the show_history, even though the timer was not
#		 added because it was previously deleted
#		-clarified some log messages
#
# 0.26 by LtCmdrLuke
#	-prevent recording of repetitions by adding a log of shows which are already covered by timers. The log is located unter
#	 /var/tuxbox/config/pr-auto-timer.show_history. Currently the description (epg-title) and info1 (usually the episode title
#	 for tv shows) is used to detect already recorded stuff. Each line of this files works as a ALL word exclude rule.
#	-prevent recording can be activated with the flag D:
#		NatGeo HD;*;Brain Games;D
#		                        ^
#		                        |
#		                        D=prevent Duplicates
#
# 0.25 by vanhofen (NI-Team)
#	-get TV channels in radio mode
#	-initialize DEL_TIMER_REFRESH=2 (off)
#	-Add possibility to ignore EXTRA_TIME_START|END
#		Das Erste HD;*;Tagesschau;I
#		                          ^
#		                          |
#		                          I=Ignore extra time additions.
#
# 0.24 by vanhofen (NI-Team)
#	-change 'date' calls to align to older busybox-versions in originalimage
#	-add helper function to simulate the nonexisting dos2unix in originalimage
#
# 0.23 by nadine
#	-Shutdown hinzugefügt
#		Wenn EPGscan nicht gefunden wird, gibt es folgende Optionen:
#		END_SHUT_DOWN:[0]-lässt die Box an;1-fährt in Standby;2-fährt in DeepStandBy;3-fährt in PowerOff;4-startet neu
#		SHUT_DOWN_ALSO_DAY:[0] - führt SHUT_DOWN nur zwischen 00:00 und 06:00 Uhr durch; 1 - führt SHUT_DOWN immer durch
#
# 0.22 by nadine
#	-add_timer2 prüft nun, ob der Eintrag schon besteht.
#	-Exclude ergänzt auf JEDES Wort. Wenn eines dieser Wörter gefunden wird, wird die Sendung nicht aufgezeichnet.
#	 Für Serien, dessen Folgen nur einen kurzen Titel haben. So kann man vorhandene nacheinander angeben.
#		Romance TV;*,04:00-18:15;Traumschiff,-Oman Bali Malaysia Vegas Savannah
#	-Include ergänzt auf JEDES Wort. Wenn eines dieser Wörter gefunden wird, wird die Sendung aufgezeichnet.
#	 Für Serien, dessen Folgen nur einen kurzen Titel haben. So kann man vorhandene nacheinander angeben.
#		Romance TV;*,04:00-18:15;Traumschiff,~Oman Bali Malaysia Vegas Savannah
#	-Weitere Paramater in der Config Datei werden ausgewertet (Für Funktion aus V0.21 (gelöschte Timer werden nicht mehr gesetzt)
#		MAX_TMR2_COUNT=250 # Behält max. 250 Einträge in der Datei /var/tuxbox/config/pr-auto-timer.tmr beim aufräumen (Standard:250)
#		DEL_TIMER_REFRESH=2 #Schaltet die Funktion ganz ab. (Standard:0)
#		DEL_TIMER_REFRESH=1 #Führt im Hintergrund die Datei weiter, um nach wiedereinschalten sofort arbeiten zu können. (Standard:0)
#	-Include Filter werden ignoriert, wenn keine Beschreibung gefunden wurde
#
# 0.21 by nadine
#	-Groß- Kleinschreibung bei Sendungstitel egal
#	-Include Wörter ermöglicht. Diese können ANSTATT der Exclude Wörter genutzt werden. Statt des ! kommt ein +
#		Das Erste HD;*;Tagesschau,+include
#	-einmal gesetzte Timer werden nicht nochmal gesetzt, auch wenn diese zwischenzeitlich manuell gelöscht wurden
#	 sollen alle wieder gesetzt werden, dann die Datei /var/tuxbox/config/pr-auto-timer.tmr löschen
#	 Einträge, dessen Stopzeit vor der aktuellen Zeit liegen, werden automatisch wieder entfernt, um die Dateigröße
#	 nicht unnötig ansteigen zu lassen
#
# 0.20 by vanhofen (NI-Team)
#	-new movies at "Sky Cinema" can be recorded at spec. days/times
#		(in conf-file: SKY_CINEMA="HD|SD|1|24[;DOW[,TIMESPAN]][;RECDIR]")
#		RECDIR is optional; default is the neutrino setting
#
# 0.19 by vanhofen (NI-Team)
#	-fix function to trim strings
#	-fix function to get channels from bouquet
#
# 0.18 by vanhofen (NI-Team)
#	-move pr-auto-timer from /var/plugins to /share/tuxbox/neutrino/plugins
#	-show pr-auto-timer as a script plugin in neutrino
#	-reformatting awk-blocks (just for my readability)
#	-remove variables to binarys
#	-search for shows and informations word by word
#	-ignore case in exclusion words
#	-search for exclusion words in detailed informations too
#	-add possibility to record all new movies at "Sky Cinema"
#	-support for external rules-file
#		(in conf-file: RULE_FILE_EXT=/path/to/file)
#
# 0.17 by vanhofen (NI-Team)
#	-accept day groups "Weekday" and "Weekend"
#	-small code optimizations
#	-accept uppercase flags only
#	-Add possible exlusion expressions
#		FOX HD;*;LOST,!Special
#
#	-Support for alternative recording directorys
#		NOTE: flag-section can be empty, but MUST be defined!
#		Sky Cinema HD;*;Ice Age;;/mnt/rec
#
#	-Add possibility to create only the timer for the first show
#		Das Erste HD;*;Tagesschau;F
#		                          ^
#		                          |
#		                          F=Create first timer only.
#
#	-Add possibility to deactivate an entry in rules-file on success (Danke, Patrick!)
#		Sky Cinema HD;*;Ice Age;O
#		                        ^
#		                        |
#		                        O=Create first timer only and deactivate rule
#
#		NOTE: Define multiple flags by separating them with commas
#		Sky Cinema HD;*;Ice Age;O,Z
#
# 0.16 by vanhofen (NI-Team)
#	-Whitespace cleanup
#	-Ignore case at search for channel-name and timers
#	-Add timespan-support
#
# 0.15 by Patrick Reinhardt
#	-Documentation
#	-Invalid channel names (including those with wrong upper/lower case) now produce a warning message in the log. (Danke, nadine)
#	-Added debugging option DEBUG_DISPLAYLOG
#	-License is now GPLv2
#
# 0.14 by Patrick Reinhardt
#	-License is now GPL
#	-Documentation
#	-Code cleanup & optimizations
#
# 0.13 by Patrick Reinhardt
#	-Updated debugging options
#	-Code optimizations
#	-Fixed special character handling in find_timer, remove_overlapping_timer
#	-Fixed a bug in remove_overlapping_timer
#
# 0.12 by Patrick Reinhardt
#	-EPG data is now acquired using the channel id rather than the channel name as a workaround for a neutrino query string bug. (Danke, tewsbo)
#
# 0.11 by Patrick Reinhardt
#	-Code cleanup & optimizations
#	-Fixed zap timers (Danke, tewsbo)
#	-Default log path changed to /tmp
#	-LOG_FILE can now be overridden in the configuration file (Setting to 'off' disables logging. Default if unset or invalid: /tmp/pr-auto-timer_DATE.log
#	-RULE_FILE can now be overridden in the configuration file. Default if unset or invalid: /var/tuxbox/config/pr-auto-timer.rules
#
# 0.10 by Patrick Reinhardt
#	-Bugfix in the comments :)
#	-Added possibility to add zapto timers.
#		Das Erste HD;Mon;Pinguin;Z
#		                         ^
#		                         |
#		                         Z=Create zap timer instead of record.
#		Backwards compatibility is ensured, i. e. the old format will continue to work
#
# 0.09 by Patrick Reinhardt
#	-Cleaned up unused code
#	-Several bugfixes in the changelog :)
#	-Check if script is already running
#	-Added command line option '--menu'/'-m' which displays a popup in Neutrino
#
# 0.08 by Patrick Reinhardt
#	-Any web server configuration should be supported now. (Danke, vanhofen)
#	-Handle non existing timerd.conf. (Danke, vanhofen)
#	-Timers file (pr-auto-timer.conf) moved to /var/tuxbox/config/pr-auto-timer.rules
#	-Config (MAX_DIFF_MINS) moved to new config file /var/tuxbox/config/pr-auto-timer.conf
#	-Removed warning if log path does not exist
#
# 0.07 by Patrick Reinhardt
#	-Cleaned up unused code
#	-It is now possible to handle whole bouquets rather than single channels
#	-EPG data is now cached
#	-Added a warning message in case the log path does not exist.
#	-Minor optimizations (Danke, vanhofen)
#
# 0.06 by Patrick Reinhardt
#	-Fixed day of week filter (Danke, fred_feuerstein)
#	-RECORD_CORRECTION_MINS removed, recording times now corrected using the values of the tuxbox menu.
#	-Reformatted log.

NAME="Auto-Timer"
ME=${0##*/}

CONFIG_FILE=/var/tuxbox/config/$ME.conf
PID_FILE=/var/run/$ME.pid

TMR2_TEMPFILE=/tmp/$ME.tmr
TMR2_FILE=/var/tuxbox/config/$ME.tmr

EXIT_SIGNAL=1
EXIT_NO_RULE_FILE=2
EXIT_ALREADY_RUNNING=3

NEUTRINO_CONF="/var/tuxbox/config/neutrino.conf"

TIMERD_EVENT_TYPE_ZAPTO=3
TIMERD_EVENT_TYPE_RECORD=5

# How many characters of the EPG-info2 field should be (at most)
# considered, if info1 is empty or the same as the title
INFO2_CHARACTERS=30

# The current version for entries in the $ME.show_history-file
EVENT_INDEX_VERSION=1

# The limit under which a recorded transport stream is considered broken, given in kb
BROKEN_FILE_SIZE_LIMIT=2

#Debugging options
#Do not remove temporary directory:
DEBUG_TEMP=0
#Do not add or remove any timers:
DEBUG_DRY_RUN=0
#Output display log to STDOUT:
DEBUG_DISPLAYLOG=0

# Default values for variables from the config file
HISTORY_FILE=/var/tuxbox/config/$ME.show_history
MYRECORDPATHS=std::neutrino
DEL_TIMER_REFRESH=2

#######################################################################################
#BEGIN SECTION "Helper functions"

signal_handler() {
	#Handle INT, TERM signals and clean up.
	log "Caught signal. Cleaning up."
	cleanup
	set +f
	log "done."
	exit $EXIT_SIGNAL
}

cleanup() {
	local result

	# first set the box back to the previous mode (in case it was in radio before starting)
	if [ "$box_mode" == "radio" ]; then
		log "\tcleanup: Switching box back to $box_mode .. "
		result=$(wget -q -O - $webserver_url/control/setmode?$box_mode | dos2unix)
		if [ "$result" != "ok" ]; then
			log "\tcleanup: Could not switch box to $box_mode: $result"
		else
			log "\tcleanup: Successfully switched box to $box_mode (Result: $result)."
		fi
		# set also the standby-mode back, since switching to radio deactivates standby...
		if [ "$box_standby" == "on" ]; then
			result=$(wget -q -O - $webserver_url/control/standby?on | dos2unix)
			if [ "$result" != "ok" ]; then
				log "\tcleanup: Could not reactivate standby: $result"
			else
				log "\tcleanup: Successfully reactivated standby after mode switching (Result: $result)."
			fi
		fi
	fi

	#Remove temporary directory
	if [ $DEBUG_TEMP == 0 ]; then
		log "\tcleanup: Removing directory '$temp_dir'..."
		rm -rf $temp_dir 2>/dev/null
	fi

	# now remove also the pid-file
	rm -rf $PID_FILE 2>/dev/null
}

log() {
	#Log message to log file
	#$*: Log message
	if [ "$LOG_FILE" != "" ]; then
		echo -e $(date +'%F %H:%M:%S') [$$]: "$*" >> $LOG_FILE
	fi
}

displaylog() {
	#Log message to display log (--menu-mode)
	#$*: Log message
	# why? if [ $opt_menu ]; then
		echo -e "$@" >> $displaylogfile
	# fi
}

trim() {
	# helper function to strip unnecessary spaces from string
	echo "$@" | sed 's/^ *//g;s/ *$//g;s/ \{1,\}/ /g'
}

dos2unix() {
	# helper function to simulate dos2unix
	sed 's/\r$//'
}

begin_ifs_block() {
	#Backup IFS (input field separator) to restore it after parsing arguments
	IFS_SAVE=$IFS
	set -f
}

end_ifs_block() {
	#Restore (input field separator) IFS after parsing arguments
	IFS=$IFS_SAVE
	set +f
}

url_encode() {
	#http://rosettacode.org/wiki/URL_encoding#AWK
	echo $*|awk '
		BEGIN {
			for (i = 0; i <= 255; i++)
				ord[sprintf("%c", i)] = i
		}

		function escape(str, c, len, res) {
			len = length(str)
			res = ""
			for (i = 1; i <= len; i++) {
				c = substr(str, i, 1);
				if (c ~ /[0-9A-Za-z]/)
					res = res c
				else
					res = res "%" sprintf("%02X", ord[c])
			}
			return res
		}

		{ print escape($0) }
	'
}

html2text() {
	# Helper function to convert html encoding (&value;) into their characters
	# Only the most common html entities are converted. For full conversion
	# we would need 'recode'
	# Reads html encoding (can be multiple lines) from std input and
	# returns the same text ASCII encoded.
	while read line; do
		res=${line//&quot;/\"}
		res=${res//&apos;/\'}
		res=${res//&amp;/\&}
		#res=${res//&gt;/\>}
		#res=${res//&lt;/\<}
		# ...other entites, but use as few as possible, since this is inefficient.
		echo "$res"
	done
}

get_corrected_start_stop_times() {
	#Calculate corrected start/stop times using the tuxbox values (EXTRA_ etc.)
	#$1: start secs
	#$2: stop secs
	#$3: timerd event type
	#returns: $start_sec_corrected $stop_sec_corrected
	if [ $3 = $TIMERD_EVENT_TYPE_ZAPTO ]; then
		start_sec_corrected=$(( $1 - $zap_correction_secs_before ))
		stop_sec_corrected=0
	else
		if [ "$ignore_record_correction" = "false" ]; then
			start_sec_corrected=$(( $1 - $record_correction_secs_before ))
			stop_sec_corrected=$(( $2 + $record_correction_secs_after ))
		else
			start_sec_corrected=$1
			stop_sec_corrected=$2
		fi
	fi
	echo $start_sec_corrected $stop_sec_corrected
}

get_tsid_from_cid() {
	# $1: channel_id
	#returns: id of the transponder
	echo ${1:4:4}
}

get_setting()
{
	test -e $NEUTRINO_CONF && \
	grep "^${1}=" $NEUTRINO_CONF | cut -d'=' -f2
}

scale2res()
{
	value=${1:-0}

	if [ $osd_resolution -eq 1 ]; then
		echo -ne $value | awk '{ printf("%04.0f", $1 + $1/2) }'
	else
		echo -ne $value
	fi
}

#END SECTION "Helper functions"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Initialization"

init_config() {
	#Parse config file (default: /var/tuxbox/config/pr-auto-timer.conf)
	if [ -e $CONFIG_FILE ]; then
		source $CONFIG_FILE 2>/dev/null
	fi

	# Initialize the logfile first, so we can write to it ...
	if [ ! -d "${LOG_FILE%/*}" ]; then
		case $LOG_FILE in
			[oO][fF][fF])
				LOG_FILE=''
				;;
			*)
				LOG_FILE=/tmp/${ME}_$(date +'%F').log
				;;
		esac
	fi

	if [ "$LOG_FILE" != "" -a ! -e "$LOG_FILE" ]; then
		# Create logfile
		touch $LOG_FILE
	fi

	# Check the settings and reset them to default if unset or invalid
	case $MAX_DIFF_MINS in
		''|*[!0-9]*)
			MAX_DIFF_MINS=10
			log "\tinit_config: MAX_DIFF_MINS not found or invalid. Setting default MAX_DIFF_MINS=$MAX_DIFF_MINS."
		;;
	esac

	case $MAX_TMR2_COUNT in
		''|*[!0-9]*)
			MAX_TMR2_COUNT=250
			log "\tinit_config: MAX_TMR2_COUNT not found or invalid. Setting default MAX_TMR2_COUNT=$MAX_TMR2_COUNT."
		;;
	esac

	case $END_SHUT_DOWN in
		''|*[!0-9]*)
			END_SHUT_DOWN=0
			log "\tinit_config: END_SHUT_DOWN not found or invalid. Setting default END_SHUT_DOWN=$END_SHUT_DOWN."
		;;
	esac

	case $SHUT_DOWN_ALSO_DAY in
		''|*[!0-9]*)
			SHUT_DOWN_ALSO_DAY=0
			log "\tinit_config: SHUT_DOWN_ALSO_DAY not found or invalid. Setting default SHUT_DOWN_ALSO_DAY=$SHUT_DOWN_ALSO_DAY."
		;;
	esac

	case $MAX_TUNERS in
		''|*[!0-9]*)
			MAX_TUNERS=2
			log "\tinit_config: MAX_TUNERS not found or invalid. Setting default MAX_TUNERS=$MAX_TUNERS."
		;;
	esac

	case $MAX_RECORDS in
		''|[!1-8])
			MAX_RECORDS=8
			log "\tinit_config: MAX_RECORDS not found or invalid. Setting default MAX_RECORDS=$MAX_RECORDS."
		;;
	esac

	case $AUTOGEN_SHOW_HISTORY in
		''|[!0-1])
			AUTOGEN_SHOW_HISTORY=1
			log "\tinit_config: AUTOGEN_SHOW_HISTORY not found or invalid. Setting default AUTOGEN_SHOW_HISTORY=$AUTOGEN_SHOW_HISTORY."
		;;
	esac

	case $BROKEN_RECORD_HANDLING in
		''|[!0-2])
			BROKEN_RECORD_HANDLING=1
			log "\tinit_config: BROKEN_RECORD_HANDLING not found or invalid. Setting default BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING."
		;;
	esac

	case $MYRECORDPATHS in
		'')
			MYRECORDPATHS=std::neutrino
			log "\tinit_config: MYRECORDPATHS not found or invalid. Setting default MYRECORDPATHS=std::neutrino."
		;;
	esac

	case $MAX_HISTORY_ENTRIES in
		''|*[!0-9]*)
			MAX_HISTORY_ENTRIES=1000
			log "\tinit_config: MAX_HISTORY_ENTRIES not found or invalid. Setting default MAX_HISTORY_ENTRIES=$MAX_HISTORY_ENTRIES."
		;;
	esac


	if [ ! -e "$RULE_FILE" ]; then
		RULE_FILE=/var/tuxbox/config/$ME.rules
		if [ ! -e "$RULE_FILE" ]; then
			echo "ERROR: Rules file '$RULE_FILE' does not exist! Exiting."
			log "ERROR: Rules file '$RULE_FILE' does not exist! Exiting."
			exit $EXIT_NO_RULE_FILE
		fi
	fi

	# support for external rule-file
	if [ ! -e "$RULE_FILE_EXT" ]; then
		RULE_FILE_EXT=""
	fi


	# Prepare some internal variables
	RULE_FILES="$RULE_FILE $RULE_FILE_EXT"
	max_diff_secs=$(( $MAX_DIFF_MINS * 60 ))
}

init_temp() {
	#Init temporary directory to cache EPG data etc.
	temp_dir=$(mktemp -dt $ME.XXXXXX)
	log "\tinit_temp: Created temporary directory '$temp_dir'."
}

init_displaylog() {
	#Init display log file used for --menu-mode
	displaylogfile="$temp_dir/display.log"
}

init_settings() {
	#Init Tuxbox settings (record/zap extra time)
	record_correction_secs_before=0
	record_correction_secs_after=0
	zap_correction_secs_before=0
	neutrino_rec_dir=""

	if [ -f /var/tuxbox/config/timerd.conf ]; then
		record_correction_secs_before=$(egrep "^EXTRA_TIME_START" /var/tuxbox/config/timerd.conf | cut -d'=' -f2)
		record_correction_secs_after=$(egrep "^EXTRA_TIME_END" /var/tuxbox/config/timerd.conf | cut -d'=' -f2)
	fi

	if [ -f /var/tuxbox/config/neutrino.conf ]; then
		zap_correction_secs_before=$(( $(egrep zapto_pre_time /var/tuxbox/config/neutrino.conf | cut -d'=' -f2) * 60 ))
		neutrino_rec_dir=$(egrep network_nfs_recordingdir /var/tuxbox/config/neutrino.conf | cut -d'=' -f2)
	fi

	log "\tinit_settings: record_correction_secs_before=$record_correction_secs_before, record_correction_secs_after=$record_correction_secs_after"
	log "\tinit_settings: zap_correction_secs_before=$zap_correction_secs_before"
	log "\tinit_settings: neutrino_rec_dir=$neutrino_rec_dir"

	osd_resolution=$(get_setting "osd_resolution")
}

init_webserver() {
	#Build web server URL for API access including authentication
	#returns: web server base URL, e. g. http://user:pass@localhost
	webserver_url=$(awk '
		BEGIN {
			FS="=";
		}

		/^WebsiteMain.port/ {
			port=$2;
		}

		/^mod_auth.authenticate=/ {
			auth=$2;
		}

		/^mod_auth.username=/ {
			username=$2;
		}

		/^mod_auth.password=/ {
			password=$2;
		}

		END {
			if ( auth == "true" ) {
				printf("http://%s:%s@localhost:%i\n", username, password, port);
			}
			else {
				printf("http://localhost:%i\n", port);
			}
		}
	' /var/tuxbox/config/nhttpd.conf)
}

#END SECTION "Initialization"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Command line options handling"

parse_options() {
	#Parse pr-auto-timer command line arguments

	local option

	while [ $# -gt 0 ]
	do
		option=$1
		shift

		case "$option" in
			-m|--menu)
				opt_menu=true
			;;
			-g|--gen-show-history)
				log "pr-auto-timer V$VERSION generating/updating your event history index file ($HISTORY_FILE)"
				init_settings
				AUTOGEN_SHOW_HISTORY=1
				generate_history_index
				if [ $DEBUG_DRY_RUN -eq 0 ]; then
					clean_history_file
				else
					log "DEBUG DRY RUN: Index created, but NOT updating your index file - printing the result on console instead"
					echo -e "The following events are already available and will\nbe ignored when planing new timers with the D-flag:"
					echo -e "---------8><-------------"
					cat $HISTORY_IDX_TMP_FILE
					echo -e "---------8><-------------"
				fi
				cleanup
				log "pr-auto-timer V$VERSION is done and exiting happily."
				exit 0
			;;
			-d|--dry-run)
				log "\tparse_options: Debug dry-run activated on command line."
				DEBUG_DRY_RUN=1
			;;
			-p|--post-action)
				if [ -n "$1" ] && [ ! "${1:0:1}" == "-" ] && [ $1 -ge 0 ] && [ $1 -le 4 ]; then
					log "\tparse_options: post-actions overridden on command line with END_SHUT_DOWN=$1"
					END_SHUT_DOWN=$1
					shift
				else
					log "\tparse_options: post-actions deactivated on command line."
					END_SHUT_DOWN=0
				fi
			;;
			-t|--print-timer-index)
				log "pr-auto-timer V$VERSION generating future timer index."
				init_settings
				init_webserver
				generate_timer_index
				echo -e "The following events are currently programmed to be recorded and will\nbe ignored when planing new timers with the D-flag:"
				echo -e "---------8><-------------"
				cat $TIMER_IDX_TMP_FILE
				echo -e "---------8><-------------"
				cleanup
				log "pr-auto-timer V$VERSION is done."
				exit 0
			;;
			-h|--help)
				usage
				cleanup
				exit 0
			;;
			*)
				echo "Unknown command line option '$option'. What did you want to do? Exiting!"
				usage
				cleanup
				exit 1
			;;
		esac
	done
}

usage() {
	# Print short usage message on screen
	echo -e "Usage: $ME [options]"
	echo -e "Valid options are:"
	echo -e "\t-m|--menu\t\tShow menu in neutrino."
	echo -e "\t-g|--gen-show-history\tGenerate and print the index from your previous recordings (and exit)."
	echo -e "\t-t|--print-timer-index\tGenerate and print the future timer index (and exit)."
	echo -e "\t-p|--post-action [x]\tOverride configured post-action with the given argument x=[0..4].\n\t\t\t\tIf no argument is given the post-action is deactivated, i.e x=0."
	echo -e "\t-d|--dry-run\t\tPerform a dry-run, i.e. do not add/remove any timers. (provide first!)"
	echo -e "\t-h|--help\t\tPrint this help and exit."
}

#END SECTION "Command line options handling"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Rule processing"

parse_regexes() {
	#Parse search regexes
	regex=$(trim "${regex}")
	xregex=$(trim ${xregex})
	xregex_option=""
	if [ ${#xregex} -le 1 ]; then
		xregex=""
	# tricking shell with an uppercase X
	# to avoid interpreting +, ~, ! and - as an operator
	# and trim again
	elif [ "X${xregex:0:1}" = "X+" ]; then
		xregex="${xregex:1}"
		xregex=$(trim ${xregex})
		xregex_option="INCLUDE_ALL"
	elif [ "X${xregex:0:1}" = "X~" ]; then
		xregex="${xregex:1}"
		xregex=$(trim ${xregex})
		xregex_option="INCLUDE_ONE"
	elif [ "X${xregex:0:1}" = "X!" ]; then
		xregex="${xregex:1}"
		xregex=$(trim ${xregex})
		xregex_option="EXCLUDE_ALL"
	elif [ "X${xregex:0:1}" = "X-" ]; then
		xregex="${xregex:1}"
		xregex=$(trim ${xregex})
		xregex_option="EXCLUDE_ONE"
	else
		regex="$regex , $xregex" # no +, ~, ! or - found; add to regex again (the comma too)
		xregex=""
	fi
}

parse_timespan() {
	#Parse possible timespan
	timespan="$1"

	if [ "$timespan" = "" ]; then
		timespan="*"
		return
	fi

	#split timespan
	begin_ifs_block
		IFS='-'
		set -- $timespan
		timespan_start=$1
		timespan_stop=$2
	end_ifs_block

	#split timespan_start
	begin_ifs_block
		IFS=':'
		set -- $timespan_start
		timespan_start_h=$1
		timespan_start_m=$2
	end_ifs_block

	#split timespan_stop
	begin_ifs_block
		IFS=':'
		set -- $timespan_stop
		timespan_stop_h=$1
		timespan_stop_m=$2
	end_ifs_block

	e=0
	#check format hh:mm
	echo $timespan_start | grep -q '^[0-9][0-9]:[0-9][0-9]'
		e=$(($e+$?))
	echo $timespan_stop | grep -q '^[0-9][0-9]:[0-9][0-9]'
		e=$(($e+$?))
	#check hours
	if [ "$timespan_start_h" -gt "23" -o "$timespan_stop_h" -gt "23" ]; then
		e=$(($e+1))
	fi
	#check minutes
	if [ "$timespan_start_m" -gt "59" -o "$timespan_stop_m" -gt "59" ]; then
		e=$(($e+1))
	fi
	#finally we use the date-command to check the full string
	date -d $timespan_start +%s 2>/dev/null 1>/dev/null
		e=$(($e+$?))
	date -d $timespan_stop +%s 2>/dev/null 1>/dev/null
		e=$(($e+$?))

	if [ $e -ne 0 ]; then
		log "\tparse_timespan: Error while checking! timespan disabled!"
		timespan="*"
	else
		timespan_start_s=$(date -d $timespan_start +%s)
		timespan_stop_s=$(date -d $timespan_stop +%s)
		if [ $timespan_start_s -gt $timespan_stop_s ]; then
			timespan_over_midnight=true
		else
			timespan_over_midnight=false
		fi
	fi
}

parse_flags() {
	#Parse flags of entries in rule file
	event_type=$TIMERD_EVENT_TYPE_RECORD
	first_match=false
	only_once=false
	ignore_record_correction=false
	prevent_duplicates=false
	ignore_timer_refresh=false
	allow_empty_info=false

	# split flags with the separator ","
	begin_ifs_block
		IFS=','
		for flag in $1; do
			#log "\tmain: Found flag $flag!"
			case $flag in
				Z)
					event_type=$TIMERD_EVENT_TYPE_ZAPTO
				;;
				R)
					event_type=$TIMERD_EVENT_TYPE_RECORD
				;;
				F)
					first_match=true
				;;
				O)
					only_once=true
					first_match=true
				;;
				I)
					ignore_record_correction=true
				;;
				D)
					prevent_duplicates=true
				;;
				W)
					ignore_timer_refresh=true
				;;
				M)
					allow_empty_info=true
				;;
				*)
					log "\tparse_flags: WARNING - Unknown flag $flag found; Ignoring!"
				;;
			esac
		done
	end_ifs_block
}

parse_recdir() {
	#Parse recording directory
	if [ $event_type = $TIMERD_EVENT_TYPE_ZAPTO ]; then
		recdir=""
	else
		recdir=$(url_encode "$recdir")
	fi
}

parse_dowgroup() {
	#Check for weekday or weekend and set var dowgroup
	local result _dow
	dowgroup=$(echo "$dowfilter" | awk '{print tolower($0)}')
	_dow=$(echo $dow | awk '{print tolower($0)}')
	if [ "$dowgroup" = "weekday" ]; then
		dowgroup="montuewedthufri"
	fi
	if [ "$dowgroup" = "weekend" ]; then
		dowgroup="satsun"
	fi
	result=$(awk -v _dowgroup="$dowgroup" -v _dow="$_dow" 'BEGIN {print match(_dowgroup, _dow); exit 0 }')
	if [ $result -gt 0 ]; then
		dowgroup=true
	fi
	#log "\t\tparse_dowgroup: dowgroup='$dowgroup', dow='$_dow'"
}

#END SECTION "Rule processing"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Retrieve & Search EPG"

get_channel_id_by_channel_name() {
	#$1: channel name (ignore case)
	#returns: id of the first(!) matching channel
	temp_file="$temp_dir/channellist"
	if [ ! -e "$temp_file" ]; then
		wget -qO "$temp_file" "$webserver_url/control/channellist"
	fi
	awk -v channel="$1" '
		/^[0-9a-f]{16} / {
			if (tolower($0) == tolower($1 " " channel)) {
				print $1;
				exit 0;
			}
		}
	' $temp_file
	#Nooooooooooooo! This wouldn't have been necessary if query string handling worked in Neutrino...
}

get_channel_xml() {
	#Fetch EPG data in XML for a specified channel.
	#$1: channel name
	#returns: XML EPG data
	channel_id=$(get_channel_id_by_channel_name "$1")
	if [ "$channel_id" != "" ]; then
		temp_file="$temp_dir/$channel_id"
		if [ ! -e "$temp_file" ]; then
			wget -qO "$temp_file" "$webserver_url/control/epg?xml=true&details=true&channelid=$channel_id"
		fi
		cat "$temp_file"
		#log "\t\tget_channel_xml: Retrieved EPG from channel '$1' (CID: $channel_id TSID: $(get_tsid_from_cid "$channel_id")) "
	else
		log "\t\tget_channel_xml: !!! WARNING: Channel '$1' was NOT found. Skipping !!!"
	fi
}

get_channels_from_bouquet() {
	#$1: bouquet number
	#returns: channel names
	_channels=$(wget -qO - "$webserver_url/control/getbouquet?bouquet=${1}&mode=TV" | dos2unix)
	echo "$_channels" | cut -d" " -f3-
}

find_show() {
	#Find a show by (exact) channel name or bouquet number and RegEx(!) for show name.
	#$1: channel
	#$2: show name regex
	#$3: extended regex
	#$4: extended regex option
	#returns: list of found shows in the format 'CHANNEL_ID|START_SEC|STOP_SEC|CHANNEL_NAME|DESCRIPTION|INFO1|INFO2'
	BC="$1"
	case ${BC:0:1} in
		"*")
			if [ "$1" = "*" ]; then
				bouquet=1
			else
				bouquet=${BC:1}
			fi
			log "\t\tfind_show: Bouquet: $bouquet"
			channels=$(get_channels_from_bouquet $bouquet)
			echo -e "$channels" | while read channel; do
				find_show_by_regex "$channel" "$2" "$3" "$4"
			done
			;;
		*)
			find_show_by_regex "$1" "$2" "$3" "$4"
			;;
	esac
}

find_show_by_regex() {
	#Find a show by (exact) channel name and RegEx(!) for show name.
	#$1: channel name
	#$2: show name regex
	#$3: extended regex
	#$4: extended regex option
	#returns: list of found shows in the format 'CHANNEL_ID|START_SEC|STOP_SEC|CHANNEL_NAME|DESCRIPTION|INFO1|INFO2'
	local xml result matches

	xml=$(get_channel_xml "$1")
	result=$(echo -ne "$xml"|awk '
		BEGIN {
			false=0;
			true=1;
			matches=0;
		}

		END {
			exit (matches == 0);
		}

		/<prog>/ {
			channel_id	= "";
			start_sec	= "";
			stop_sec	= "";
			description	= "";
			info1		= "";
			info2_1		= "";
			info2_2		= "";
			info2_open	= 0;
		}

		/<channel_id>[0-9a-z]+<\/channel_id>/ {
			channel_id	= gensub(/<\/?[^>]+>/, "", "g");
		}

		/<start_sec>[0-9]+<\/start_sec>/ {
			start_sec	= gensub(/<\/?[^>]+>/, "", "g");
		}

		/<stop_sec>[0-9]+<\/stop_sec>/ {
			stop_sec	= gensub(/<\/?[^>]+>/, "", "g");
		}

		/<info1>.*<\/info1>/ {
			info1		= gensub(/(<info1><!\[CDATA\[|\]\]><\/info1>)/, "", "g");
		}

		/<info2>.*/ {
			info2_1		= gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
			info2_open	= 1;
		}

		/.*<\/info2>/ {
			info2_2		= gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
			info2_open	= 0;
		}

		# This 'captures' also lines inbetween newlines for info2
		!/.*info2.*/ {
			if (info2_open)
			{
				info2_1=info2_1 " " $0
			}
		}

		/<description><!\[CDATA\[.*\]\]><\/description>/ {
			description	= gensub(/(<description><!\[CDATA\[|\]\]><\/description>)/, "", "g");
		}

		/<\/prog>/ {
			if (info2_1 == info2_2)
			{
				info2 = info2_1;
			}
			else
			{
				info2 = info2_1 " " info2_2;
			}

			# ignore case in fulldescription
			fulldescription	= tolower(description);

			if (length(info1) > 0)
			{
				fulldescription	= tolower(fulldescription " " info1);
			}

			if (length(info2) > 0)
			{
				fulldescription	= tolower(fulldescription " " info2);
			}

			info_length = length(info1) + length(info2);

			find_type = ""
			include_regex = ""
			exclude_regex = ""
			if (match(extended_regex_option, "INCLUDE"))
			{
				find_type = "include"
				include_regex = tolower(extended_regex);
			}
			else if (match(extended_regex_option, "EXCLUDE"))
			{
				find_type = "exclude"
				exclude_regex = tolower(extended_regex);
			}
			include_regex = tolower(include_regex);
			exclude_regex = tolower(exclude_regex);

			find_option = ""
			if (match(extended_regex_option, "ALL"))
			{
				find_option = "all";
			}
			else if (match(extended_regex_option, "ONE"))
			{
				find_option = "one";
			}

			# split into arrays
			split(show_regex,	show_array);
			split(include_regex,	include_array);
			split(exclude_regex,	exclude_array);

			show_found = false;

			show_counter = 0;
			show_matches = 0;

			if (show_regex == "*")
			{
				# no need to search; simply grab the show
				show_counter = 1;
				show_matches = 1;
			}
			else
			{
				for (i in show_array)
				{
					# search for every regex/word from show_regex in description only
					show_counter++;
					if (match(string tolower(description), string tolower(show_array[i])))
					{
						show_matches++;
					}
				}
			}

			if ( (show_matches > 0) && (show_counter == show_matches) )
			{
				# all search words or "*" found
				show_found = true;

				if (find_type == "exclude")
				{
					exclude_counter = 0;
					exclude_matches = 0;

					for (i in exclude_array)
					{
						# search for every regex/word from exclude_regex in fulldescription
						exclude_counter++;
						if (match(fulldescription, exclude_array[i]))
						{
							exclude_matches++;
						}
					}

					if (find_option == "all")
					{
						if ( (exclude_matches > 0) && (exclude_counter == exclude_matches) )
						{
							# all exclude words found; mark this show as "false"
							show_found = false;
						}
					}
					else if (find_option == "one")
					{
						if ( (exclude_matches > 0) && (exclude_counter >= 1) )
						{
							# one exclude word found; mark this show as "false"
							show_found = false;
						}
					}
				}
				else if (find_type == "include")
				{
					include_counter = 0;
					include_matches = 0;

					for (i in include_array)
					{
						# search for every regex/word from include_regex in fulldescription
						include_counter++;
						if (match(fulldescription, include_array[i]))
						{
							include_matches++;
						}
					}

					if (find_option == "all")
					{
						if ( (include_counter > 0) && (include_counter != include_matches) )
						{
							# not all include words found; mark this show as "false"
							show_found = false;
						}
					}
					else if (find_option == "one")
					{
						if ( (include_counter > 0) && (include_matches < 1) )
						{
							# not any include word found; mark this show as "false"
							show_found = false;
						}
					}
				}
			}

			if (show_found == true)
			{
				matches++;
				printf("%s|%i|%i|%s|%s|%s|%s\n", channel_id, start_sec, stop_sec, channel_name, description, info1, info2);
			}
		}
	' channel_name="$1" show_regex="$2" extended_regex="$3" extended_regex_option="$4")

	if [ $? -eq 0 ]; then
		matches=$(echo "$result"|wc -l)
		log "\tfind_show_by_regex: Found $matches matches for regex '$2' (extended regex '$3', option '$4') on channel '$1'."
		echo -e "$result"
		#log "\t\tfind_show_by_regex: Result: '$result'"
		return 0
	else
		log "\tfind_show_by_regex: Found NO matches for regex '$2' (extended regex '$3', option '$4') on channel '$1'."
		return 1
	fi
}

find_show_by_start_sec() {
	#Find a show by (exact) channel name and start time.
	#$1: channel name
	#$2: start time in seconds
	#returns: list of found shows in the format 'CHANNEL_ID|START_SEC|STOP_SEC|CHANNEL_NAME|DESCRIPTION|INFO1|INFO2'
	xml=$(get_channel_xml "$1")
	result=$(echo -ne "$xml"|awk '
		BEGIN {
			matches=0;
		}

		END {
			exit (matches == 0);
		}

		/<prog>/ {
			channel_id	= "";
			start_sec	= "";
			stop_sec	= "";
			description	= "";
			info1		= "";
			info2_1		= "";
			info2_2		= "";
			info2_open	= 0;
		}

		/<channel_id>[0-9a-z]+<\/channel_id>/ {
			channel_id	= gensub(/<\/?[^>]+>/, "", "g");
		}

		/<start_sec>[0-9]+<\/start_sec>/ {
			start_sec	= gensub(/<\/?[^>]+>/, "", "g");
		}

		/<stop_sec>[0-9]+<\/stop_sec>/ {
			stop_sec	= gensub(/<\/?[^>]+>/, "", "g");
		}

		/<info1>.*<\/info1>/ {
			info1		= gensub(/(<info1><!\[CDATA\[|\]\]><\/info1>)/, "", "g");
		}

		/<info2>.*/ {
			info2_1		= gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
			info2_open	= 1;
		}

		/.*<\/info2>/ {
			info2_2		= gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
			info2_open	= 0;
		}

		# This 'captures' also lines inbetween newlines for info2
		!/.*info2.*/ {
			if (info2_open)
			{
				info2_1=info2_1 " " $0
			}
		}

		/<description><!\[CDATA\[.*\]\]><\/description>/ {
			description	= gensub(/(<description><!\[CDATA\[|\]\]><\/description>)/, "", "g");
		}

		/<\/prog>/ {
			if (info2_1 == info2_2)
			{
				info2 = info2_1;
			}
			else
			{
				info2 = info2_1 " " info2_2;
			}

			if ((search_start_sec == start_sec) || (search_start_sec == start_sec-record_correction_secs_before) )
			{
				matches=1;
				printf("%s|%i|%i|%s|%s|%s|%s\n", channel_id, start_sec, stop_sec, channel_name, description, info1, info2);
			}
		}
	'  channel_name="$1" search_start_sec="$2" record_correction_secs_before="$record_correction_secs_before")

	if [ $? -eq 0 ]; then
		log "\t\tfind_show_by_start_sec: Found match for start time '$2' on channel '$1'."
		echo -e "$result"
		return 0
	fi
}

#END SECTION "Retrieve & Search EPG"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Event & Timer Index"

read_history_index() {
	# Reads and upgrades the event index from the configured file $HISTORY_FILE.
	# No parameters
	# Returns: The read and upgraded index is available in the temporary file $HISTORY_IDX_TMP_FILE

	local line title info timestamp new_timestamp filename

	# First remove the temporary file, in case it exists - this shouldn't happen, but lets be safe.
	rm -f "$HISTORY_IDX_TMP_FILE" 2>/dev/null

	# Now read the configured HISTORY FILE (if it exists) and upgrade it if necessary.
	if [ -e $HISTORY_FILE ]; then

		# With the version property, future upgrades of the format are possible
		# It is currently (v0.34) not used, since with the first format change we can deal implicitly.
		if [ -f "${HISTORY_FILE}.properties" ]; then
			source "${HISTORY_FILE}.properties" 2>/dev/null
		else
			event_index_version=0
		fi

		log "\t\t+- Reading $HISTORY_FILE (v$event_index_version) with $(wc -l $HISTORY_FILE | cut -d ' ' -f1) events ..."

		# for unavailable timestamps (upgrading) put the timestamp 2 days in the future
		# a subsequent run from pr-auto-timer, will either correct this or leave it;
		# if it remains, than the entry is considered old after two days..
		new_timestamp=$(($(date +%s)+2*24*60*60))

		while read line; do

			# ignore empty lines
			if [ -z "$line" ]; then
				log "\t\t+- Removing empty line from index."
				continue
			fi

			# now split it in it's components
			begin_ifs_block
				IFS='|'
				set -- $line
				title=$1
				info=$2
				timestamp=$3
				filename=$4
			end_ifs_block

			# now check, if some fields are empty and provide some initial values for them
			# this can happen with manual entries and when upgrading from an older file format
			# in case we anyhow generate a new index, we also reset the timestamp - this way we
			# can also get updated timestamps if the modification date changes
			if [ -z "$timestamp" ] || ( [ $AUTOGEN_SHOW_HISTORY -eq 1 ] && [ -f "$filename" ] ); then
				#log "\t\t+- Upgrading '$line' with new timestamp '$new_timestamp'."
				timestamp=$((new_timestamp++))
			fi

			# if there is no filename yet, we set some placeholder
			# we also remove/reset the filename, in case we are going to regenerate the index anyhow
			# the given placeholder ensures, that a new found path always 'wins' in sort against the placeholder
			if [ -z "$filename" ] || [ $AUTOGEN_SHOW_HISTORY -eq 1 ]; then
				#log "\t\t+- Adding placeholder filename to '$line'".
				filename="ZZZZ-not-available-ZZZZ"
			fi

			#write the cleaned/upgraded line to the temporary index
			echo -e "$title|$info|$timestamp|$filename|unverified-magic-MYmtmIQMsoQ=" >> "$HISTORY_IDX_TMP_FILE"

		done < "$HISTORY_FILE"
	else
		log "\t\t+- $HISTORY_FILE does not yet exist. Starting with empty index."
		touch "$HISTORY_IDX_TMP_FILE"
	fi
}

generate_history_index() {
	# Generates/updates the $ME.show_history-file from your recordings
	# No parameters
	# Result: merged $ME.show_history with previous values and available records

	local rec_path info info1 info2 title show history_index ts_file timestamp magic_token broken_records broken_records_count recorded_shows

	log "\t\tgenerate_history_index: collecting information from previously recorded events ..."

	read_history_index
	history_index="$(cat $HISTORY_IDX_TMP_FILE)\n"

	# shall we auto-generate the history index?
	if [ $AUTOGEN_SHOW_HISTORY -eq 1 ]; then
		# now iterate over all provided paths
		begin_ifs_block
			IFS=';'
			for rec_path in ${MYRECORDPATHS//"std::neutrino"/$neutrino_rec_dir}
			do
				if [ $BROKEN_RECORD_HANDLING -gt 0 ]; then
					log "\t\t+- Searching path '$rec_path' recursively for broken *.ts-files (file size smaller than ${BROKEN_FILE_SIZE_LIMIT}kB)."

					broken_records=$(find "$rec_path" -name '*.ts' -size -${BROKEN_FILE_SIZE_LIMIT}k)
					broken_records_count=$(echo -e "$broken_records" | sed '/^$/d' | wc -l)

					if [ $broken_records_count -gt 0 ]; then
						if [ $BROKEN_RECORD_HANDLING -eq 2 ]; then
							if [ $DEBUG_DRY_RUN -eq 0 ]; then
								log "\t\t| \\- Removing $broken_records_count broken record(s) (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
								echo -e "$broken_records" | xargs rm 2>/dev/null
							else
								log "\t\t| \\- Would remove $broken_records_count broken records now (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING). Skipped due to DEBUG-DRY-RUN."
							fi
							# In this case, we will detect the a missing transport stream and handle the xml with the next part accordingly.
							# To avoid double work, we therefore reset BROKEN_RECORDS=""
							broken_records=""
						else
							log "\t\t| \\- Found $broken_records_count broken record(s), but leaving files untouched (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
						fi
					else
						log "\t\t| \\- No broken records found."
					fi
				fi

				log "\t\t+- Searching path '$rec_path' recursively for *.xml-files"
				recorded_shows=$(find "$rec_path" -name '*.xml')
				IFS=$'\n'
				for show in $recorded_shows
				do

					# Note: info2 is only extracted until the first newline. This is probably ok, since we need anyhow only the
					# first $INFO2_CHARACTERS (30) characters from it iff info1 is empty.

					#echo "Processing '$show' .."
					title=$(grep epgtitle "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s/<epgtitle>//g;s/<\/epgtitle>//g' | html2text)
					info1=$(grep info1 "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s/<info1>//g;s/<\/info1>//g' | html2text)
					info2=$(grep "<info2>" "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s/<info2>//g;s/<\/info2>//g' | html2text)

					# now check which information is available in the stored xml
					if [ -z "$info1" ] || [ "$info1" == "$title" ]; then
						# info1 was empty or exactly the same as the title
						# in this case, we use the $INFO2_CHARACTERS from info2; nothing else to do; if this is empty too, we can't do anything
						info=${info2:0:$INFO2_CHARACTERS}
					else
						info="$info1"
					fi

					# create a magic token for good files. It is important, that this token is alphabetically BEFORE the bad-token
					# and also the unverified token (therefore it starts with 'a' - the random string is just to make it unique, so we can grep later for it
					# note that the random string used when reading the old HISTORY_FILE must be the same as this one here.
					magic_token="alright-magic-MYmtmIQMsoQ="

					# finally check if the transport stream exists and if it bigger than a given size
					ts_file=${show%".xml"}".ts"
					if ( [ -f "$ts_file" ] && ([ $BROKEN_RECORD_HANDLING -eq 0 ] || [ "$(echo -e "$broken_records" | grep "$ts_file")" == "" ]) ); then
						# all good, get the last modification time of the  .ts-file as a timestamp
						timestamp=$(date -r "$ts_file" +%s)
						#file_size_kb=`du -k "$filename" | cut -f1`
						#echo "$ts: $timestamp - $(date -r "$show" +%s)"
					else
						# No transport stream (recording) found. In this case we must handle a broken record if activated
						timestamp=$(date -r "$show" +%s)
						case $BROKEN_RECORD_HANDLING in
							0)
								log "\t\t| +- Warning: No or broken transport stream for $show found (Title='$title', Info='$info'). Still adding it to history index (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
							;;
							1)
								log "\t\t| +- Detected missing or broken transport stream for $show (Title='$title', Info='$info'). Removing from history index (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
								# make sure, this comes alphanumerically after the "good" token - with the later unique sort this should "survive" against the good one.
								magic_token="bad-magic-5NZm8PpjT8w="
							;;
							2)
								if [ $DEBUG_DRY_RUN -eq 0 ]; then
									log "\t\t| +- Detected missing or broken transport stream for $show (Title='$title', Info='$info'). Removing from history index and deleting. (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
									rm -f "$show" 2>/dev/null
								else
									log "\t\t| +- Detected missing or broken transport stream for $show (Title='$title', Info='$info'). Would now remove it from history index and delete it. (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING). Skipping the real operation due to DEBUG_DRY_RUN=$DEBUG_DRY_RUN."
								fi
								magic_token="bad-magic-5NZm8PpjT8w="
							;;
							*)
								log "\t\t| +- ERROR: This should not happen - $BROKEN_RECORD_HANDLING is no valid value BROKEN_RECORD_HANDLING ($show: Title='$title', Info='$info')."
								continue
							;;
						esac
					fi

					# add to the index iff there are meaningful values available.
					if  [ -n "$title" ] && [ "$title" != "not available" ] && [ -n "$info" ]; then
						history_index="$history_index$title|$info|$timestamp|$ts_file|$magic_token\n"
					else
						log "\t\t| +- Skipping file $show ($(date -D %s -d $timestamp +'%Y-%m-%d %H:%M:%S')) due to invalid info: Title='$title', Info='$info'"
					fi
				done
			done
		end_ifs_block
	fi

	# at this point HISTORY_INDEX contains all values from the old files and
	# also a value for all collected files from the recorded directories (if active)

	# Let's sort it and remove all duplicates, ignoring timestamps and ignoring bad records
	history_index=$(echo -ne "$history_index" | sort -t'|' -u -k1,2 | grep "MYmtmIQMsoQ=")

	# write it to a temp file, so we can access it from different subprocesses
	echo -e "$history_index" > $HISTORY_IDX_TMP_FILE
}

clean_history_file() {
	# Write (potentially updated) history index of recorded shows to the index file
	# Clean the file by removing n entries, so that the maximum entries given in MAX_HISTORY_ENTRIES is not violated
	# Only entries, to which no recorded file exists will be removed - otherwise they would be added over and over again
	# A setting of MAX_HISTORY_ENTRIES=0 deactivates the size check. If there are more physically available files, than
	# entries allowed, the MAX_HISTORY_ENTRIES will be ignored and a warning is issued in the logfile.
	# No parameter
	local entries line title info timestamp filename nr_removed_entries=0 new_timestamp

	# proceed only if there is a tempfile, otherwise there is nothing to do here
	if [ -e "$HISTORY_IDX_TMP_FILE" ]; then

		# check if we are in debug-dry-run. If yes, just skip any processing of the file. Otherwise proceed by writing
		# a new history index
		if [ $DEBUG_DRY_RUN -eq 0 ]; then
			entries=$(wc -l $HISTORY_IDX_TMP_FILE | cut -d ' ' -f1)

			# calculate the number of lines which need to be removed
			if [ $MAX_HISTORY_ENTRIES -gt 0 ]  && [ $entries -gt $MAX_HISTORY_ENTRIES ]; then
				over_limit=$((entries-MAX_HISTORY_ENTRIES))
				log "\tTrying to minimize generated history index with $entries entries by $over_limit entries before writing to file $HISTORY_FILE .."
			else
				over_limit=0
				log "\tCleaning generated history index with $entries entries and writing to file $HISTORY_FILE .."
			fi

			# create also the temporary file for storing the number of removed elements
			echo 0 > "${HISTORY_IDX_TMP_FILE}_nr_removed"

			# try to minimize the file by removing old entries
			sort -t '|' -k3 "$HISTORY_IDX_TMP_FILE" | while read line; do
				# ignore empty lines
				if [ -z "$line" ]; then
					echo $((++nr_removed_entries)) > "${HISTORY_IDX_TMP_FILE}_nr_removed"
					log "\t+- Removing empty line from index."
					continue
				fi

				begin_ifs_block
					IFS='|'
					set -- $line
					title=$1
					info=$2
					timestamp=$3
					filename=$4
				end_ifs_block


				if [ $nr_removed_entries -ge $over_limit ] || [ -f "$filename" ]; then
					# not oversize or enough entries removed or filename exists
					# insert this line also into the new file, and use the current format
					echo -e "$title|$info|$timestamp|$filename" >> "${HISTORY_IDX_TMP_FILE}2"
				else
					# not enough events removed and the file does not exist anymore
					# hence this entry will be removed -- here we just skip it
					echo $((++nr_removed_entries)) > "${HISTORY_IDX_TMP_FILE}_nr_removed"
					log "\t+- Removing '$title' ('$info') with timestamp $timestamp ($(date -D %s -d $timestamp +'%Y-%m-%d %H:%M:%S')) from index."
				fi
			done

			# grab the number from the file, since the subprocess from read does not allow variables to pass back
			nr_removed_entries=$(cat "${HISTORY_IDX_TMP_FILE}_nr_removed")

			if [ $nr_removed_entries -lt $over_limit ]; then
				#we could not remove enough. print a warning in the log
				log "\tWARNING: Removed only $nr_removed_entries / $over_limit entries entries from index. Increase MAX_HISTORY_ENTRIES (=$MAX_HISTORY_ENTRIES) or delete some old files!"
			else
				# everything is fine. minimizing worked
				log "\tCleaned the generated history index by removing $nr_removed_entries entries. (required $over_limit entries)"
			fi

			# just write the new index again sorted by column 1 and 2
			sort -t '|' -u -k 1,2 "${HISTORY_IDX_TMP_FILE}2" > "$HISTORY_FILE"
			entries=$(cat $HISTORY_FILE | wc -l)
			log "\tWrote the updated history index with $entries entries to file $HISTORY_FILE."

			# write also the meta-information, for now only the version of the file
			prop_file="${HISTORY_FILE}.properties"
			echo -e "# This file was automatically generated by pr-auto-timer v$VERSION" > $prop_file
			echo -e "# Do not delete or change this file manually. It contains properties" >> $prop_file
			echo -e "# about the .show_history file which are used by pr-auto-timer to" >> $prop_file
			echo -e "# determine the correct format of the file." >> $prop_file
			echo -e "event_index_version=1" >> $prop_file
		else
			log "\tDEBUG DRY RUN: Would update now $HISTORY_FILE now. Skipped due to dry run option."
		fi
	fi
}

generate_timer_index() {
	# Retrieves all timers, collects EPG-information for each one and generates an index with this information
	# No parameters
	# Result: The temporary file TIMER_IDX_TMP_FILE contains info all currently set timers.
	# Currently there is a single line of the format "Title|Info" for each timer.

	local timerlist timer_id timer_type timer_start channel_name epg_data title info1 info2 info timer_index

	log "\t\tgenerate_timer_index: collecting information from already set timers ..."

	# first retrieve all set timers
	timerlist=$(wget -qO - "$webserver_url/control/timer")

	# process each timer, and try to find EPG-data for it
	timer_index=$(echo -e "$timerlist" | while read timer;
	do
		channel_name=$(echo "$timer" | cut -d ' ' -f 8-)
		timer_id=$(echo "$timer" | cut -d ' ' -f 1)
		timer_type=$(echo "$timer" | cut -d ' ' -f 2)
		timer_start=$(echo "$timer" | cut -d ' ' -f 6)

		# We care only for record-type timers
		if [ $timer_type -eq 5 ]; then
			# get EPG information for each of them
			log "\t\t+- Retrieving EPG-data for timer id $timer_id starting at $(date -D %s -d $timer_start +'%Y-%m-%d %H:%M:%S') ($timer_start) on channel $channel_name ..."
			epg_data=$(find_show_by_start_sec "$channel_name" "$timer_start")
			if [ -n "$epg_data" ]; then
				begin_ifs_block
					IFS='|'
					set -- $epg_data
					title=$5
					info1=$6
					info2=$7
				end_ifs_block

				# now check which information is available in the stored xml
				if [ -z "$info1" ] || [ "$info1" == "$title" ]; then
					# info1 was empty or exactly the same as the title
					# in this case, we use the INFO2_CHARACTERS from info2 nothing else to do; if this is empty too, nothing can be done
					info=${info2:0:$INFO2_CHARACTERS}
				else
					info="$info1"
				fi

				log "\t\tAdding '$title|$info' to the future timer index."

				# update the index
				echo -e "$title|$info\n"

			else
				log "\t\t| +- Warning: No EPG info found for timer id $timer_id starting at $timer_start on channel $channel_name. Skipping"
			fi
		fi
	done;)

	# For the sake of completeness, just sort it - there should not be any duplicates here, but to be sure
	# (could only happen if a timer was set additionally)
	timer_index=$(echo -e "$timer_index" | sort -u)

	# write it to a temporary file, in order to access from different subprocesses
	echo -e "$timer_index" > $TIMER_IDX_TMP_FILE
}

add_historic_timer() {
	#Timer zur internen Liste hinzufügen, um nochmaligen Eintrag nach manuellem löschen zu verhindern.
	#$1: channel name
	#$2: alarm time
	#$3: stop time
	#$4: timerd event type
	local S
	S="$1;$2;$3;$4"
	if [ $DEL_TIMER_REFRESH -lt 2 ] && [ "$TMR2_FILE" != "" ]; then
		if  ! find_historic_timer "$1" $2 $3 $4 1; then
			echo -e "$S" >> $TMR2_FILE
		fi
	fi
}

clean_historic_timer_file() {
	local jetzt=$(date +%s)
	local i=0
	local a=0
	if [ -e $TMR2_TEMPFILE ]; then
		rm $TMR2_TEMPFILE
		log "\tFound old tempfile for historic timers and deleted it."
	fi

	if [ $DEL_TIMER_REFRESH -lt 2 ]; then
		log "\tProcessing historic timers index (DEL_TIMER_REFRESH=$DEL_TIMER_REFRESH).."
		if [ -e $TMR2_FILE ]; then
			while read line
			do
				# split line
				begin_ifs_block
					IFS=';'
					set -- $line
					channelname="$1"
					starttime=$2
					endtime=$3
					type=$4
				end_ifs_block
				if [ $endtime -gt $jetzt ]; then
						if [ $MAX_TMR2_COUNT -gt $a ]; then
							echo -e "$line" >> $TMR2_TEMPFILE
						fi
					a=$(($a+1))
				else
					i=1
				fi
			done < $TMR2_FILE
			if [ $i -eq 1 ]; then
				log "\t$TMR2_FILE cleaned (removed $a entries)."
				rm $TMR2_FILE
				if [ -e $TMR2_TEMPFILE ]; then
					cp $TMR2_TEMPFILE $TMR2_FILE
				fi
			fi
		fi
		if [ -e $TMR2_TEMPFILE ]; then
			rm $TMR2_TEMPFILE
			log "\tDeleted tempfile for historic timers."
		fi
	else
		if [ -e $TMR2_FILE ]; then
			rm $TMR2_FILE
			log "\tFound historic timer file, but historic timers are deactivated in settings (DEL_TIMER_REFRESH=2). Deleted $TMR2_FILE."
		fi
	fi
}

find_historic_timer() {
	#Find an (exact) existing timer using channel name, alarm time, stop time and event type
	#$1: channel name
	#$2: alarm time
	#$3: stop time
	#$4: event type (5: record, 3: zapto)
	#$5: Override = 1
	local S
	local ret
	S="$1;$2;$3;$4"
	ret=1
	if [ $DEL_TIMER_REFRESH -eq 0 ] || [ $5 -eq 1 ]; then
		if [ -e $TMR2_FILE ]; then
			while read line
			do
				if [ "$line" == "$S" ]; then
					ret=0
					break
				fi
			done < $TMR2_FILE
		fi
	fi
	return $ret
}

find_already_recorded() {
	# Find if a show with a certain description and info is either already recorded (i.e. in the history-file) or
	# a timer was already set (i.e. it is in the future timer index)
	#$1: description
	#$2: info
	local show ret title info timestamp

	show="$1|$2"
	ret=1

	# determine if the the HISTORY_INDEX needs updating
	if [ ! -e "$HISTORY_IDX_TMP_FILE" ]; then
		log "\t\tfind_already_recorded: Updating/generating index file for previous records..."
		generate_history_index
	fi

	# determine if the TIMER_INDEX needs updating
	if [ ! -e "$TIMER_IDX_TMP_FILE" ]; then
		log "\t\tfind_already_recorded: Updating future timer index ..."
		generate_timer_index
	fi

	if [ -e $HISTORY_IDX_TMP_FILE ]; then
		while read line
		do
			#echo -e "processing line $line .."
			begin_ifs_block
				IFS='|'
				set -- $line
				title=$1
				info=$2
			end_ifs_block

			if [ "$title|$info" == "$show" ]; then
				ret=0
				break
			fi
		done < $HISTORY_IDX_TMP_FILE
	fi

	if [ -e $TIMER_IDX_TMP_FILE ]; then
		while read line
		do
			#echo -e "processing line $line .."
			if [ "$line" == "$show" ]; then
				ret=0
				break
			fi
		done < $TIMER_IDX_TMP_FILE
	fi

	return $ret
}

#END SECTION "Event & Timer Index"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Timer Handling"

add_timer() {
	#Add a timer using channel name, channel id, alarm time, stop time and event type if it does not already exist. Overlapping timers are removed before.
	#$1: channel name
	#$2: channel id
	#$3: alarm time
	#$4: stop time
	#$5: timerd event type
	#$6: alt rec dir
	#Note: Announce time is ignored.
	local S ret
	S="$1;$3;$4;$5"
	ret=1
	if [ "$first_match" = "done" ]; then
		return $ret
	fi
	if find_neutrino_timer "$1" $3 $4 $5; then
		log "\t\tadd_timer: --- An identical timer '$1' $3 $4 $5 is currently already set. Skipping. ---"
	elif [ "$ignore_timer_refresh" == "false" ] && find_historic_timer "$1" $3 $4 $5 0; then
		log "\t\tadd_timer: --- An identical timer '$1' $3 $4 $5 was previously set. Skipping. ---"
	elif prevent_timer_by_timespan $3; then
		log "\t\tadd_timer: --- Preventing timer '$1' $3 ($(date -D %s -d ${3} +"%H:%M")) by timespan ($timespan). Skipping. ---"
	else
		remove_overlapping_timer "$1" $3 $5
		if [ $DEBUG_DRY_RUN == 0 ]; then
			result=$(wget -qO - "$webserver_url/control/timer?action=new&type=$5&alarm=$3&stop=$4&channel_id=$2&rec_dir=$6")
			if [ "$ignore_timer_refresh" == "false" ]; then
				add_historic_timer "$1" $3 $4 $5
				ret=0
			fi
		else
			result="DISABLED (DEBUG_DRY_RUN)"
		fi
		log "\t\tadd_timer: Adding timer '$1' $3 $4 $5: $result."
		if [ "$first_match" = "true" ]; then
			log "\t\tadd_timer: Ignoring possible following shows on '$1'."
			first_match=done;
		fi
	fi
	return $ret
}

remove_overlapping_timer() {
	#Find and remove overlapping/similar timers resulting from changed EPG data
	#$1: channel name (ignore case)
	#$2: alarm time
	#$3: timerd event type
	#returns: API result (hopefully: 'ok')
	#Note: remove_overlapping_timer ignores repeated timers
	alarm_time=$2
	wget -qO - "$webserver_url/control/timer" | awk -v pattern="# $3 0 0 # # # $1" '{
		timer_id=$1;
		tstart_sec=$6;
		$1=$5=$6=$7="#";
		if(tolower($0)==tolower(pattern)) {
			print timer_id " " tstart_sec;
		}
	}' | while read timer; do
		begin_ifs_block
			IFS=' '
			set -- $timer
			timer_id=$1
			tstart_sec=$2
		end_ifs_block

		if [ $alarm_time -gt $tstart_sec ]; then
			diff_secs=$(( $alarm_time - $tstart_sec ))
		else
			diff_secs=$(( $tstart_sec - $alarm_time ))
		fi
		if [ $diff_secs -le $max_diff_secs ]; then
			if [ $DEBUG_DRY_RUN == 0 ]; then
				result=$(wget -qO - "$webserver_url/control/timer?action=remove&id=$timer_id")
			else
				result="DISABLED: DEBUG_DRY_RUN"
			fi
			log "\t\tremove_overlapping_timer: Timer id: $timer_id overlaps by $diff_secs seconds (max: $max_diff_secs). Removing: $result"
		fi
	done
}

find_neutrino_timer() {
	#Find an (exact) existing timer using channel name, alarm time, stop time and event type
	#$1: channel name (ignore case)
	#$2: alarm time
	#$3: stop time
	#$4: event type (5: record, 3: zapto)
	#Note: find_neutrino_timer ignores repeated timers
	wget -qO - "$webserver_url/control/timer" | awk -v pattern="# $4 0 0 # $2 $3 $1" '
		BEGIN {
			returncode=1;
		}
		{
			$1=$5="#";
			if(tolower($0)==tolower(pattern)) {
				returncode=0;
			}
		}
		END {
			exit returncode;
		}
	'
}

get_overlapping_timers() {
	# Retrieve the maximum number of concurrent/overlapping timers for a given time span
	# $1: alarm time
	# $2: stop time
	# $3: channel_id for the new timer
	# Returns a value of the form "max_transponders|max_recordings", where max_transponders is the number of how many tuners
	# would be required, when setting the new timer and max_recordings is the number of how many recordings would happen
	# at most in parallel during the timespan of the new timer
	local result timerlist timerlist_extended maxts maxrec

	# get the timerlist; Note: this cannot be cached, otherwise we would miss timers set during the same run
	# if we really need to cache it, we would need to update the cache as well, when adding a new timer
	timerlist=$(wget -qO - "$webserver_url/control/timer")

	# we need the transponder_id inside awk, to determine how many tuners are really needed
	# therefore, the retrieved timerlist is extended to include this information before passing
	# it to awk.
	timerlist_extended=$(echo -e "$timerlist" | while read timer; do
		channel_name=$(echo "$timer" | cut -d ' ' -f 8-)
		timer_line=$(echo "$timer" | cut -d ' ' -f 1-7)
		sid=$(get_channel_id_by_channel_name "$channel_name")

		echo "$timer_line $sid $channel_name"
	done;)

	result=$(echo -e "$timerlist_extended" | awk -v span_start="$1" -v span_stop="$2" -v span_sid="$3" '
		BEGIN {
			#printf ("Analysing timespan %i-%i (%is = %i Min) with respect to new timer on sid %s\n",span_start,span_stop,span_stop-span_start,(span_stop-span_start)/60,span_sid);
			span_tsid=substr(span_sid,5,4);

			#initalize the array with the tsid of the potential new timer
			for (time_point=span_start; time_point<=span_stop; time_point++) {
				time_span[time_point,"ts"] = span_tsid;
				time_span[time_point,"rec"] = 1;
			}
		}
		{
			timer_type=$2;
			# process timers only for record timers
			if (timer_type == 5) {
				# check for each existing timer, if it overlaps with the provided timespan
				timer_start=$6;
				timer_stop=$7;
				timer_sid=$8;
				timer_tsid=substr(timer_sid,5,4)
				timer_channel_name=substr($0, index($0,$9));

				# uncomment for debugging, keep commented in normal usage, since the output is the result, where a single integer ist expected
				#printf ("%s: alarmTime=%i, stopTime=%i, Type=%i, channel=%s (CID: %s, TSID: %s)\n", $0,timer_start,timer_stop,timer_type,timer_channel_name,timer_sid, timer_tsid);

				# create an array with an entry for each second of the given time span with the number of concurrent timers
				# wondering, if there is a more efficient way to do this.. ?
				for (time_point=span_start; time_point<=span_stop; time_point++) {
					# verify if timepoint is within the currently processed timer
					if ((time_point >= timer_start) && (time_point <= timer_stop)) {
						# this timer clashes with the new timer. Check if the tsid is already used, and if not add the tsid
						if (! match(time_span[time_point,"ts"],timer_tsid)) {
							time_span[time_point,"ts"] = time_span[time_point,"ts"] "|" timer_tsid;
						}
						# increase the number of recordings for this second
						time_span[time_point,"rec"]++;
					}
				}
			}
		}
		END {
			#printf "done - calculating max tuners required.. \n";
			max_transponders=0;
			max_recordings=0;
			for (i=span_start;i<=span_stop;i++) {
				different_transponders=split(time_span[i,"ts"],array,"|")
				#printf time_span[i,"ts"] ";";
				#printf different_transponders;
				if (different_transponders > max_transponders) {
					max_transponders=different_transponders;
				}
				if (time_span[i,"rec"] > max_recordings) {
					max_recordings=time_span[i,"rec"];
				}
			}
			printf ("%i|%i", max_transponders,max_recordings);
			exit 0
		}

	')

	if [ $? -eq 0 ]; then
		#maxts=$(echo $result | cut -d '|' -f1)
		#maxrec=$(echo $result | cut -d '|' -f2)
		#log "\t\tget_overlapping_timers: Would require $maxts tuners (for $maxrec parallel recordings) to set new timer in timespan '$1-$2'. (Max available tuners: $MAX_TUNERS)"
		echo -e "$result"
		return 0
	fi

}

prevent_timer_by_timespan() {
	#$1: alarm time
	local ret

	if [ "$timespan" = "*" ]; then
		return 1 # don't prevent
	fi

	timer_start_h=$(($(echo $(date -D %s -d ${1} +%H) | sed 's/^0//') * 60 * 60))
	timer_start_m=$(($(echo $(date -D %s -d ${1} +%M) | sed 's/^0//') * 60))
	timer_start=$((${timer_start_h} + ${timer_start_m}))

	timespan_from_h=$(($(echo ${timespan_start_h} | sed 's/^0//') * 60 * 60))
	timespan_from_m=$(($(echo ${timespan_start_m} | sed 's/^0//') * 60))
	timespan_from=$((${timespan_from_h} + ${timespan_from_m}))

	timespan_till_h=$(($(echo ${timespan_stop_h} | sed 's/^0//') * 60 * 60))
	timespan_till_m=$(($(echo ${timespan_stop_m} | sed 's/^0//') * 60))
	timespan_till=$((${timespan_till_h} + ${timespan_till_m}))

	ret=1
	if [ "$timespan_over_midnight" = "false" ]; then
		if ! [ $timer_start -ge $timespan_from -a $timer_start -le $timespan_till ]; then
			# timer doesn't start inside timespan
			ret=0 # prevent
		fi
	else
		if [ $timer_start -gt $timespan_till -a $timer_start -lt $timespan_from ]; then
			# timer starts outside timespan
			ret=0 # prevent
		fi
	fi
	return $ret
}

#END SECTION "Timer Handling"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Sky Cinema"

get_sky_moviedata() {
	# we use a static tmp-dir
	test -d /tmp/$ME || mkdir -p /tmp/$ME

	data_file=/tmp/$ME/sky_moviedata
	rm -f $data_file # force download; debug
	if [ -e $data_file ]; then
		if [ $(($(date +%s)\-$(date -r $data_file +%s))) -gt 86400 ]; then
			rm -f $data_file
		fi
	fi
	if [ ! -e $data_file ]; then
		log "\t\tget_sky_moviedata: get informations ..."
		wget -qO "$data_file"_orig "http://www.moviepilot.de/liste/sky-neustarts-filmschauer"
		grep -o "itemTitle\(.*\)itemData" "$data_file"_orig | \
		sed 's/itemTitle/&\n/g' | \
		sed 's/description/&\nDatum\n/g' | \
		sed 's/itemData/&\n/g' | \
		sed 's/.*itemTitle/Titel/g' | \
		sed 's/\\u0026/\&amp;/g' | \
		sed 's/","/&\n/g' | \
		sed 's/","//g' | \
		sed 's/":"//g' > $data_file
	fi

	test -e $data_file && cat $data_file | awk '
		BEGIN {
			false=0;
			found=false;
		}

		END {
		}

		/Titel/ {
			found="movie";
			movie="";
			next;
		}

		/description/ {
			found=false;
			next;
		}

		/Datum/ {
			found="date";
			date="";
			next;
		}

		/itemData/ {
			found=false;
			next;
		}

		// {
			if ( found == "date" ) {
				gsub (/(\,|\.|\:|\|)/, " "); # replace ",.:|" with " "

				gsub ("Januar"   , "1");
				gsub ("Februar"  , "2");
				gsub ("März"     , "3");
				gsub ("April"    , "4");
				gsub ("Mai"      , "5");
				gsub ("Juni"     , "6");
				gsub ("Juli"     , "7");
				gsub ("August"   , "8");
				gsub ("September", "9");
				gsub ("Oktober"  , "10");
				gsub ("November" , "11");
				gsub ("Dezember" , "12");

				#	  YYMMDDhhmm
				#date	= 9912312313
				date	= $4$3$2$5$6;
			}
			if ( found == "movie" ) {
				gsub (/^[[:space:]]*/, "");
				gsub (/&#x27;/, "'\''");

				movie	= gensub(/<\/?[^>]+>/, "", "g");
				printf("%s %s\n", date, movie);
			}
			next;
		}

		// {
			found=false;
			next;
		}
	'
}

get_sky_cinema_channel() {
	#$1: include search words (ignore case)
	#$2: exclude search words (ignore case)
	#returns: name of the first(!) matching channel

	# replace ' ' with '.*' and append '.*'
	include="${1// /.*}.*"
	# replace ' ' with '|'
	exclude="${2// /|}"

	# replace '+' with '\+'
	include="${include//+/\\+}"
	exclude="${exclude//+/\\+}"

	# tolower
	include=$(echo $include | awk '{print tolower($0)}')
	exclude=$(echo $exclude | awk '{print tolower($0)}')

	temp_file="$temp_dir/channellist"
	if [ ! -e "$temp_file" ]; then
		wget -qO "$temp_file" "$webserver_url/control/channellist"
	fi
	sky_cinema_channels=$(grep -iE "Sky.*Cinema.*Premieren.*" $temp_file | cut -d" " -f2- | sort -u )
	echo "$sky_cinema_channels" | \
	while read channel; do
		echo "$channel" | awk '{print tolower($0)}' | grep -qE "($include)"
		if [ $? -ne 0 ]; then
			continue
		fi
		echo "$channel" | awk '{print tolower($0)}' | grep -qvE "($exclude)"
		if [ $? -eq 0  ]; then
			echo "$channel"
			break
		fi
	done
}

parse_sky_cinema() {
	if [ "$SKY_CINEMA" = "" ]; then
		sky_cinema=""
		return
	fi

	# split $SKY_CINEMA
	begin_ifs_block
		IFS=';'
		set -- $SKY_CINEMA
		sky_cinema=$1
		dowfilter=${2:-*}
		recdir=$3
	end_ifs_block

	# split dowfilter
	begin_ifs_block
		IFS=','
		set -- $dowfilter
		dowfilter=$1
		timespan=$2
	end_ifs_block

	sky_start_sec_add=0
	sky_exclude_channels="Active Action Comedy Emotion Family Hits Nostalgie Uno Stars Romance Suspense Drama Due Special"
	sky_exclude_channels="Active Action Adventure Best Comedy Drama Due Emotion Family Hits Nostalgie Romance Special Stars Suspense Uno"

	case $(echo $sky_cinema | awk '{print toupper($0)}') in
		SD)
			sky_cinema=$(get_sky_cinema_channel "Sky Cinema Premieren" "+24 HD $sky_exclude_channels")
		;;
		HD)
			sky_cinema=$(get_sky_cinema_channel "Sky Cinema Premieren HD" "+24 $sky_exclude_channels")
		;;
		24)
			sky_cinema=$(get_sky_cinema_channel "Sky Cinema Premieren +24" "HD $sky_exclude_channels")
			sky_start_sec_add=$((60 \* 60 \* 24))
		;;
		24HD)
			sky_cinema=$(get_sky_cinema_channel "Sky Cinema Premieren +24 HD" "$sky_exclude_channels")
			sky_start_sec_add=$((60 \* 60 \* 24))
		;;
		*)
			sky_cinema=""
		;;
	esac
}

#END SECTION "Sky Cinema"
#######################################################################################

#######################################################################################
#BEGIN SECTION "Main"

# set the signal handler
trap signal_handler INT TERM

# Initialize the values from the config, otherwise we cannot log anywhere
init_config
init_temp

# We happily started ...
log "$ME V$VERSION started."

HISTORY_IDX_TMP_FILE="$temp_dir/history_index"
TIMER_IDX_TMP_FILE="$temp_dir/timer_index"

# Now get command line option, as these might override some values from the config or default variables
parse_options $@

if [ -e $PID_FILE ]; then
	log "$ME ist already running. Exiting."
	exit $EXIT_ALREADY_RUNNING
else
	echo $$ > $PID_FILE
fi

if [ $DEBUG_TEMP == 1 ]; then
	log "\tmain: !!! WARNING: DEBUG_TEMP is enabled. Temporary files will not be removed !!!"
fi
if [ $DEBUG_DRY_RUN == 1 ]; then
	log "\tmain: !!! WARNING: DEBUG_DRY_RUN is enabled. Timers will not be modified !!!"
fi

# prepare for main operation
init_settings
init_webserver
init_displaylog

# Perform pre-actions, if activated.
log "Executing configured pre-actions ... "

if [ -n "$PRE_ACTION" ]; then
	log "\tExecuting '$PRE_ACTION'"
	eval $PRE_ACTION
	log "\tDone."
else
	log "\tNo pre-action configured."
fi

# check for radio/tv mode of the box and switch to tv, in case radio was found
# todo: we should also give some feedback on the screen, in case -m is used.
box_mode=$(wget -q -O - $webserver_url/control/getmode?channelsmode=true | dos2unix)
case $box_mode in
	tv)
		log "\tmain: Box is in ${box_mode}-mode. Proceeding without changing mode."
	;;
	radio)
		box_standby=$(wget -q -O - $webserver_url/control/standby | dos2unix)
		log "\tmain: Box is in ${box_mode}-mode (standby=$box_standby). Trying to switch to tv-mode ..."
		result=$(wget -q -O - $webserver_url/control/setmode?tv | dos2unix)
		if [ "$result" == "ok" ]; then
			log "\tmain: Successfully switched to tv-mode. Will switch back to $box_mode when finished."
		else
			log "\tmain: Could not switch to tv-mode. This is bad and adding timers will not work. Exiting now."
			cleanup
			exit 0
		fi
	;;
	*)
		log "\tmain: Box is in ${box_mode}-mode! This should not happen and is considered a bad thing. Exiting now."
		cleanup
		exit 0
	;;
esac

if [ $opt_menu ]; then
	msgbox title="$NAME" size=20 timeout=5 refresh=0 cyclic=0 popup="Running..." >/dev/null
fi

for rule_file in $RULE_FILES; do
	log "\tmain: Processing '${rule_file}'"
	rule_line=0
	cat $rule_file | while read line ;do
		rule_line=$((${rule_line}+1))
		if echo $line | egrep -q '^[[:space:]]*([^#;]+);+([^;]+);+([^;]+);?([^;/]+)?;?([^;]+)?;?$'; then

			log ""
			log "\tmain: Processing rule: $line"

			# split config line
			begin_ifs_block
				IFS=';'
				set -- $line
				channelname=$1
				dowfilter=$2
				regex=$3
				flags=$4
				recdir=$5
			end_ifs_block

			# split dowfilter
			begin_ifs_block
				IFS=','
				set -- $dowfilter
				dowfilter=$1
				timespan=$2
			end_ifs_block

			# split regex
			begin_ifs_block
				IFS=','
				set -- $regex
				regex=$1
				xregex=$2
			end_ifs_block

			parse_regexes
			parse_timespan "$timespan"
			parse_flags "$flags"
			parse_recdir

			log "\tmain: channel: '$channelname', dow: '$dowfilter', timespan: '$timespan', regex: '$regex', extended regex: '${xregex:-none}', extended regex option: '${xregex_option:-none}', event type: '$event_type', flags: '${flags:-none}', rec dir: '${recdir:-std::neutrino}'"

			shows=$(find_show "$channelname" "$regex" "$xregex" "$xregex_option")
			shows_found=0
			matches=$(echo "$shows"|wc -l)

			echo -e "$shows" | {
				while read result; do
					if [ "$result" = "" ]; then
						break
					fi
					shows_found=$(( $shows_found + 1 ))

					log "\tmain: Processing match $shows_found / $matches ..."

					begin_ifs_block
						IFS='|'
						set -- $result
						channelid=$1
						start_sec=$2
						stop_sec=$3
						channelname=$4
						description=$5
						info1=$6
						info2=$7
					end_ifs_block

					set -- $(get_corrected_start_stop_times $start_sec $stop_sec $event_type)
					start_sec_corrected=$1
					stop_sec_corrected=$2

					dow=$(date -D %s -d ${start_sec} +%a)
					parse_dowgroup

					# check which information was send in the EPG
					if [ -z "$info1" ] || [ "$info1" == "$description" ]; then
						# info1 was empty or exactly the same as the title
						# in this case, we use the $INFO2_CHARACTERS from info2; nothing else to do; if this is empty too, we can't do anything
						info=${info2:0:$INFO2_CHARACTERS}
					else
						info="$info1"
					fi

					log "\t\tEvent content: '$description' ('$info') on channel '$channelname' ($channelid)"
					log "\t\tEvent times: $(date -D %s -d ${start_sec} +'%a, %Y-%m-%d'), $(date -D %s -d ${start_sec} +'%H:%M:%S')-$(date -D %s -d ${stop_sec} +'%H:%M:%S') ($start_sec-$stop_sec), Corrected: $(date -D %s -d ${start_sec_corrected} +'%H:%M:%S')-$(date -D %s -d ${stop_sec_corrected} +'%H:%M:%S') ($start_sec_corrected-$stop_sec_corrected)"

					if [ $start_sec_corrected -gt $(date +%s) ]; then
						if [ "$dow" = "$dowfilter" -o "$dowfilter" = "*" -o "$dowgroup" = "true" ]; then
							if  [ "$prevent_duplicates" == "false" ] || ! find_already_recorded "$description" "$info" "$info2"; then
								overlaps=$(get_overlapping_timers $start_sec_corrected $stop_sec_corrected $channelid)
								maxts=$(echo $overlaps | cut -d '|' -f1)
								maxrec=$(echo $overlaps | cut -d '|' -f2)
								log "\t\tmain: Would require $maxts / $MAX_TUNERS tuners (for $maxrec / $MAX_RECORDS parallel recordings) to set new timer in timespan '$(date -D %s -d ${start_sec_corrected} +'%H:%M:%S')-$(date -D %s -d ${stop_sec_corrected} +'%H:%M:%S')'."
								if [ $maxts -le $MAX_TUNERS ] && [ $maxrec -le $MAX_RECORDS ]; then
									if add_timer "$channelname" $channelid $start_sec_corrected $stop_sec_corrected $event_type $recdir; then
										log "\t\tmain: ########### Timer for '$description' ('$info') on channel '$channelname' successfully added ###########!"
										if [ "$prevent_duplicates" == "true" ] && ( [ -n "$info" ] || [ "$allow_empty_info" == "true" ] ); then
											log "\t\tmain: Duplicate prevention is active; adding '$description|$info' to the future timer index."
											echo "$description|$info" >> $TIMER_IDX_TMP_FILE
										fi
									#else
									#	log "\t\tmain: Timer NOT added."
									fi
									if [ "$first_match" = "done" ]; then
										if [ "$only_once" = "true" ]; then
											log "\t\tmain: Deactivating entry in rules-file as requested."
											sed -i "${rule_line} s/^/#/g" ${rule_file}
										fi
										break
									fi
								else
									log "\t\tmain: --- NOT adding timer because no more tuners available/to many parallel recordings for this time span. ---"
								fi
							else
								log "\t\tmain: --- NOT adding timer because show was already recorded/planed before. ---"
							fi
						else
							log "\t\tmain: --- NOT adding timer because of non-matching weekday. ---"
						fi
					else
						log "\t\tmain: --- NOT adding timer because start_secs lies in the past. ---"
					fi
				done
				displaylog "~T`scale2res 0150` $channelname ~T`scale2res 0300` $dowfilter ~T`scale2res 0400` $shows_found Treffer ~T`scale2res 0500` $regex"
			}
		fi
	done # while loop
done # for loop

# After processing all rules from the rule-file, we go for the "sky-cinema" function, if activated.
parse_timespan	# reset timespan
parse_flags	# reset flags
parse_sky_cinema

if [ ! "$sky_cinema" = "" ]; then
	log ""
	log "\tmain: Processing new movies at \"$sky_cinema\""

	parse_timespan "$timespan"
	parse_recdir

	sky_moviedata=$(get_sky_moviedata)

	echo "$sky_moviedata" | while read sky_date sky_movie; do
		if sky_start_sec=$(date -d "$sky_date" +%s 2>/dev/null); then
			now=$(date +%s)
			if [ $sky_start_sec -lt $now ]; then
				continue
			fi
			sky_start_sec=$((sky_start_sec + sky_start_sec_add))
			sky_show_data=$(find_show_by_start_sec "$sky_cinema" "$sky_start_sec")
			if [ ! "$sky_show_data" = "" ]; then
				# split sky_show_data
				begin_ifs_block
					IFS='|'
					set -- $sky_show_data
					channelid=$1
					start_sec=$2
					stop_sec=$3
				end_ifs_block

				set -- $(get_corrected_start_stop_times $start_sec $stop_sec $event_type)
				start_sec_corrected=$1
				stop_sec_corrected=$2

				dow=$(date -D %s -d ${start_sec} +%a)
				parse_dowgroup

				if [ $start_sec_corrected -gt $(date +%s) ]; then
					if [ "$dow" = "$dowfilter" -o "$dowfilter" = "*" -o "$dowgroup" = "true" ]; then
						add_timer "$sky_cinema" $channelid $start_sec_corrected $stop_sec_corrected $event_type $recdir
						displaylog "~T`scale2res 0150` $sky_cinema ~T`scale2res 0300` $(date -D %s -d ${start_sec} '+%d.%m.%Y %H:%M') ~T`scale2res 0500` $sky_movie"
					else
						log "\t\tmain: NOT adding timer because of non-matching weekday."
					fi
				fi
			fi
		fi
	done
fi

# Display some results to the screen, if the option -m is given
if [ $opt_menu ]; then
	msgbox title="$NAME" size=20 refresh=0 cyclic=0 timeout=15 popup=$displaylogfile >/dev/null
fi

if [ $DEBUG_DISPLAYLOG == 1 ]; then
	cat $displaylogfile
fi

log "$ME V$VERSION now performing cleanup .."

# Perform some cleanup before exiting
# maybe bundle the calls to all cleanup actions in one function ?
clean_historic_timer_file
clean_history_file
cleanup

# Perform post-actions, if activated.
log "Executing configured post-actions ... "

if [ -n "$POST_ACTION" ]; then
	log "\tExecuting '$POST_ACTION'"
	eval $POST_ACTION
	log "\tDone."
else
	log "\tNo post-action configured."
fi

# Perform final operation, if activated.
log "Performing final operation ... (END_SHUT_DOWN=$END_SHUT_DOWN)"
if [ $END_SHUT_DOWN -gt 0 ]; then
	# wait while recording
	while [ $DEBUG_DRY_RUN -eq 0 ] && [ $(wget -q -O - $webserver_url/control/setmode?status | dos2unix) = "on" ]
	do
		log "\tRecording is active, waiting with final operation for 900 seconds."
		sleep 900
	done

	Stunde=$(date +%H)
	if [ x"$(pidof EPGscan.sh)" = "x" ]; then
		if [ $SHUT_DOWN_ALSO_DAY -eq 1 ] || [ $Stunde -lt 6 ]; then
			case $END_SHUT_DOWN in
				1)
					if [ $DEBUG_DRY_RUN -eq 0 ]; then
						log "\tSetting the box to standby in 2 seconds."
						(sleep 2; wget -qO "/dev/null" "$webserver_url/control/standby?on") &
					else
						log "\tDEBUG DRY RUN: Would now set the box to standby in 2 seconds (using control/standby?on). Skipping."
					fi
				;;
				2)
					if [ $DEBUG_DRY_RUN -eq 0 ]; then
						log "\tInitiating shutdown of the box in 2 seconds."
						(sleep 2; wget -qO "/dev/null" "$webserver_url/control/shutdown") &
					else
						log "\tDEBUG DRY RUN: Would now shutdown the box in 2 seconds (using /control/shutdown). Skipping."
					fi
				;;
				3)
					if [ $DEBUG_DRY_RUN -eq 0 ]; then
						log "\tHalting the system in 2 seconds."
						(sleep 2; halt) &
					else
						log "\tDEBUG DRY RUN: Would now halt the box in 2 seconds (using command 'halt'). Skipping."
					fi
				;;
				4)
					if [ $DEBUG_DRY_RUN -eq 0 ]; then
						log "\tRebooting the system in 2 seconds."
						(sleep 2; reboot) &
					else
						log "\tDEBUG DRY RUN: Would now reboot the box in 2 seconds (using command 'reboot'). Skipping."
					fi
				;;
			esac
		else
			log "\tSkipping final operation during day (SHUT_DOWN_ALSO_DAY=$SHUT_DOWN_ALSO_DAY)."
		fi
	else
		log "\tIgnoring final operation since a running EPGscan was found!"
	fi
else
	log "\tFinal operation is deactivated!"
fi

log "$ME V$VERSION exiting."
log "=================================================="

#END SECTION "Main"
#######################################################################################
