#!/usr/bin/env python
# -*- coding: UTF-8 -*-

PLUGIN_VERSION                       = "3.0.1"
SUPPORTED_DVBVIEWER_VERSIONS         = '4.9.x (older versions might work but are untested)'
SUPPORTED_RECORDING_SERVICE_VERSIONS = '1.10.x (older versions might work but are untested)'

# This file is a plugin for EventGhost.
# Copyright (C) 2005-2009 Lars-Peter Voss <bitmonster@eventghost.org>
#
# EventGhost is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 2 as published by the
# Free Software Foundation;
#
# EventGhost 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, see <http://www.gnu.org/licenses/>.
#
# Version history (newest on top):
# 3.0.1: Debug log output reduced
#        Renamed GetRecordingIDs -> GetRecordingsIDs because of accidentally broken backward compatibility in 3.0.0
# 3.0.0: Improved stability and robustness by refactoring thread synchronisation and error handling. 
#        - Fixed most situations of EG hangers when DVBViewer crashes
#        - Fixed most situations of error 'Lock released to prevent a dead lock' (some situations can't be fixed, like RS unavailable)
#        - EG no longer hangs at startup when DVBViewer recording service address is wrong / unavailable
#        - Fixed behavior in GetNumberOfActiveRecordings, GetRecordingsIDs, GetDateOfRecordings when RS is not enabled by plugin config.
#        - Fixed a lock timeout in case that system performs suspend just while WatchDogThread executes server requests.
#        Added action DeleteRecordings
#        - supposed to implement an automated housekeeping
#        Added action GetRecordingDetails
#        Added action IsDVBViewerProcessRunning
#        Added action WaitUntilPluginIdle 
#        - supposed to be called before suspend
#        Added action GetCurrentShowDetails
#        - provides details about what is currently shown in DVBViewer
#        Added action GetDataManagerValues
#        - provides all available information of DVBViewer's data manager
#        Added event 'DVBViewer.SevereError.WMI' - triggered on exceptions in WMI
#        Added event 'DVBViewer.SevereError.LockTimeout' - triggered on dead locks / lock timeouts
#        Added event 'DVBViewer.SevereError.COM' - triggered on COM initialization errors
#        Added event 'DVBViewer.SevereError.Connect' - triggered on connection errors between EG and DVBV (replaces earlier DVBViewerCouldNotBeConnected event)
#        Suppress repeated identical events like 'DVBViewer.ControlChange (603, 0)'
#        Grouped and reorderded all actions, put them into subfolders; improved action names and descriptions
#        Plugin config: auto correct os path to dvbviewer.exe while opening plugin config and start plugin
#        Updated plugin documentation (HTML help)
#        Updated source documentation of main classes and methods
#        Many other refactorings and minor improvements
# 2.1.2: Fixed Unicode problem in GetChannelDetails; 
#        channel names with special characters or umlauts caused an exception.
# 2.1.1: Documentation and exception handling improved (minor changes)
#        Return type of GetChannelDetails is now always a dictionary with the channelID as key.
# 2.1.0: Added action GetTimerDetails
#        Added action TuneChannel
#        Added action GetChannelDetails
#        Improved creation of channelIDs: generate 64 bit IDs (new format introduced in 2011)
#        Extracted help into separate html file
#        Some Eclipse (IDE) warnings fixed

import eg
from os.path import join, dirname, abspath, isfile
import codecs

HELPFILE = abspath(join(dirname(__file__.decode('mbcs')), "DVBViewer-Help.html"))

def GetHelp():
    try:
        f = codecs.open(HELPFILE, mode="rt", encoding="latin_1", buffering=-1)
        hlp = f.read()
        f.close()
        hlp = hlp % (PLUGIN_VERSION, SUPPORTED_DVBVIEWER_VERSIONS, SUPPORTED_RECORDING_SERVICE_VERSIONS)
        return hlp
    except Exception, exc:
        msg = "Error reading help file " + HELPFILE + ", error=" + unicode(exc)
        eg.PrintTraceback(msg)
        return msg

HELP = GetHelp()

eg.RegisterPlugin(
    name = "DVBViewer",
    author = "Bitmonster & Stefan Gollmer & Nativityplay & Daniel Brugger",
    version = PLUGIN_VERSION,
    kind = "program",
    guid = "{747B54F6-59F6-4602-A777-984EA76D2D8C}",
    createMacrosOnAdd = True,
    url = "http://www.eventghost.net/forum/viewtopic.php?f=9&t=1564",
    description = (
        'Adds support functions to control <a href="http://www.dvbviewer.com/">'
        'DVBViewer Pro/GE and DVBViewerService</a> and returns events.'
    ),
    help = HELP,
    icon = (
        "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADK0lEQVR42j2TXWgcVRTH"
        "f/fOzM5uNknJxvoRt6kaCwlBjFJRsIgasUFLH1KFCqIIBTHUh9aCRQTRPviSokiLFREh"
        "iCXRh5ZiH6TWj1KxUqG2xmKwSlsrtKFu0/2a2Ttzr2d2jXMZLveee875n//5H5XWz6C7"
        "RsAkoBU4D2qX5RyBys50vp4bIQjBJp07P0/z2Guo+pGts0oXB12SSgAtFnFKY/lTnMpO"
        "qu3v/BxKS3CbZv5WezmdVs7OKTM/63SwEmctKk1wSYzqWYkq9uGCoty1cHEVV10UVGLL"
        "dclbI3sv8cIMKv7qbaOCknK1a+JUwhvbgOq9uZ01y71cgatfxf5xAvvXL6hCDzpfpPn3"
        "Yaean7/kqErEvjK58SnSS2exf/6IWxIewiL61lH80cdQXYJIVnpyjta3M3g3rcboc6jq"
        "9JjT9OLft5nk5EHSiz/j3/lg29ElEfb3H7CVSwQT2wkfmcIJP43pDbjLP2GHpdTrb/Q7"
        "nbsFe3VRspTIP7sXPXi3wCz9Rx+ScR/RJy8TbtpFOLETM/8lrY+fIh0eQNV2Dzn3T13g"
        "9otxB8nCMZIzh6G5hB4YJVy/E39sE+b0QZr7Jim+fgpdup1oz/2Yrqq08d3bnKsKu92r"
        "cDUh6so5/Hsm0eW1pAtHSOePkpt4hXDjNM0PnoBckcILnxF9NE6rcUoCvFd2RFpaZQXr"
        "ErnH3yRYt63DfAb/0HbM0XcobPseV7lA/MWrFHb8itn/JLGIUNXfX+VUrCSAKEzaWNhy"
        "XLJ0t/WgRW22sUhz95Cgeg5/ZCPxgRcJnzmAOfQ0JtdCNT4ccsoobBShewYIn/8a5YVY"
        "l7aV56LrxHuHpaQHUIPrpKQ5/LVbSb+ZwtxQRjVn7jUSQNlEZBxXCB7ehXfXZpa/5MQe"
        "zHdv4Y1MCkfZjIgqrUJHv9HK+4L+04ec8n1EDChnBXoL7471qNIa3JXTpOc7iAh7JcG1"
        "DjfyRncHmEbWhdlHZ5WrrrZkU9aOIi2stFWXDZcK+zpQRP/oYFncVvueTm3/fpHyuGRo"
        "YGVlk+dkZ+kiztQgv0IyleVOLNmEesH/3ZEAJPEK/gVNWWvcNcmGsgAAAABJRU5ErkJg"
        "gg=="
    ),
)


CALLWAIT_TIMEOUT  = 60.0
TERMINATE_TIMEOUT = 120
DUMMY_ACTION      = 27536
ACCOUNT_CHOICES   = ['DVBService','Task scheduler']
INDEX_DVBSERVICE  = ACCOUNT_CHOICES.index( 'DVBService' )
INDEX_SCHEDULER   = ACCOUNT_CHOICES.index( 'Task scheduler' )

UPDATE_TIMERS     = 1
UPDATE_STREAM     = 2
UPDATE_RECORDINGS = 4
UPDATE_ALL        = UPDATE_TIMERS | UPDATE_STREAM | UPDATE_RECORDINGS

#connectionMode:
WAIT_CHECK_START_CONNECT  = 0   #wait for free, check if executing, start if not executing, connect
CONNECT                   = 1   #connect
CHECK_CONNECT             = 2   #connect only, if executing

DVBVIEWER_WINDOWS = {
    2007: "SLIDESHOW",
    2000: "TELETEXT",
}

DVBVIEWER_CLOSE = ( "Close DVBViewer", 12326)

FIND_DVBVIEWER_PROCESS = 'select * from Win32_Process where Name="dvbviewer.exe"'

# Channel list properties            
CH_1_NAME        = 1
CH_3_FLAGS       = 3
CH_4_TUNERTYPE   = 4
CH_5_FREQUENCY   = 5
CH_15_ORBITALPOS = 15
CH_20_AUDIOPID   = 20
CH_22_VIDEOPID   = 22
CH_23_TSID       = 23
CH_26_SID        = 26

# GetTimerList properties
TI_0_DESCRIPTION  = 0
TI_1_CHANNEL      = 1
TI_4_ID           = 4
TI_5_DATE         = 5
TI_6_STARTTIME    = 6
TI_7_ENDTIME      = 7
TI_8_ENABLED      = 8
TI_11_RECORDING   = 11
TI_15_DAYS        = 15
TI_18_TIMERACTION = 18

# Recording Entry properties
RE_RECID = 'recID'
RE_CHANNEL = 'channel'
RE_STARTDATE = 'startdate'
RE_DESCRIPTION = 'description'
RE_DURATION = 'duration'
RE_FILENAME = 'filename'
RE_PLAYED = 'played'
RE_TITLE = 'title'
RE_SERIES = 'series'
RE_FROMRS = 'fromRS'

EVENT_LIST = (
    ("Action",                          "Gets fired whenever a new action is processed"),
    ("Channel",                         "Gets fired on every channelchange"),
    ("AddRecord",                       "Gets fired whenever a new Timer is added"),
    ("TimerListUpdated",                "Gets fired whenever the timer list is changed. Watch dog must be enabled."),
    ("StartRecord",                     "Gets fired whenever a recording starts"),
    ("AllActiveRecordingsFinished",     "Gets fired whenever the last active recording finishs"),
    ("Window",                          "Gets fired whenever a OSD-window is activated"),
    ("ControlChange",                   "Gets fired whenever an OSD Control gets the focus"),
    ("SelectedItemChange",              "Gets fired whenever the selectedItem in an OSD list changes"),
    ("RDS",                             "Gets fired whenever a new RDS Text arrives"),
    ("Playlist",                        "Gets fired whenever a new playlistitem starts playing"),
    ("Playbackstart",                   "Gets fired whenever a media playback starts"),
    ("PlaybackEnd",                     "Gets fired whenever a media playback ends"),
    ("Close",                           "Gets fired when the DVBViewer is shutting down"),
    ("PlaystateChange",                 "Gets fired whenever the play state changes"),
    ("RatioChange",                     "Gets fired whenever the ratio changes"),
    ("DisplayChange",                   "Gets fired whenever the display type changes"),
    ("RenderPlaystateChange",           "Gets fired whenever the internal playstate changes"),
    ("RendererChange",                  "Gets fired whenever the renderer changes"),
    ("DVBViewerIsConnected",            "Gets fired whenever the DVBViewer is connected to the plugin"),
    ("DVBViewerCouldNotBeTerminated",   "Gets fired whenever the DVBViewer can't terminated by the plugin"),
    ("DVBViewerEventHandlingNotAlive",  "Gets fired whenever the plugin can't receive events from the DVBViewer" ),
    ("SevereError.WMI",                 "Gets fired when WMI is not available"),
    ("SevereError.LockTimeout",         "Gets fired when a lock timeout or deadlock occurs"),
    ("SevereError.COM",                 "Gets fired on COM initialization errors"),
    ("SevereError.Connect",             "Gets fired on connection errors between EG and DVBViewer"),
    ("Close",                           "Gets fired when the DVBViewer is shutting down"),
)


PGM_ACTIONS = (
    ("WindowMinimize",                   "WindowMinimize",                      None, (16382,True )),
    ("WindowRestore",                    "WindowRestore",                       None, (16397,True )),
    ("Fullscreen",                       "Fullscreen",                          None,     (5,True )),
    ("Screenshot",                       "Screenshot",                          None,   (115,True )),
    ("OnTop",                            "Window always On Top",                None,     (1,True )),
    ("HideMenu",                         "Toggle Menubar",                      None,     (2,True )),
    ("ShowStatusbar",                    "Toggle Statusbar",                    None,     (3,True )),
    ("TitlebarHide",                     "Toggle Window Frame",                 None,    (54,True )),
    ("Toolbar",                          "Toggle Toolbar",                      None,     (4,True )),
    ("HideAll",                          "Toggle Show / Hide All",              None,    (71,True )),
    
    ("Teletext",                         "Toggle Teletext window",              None,    (35,True )),
    ("EPG",                              "Toggle EPG window",                   None,    (37,True )),
    ("Options",                          "Show Options window",                 None,    (24,True )),

    ("ShutdownCard",                     "Shutdown Card",                       None, (12327,True )),
    ("ShutdownMonitor",                  "Shutdown Monitor",                    None, (12328,True )),
    ("Hibernate",                        "Hibernate",                           None, (12323,True )),
    ("Standby",                          "Standby",                             None, (12324,True )),
    ("Slumbermode",                      "Slumbermode",                         None, (12325,True )),
    ("Reboot",                           "Reboot",                              None, (12329,True )),
    ("Shutdown",                         "Shutdown",                            None, (12325,True )),
    ("Exit1",                            "Exit DVBViewer (method I)",           None,     (6,False)),  # ? see 12294?
    ("Exit2",                            "Exit DVBViewer (method II)",          None, (12294,False)),
)

TIMER_ACTIONS = (
    ("RecordSettings",                   "Show Recording dialog",               None,    (33,True )),
    ("Record",                           "Direct Recording",                    None,    (34,True )),
    ("TimeShift",                        "TimeShift Start",                     None,    (50,True )),
    ("TimeShiftWindow",                  "TimeShift Window",                    None,    (51,True )),
    ("TimeshiftStop",                    "Timeshift Stop",                      None,    (52,True )),
    ("KeepTimeshiftFile",                "Toggle keep Timeshift file",          None,  (2012,True )),
)

RECORDING_ACTIONS = (
    ("RecordedShowsAndTimerStatistics",  "Show Recordings window",              None,  (2011,True )),
    ("RefreshRecDB",                     "Refresh Recording DB",                None,  (8260,True )),
    ("CleanupRecDB",                     "Cleanup Recording DB",                None,  (8261,True )),
    ("CompressRecDB",                    "Compress Recording DB",               None,  (8262,True )),
    ("RefreshCleanupCompressRecDB",      "Refresh Cleanup Compress RecDB",      None,  (8263,True )),
)

CHANNEL_ACTIONS = (
    ("Channellist",                      "Channellist",                         None,     (7,True )),
    ("ChannelMinus",                     "Channel -",                           None,     (8,True )),
    ("ChannelPlus",                      "Channel +",                           None,     (9,True )),
    ("Channel0",                         "Channel 0",                           None,    (40,True )),
    ("Channel1",                         "Channel 1",                           None,    (41,True )),
    ("Channel2",                         "Channel 2",                           None,    (42,True )),
    ("Channel3",                         "Channel 3",                           None,    (43,True )),
    ("Channel4",                         "Channel 4",                           None,    (44,True )),
    ("Channel5",                         "Channel 5",                           None,    (45,True )),
    ("Channel6",                         "Channel 6",                           None,    (46,True )),
    ("Channel7",                         "Channel 7",                           None,    (47,True )),
    ("Channel8",                         "Channel 8",                           None,    (48,True )),
    ("Channel9",                         "Channel 9",                           None,    (49,True )),
    ("LastChannel",                      "Last Channel",                        None,    (63,True )),
    ("ClearChannelUsageCounter",         "Clear Channel usage counter",         None,  (8255,True )),
    ("ChannelScan",                      "ChannelScan",                         None,   (119,True )),
    ("ChannelEdit",                      "ChannelEdit",                         None,   (117,True )),
    ("ChannelSave",                      "Channel Save",                        None,    (10,True )),
    ("FavouritePlus",                    "Favourite +",                         None,    (20,True )),
    ("FavouriteMinus",                   "Favourite -",                         None,    (21,True )),
    ("Favourite0",                       "Favourite 0",                         None,    (38,True )),
    ("Favourite1",                       "Favourite 1",                         None,    (11,True )),
    ("Favourite2",                       "Favourite 2",                         None,    (12,True )),
    ("Favourite3",                       "Favourite 3",                         None,    (13,True )),
    ("Favourite4",                       "Favourite 4",                         None,    (14,True )),
    ("Favourite5",                       "Favourite 5",                         None,    (15,True )),
    ("Favourite6",                       "Favourite 6",                         None,    (16,True )),
    ("Favourite7",                       "Favourite 7",                         None,    (17,True )),
    ("Favourite8",                       "Favourite 8",                         None,    (18,True )),
    ("Favourite9",                       "Favourite 9",                         None,    (19,True )),
)

OSD_ACTIONS = (
    ("OSDMenu",                          "OSD-Menu",                            None,   (111,True )),
    ("OSDLeft",                          "OSD-Left",                            None,  (2000,True )),
    ("OSDRight",                         "OSD-Right",                           None,  (2100,True )),
    ("OSDUp",                            "OSD-Up",                              None,    (78,True )),
    ("OSDDown",                          "OSD-Down",                            None,    (79,True )),
    ("OSDOK",                            "OSD-OK",                              None,    (73,True )),
    ("OSDRed",                           "OSD-Red",                             None,    (74,True )),
    ("OSDGreen",                         "OSD-Green",                           None,    (75,True )),
    ("OSDYellow",                        "OSD-Yellow",                          None,    (76,True )),
    ("OSDBlue",                          "OSD-Blue",                            None,    (77,True )),
    ("OSDFirst",                         "OSD-First",                           None,    (80,True )),
    ("OSDLast",                          "OSD-Last",                            None,    (81,True )),
    ("OSDPrevious",                      "OSD-Previous",                        None,    (82,True )),
    ("OSDNext",                          "OSD-Next",                            None,    (83,True )),
    ("OSDClose",                         "OSD-Close",                           None,    (84,True )),
    ("OSDPositioning",                   "OSD-Positioning",                     None,    (85,True )), #???
    ("OSDClock",                         "OSD-Clock",                           None,  (2010,True )),
    ("OSDShowHTPC",                      "OSD-Show HTPC",                       None,  (2110,True )),
    ("OSDBackgroundToggle",              "OSD-Background Toggle",               None,  (8194,True )),
    ("ToggleBackground",                 "OSD Toggle Background",               None, (12297,True )),
    ("OSDTeletext",                      "OSD-Teletext",                        None,   (101,True )),
    ("OSDShowTimer",                     "OSD-Show Timer",                      None,  (8195,True )),
    ("OSDShowRecordings",                "OSD-Show Recordings",                 None,  (8196,True )),
    ("OSDShowNow",                       "OSD-Show Now",                        None,  (8197,True )),
    ("OSDShowEPG",                       "OSD-Show EPG",                        None,  (8198,True )),
    ("OSDShowChannels",                  "OSD-Show Channels",                   None,  (8199,True )),
    ("OSDShowFavourites",                "OSD-Show Favourites",                 None,  (8200,True )),
    ("OSDShowTimeline",                  "OSD-Show Timeline",                   None,  (8201,True )),
    ("OSDShowSubtitlemenu",              "OSD-Show Subtitlemenu",               None,  (8247,True )),
    ("OSDShowAudiomenu",                 "OSD-Show Audiomenu",                  None,  (8248,True )),
    ("OSDShowPicture",                   "OSD-Show Picture",                    None,  (8202,True )),
    ("OSDShowMusic",                     "OSD-Show Music",                      None,  (8203,True )),
    ("OSDShowVideo",                     "OSD-Show Video",                      None,  (8204,True )),
    ("OSDShowNews",                      "OSD-Show News",                       None,  (8205,True )),
    ("OSDShowWeather",                   "OSD-Show Weather",                    None,  (8206,True )),
    ("OSDShowMiniepg",                   "OSD-Show Miniepg",                    None,  (8207,True )),
    ("OSDShowMusicPlaylist",             "OSD-Show Music playlist",             None,  (8208,True )),
    ("OSDShowVideoPlaylist",             "OSD-Show Video playlist",             None,  (8209,True )),
    ("OSDShowComputer",                  "OSD-Show Computer",                   None,  (8210,True )),
    ("OSDShowAlarms",                    "OSD-Show Alarms",                     None,  (8212,True )),
    ("ShowVersion",                      "OSD-Show Version",                    None, (16384,True )),
    ("ShowCurrentInfo",                  "OSD-Show Current Info",               None,  (8264,True )),
    ("ShowRadioList",                    "OSD-Show Radio List",                 None,  (8265,True )),
    ("OSDCAM",                           "OSD-CAM",                             None,  (8259,True )),
)

PLAY_ACTIONS = (
    ("Play",                             "Play",                                None,    (92,True )),
    ("Pause",                            "Pause",                               None,     (0,True )),
    ("Previous",                         "Previous",                            None,   (112,True )),
    ("Next",                             "Next",                                None,   (113,True )),
    ("Stop",                             "Stop",                                None,   (114,True )),
    ("JumpMinus10",                      "Jump Minus 10",                       None,   (102,True )),
    ("JumpPlus10",                       "Jump Plus 10",                        None,   (103,True )),
    ("Forward",                          "Forward",                             None, (12304,True )),
    ("Rewind",                           "Rewind",                              None, (12305,True )),
    ("SpeedUp",                          "Speed Up",                            None, (12382,True )),
    ("SpeedDown",                        "Speed Down",                          None, (12383,True )),
    ("Playlist",                         "Playlist",                            None,    (64,True )),
    ("PlaylistFirst",                    "Playlist First",                      None,    (65,True )),
    ("PlaylistNext",                     "Playlist Next",                       None,    (66,True )),
    ("PlaylistPrevious",                 "Playlist Previous",                   None,    (67,True )),
    ("PlaylistLoop",                     "Playlist Loop",                       None,    (68,True )),
    ("PlaylistStop",                     "Playlist Stop",                       None,    (69,True )),
    ("PlaylistRandom",                   "Playlist Random",                     None,    (70,True )),
    ("AddBookmark",                      "Add Bookmark",                        None, (12306,True )),
    ("ShowPlaylist",                     "Show Playlist",                       None, (12384,True )),
    ("OpenFile",                         "Open File",                           None,    (94,True )),
    ("LastFile",                         "Last File",                           None,   (118,True )),
    ("ShowHelp",                         "Show Help",                           None,  (8213,True )),
    ("PlayAudioCD",                      "Play AudioCD",                        None,  (8257,True )),
    ("PlayDVD",                          "Play DVD",                            None,  (8250,True )),
    ("EjectCD",                          "Eject CD",                            None, (12299,True )),
    ("DVDMenu",                          "DVD Menu",                            None,  (8246,True )),
)

VIDEO_AUDIO_ACTIONS = (
    ("RebuildGraph",                     "Rebuild Graph",                       None,    (53,True )),
    ("StopGraph",                        "Stop Graph",                          None, (16383,False)),
    ("StopRenderer",                     "Stop Renderer",                       None,  (8256,False)),
    ("TogglePreview",                    "Toggle Preview",                      None, (16395,True )),
    ("ToggleMosaicpreview",              "Toggle Mosaic preview",               None,  (8211,True )),
    ("Desktop",                          "Desktop",                             None,    (32,True )),
    ("PortalSelect",                     "Portal select",                       None,  (8254,True )),
    ("Display",                          "Show Display dialog",                 None,    (28,True )),
    ("Aspect",                           "Toggle Aspect Ratio",                 None,    (22,True )),
    ("BestWidth",                        "Set Best Window Width",               None,    (89,True )),
    ("BrightnessUp",                     "Brightness Up",                       None,    (55,True )),
    ("BrightnessDown",                   "Brightness Down",                     None,    (56,True )),
    ("SaturationUp",                     "Saturation Up",                       None,    (57,True )),
    ("SaturationDown",                   "Saturation Down",                     None,    (58,True )),
    ("ContrastUp",                       "Contrast Up",                         None,    (59,True )),
    ("ContrastDown",                     "Contrast Down",                       None,    (60,True )),
    ("HueUp",                            "Hue Up",                              None,    (61,True )),
    ("HueDown",                          "Hue Down",                            None,    (62,True )),
    ("RestoreDefaultColors",             "Restore Default Colors",              None, (16396,True )),
    ("ShowVideowindow",                  "Show Video window",                   None,   (821,True )),
    ("HideVideowindow",                  "Hide Video window",                   None,  (8214,True )),

    ("Mute",                             "Toggle Mute",                         None,    (25,True )),
    ("VolumeUp",                         "Volume Up",                           None,    (26,True )),
    ("VolumeDown",                       "Volume Down",                         None,    (27,True )),
    ("AudioChannel",                     "Toggle Audio Channel",                None,    (72,True )),
    ("Equalizer",                        "Show Equalizer dialog",               None,   (116,True )),
    ("StereoLeftRight",                  "Stereo/Left/Right",                   None,    (95,True )),

    ("DisableAudio",                     "Disable Audio",                       None, (16385,True )),
    ("DisableAudioVideo",                "Disable AudioVideo",                  None, (16386,True )),
    ("DisableVideo",                     "Disable Video",                       None, (16387,True )),
    ("EnableAudioVideo",                 "Enable AudioVideo",                   None, (16388,True )),
    ("VideoOutputAB",                    "Video Output A/B",                    None,   (132,True )),
    ("AudioOutputAB",                    "Audio Output A/B",                    None,   (133,True )),
    ("DVBSourceProperties",              "Show DVB Source Properties dialog",   None,   (134,True )),

    ("Zoom",                             "Show Zoom dialog",                    None,    (23,True )),
    ("Zoom50",                           "Zoom window 50%",                     None,    (29,True )),
    ("Zoom75",                           "Zoom window 75%",                     None,  (2013,True )),
    ("Zoom100",                          "Zoom window 100%",                    None,    (30,True )),
    ("Zoom200",                          "Zoom window 200%",                    None,    (31,True )),
    ("ZoomUp",                           "Zoom image Up",                       None,   (104,True )),
    ("ZoomDown",                         "Zoom image Down",                     None,   (105,True )),
    ("ZoomlevelStandard",                "Zoomlevel Standard",                  None, (16389,True )),
    ("Zoomlevel0",                       "Zoomlevel 0",                         None, (16390,True )),
    ("Zoomlevel1",                       "Zoomlevel 1",                         None, (16391,True )),
    ("Zoomlevel2",                       "Zoomlevel 2",                         None, (16392,True )),
    ("Zoomlevel3",                       "Zoomlevel 3",                         None, (16393,True )),
    ("ZoomlevelToggle",                  "Zoomlevel Toggle",                    None, (16394,True )),
    ("StretchHUp",                       "StretchH Up",                         None,   (106,True )),
    ("StretchHDown",                     "StretchH Down",                       None,   (107,True )),
    ("StretchVUp",                       "StretchV Up",                         None,   (108,True )),
    ("StretchVDown",                     "StretchV Down",                       None,   (109,True )),
    ("StretchReset",                     "Stretch Reset",                       None,   (110,True )),
)

RS_ACTIONS = (
    ("ServiceStandby",                   "Recording Service Standby",           None,  (8272,True )),
    ("ServiceHibernate",                 "Recording Service Hibernate",         None,  (8274,True )),
    ("ServiceShutdown",                  "Recording Service Shutdown",          None,  (8273,True )),
    ("ServiceWakeOnLAN",                 "Recording Service Wake on LAN",       None,  (8275,True )),
    ("ServiceGetEPG",                    "Recording Service Get EPG",           None,  (8276,True )),
)


import wx
import sys
import os
import random
import hashlib
import inspect
from pythoncom import CoInitialize, CoUninitialize
from pythoncom import CoCreateInstance, CLSCTX_INPROC_SERVER, IID_IPersistFile
from pywintypes import Time as PyTime
import wx.lib.masked as masked
from functools import partial
from copy import deepcopy as cpy
from win32com.client import Dispatch, DispatchWithEvents
from win32com.client.gencache import EnsureDispatch
from win32com.client import GetObject
from win32com.taskscheduler import taskscheduler
from eg.WinApi import SendMessageTimeout
from threading import Thread, Event, Timer, Lock
from time import time, strptime, mktime, ctime, strftime, localtime, asctime, sleep
from datetime import datetime as dt, timedelta as td
from eg.WinApi.Dynamic import (
    byref, sizeof, CreateProcess, WaitForSingleObject, FormatError,
    CloseHandle, create_unicode_buffer,
    STARTUPINFO, PROCESS_INFORMATION,
    CREATE_NEW_CONSOLE, STARTF_USESHOWWINDOW
)
from urllib2 import ( HTTPPasswordMgrWithDefaultRealm, HTTPPasswordMgr, HTTPBasicAuthHandler,
                      install_opener, urlopen, Request, build_opener
                    )
from urlparse import urlparse
from re import search as reSearch
from base64 import encodestring as encodestring64
from xml.etree import cElementTree as ElementTree
from tempfile import NamedTemporaryFile
from _winreg import OpenKey, QueryValue, CloseKey, HKEY_LOCAL_MACHINE, KEY_READ


class Text:
    interfaceBox          = "Interface API"
    useComApi             = "COM-API (for DVBViewer Pro)"
    useSendMessage        = "SendMessage-API (for DVBViewer GE)"
    eventType             = "Events"
    useOldEvents          = "Like version 1.2       "
    useNewEvents          = "New definitions       "
    eventFormat           = "Event format"
    playStateFormat       = "PlayState format"
    displayChangeFormat   = "DisplayChange format"
    long                  = "Long "
    short                 = "Short"
    dvbViewerStart        = "Start DVBViewer"
    useCommandLine        = "Through command line"
    dvbviewerFile         = "DVBViewer filepath: "
    dvbviewerArguments    = "Arguments: "
    waitTimeBeforeConnect = "Waiting time after detection of the window: "
    dvbviewerBox          = "Choose the DVBViewer"
    watchDog              = "Watch dog"
    useWatchDog           = "Enable"
    watchDogTime          = "Watch dog cycle time: "
    accountInfo           = "Account information"
    accountName           = "Account name: "
    password1             = "Password: "
    password2             = "Repeat password: "
    changePassword        = "Change password"
    taskSchedulerInfo     = "Task scheduler information"
    schedulerPrefix       = "Task name prefix: "
    schedulerEvent        = "Event: "
    schedulerLead         = "Lead time [min]: "
    scheduleAllRecordings = "All recordings"
    schedulerEntryHidden  = "Hidden"
    accountChoices        = ACCOUNT_CHOICES
    accountType           = "Account type: "
    serviceHeader         = "DVBViewerService"
    serviceEnable         = "Enable     "
    serviceAddress        = "Address and Web port"
    serviceEvent          = "Source event name"

    serviceDVBViewer      = "By DVBViewer"
    serviceDVBService     = "By DVBViewerService"
    serviceUpdate         = "Update from DVBViewerService"


    class Start :
        name = "Start DVBViewer"
        description = "Start DVBViewer through COM-API. For DVBViewer Pro only."

    class IsConnected :
        name = "Is DVBViewer running and connected"
        description = "Returns True if DVBViewer is running and the plugin is connected to DVBViewer's COM API."

    class IsDVBViewerProcessRunning :
        name = "Is dvbviewer.exe running"
        description = "Returns True if program 'dvbviewer.exe' is running"

    class CloseDVBViewer :
        name = DVBVIEWER_CLOSE[0]
        checkBoxText = "Wait until DVBViewer is terminated"

    class StopAllActiveRecordings :
        name = "Stop all active recording timers"
        description = "Remove all active recording timers from the timer list."

    class SendAction :
        name   = "Send generic action to DVBViewer"
        action = "Action ID: "

    class GetSetupValue :
        name = "Get a value from the setup.xml of DVBViewer"
        section = "Section: "
        setupName = "Name: "
        default = "Default: "

    class GetDateOfRecordings :
        name = "Get dates of next recording timers"
        description =   (
                            "Deprecated, will be removed in a future release - use GetTimerDetails instead. "
                            "Return the date(s) of the recording timers as a floating "
                            "point number expressed in seconds since the epoch, in UTC. "
                            "The date is negative if no recording is planed. "
                        )
        allDates = "Get dates of all recoding timers (otherwise: get date of next timer)"

    class GetRecordingsIDs :
        name   = "Get IDs of recording timers"
        description = "Returns a list of recording IDs"
        active = "Get only IDs of active recording timers"
        
    class IsRecording :
        name = "Is Recording"
        description = "Returns True if recording is ongoing"

    class GetNumberOfActiveRecordings :
        name = "Get number of active recordings"
        description = "Returns the number of currently ongoing recordings."

    class GetTimerDetails :
        name = "Get recording timer details"
        description = (
            "Returns a list of planned recording timer details."
        )
        allRecordings = "Get all recording timers (otherwise: just the next one)"
        enabled = "Skip disabled timers"
        active = "Get only currently active recording timers"

    class GetChannelDetails:
        name = "Get channel details"
        description = (
            "Get the details of one or all DVBViewer channels. The result is a data dictionary provided in 'eg.result'. " 
            "The returned list items correspond to DVBViewer's COM API."
        )
        allChannelsDescr = "Get the details of all channels (full channel list)"
        currentChannelDescr = "Get the details of current channel"
        channelIDDescr = "Channel by ID (note that the channel list might be empty if DVBViewer is not running)"
        channelID = "Channel ID"
        fromVariableDescr = "Evaluate channelID from global variable"
        variableName = "Variable name"
        tvButton = "TV"
        radioButton = "Radio"
        allChannels = "All channels"
        currentChannel = "Current channel"
        
    class TuneChannel :
        name = "Tune to a channel"
        description =   (
            "Tunes to an arbitrary channel as designated with the given channelID. "
            "This action is mainly intended to be called by other actions or to be called by scripts. "
        )
        channelIDDescr = "Channel by ID (note that the channel list might be empty if DVBViewer is not running)"
        channelID = "Channel ID"
        fromVariableDescr = "Evaluate channelID from global variable"
        variableName = "Variable name"
        tvButton = "TV"
        radioButton = "Radio"

    class ShowWindow :
        name     = "Show specific OSD window"
        windowID = "WindowID"

    class ShowInfoinTVPic :
        name  = "Show OSD info bar in TV picture"
        text  = "Displayed message: "
        time  = "Timeinterval [s]: "
        force = "Start DVBViewer if not executing"

    class DeleteInfoinTVPic :
        name  = "Hide OSD info bar in TV picture"

    class UpdateEPG :
        name = "Update EPG"
        description =   (
                            "For updating, one channel change for each transponder/booklet "
                            "will be done. If a recording is active, the executing of the EPG "
                            "update will be delayed until the recording is finished. "
                        )
        disableAV             = "Disable AV"
        time  = "Time between channel change: "
        event = "Fired event after update finished: "

    class AddRecording :
        name = "Add a recording timer"
        description = (
                    'Add a recording timer . This action should '
                    'be used by scripts or other plugins. The configuration '
                    'tool is only for demonstration. '
                    'This command might not be supported in newer versions because '
                    'DVBViewer might not support functions which are necessary '
                    'for this macro in future. '
                    )

        source                = "Source"
        station               = "Station: "
        tvButton              = "TV"
        radioButton           = "Radio"
        recordingDate         = "Date"
        date                  = "Date of recording: "
        start                 = "Start time: "
        end                   = "End time: "
        days                  = ( "Mo","Tu", "We","Th","Fr","Sa","Su" )
        recordingDescription  = "Description: "
        disableAV             = "Disable AV"
        enabled               = "Enable recording"
        mode                  = "Mode: "
        recActionChoices      = ( "intern", "tune only",  "AudioPlugin", "Videoplugin" )
        afterRecording        = "After recording: "
        actionAfterRecChoices = ( "No action","PowerOff", "Standby", "Hibernate",
                                  "Close", "Playlist", "Slumbermode" )


    class TaskScheduler :
        name = "Update Windows task scheduler"
        description = (
                    'Add the DVBViewer timer list entries to the Windows task scheduler. '
                    'To use this feature, the account name and the password should be entered '
                    'in the configuration of the DVBViewer plugin. The password is written '
                    'encryped in the file "DVBViewerAccount.dat" located in the event ghost user '
                    'directory.'
                    )


    class GetNumberOfClients :
        name =          "Get the number of clients connected to the service"
        serviceUpdate = " Update from DVBViewerService"


    class IsEPGUpdating :
        name =          "Get the DVBViewerService EPG update status"
        serviceUpdate = " Update from DVBViewerService"


def toDateTime( dateP, timeP ) :
    temp = mktime( localtime(int(dateP)) ) + float( timeP ) * 60 * 60 * 24
    #print localtime( temp )
    return temp


def toTimerEntry(timerID, channelID, channelName, dateStr, startTimeStr, endTimeStr, startDateTime, endDateTime, 
                 days, description, enabled, recording, action, fromRS):
        timerentry = {
            'timerID': str(timerID),
            'channelID': long(channelID), 
            'channelName': unicode(channelName), 
            'date': str(dateStr), 
            'startTime': str(startTimeStr), 
            'endTime': str(endTimeStr), 
            'startDateTime': float(startDateTime),
            'endDateTime': float(endDateTime),
            'days': str(days), 
            'description': unicode(description), 
            'enabled': bool(enabled), 
            'recording': bool(recording), 
            'action': int(action),
            'fromRS': bool(fromRS)
        }
        #print "timerentry=", timerentry
        return timerentry


def toRecordingEntry(recID, channel, startDate, description, duration, filename, played, title, series, fromRS):
        recordingEntry = {
            RE_RECID:       int(recID),
            RE_CHANNEL:     unicode(channel), 
            RE_STARTDATE:   startDate, 
            #RE_DESCRIPTION: unicode(description), 
            RE_DURATION:    duration,  
            RE_FILENAME:    unicode(filename), 
            RE_PLAYED:      played,
            RE_TITLE:       unicode(title),
            RE_SERIES:      unicode(series),
            RE_FROMRS:      bool(fromRS)
        }
        #print "recordingEntry=", recordingEntry
        return recordingEntry


@eg.LogItWithReturn
def WaitForDVBViewerWindow( timeout=60.0 ) :
    winmatcher = eg.WindowMatcher( u'dvbviewer.exe', 
                          winName=u'DVB Viewer{*}', 
                          includeInvisible=True, 
                          timeout=timeout )
    return winmatcher()


class DVBViewer(eg.PluginClass):
    '''
    The DVBViewer plugin main class. Responsible for configuration, setup and runstate management of the plugin.
    One of the central points in the plugin class is 'Connect()' - see there
    A few other "hot spots" in the plugin:
    - DVBViewerWatchdogThread - background thread which monitors the dvbviewer.exe program state and initializes the DVBViewerWorkerThread
    - DVBViewerWorkerThread - a "standby thread". Connects to DVBViewer's COM API and provides all methods to be executed on COM.
    - ComEventHandler - handles events from DVBViewer and triggers EventGhost events from it
    - DVBViewerService - provides methods to access and manage information from Recording Service
    '''
    text = Text

    @eg.LogIt
    def __init__(self):
        self.AddEvents(*EVENT_LIST)
        
        group = self.AddGroup("DVBViewer program actions", "DVBViewer program management")
        group.AddAction(Start)
        group.AddAction(IsConnected)
        group.AddAction(IsDVBViewerProcessRunning)
        group.AddAction(GetNumberOfClients)
        group.AddAction(GetSetupValue)
        group.AddAction(GetDataManagerValues)
        group.AddAction(WaitUntilPluginIdle)
        group.AddAction(CloseDVBViewer)
        group.AddActionsFromList(PGM_ACTIONS, ActionPrototype)
        
        group = self.AddGroup("Timer actions", "Timer related actions")
        group.AddAction(GetTimerDetails)
        group.AddAction(AddRecording)
        group.AddAction(GetDateOfRecordings)
        group.AddAction(GetRecordingsIDs)
        group.AddAction(IsRecording)
        group.AddAction(GetNumberOfActiveRecordings)
        group.AddAction(StopAllActiveRecordings)
        group.AddAction(TaskScheduler)
        group.AddActionsFromList(TIMER_ACTIONS, ActionPrototype)

        group = self.AddGroup("Recording actions", "Recording related actions")
        group.AddAction(GetRecordingDetails)
        group.AddAction(DeleteRecordings)
        group.AddActionsFromList(RECORDING_ACTIONS, ActionPrototype)
        
        group = self.AddGroup("Channel actions", "Channel related actions")
        group.AddAction(GetChannelDetails)
        group.AddAction(TuneChannel)
        group.AddAction(GetCurrentShowDetails)
        group.AddActionsFromList(CHANNEL_ACTIONS, ActionPrototype)
        
        group = self.AddGroup("OSD actions", "OSD related actions")
        group.AddAction(ShowInfoinTVPic)
        group.AddAction(DeleteInfoinTVPic)
        group.AddAction(ShowWindow)
        group.AddActionsFromList(OSD_ACTIONS, ActionPrototype)

        group = self.AddGroup("Play actions", "Player related actions")
        group.AddActionsFromList(PLAY_ACTIONS, ActionPrototype)
        
        group = self.AddGroup("Video and audio actions", "Video and audio related actions")
        group.AddActionsFromList(VIDEO_AUDIO_ACTIONS, ActionPrototype)
        
        group = self.AddGroup("Various actions", "Various DVBViewer and Recording Service actions")
        group.AddAction(SendAction)
        group.AddAction(UpdateEPG)
        group.AddAction(IsEPGUpdating)
        group.AddActionsFromList(RS_ACTIONS, ActionPrototype)
        
        self.AddAction(GetDVBViewerObject, hidden = True)
        self.AddAction(ExecuteDVBViewerCommandViaCOM, hidden = True)

        # create a new subclass of the EventHandler with the ability to use
        # the plugin's TriggerEvent method
        class SubEventHandler(ComEventHandler):
            plugin = self
            TriggerEvent = self.TriggerEvent
        self.EventHandler = SubEventHandler

        self.workerThread    = None
        self.terminateThread = None
        self.watchDogThread  = None

        self.oldInterface = False
        self.newInterface = True
        self.startDVBViewerByCOM = False,
        self.pathDVBViewer = ""

        #new status variables
        self.DVBViewerStartedByCOM = False

        self.numberOfActiveTimers = 0
        self.actualWindowID = -1
        self.actualDisplayMode=""
        self.lastDisplayMode=""
        self.actualChannel = -1
        self.actualRendererType = 0
        self.lastRendererType = -1
        self.actualPlayState = "STOP"
        self.lastPlayState = ""
        self.playBackMode = "NONE"
        self.lastRatio   = -1
        self.actualRatio = -1

        self.tvChannels = []
        self.radioChannels = []
        self.channelIDbyIDList = {}
        self.IDbychannelIDList = {}
        self.channelDetailsList = {}

        self.frequencies = {}

        self.completeTimerInfo = []

        self.indexTV    = 0
        self.indexRadio = 0

        self.tuneEPGThread = None
        self.disableAV = False

        self.accounts  = self.HandlePasswordFile()
        if self.accounts is None or len( self.accounts ) != len( self.text.accountChoices ) :
            self.accounts  = [('','')]*len( self.text.accountChoices )

        self.key = []
        self.scheduledRecordings = []
        self.numberOfScheduledRecordings = -1

        self.updateRecordingsLock      = LockWithTimeout( self, "UpdateDVTimers" )
        self.updateDVTimer = None

        self.executionStatusChangeLock = LockWithTimeout( self, "ExecutionStatusChange" )
        self.lockedByTerminate = False

        def CheckEventHandlingTimeoutFunc(self) :
            self.checkEventHandlingLock.release()
            self.eventHandlingIsAlive = False
            eg.PrintDebugNotice("DVBViewer event handling not alive")
            self.TriggerEvent( "DVBViewerEventHandlingNotAlive" )
            return False

        # purpose of checkEventHandlingLock is just to TriggerEvent( "DVBViewerEventHandlingNotAlive" ) in case that event handling does not work
        self.checkEventHandlingLock = LockWithTimeout(self, "CheckEventHandling", partial(CheckEventHandlingTimeoutFunc, self))
        self.eventHandlingIsAlive = True

        def WaitForTerminationTimeoutFunc(self) :
            self.closeWaitLock.release()
            self.closeWaitActive = False
            self.timeout=True
            eg.PrintDebugNotice("DVBViewer could not be terminated")
            self.TriggerEvent( "DVBViewerCouldNotBeTerminated" )
            return False

        # purpose of closeWaitLock is just to TriggerEvent( "DVBViewerCouldNotBeTerminated" ) in case that DVBViewer does not terminate
        self.closeWaitLock    = LockWithTimeout(self, "CloseWait", partial(WaitForTerminationTimeoutFunc, self))
        self.closeWaitActive  = False
        self.timeout=False

        self.infoInTVPictimeout=0.0

        self.serviceInUse = LockWithTimeout( self, 'ServiceInUse', severeErrorEventOnTimeout=False )
        self.DVBViewerService = None
        self.useService = False



    @eg.LogItWithReturn
    def __start__(  self,
                    useSendMessage=False,
                    oldInterface=True,
                    newInterface=False,
                    startDVBViewerByCOM=False,
                    pathDVBViewer="",
                    argumentsDVBViewer="",
                    longDisplay=True,
                    shortDisplay=False,
                    longPlayState=True,
                    shortPlayState=False,
                    useService=False,
                    watchDogTime=60.0,
                    schedulerTaskNamePrefix="StartRecording",
                    schedulerEventName     ="StartRecording",
                    schedulerLeadTime      =3.0,
                    scheduleAllRecordings  =False,
                    schedulerEntryHidden   =False,
                    waitTimeBeforeConnect=5.0,
                    serviceAddress='127.0.0.1:80',
                    serviceEvent='DVBViewerService',
                    dummy=False
                    ) :

        eg.PrintDebugNotice("DVBViewer plugin " + PLUGIN_VERSION)
        
        pathDVBViewer = self.CheckGetDVBViewerPath(pathDVBViewer)

        if useSendMessage:
            self.SendCommand = self.SendCommandThroughSendMessage
        else:
            self.SendCommand = self.SendCommandThroughCOM
            self.watchDogThread = DVBViewerWatchDogThread( self, watchDogTime )
            self.watchDogThread.start()
            
        self.oldInterface = oldInterface
        self.newInterface = newInterface
        self.startDVBViewerByCOM   = startDVBViewerByCOM
        self.waitTimeBeforeConnect = waitTimeBeforeConnect
        self.pathDVBViewer         = eg.ParseString(pathDVBViewer)
        self.argumentsDVBViewer    = eg.ParseString(argumentsDVBViewer)
        self.longDisplayEvent      = longDisplay
        self.shortDisplayEvent     = shortDisplay
        self.longPlayStateEvent    = longPlayState
        self.shortPlayStateEvent   = shortPlayState

        self.schedulerTaskNamePrefix = schedulerTaskNamePrefix
        self.schedulerEventName      = schedulerEventName
        self.schedulerLeadTime       = schedulerLeadTime
        self.scheduleAllRecordings   = scheduleAllRecordings
        self.schedulerEntryHidden    = schedulerEntryHidden

        self.scheduledRecordings = []
        self.numberOfScheduledRecordings = -1

        self.firedRecordingsIDs = []

        eg.PrintDebugNotice( "DVBViewer plugin started on " + strftime("%d %b %Y %H:%M:%S", localtime() ))

        self.useService     = useService
        self.serviceAddress = serviceAddress
        self.serviceEvent   = serviceEvent

        if not self.useService :
            return True

        #DVBViewerService:
        self.service = DVBViewerService( self, serviceAddress = self.serviceAddress,
                                         account = self.accounts[INDEX_DVBSERVICE],
                                         serviceEvent = self.serviceEvent )
        return True


    @eg.LogItWithReturn
    def __stop__(self):
        # If watchDogThread has been paused, resume it again
        if self.watchDogThread is not None:
            self.watchDogThread.pauseEvent.set()

        # If the DVBViewer was started by the COM interface, the DVBViewer must be terminated before
        # stopping the DVBViewer Thread. Otherwise the DVBViewer is going into an endless loop.
        if self.tuneEPGThread is not None :
            self.tuneEPGThread.Finish()

        if self.watchDogThread is not None :
            self.watchDogThread.Finish()
            self.watchDogThread.join()
            self.watchDogThread = None

        self.executionStatusChangeLock.acquire( timeout=TERMINATE_TIMEOUT )
        if self.workerThread is not None :
            if self.DVBViewerStartedByCOM :
                self.executionStatusChangeLock.release()
                StopAllActiveRecordings()
                self.WaitForTermination( sendCloseCommand = True )
            else :
                if self.workerThread.Stop( TERMINATE_TIMEOUT ) :
                    eg.PrintError("Could not terminate DVBViewer thread")
                self.executionStatusChangeLock.release()
        else :
            self.executionStatusChangeLock.release()

        if self.DVBViewerService is not None :
            del self.DVBViewerService

        return True


    @eg.LogIt
    def OnComputerSuspend(self, suspendType):
        pass
    
    @eg.LogIt
    def OnComputerResume(self, suspendType):
        # If watchDogThread has been paused, resume it again
        if self.watchDogThread is not None:
            self.watchDogThread.pauseEvent.set()

    @eg.LogIt
    def DVBViewerIsFinished( self ) :
        self.UpdateDVTimers( lock = False )
        self.TriggerEvent( "Close" )
        if self.closeWaitActive :
            self.closeWaitActive = False
            self.closeWaitLock.release()
        self.checkEventHandlingLock.acquire( blocking = False )
        self.checkEventHandlingLock.release()
        return True


    @eg.LogIt
    def UpdateDisplayMode( self ) :
        windowID = self.actualWindowID
        if windowID in DVBVIEWER_WINDOWS :
            self.actualDisplayMode = DVBVIEWER_WINDOWS[ windowID ]
        elif windowID != -1 and windowID != 500 :
            self.actualDisplayMode="OSD"
        else :
            if self.actualChannel >= 0 :
                self.actualDisplayMode="TV"
            else :
                self.actualDisplayMode = self.playBackMode
        if self.lastDisplayMode != self.actualDisplayMode :
            self.lastDisplayMode = self.actualDisplayMode
            if self.longDisplayEvent :
                self.TriggerEvent( "DisplayChange:"+ self.actualDisplayMode, self.actualDisplayMode )
            if self.shortDisplayEvent :
                self.TriggerEvent( "DisplayChange", self.actualDisplayMode )
        return True


    @eg.LogIt
    def UpdateDVTimersByDVEvent( self ) :

        @eg.LogItWithReturn
        def UpdateDVTimers(self) :
            plugin = self
            plugin.updateRecordingsLock.acquire()
            plugin.updateDVTimer = None
            plugin.updateRecordingsLock.release()
            plugin.UpdateDVTimers()

        self.updateRecordingsLock.acquire()

        if self.updateDVTimer is not None :
            self.updateDVTimer.cancel()

        self.updateDVTimer = Timer( 1.1, self.UpdateDVTimers )  #A thread is necessary in case of a DVBViewer dead lock
        self.updateDVTimer.start()

        self.updateRecordingsLock.release()

        return True


    @eg.LogIt
    def UpdateDVTimers( self, lock=True, updateService=False ) :

        timerIDs = []
        completeTimerInfo = []

        updatedTimers = 0
        started = False

        try:
            if self.workerThread is not None :
                if lock:
                    self.executionStatusChangeLock.acquire()
                try:
                    completeTimerInfo = self.workerThread.CallWait(
                        partial(self.workerThread.GetTimers, False, updateService ),
                        CALLWAIT_TIMEOUT
                    )
                finally:
                    if lock:
                        self.executionStatusChangeLock.release()
                        
                timerIDs = [ record[ TI_4_ID ] for record in completeTimerInfo if record[ TI_11_RECORDING ] ]
                started = True
    
            numberOfActiveTimers = len( timerIDs )
    
            newTimerIDs = [ ID for ID in timerIDs if ID not in self.firedRecordingsIDs ]
            #print "newTimerIDs = ", newTimerIDs
    
    
            if self.numberOfActiveTimers != numberOfActiveTimers or len( newTimerIDs ) != 0 :
    
                deletedTimerIDs = [ ( count, ID ) for count, ID in enumerate( self.firedRecordingsIDs ) if ID not in timerIDs ]
                #print "deletedTimerIDs = ", deletedTimerIDs
                #print "self.firedRecordingsIDs = ", self.firedRecordingsIDs
    
                removed = 0
                for v in deletedTimerIDs :
                    self.numberOfActiveTimers -= 1
                    if self.oldInterface :
                        self.TriggerEvent( "EndRecord" )
                    if self.newInterface :
                        self.TriggerEvent( "EndRecord", ( v[1], self.numberOfActiveTimers ) )
                    del self.firedRecordingsIDs[ v[0] - removed ]
                    removed += 1
    
                for ID in newTimerIDs :
                    self.numberOfActiveTimers += 1
                    if self.oldInterface :
                        self.TriggerEvent("StartRecord", str(ID) )
                    if self.newInterface :
                        self.TriggerEvent( "StartRecord", ( ID, self.numberOfActiveTimers ) )
                    self.firedRecordingsIDs.append( ID )
    
                newRecordings     = len( newTimerIDs )
                updatedTimers = newRecordings + removed
    
                if self.numberOfActiveTimers == 0 :
                    self.TriggerEvent( "AllActiveRecordingsFinished" )
    
            if started :
                if completeTimerInfo != self.completeTimerInfo :
                    self.TriggerEvent( "TimerListUpdated" )
                    self.completeTimerInfo = completeTimerInfo
        except Exception, exc:
            msg = 'Unexpected error: ' + unicode(exc)
            eg.PrintTraceback(msg)

        return updatedTimers


    @eg.LogIt
    def SendCommandThroughSendMessage(self, value, lock=True, connectionMode=WAIT_CHECK_START_CONNECT):
        try:
            hwnd = WaitForDVBViewerWindow()[0]
            return SendMessageTimeout(hwnd, 45762, 2069, 100 + value)
        except:
            raise self.Exceptions.ProgramNotRunning
        return True


    @eg.LogIt
    def SendCommandThroughCOM(self, value, lock=True, connectionMode=WAIT_CHECK_START_CONNECT ):
        executed = False
        if lock :
            self.executionStatusChangeLock.acquire()
        try:
            if self.Connect( connectionMode ) :
                self.workerThread.CallWait(
                    partial(self.workerThread.dvbviewer.SendCommand, value),
                    CALLWAIT_TIMEOUT
                )
                executed = True
        finally:
            if lock :
                self.executionStatusChangeLock.release()
        return executed


    #@eg.LogIt
    def IsDVBViewerProcessRunning(self, WMI=None, eventOnException=True):
        '''Checks if the dvbviewer.exe process is running'''
        wmiCreated = WMI is None
        if wmiCreated:
            try:
                WMI = None
                WMI = GetObject('winmgmts:')
            except Exception, exc:
                msg = 'Error getting WMI object: ' + unicode(exc)
                eg.PrintError(msg)
                if eventOnException:
                    self.TriggerEvent('SevereError.WMI', msg)
                del WMI
                return False
            
        running = False
        try:
            running = len( WMI.ExecQuery(FIND_DVBVIEWER_PROCESS) ) > 0
        except Exception, exc:
            msg = 'Error getting WMI process list' + unicode(exc)
            eg.PrintError(msg)
            if eventOnException:
                self.TriggerEvent('SevereError.WMI', msg)
            return False
        finally:
            if wmiCreated:
                del WMI

        return running
        
        
    @eg.LogIt
    def GetChannelLists( self, lock=True ) :

        def GetChannelList( rawlist, isTV, channelIDbyIDList, IDbychannelIDList, channelList ) :
            for ix in xrange( len(rawlist) ) :
                nr = ( ix, isTV )
                entry = rawlist[ix]
                channelID = str( entry[0] ) + '|' + entry[1]
                channelIDbyIDList[ nr ] = channelID
                IDbychannelIDList[ channelID ] = nr
                channelList.append( entry[1] )

        tvChannels = []
        radioChannels = []
        self.channelDetailsList = {}

        if lock:
            self.executionStatusChangeLock.acquire()
        try:
            rawlist = self.workerThread.CallWait( partial( self.workerThread.GetChannelList ), CALLWAIT_TIMEOUT )
        finally:
            if lock:
                self.executionStatusChangeLock.release()
                
        channelNr = 0
        for channel in rawlist[1] :
            tv = not ( channel[CH_22_VIDEOPID] == 0 )
            channelID = str( 
                int(tv) << 61 
                | channel[CH_15_ORBITALPOS] << 48 
                | channel[CH_23_TSID] << 32 
                | (channel[CH_4_TUNERTYPE] + 1) << 29 
                | channel[CH_20_AUDIOPID] << 16 
                | channel[CH_26_SID] 
            )
            self.channelDetailsList[channelID] = channel
            
            if tv :
                tvChannels.append( ( channelID, channel[CH_1_NAME] ) )
            else :
                radioChannels.append( ( channelID, channel[CH_1_NAME] ) )
            if ( channel[ CH_3_FLAGS ] & 2 ) == 0 : # encrypted?
                if not self.frequencies.has_key( channel[CH_5_FREQUENCY] ) :
                    self.frequencies[ channel[CH_5_FREQUENCY] ] = ( channelNr, tv )
                elif not self.frequencies[ channel[CH_5_FREQUENCY] ][1] and tv :
                    self.frequencies[ channel[CH_5_FREQUENCY] ] = ( channelNr, tv )
            channelNr += 1

        radioChannels.sort( cmp=lambda x,y: cmp(x[CH_1_NAME].lower(), y[CH_1_NAME].lower()) )
        tvChannels.sort( cmp=lambda x,y: cmp(x[CH_1_NAME].lower(), y[CH_1_NAME].lower()) )

        GetChannelList( tvChannels, True, self.channelIDbyIDList, self.IDbychannelIDList, self.tvChannels )
        GetChannelList( radioChannels, False, self.channelIDbyIDList, self.IDbychannelIDList, self.radioChannels )

        #print "self.frequencies=", self.frequencies
        #print "self.channelIDbyIDList=", self.channelIDbyIDList
        #print "self.IDbychannelIDList=", self.IDbychannelIDList
        #print "self.tvChannels=", self.tvChannels

        return True


    @eg.LogIt
    def TuneChannelIfNotRecording( self, channelNr, text = "", time=0.0) :
        ret = False
        self.executionStatusChangeLock.acquire()
        try :
            if self.Connect( WAIT_CHECK_START_CONNECT ) :
                ret = self.workerThread.CallWait(
                        partial( self.workerThread.TuneChannelIfNotRecording, channelNr, text, time ),
                        CALLWAIT_TIMEOUT
                     )
        finally:
            self.executionStatusChangeLock.release()
        return ret


    @eg.LogItWithReturn
    def WaitForTermination( self, sendCloseCommand=False, block=True ) :

        self.executionStatusChangeLock.acquire()
        try:
            if block :
                self.closeWaitActive = True

            if sendCloseCommand :
                self.SendCommand( DVBVIEWER_CLOSE[1], lock=False )

            if block :
                self.closeWaitLock.acquire( timeout=TERMINATE_TIMEOUT )

            self.timeout = False
            
        finally:
            self.executionStatusChangeLock.release()

        if block :
            self.closeWaitLock.acquire()
            self.closeWaitLock.release()

        return not self.timeout


    @eg.LogIt #WithReturn
    def Connect( self, connectingMode=WAIT_CHECK_START_CONNECT, lock=False ):
        '''
        This is one of the key methods of the plugin. The method has three modes:
        # WAIT_CHECK_START_CONNECT  = 0   # check if executing, start if not executing, connect
        # CONNECT                   = 1   # connect to a running DVBViewer instance (only to be used by the watchdog thread)
        # CHECK_CONNECT             = 2   # check if executing, connect only if already executing
        
        So basically, the method searches for a running DVBViewer instance or starts a new one
        and then starts the worker thread which connects to the COM interface.
        
        The method returns None if not connected and (not None) if connected.
        '''

        self.closeWaitLock.acquire()
        self.closeWaitLock.release()

        if lock :
            self.executionStatusChangeLock.acquire()

        try:
            started = False

            if self.workerThread is None:
                timeout = 20.0
                if connectingMode in (CHECK_CONNECT, WAIT_CHECK_START_CONNECT):
                    if self.IsDVBViewerProcessRunning() :
                        self.DVBViewerStartedByCOM = False
                        #print "DVBViewer is executing"
                        started = True
                    elif connectingMode == WAIT_CHECK_START_CONNECT:
                        if not self.startDVBViewerByCOM :
                            startupInfo = STARTUPINFO()
                            startupInfo.cb = sizeof(STARTUPINFO)
                            startupInfo.dwFlags = STARTF_USESHOWWINDOW
                            startupInfo.wShowWindow = 1
                            processInformation = PROCESS_INFORMATION()
                            commandLine = create_unicode_buffer(
                                '"%s" %s' % (self.pathDVBViewer, self.argumentsDVBViewer)
                            )
                            res = CreateProcess(
                                    None,                   # lpApplicationName
                                    commandLine,            # lpCommandLine
                                    None,                   # lpProcessAttributes
                                    None,                   # lpThreadAttributes
                                    False,                  # bInheritHandles
                                    32|CREATE_NEW_CONSOLE,  # dwCreationFlags
                                    None,                   # lpEnvironment
                                    None,                   # lpCurrentDirectory
                                    startupInfo,            # lpStartupInfo
                                    processInformation      # lpProcessInformation
                                    )
                            CloseHandle(processInformation.hProcess)
                            CloseHandle(processInformation.hThread)
                            if res == 0 :
                                eg.PrintError( "DVBViewer couldn't started" )
                                return False
                            self.DVBViewerStartedByCOM = False
                            timeout = 60.0
                        else:
                            self.DVBViewerStartedByCOM = True
                        started = True
                    else:
                        started = False

                elif connectingMode == CONNECT:
                    self.DVBViewerStartedByCOM = False
                    started = True

                if started :
                    if not self.DVBViewerStartedByCOM :
                        found = len(WaitForDVBViewerWindow(timeout)) > 0
                        if not found:
                            eg.PrintError( "Warning: DVBViewer window not found. Hidden?" )
                            eg.PrintDebugNotice( "DVBViewer window not found. Hidden?" )
                        eg.Wait( self.waitTimeBeforeConnect )    #  necessary otherwise hang up
                    self.workerThread = DVBViewerWorkerThread(self)
                    try:
                        self.workerThread.Start( 60.0 )
                    except:
                        msg = "DVBViewer process running but couldn't be connected. Restart of DVBViewer is suggested."
                        self.TriggerEvent( "SevereError.Connect" )
                        eg.PrintError(msg)
                        eg.PrintDebugNotice( msg )
                        # cleanup! - essential in order to avoid that EG hangs!
                        self.workerThread.Stop()
                        del self.workerThread
                        self.workerThread = None
                        return False
    
                    if self.workerThread and len( self.tvChannels ) == 0 and len( self.radioChannels ) == 0 :
                        self.GetChannelLists(lock=False)
                        #print self.tvChannels
                        #print self.radioChannels
                
        finally:
            if lock :
                self.executionStatusChangeLock.release()
                
        return self.workerThread


    @eg.LogIt
    def InitAfterDVBViewerConnected( self ) :
        thread = self.workerThread
        self.actualChannel = thread.GetCurrentChannelNr()
        self.numberOfActiveTimers = 0
        self.firedRecordingsIDs = []
        self.actualWindowID = -1
        self.actualDisplayMode=""
        self.lastDisplayMode=""
        self.actualRendererType = 0
        self.lastRendererType = -1
        self.actualPlayState = "STOP"
        self.lastPlayState = ""
        self.lastRatio   = -1
        self.actualRatio = -1
        self.controlChangeData = None
        thread.ProcessMediaplayback()
        self.UpdateDisplayMode()
        self.eventHandlingIsAlive = True
        return True


    def ServiceConfigure(  self, enableDVBViewer=True, enableDVBService=False, updateDVBService=False, affirmed=True, panel=None ) :

        def onCheckBox( event ) :
            viewer  =  viewerCheckBoxCtrl.GetValue()
            service = serviceCheckBoxCtrl.GetValue()

            if viewer == False and service == False :
                viewerCheckBoxCtrl.SetValue(  not self.lastEnableDVBViewer )
                serviceCheckBoxCtrl.SetValue( not self.lastEnableDVBService )
            self.lastEnableDVBViewer  = viewerCheckBoxCtrl.GetValue()
            self.lastEnableDVBService = serviceCheckBoxCtrl.GetValue()
            event.Skip()

        def getFlags() :
            return viewerCheckBoxCtrl.GetValue(), serviceCheckBoxCtrl.GetValue(), updateCheckBoxCtrl.GetValue()

        if panel is None :
            panel = eg.ConfigPanel()

        text = self.text

        self.lastEnableDVBViewer  = enableDVBViewer
        self.lastEnableDVBService = enableDVBService

        viewerCheckBoxCtrl = wx.CheckBox(panel, -1, text.serviceDVBViewer)
        viewerCheckBoxCtrl.SetValue( enableDVBViewer )
        viewerCheckBoxCtrl.Bind(wx.EVT_CHECKBOX, onCheckBox)

        serviceCheckBoxCtrl = wx.CheckBox(panel, -1, text.serviceDVBService)
        serviceCheckBoxCtrl.SetValue( enableDVBService )
        serviceCheckBoxCtrl.Bind(wx.EVT_CHECKBOX, onCheckBox)

        updateCheckBoxCtrl = wx.CheckBox(panel, -1, text.serviceUpdate)
        updateCheckBoxCtrl.SetValue( updateDVBService )

        panel.sizer.Add( viewerCheckBoxCtrl )
        panel.sizer.Add(wx.Size(0,5))
        panel.sizer.Add( serviceCheckBoxCtrl )
        panel.sizer.Add(wx.Size(0,10))
        panel.sizer.Add( updateCheckBoxCtrl )


        if affirmed :
            while panel.Affirmed():

                panel.SetResult(
                                viewerCheckBoxCtrl.GetValue(),
                                serviceCheckBoxCtrl.GetValue(),
                                updateCheckBoxCtrl.GetValue()
                               )
        else :
            return getFlags


    def CheckGetDVBViewerPath(self, pathDVBViewer) :
        if pathDVBViewer is None: 
            pathDVBViewer = ''
        else:
            pathDVBViewer = unicode(pathDVBViewer).strip()
            if pathDVBViewer != '' and (not os.path.isfile(pathDVBViewer) \
                                        or pathDVBViewer.lower().find('dvbviewer.exe') < 0):
                pathDVBViewer = ''
            
        if pathDVBViewer == '':
            try :
                regHandle = OpenKey(
                           HKEY_LOCAL_MACHINE,
                           'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths'
                        )
                pathDVBViewer = QueryValue(regHandle, 'dvbviewer.exe')
                CloseKey( regHandle )
            except :
                pass
        return pathDVBViewer


    @eg.LogIt
    def Configure(  self,
                    useSendMessage=False,
                    oldInterface=False,
                    newInterface=True,
                    startDVBViewerByCOM=False,
                    pathDVBViewer='',
                    argumentsDVBViewer="",
                    longDisplay=True,
                    shortDisplay=False,
                    longPlayState=True,
                    shortPlayState=False,
                    useService=False,
                    watchDogTime=60.0,
                    schedulerTaskNamePrefix="StartRecording",
                    schedulerEventName     ="StartRecording",
                    schedulerLeadTime      =3.0,
                    scheduleAllRecordings  =False,
                    schedulerEntryHidden   =False,
                    waitTimeBeforeConnect=5.0,
                    serviceAddress='127.0.0.1:8089',
                    serviceEvent='DVBViewerService',
                    dummy=False
                    ) :


        def onRadioBox( event ) :
            pro = radioBox.GetSelection() != 1
            for ctrl in enableControls :
                ctrl.Enable( pro )

            if pro :
                onCommandCheckBox(wx.CommandEvent())
                onPasswordChange(wx.CommandEvent())
                onServiceCheckBox(wx.CommandEvent())

            event.Skip()


        def onEventCheckBox( event ) :
            old = oldEventsCheckBoxCtrl.GetValue()
            new = newEventsCheckBoxCtrl.GetValue()
            if not old and not new :
                old = not self.lastOld
                new = not self.lastNew
                oldEventsCheckBoxCtrl.SetValue(old)
                newEventsCheckBoxCtrl.SetValue(new)
            self.lastOld = old
            self.lastNew = new
            event.Skip()


        def onCommandCheckBox( event ) :
            enable = useCommandCheckBoxCtrl.GetValue()
            dvbviewerFileCtrl.Enable( enable )
            argumentsCtrl.Enable( enable )
            event.Skip()


        def onServiceCheckBox( event ) :
            enable = serviceCheckBoxCtrl.GetValue()
            serviceAddressCtrl.Enable( enable )
            serviceEventCtrl.Enable( enable )
            index = INDEX_SCHEDULER
            if enable :
                index = INDEX_DVBSERVICE
            onPasswordChange(wx.CommandEvent(), index)
            event.Skip()


        def onAccountTypeChange( event ) :
            accountTypeIndex = text.accountChoices.index( accountTypeCtrl.GetValue() )

            if self.lastAccountTypeIndex >= 0 :
                self.accounts[ self.lastAccountTypeIndex ]  = ( accountCtrl.GetValue(), password1Ctrl.GetValue())

            accountCtrl.SetValue( self.accounts[ accountTypeIndex ][0] )
            password1Ctrl.SetValue( self.accounts[ accountTypeIndex ][1] )
            password2Ctrl.SetValue( self.accounts[ accountTypeIndex ][1] )

            self.lastAccountTypeIndex = accountTypeIndex
            #event.Skip()


        def onPasswordChange( event, forceAccount=-1 ) :
            p1 = password1Ctrl.GetValue()
            p2 = password2Ctrl.GetValue()
            enable = ( p1 == p2 )
            panel.EnableButtons( enable )
            accountTypeCtrl.Enable( enable )
            if enable and forceAccount >= 0 :
                accountTypeCtrl.SetValue( text.accountChoices[forceAccount] )
                onAccountTypeChange(wx.CommandEvent())
            event.Skip()

        self.lastOld = oldInterface
        self.lastNew = newInterface
        self.lastPlayStateLong  = longPlayState
        self.lastPlayStateShort = shortPlayState
        self.lastDisplayLong    = longDisplay
        self.lastDisplayShort = shortDisplay

        pathDVBViewer = self.CheckGetDVBViewerPath(pathDVBViewer)

        enableControls = []

        text = self.text
        panel = eg.ConfigPanel()
        radioBox = wx.RadioBox(
            panel,
            -1,
            text.interfaceBox,
            choices=[text.useComApi, text.useSendMessage],
            style=wx.RA_SPECIFY_ROWS
        )
        radioBox.SetSelection(int(useSendMessage))
        radioBox.Bind(wx.EVT_RADIOBOX, onRadioBox)

        watchDogTimeCtrl = panel.SpinNumCtrl(watchDogTime, min=0, max=999, fractionWidth=0, integerWidth=3)
        enableControls.append( watchDogTimeCtrl )

        oldEventsCheckBoxCtrl = wx.CheckBox(panel, -1, text.useOldEvents)
        oldEventsCheckBoxCtrl.SetValue( oldInterface )
        oldEventsCheckBoxCtrl.Bind(wx.EVT_CHECKBOX, onEventCheckBox)
        enableControls.append( oldEventsCheckBoxCtrl )

        newEventsCheckBoxCtrl = wx.CheckBox(panel, -1, text.useNewEvents)
        newEventsCheckBoxCtrl.SetValue( newInterface )
        newEventsCheckBoxCtrl.Bind(wx.EVT_CHECKBOX, onEventCheckBox)
        enableControls.append( newEventsCheckBoxCtrl )

        longDisplayCheckBoxCtrl = wx.CheckBox(panel, -1, text.long)
        longDisplayCheckBoxCtrl.SetValue( longDisplay )
        enableControls.append( longDisplayCheckBoxCtrl )

        shortDisplayCheckBoxCtrl = wx.CheckBox(panel, -1, text.short)
        shortDisplayCheckBoxCtrl.SetValue( shortDisplay )
        enableControls.append( shortDisplayCheckBoxCtrl )

        longPlayStateCheckBoxCtrl = wx.CheckBox(panel, -1, text.long)
        longPlayStateCheckBoxCtrl.SetValue( longPlayState )
        enableControls.append( longPlayStateCheckBoxCtrl )

        shortPlayStateCheckBoxCtrl = wx.CheckBox(panel, -1, text.short)
        shortPlayStateCheckBoxCtrl.SetValue( shortPlayState )
        enableControls.append( shortPlayStateCheckBoxCtrl )

        useCommandCheckBoxCtrl = wx.CheckBox(panel, -1, text.useCommandLine)
        useCommandCheckBoxCtrl.SetValue( not startDVBViewerByCOM )
        #useCommandCheckBoxCtrl.SetValue( True )
        #useCommandCheckBoxCtrl.Enable(False)
        useCommandCheckBoxCtrl.Bind(wx.EVT_CHECKBOX, onCommandCheckBox)
        enableControls.append( useCommandCheckBoxCtrl )

        dvbviewerFileCtrl = eg.FileBrowseButton(
            panel,
            -1,
            size=(320,-1),
            initialValue=pathDVBViewer,
            labelText="",
            fileMask="*.exe",
            dialogTitle=text.dvbviewerBox
        )
        enableControls.append( dvbviewerFileCtrl )

        argumentsCtrl = wx.TextCtrl( panel, size=(200,-1) )
        argumentsCtrl.SetValue( argumentsDVBViewer )
        enableControls.append( argumentsCtrl )

        waitTimeBeforeConnectCtrl = panel.SpinNumCtrl(waitTimeBeforeConnect, min=0, max=99, fractionWidth=0, integerWidth=2)
        waitTimeBeforeConnectCtrl.Enable( not startDVBViewerByCOM )
        enableControls.append( waitTimeBeforeConnectCtrl )

        serviceCheckBoxCtrl = wx.CheckBox(panel, -1, text.serviceEnable)
        serviceCheckBoxCtrl.SetValue( useService )
        serviceCheckBoxCtrl.Bind(wx.EVT_CHECKBOX, onServiceCheckBox)
        enableControls.append( serviceCheckBoxCtrl )

        serviceAddressCtrl = wx.TextCtrl( panel )#, size=(200,-1) )
        serviceAddressCtrl.SetValue( serviceAddress )
        enableControls.append( serviceAddressCtrl )

        serviceEventCtrl = wx.TextCtrl( panel , size=(150,-1) )
        serviceEventCtrl.SetValue( serviceEvent )
        enableControls.append( serviceEventCtrl )

        self.lastAccountTypeIndex = -1

        accountTypeCtrl = wx.ComboBox( panel,
                                    -1,
                                    value=text.accountChoices[INDEX_SCHEDULER],
                                    choices = text.accountChoices,
                                    style = wx.CB_READONLY
                                    )
        accountTypeCtrl.Bind(wx.EVT_COMBOBOX, onAccountTypeChange)
        enableControls.append( accountTypeCtrl )

        accountCtrl = wx.TextCtrl( panel, size=(125,-1) )
        enableControls.append( accountCtrl )

        password1Ctrl = wx.TextCtrl( panel, size=(125,-1), style=wx.TE_PASSWORD )
        password1Ctrl.Bind(wx.EVT_TEXT, onPasswordChange)
        enableControls.append( password1Ctrl )

        password2Ctrl = wx.TextCtrl( panel, size=(125,-1), style=wx.TE_PASSWORD )
        password2Ctrl.Bind(wx.EVT_TEXT, onPasswordChange)
        enableControls.append( password2Ctrl )

        schedulerPrefixCtrl = wx.TextCtrl( panel, size=(125,-1) )
        schedulerPrefixCtrl.SetValue( schedulerTaskNamePrefix )
        enableControls.append( schedulerPrefixCtrl )

        schedulerEventCtrl = wx.TextCtrl( panel, size=(125,-1) )
        schedulerEventCtrl.SetValue( schedulerEventName )
        enableControls.append( schedulerEventCtrl )

        schedulerLeadCtrl = panel.SpinNumCtrl(schedulerLeadTime, min=0, max=30, fractionWidth=0, integerWidth=2)
        enableControls.append( schedulerLeadCtrl )

        scheduleAllCheckBoxCtrl = wx.CheckBox(panel, -1, text.scheduleAllRecordings)
        scheduleAllCheckBoxCtrl.SetValue( scheduleAllRecordings )
        enableControls.append( scheduleAllCheckBoxCtrl )

        scheduleHiddenCheckBoxCtrl = wx.CheckBox(panel, -1, text.schedulerEntryHidden)
        scheduleHiddenCheckBoxCtrl.SetValue( schedulerEntryHidden )
        enableControls.append( scheduleHiddenCheckBoxCtrl )

        boxSizerO = wx.BoxSizer( wx.HORIZONTAL )
        boxSizerO.Add( radioBox, 1 )

        sb = wx.StaticBox( panel, -1, text.watchDog )
        boxSizerI = wx.StaticBoxSizer( sb, wx.VERTICAL )

        gridBagSizer = wx.GridBagSizer( 2, 10 )

        gridBagSizer.Add( wx.StaticText(panel, -1, text.watchDogTime), (0, 0), flag =  wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )
        gridBagSizer.Add( watchDogTimeCtrl,        (0, 1), flag =  wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        boxSizerI.Add(wx.Size(0,4))
        boxSizerI.Add( gridBagSizer )

        boxSizerO.Add( boxSizerI, 1, wx.EXPAND | wx.ALIGN_RIGHT )

        panel.sizer.Add(boxSizerO, 0, wx.EXPAND)

        panel.sizer.Add(wx.Size(0,2) )

        sb = wx.StaticBox( panel, -1, text.eventType )
        sBoxSizer = wx.StaticBoxSizer( sb, wx.HORIZONTAL )

        boxSizerI = wx.BoxSizer( wx.VERTICAL )
        boxSizerI.Add( oldEventsCheckBoxCtrl, 1, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )
        boxSizerI.Add( newEventsCheckBoxCtrl, 1, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sBoxSizer.Add( boxSizerI, 0, wx.EXPAND )


        boxSizerO = wx.BoxSizer( wx.HORIZONTAL )

        sb = wx.StaticBox( panel, -1, text.playStateFormat )
        boxSizer = wx.StaticBoxSizer( sb, wx.VERTICAL )
        boxSizer.Add(wx.Size(0,3))

        boxSizerI = wx.BoxSizer( wx.HORIZONTAL )
        boxSizerI.Add(longPlayStateCheckBoxCtrl, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
        boxSizerI.Add(shortPlayStateCheckBoxCtrl, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
        boxSizer.Add(boxSizerI, 1, wx.EXPAND )

        boxSizerO.Add( boxSizer, 1, wx.EXPAND )

        sb = wx.StaticBox( panel, -1, text.displayChangeFormat )
        boxSizer = wx.StaticBoxSizer( sb, wx.VERTICAL )
        boxSizer.Add(wx.Size(0,3))

        boxSizerI = wx.BoxSizer( wx.HORIZONTAL )
        boxSizerI.Add( longDisplayCheckBoxCtrl, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
        boxSizerI.Add(shortDisplayCheckBoxCtrl, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
        boxSizer.Add(boxSizerI, 1, wx.EXPAND )

        boxSizerO.Add( boxSizer, 1, wx.EXPAND )

        #gridSizer.Add(boxSizerO, (rowCount, 0 ), flag = wx.EXPAND, span = wx.GBSpan( 1, 2 ) )

        sBoxSizer.Add(boxSizerO, 1, wx.EXPAND)
        panel.sizer.Add(sBoxSizer, 0, wx.EXPAND)

        panel.sizer.Add(wx.Size(0,2))

        sb = wx.StaticBox( panel, -1, text.dvbViewerStart )
        boxSizer = wx.StaticBoxSizer( sb, wx.VERTICAL )

        #boxSizer.Add(wx.Size(0,3))

        sizer = wx.GridBagSizer( 0, 0 )

        sizer.Add( useCommandCheckBoxCtrl,                            (0,0), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT )

        boxSizerI = wx.BoxSizer( wx.HORIZONTAL )
        boxSizerI.Add( wx.StaticText(panel, -1, text.waitTimeBeforeConnect), 0, wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT)
        boxSizerI.Add( waitTimeBeforeConnectCtrl, 1, wx.ALIGN_LEFT)

        sizer.Add( boxSizerI,                                         (0,1), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT )

        sizer.Add( wx.Size(0,2),                                      (1,0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.dvbviewerFile),      (2,0), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT )
        sizer.Add( dvbviewerFileCtrl,                                 (2,1), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.Size(0,2),                                      (3,0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.dvbviewerArguments), (4,0), flag= wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT )
        sizer.Add( argumentsCtrl,                                     (4,1), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        boxSizer.Add( sizer, 1, wx.EXPAND)

        panel.sizer.Add(boxSizer, 0, wx.EXPAND)


        sb = wx.StaticBox( panel, -1, text.serviceHeader )
        boxSizer = wx.StaticBoxSizer( sb, wx.HORIZONTAL )

        boxSizer.Add( serviceCheckBoxCtrl, 0, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        boxSizerI = wx.BoxSizer( wx.VERTICAL )
        boxSizerI.Add( wx.StaticText(panel, -1, text.serviceAddress), 0, flag = wx.EXPAND | wx.ALIGN_BOTTOM )
        boxSizerI.Add( wx.Size(0,4) )
        boxSizerI.Add( serviceAddressCtrl                           , 1, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        boxSizer.Add( boxSizerI, 1 )
        boxSizer.Add( wx.Size(10,0) )

        boxSizerI = wx.BoxSizer( wx.VERTICAL )
        boxSizerI.Add( wx.StaticText(panel, -1, text.serviceEvent)  , 0, flag = wx.EXPAND | wx.ALIGN_BOTTOM )
        boxSizerI.Add( wx.Size(0,4) )
        boxSizerI.Add( serviceEventCtrl                             , 1, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        boxSizer.Add( boxSizerI, 0 )

        panel.sizer.Add(boxSizer, 0, wx.EXPAND)


        panel.sizer.Add(wx.Size(0,2))

        boxSizer = wx.BoxSizer( wx.HORIZONTAL )

        sb = wx.StaticBox( panel, -1, text.accountInfo )
        boxSizerI = wx.StaticBoxSizer( sb, wx.HORIZONTAL )

        sizer = wx.GridBagSizer( 0, 0 )

        sizer.Add(wx.Size(0,3),                                    (0,0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.accountType),     (1,0), flag = wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT )
        sizer.Add( accountTypeCtrl,                                (1,1), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add(wx.Size(0,4),                                    (2,0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.accountName),     (3,0), flag = wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT )
        sizer.Add( accountCtrl,                                    (3,1), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.password1),       (4,0), flag = wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT )
        sizer.Add( password1Ctrl,                                  (4,1), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.password2),       (5,0), flag = wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT )
        sizer.Add( password2Ctrl,                                  (5,1), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        boxSizerI.Add( sizer, 0, wx.EXPAND )
        boxSizer.Add( boxSizerI, 1, wx.EXPAND )


        sb = wx.StaticBox( panel, -1, text.taskSchedulerInfo )
        boxSizerI = wx.StaticBoxSizer( sb, wx.HORIZONTAL )
        sizer = wx.GridBagSizer( 0, 0 )

        sizer.Add(wx.Size(0,7),                                    (0,0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( scheduleAllCheckBoxCtrl,                        (1,0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )
        sizer.Add( scheduleHiddenCheckBoxCtrl,                     (1,1), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add(wx.Size(0,7),                                    (2,0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.schedulerPrefix), (3,0), flag = wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT )
        sizer.Add( schedulerPrefixCtrl,                            (3,1), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.schedulerEvent),  (4,0), flag = wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT )
        sizer.Add( schedulerEventCtrl,                             (4,1), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )

        sizer.Add( wx.StaticText(panel, -1, text.schedulerLead),   (5,0), flag = wx.ALIGN_CENTER_VERTICAL| wx.ALIGN_RIGHT )
        sizer.Add( schedulerLeadCtrl,                              (5,1), flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL )


        boxSizerI.Add( sizer, 0, wx.EXPAND )
        boxSizer.Add( boxSizerI, 1, wx.EXPAND )

        panel.sizer.Add(boxSizer, 1, wx.EXPAND)

        onPasswordChange( wx.CommandEvent() )
        onAccountTypeChange( wx.CommandEvent() )
        onRadioBox( wx.CommandEvent() )

        while panel.Affirmed():
            useSendMessage        = radioBox.GetSelection() == 1
            oldInterface          = oldEventsCheckBoxCtrl.GetValue()
            newInterface          = newEventsCheckBoxCtrl.GetValue()
            startDVBViewerByCOM   = not useCommandCheckBoxCtrl.GetValue()
            argumentsDVBViewer    = argumentsCtrl.GetValue()
            pathDVBViewer         = dvbviewerFileCtrl.GetValue()
            waitTimeBeforeConnect = waitTimeBeforeConnectCtrl.GetValue()
            longDisplay           =  longDisplayCheckBoxCtrl.GetValue()
            shortDisplay          = shortDisplayCheckBoxCtrl.GetValue()
            longPlayState         =  longPlayStateCheckBoxCtrl.GetValue()
            shortPlayState        = shortPlayStateCheckBoxCtrl.GetValue()
            watchDogTime          = watchDogTimeCtrl.GetValue()

            useService            = serviceCheckBoxCtrl.GetValue()
            serviceAddress        = serviceAddressCtrl.GetValue()
            serviceEvent          = serviceEventCtrl.GetValue()

            schedulerTaskNamePrefix = schedulerPrefixCtrl.GetValue()
            schedulerEventName      = schedulerEventCtrl.GetValue()
            schedulerLeadTime       = schedulerLeadCtrl.GetValue()
            scheduleAllRecordings   = scheduleAllCheckBoxCtrl.GetValue()
            schedulerEntryHidden    = scheduleHiddenCheckBoxCtrl.GetValue()


            if accountCtrl.IsModified() or password1Ctrl.IsModified() :
                onAccountTypeChange( wx.CommandEvent() )
                self.HandlePasswordFile( write = True,accounts = self.accounts )
                dummy = not dummy

            if not oldInterface and not newInterface :
                newInterface = True

            panel.SetResult( useSendMessage,
                             oldInterface,
                             newInterface,
                             startDVBViewerByCOM,
                             pathDVBViewer,
                             argumentsDVBViewer,
                             longDisplay,
                             shortDisplay,
                             longPlayState,
                             shortPlayState,
                             useService,
                             watchDogTime,
                             schedulerTaskNamePrefix,
                             schedulerEventName,
                             schedulerLeadTime,
                             scheduleAllRecordings,
                             schedulerEntryHidden,
                             waitTimeBeforeConnect,
                             serviceAddress,
                             serviceEvent,
                             dummy
                           )



    def HandlePasswordFile( self, write=False, accounts=[('','')] ) :

        passwordFileName = os.path.join(eg.folderPath.RoamingAppData, eg.APP_NAME, 'DVBViewerAccount.dat')

        crypt = self.Crypt

        previousAccounts  = []

        if not write :
            tree = ElementTree.ElementTree()

            try:
                tree.parse( passwordFileName )
            except :
                previousAccounts  = None
            else :
                key = tree.find("Key").text

                for account in tree.findall("Account") :
                    accountName = account.get("Name","" )
                    passwordCrypted    = account.get("Password","" )
                    coded = "".join( [ chr(int(passwordCrypted[i:i+2],16)) for i in range(0,len(passwordCrypted),2) ] )
                    password = crypt( coded, key, False )
                    previousAccounts.append( ( accountName, password ) )

            return ( previousAccounts )

        key = ''
        for i in xrange(16):
            r = random.randint(0,255)
            key += '%02x'%r

        root = ElementTree.Element( 'Accounts' )

        subElement = ElementTree.Element( 'Key' )
        subElement.text = key
        root.append( subElement )

        for account in accounts :
            name = account[0]
            password = account[1]
            attributes = {}
            attributes['Name' ] = name
            attributes['Password' ] = "".join( ['%02x'%ord(c) for c in crypt( password, key, True ) ] )
            subElement = ElementTree.Element( 'Account', attributes )
            root.append( subElement )

        tree = ElementTree.ElementTree( root )
        tree.write(passwordFileName, sys.getdefaultencoding() )

        return True



    def Crypt( self, string, key, gen=True ) :
        m=hashlib.md5()
        m.update('37c710146322502a230dd8781ec3f5a')
        m.update(key)
        digest = m.digest()
        result = ''
        for index in xrange( len(string) ) :
            char = chr(ord(digest[index%16]) ^ ord(string[index]))
            modifier = char
            if gen :
                modifier = string[index]
            result += char
            m.update(modifier)
            digest = m.digest()

        #print result
        return result




class ComEventHandler:
    '''
    Handles all events from DVBViewer's COM interface
    '''

    def __init__( self ) :
        self.lastActiveChannelNr = -1


    # The event gets fired whenever a new action is processed.
    # Parameter :
    #       ActionID        ID of the action ( see actions.ini in the DVBViewer folder)
    #
    @eg.LogIt
    def OnonAction(self, ActionID):
        plugin = self.plugin
        plugin.eventHandlingIsAlive = True
        if ActionID == DUMMY_ACTION :
            plugin.checkEventHandlingLock.acquire( blocking = False )
            plugin.checkEventHandlingLock.release()
            return True

        if plugin.oldInterface :
            self.TriggerEvent("Action:" + str(ActionID))
        if plugin.newInterface :
            self.TriggerEvent("Action", ActionID )
        return True


    # The event gets fired on every channelchange.
    # Parameter :
    #       ChannelNr       The new channel number.
    #
    @eg.LogIt
    def OnChannelChange(self, ChannelNr):
        def DisableAV() :
            #print "DisableAV"
            #CoInitialize() # FIXME - I cannot imagine why CoInitialize should be needed here
            self.plugin.SendCommand( 16386 )
            #CoUninitialize()
        plugin = self.plugin
        plugin.eventHandlingIsAlive = True
        self.TriggerEvent("Channel", ChannelNr)
        plugin.actualChannel = ChannelNr
        plugin.UpdateDisplayMode()
        if ChannelNr != -1 :
            if plugin.disableAV :
                #print "DisableAV", ChannelNr
                disableAVTimer = Timer( 3.0, DisableAV )
                disableAVTimer.start()
            self.lastActiveChannelNr = ChannelNr
        return True


    # The event gets fired whenever a new Timer is added.
    # Parameter :
    #       ID              ID of the newly added timer.
    #
    @eg.LogIt
    def OnonAddRecord(self, ID):
        plugin = self.plugin
        plugin.eventHandlingIsAlive = True
        plugin.UpdateDVTimersByDVEvent()
        if plugin.oldInterface :
            self.TriggerEvent( "AddRecord:" + str(ID) )
        if plugin.newInterface :
            self.TriggerEvent( "AddRecord", ID )
        return True


    # The event gets fired whenever a recording starts.
    # Parameter :
    #       ID                          ID of the timer.
    #
    @eg.LogIt
    def OnonStartRecord(self, ID):
        self.plugin.eventHandlingIsAlive = True
        self.plugin.UpdateDVTimersByDVEvent()
        return True


    # The event gets fired whenever a recording ends.
    #
    @eg.LogIt
    def OnonEndRecord(self):
        self.plugin.eventHandlingIsAlive = True
        self.plugin.UpdateDVTimersByDVEvent()
        return True


    # The event gets fired whenever a OSD-window is activated.
    # Parameter :
    #       WindowID        ID of the OSD-window.
    #
    @eg.LogIt
    def OnonOSDWindow(self, WindowID):
        plugin = self.plugin
        plugin.eventHandlingIsAlive = True
        plugin.actualWindowID = WindowID
        if plugin.oldInterface :
            self.TriggerEvent("Window:" + str(WindowID))
        if plugin.newInterface :
            self.TriggerEvent("Window", WindowID )
        plugin.UpdateDisplayMode()
        return True


    # The event gets fired whenever an OSD Control gets the focus.
    # Parameters
    #       WindowID        ID of the OSD window the control belongs to.
    #       ControlID       ID of the OSD-control.
    #
    @eg.LogIt
    def OnonControlChange(self, WindowID, ControlID):
        plugin = self.plugin
        plugin.eventHandlingIsAlive = True
        controlChangeData = (WindowID, ControlID)
        if controlChangeData != plugin.controlChangeData:
            plugin.controlChangeData = controlChangeData
            if plugin.oldInterface :
                self.TriggerEvent("ControlChange:WindID" + str(WindowID) + "ContrID"+ str(ControlID))
            if plugin.newInterface :
                self.TriggerEvent("ControlChange", controlChangeData)
        return True


    # The event gets fired whenever the selectedItem in an OSD list changes.
    #
    @eg.LogIt
    def OnonSelectedItemChange(self):
        self.plugin.eventHandlingIsAlive = True
        self.TriggerEvent("SelectedItemChange")
        return True


    # The event gets fired whenever a new RDS Text arrives.
    # Parameters
    #       RDS             The RDS text.
    #
    @eg.LogIt
    def OnonRDS(self, RDS):
        plugin = self.plugin
        plugin.eventHandlingIsAlive = True
        if plugin.oldInterface :
            self.TriggerEvent("RDS:" + unicode(RDS))
        if plugin.newInterface :
            self.TriggerEvent("RDS", unicode(RDS))
        return True


    # The event gets fired whenever a new playlistitem starts playing.
    # Parameter :
    #       Filename        Filename of the starting playlistitem.
    #
    @eg.LogIt
    def OnonPlaylist(self, Filename):
        self.plugin.eventHandlingIsAlive = True
        self.TriggerEvent("Playlist", str(Filename))
        return True


    # The event gets fired whenever a media playback starts.
    #
    @eg.LogIt
    def OnonPlaybackstart(self):
        self.plugin.eventHandlingIsAlive = True
        thread = self.plugin.workerThread
        thread.Call( thread.ProcessMediaplayback )
        return True


    # The event gets fired whenever a media playback ends.
    #
    @eg.LogIt
    def OnPlaybackEnd(self):
        self.plugin.eventHandlingIsAlive = True
        thread = self.plugin.workerThread
        thread.Call( thread.ProcessMediaplayback )
        return True


    # The event gets fired whenever the internal playstate changes.
    # Parameter :
    #       RendererType    Type of renderer causing the event.
    #                       :=  0   Unknown
    #                       :=  1   VideoAudioDVD
    #                       :=  2   DVB
    #                       :=  3   MPG2TS
    #                       := -1   Ratio changed, state shows ratio
    #
    #       State           state changed into.
    #                       :=  0   Stop
    #                       :=  1   Pause
    #                       :=  2   Play
    #
    @eg.LogIt
    def OnPlaystatechange(self, RendererType, State):
        plugin = self.plugin
        plugin.eventHandlingIsAlive = True
        if plugin.oldInterface :
            self.TriggerEvent( "Playstatechange:RenderTy" + str(RendererType) + "State"+ str(State) )
        if plugin.newInterface :
            self.TriggerEvent( "RenderPlaystateChange", ( RendererType, State ) )
            pass
        if RendererType == -1 :
            plugin.actualRatio = State
            if plugin.lastRatio != plugin.actualRatio :
                plugin.lastRatio = plugin.actualRatio
                self.TriggerEvent( "RatioChange", State )
        else :
            plugin.actualRendererType = RendererType
            if plugin.lastRendererType != plugin.actualRendererType :
                plugin.lastRendererType = plugin.actualRendererType
                self.TriggerEvent( "RendererChange", plugin.actualRendererType )
                plugin.UpdateDisplayMode()
            if State == 2:
                plugin.actualPlayState = "PLAY"
            elif State == 1 :
                plugin.actualPlayState = "PAUSE"
            else :
                plugin.actualPlayState = "STOP"
            if plugin.lastPlayState != plugin.actualPlayState :
                plugin.lastPlayState = plugin.actualPlayState
                if plugin.longPlayStateEvent :
                    self.TriggerEvent( "PlaystateChange:"+plugin.actualPlayState, plugin.actualPlayState )
                if plugin.shortPlayStateEvent :
                    self.TriggerEvent( "PlaystateChange", plugin.actualPlayState )
        return True


    # The event gets fired when the DVBViewer is shutting down.
    @eg.LogIt
    def OnonDVBVClose(self):
        plugin = self.plugin

        plugin.lockedByTerminate = plugin.executionStatusChangeLock.acquire( blocking=False, timeout = TERMINATE_TIMEOUT )
        plugin.eventHandlingIsAlive = True

        if not plugin.closeWaitActive :
            plugin.closeWaitActive = True
            plugin.closeWaitLock.acquire( timeout=TERMINATE_TIMEOUT )

        plugin.terminateThread = DVBViewerTerminateThread( plugin )
        plugin.terminateThread.start()
        return True



class DVBViewerWorkerThread(eg.ThreadWorker):
    """
    This thread connects to DVBViewer's COM interface and performs all tasks related to this.
    It is started by the plugin.Connect() method which is called from the watchdog thread 
    as soon as a running dvbviewer.exe instance is detected.
    This thread is kind of a 'standby thread' since it has no continuous task, but it waits for methods to be
    executed asynchronously.
    Other threads calling methods of this thread...
    - must acquire the 'ExecutionStatusChange' lock before the method call and release it afterwards
    - pack method calls into a worker.CallWait() or worker.Call() method to be executed in background.
    """

    @eg.LogItWithReturn
    def Setup(self, plugin):
        """
        This will be called inside the thread at the beginning.
        """
        self.plugin = plugin

        self.dvbviewer                 = None
        self.recordManager             = None
        self.comObj_IDVBViewerEvents   = None

        try:
            self.dvbviewer = EnsureDispatch("DVBViewerServer.DVBViewer")
            #self.dvbviewer = GetObject(Class="DVBViewerServer.DVBViewer")
    
            # try if we can get an attribute from the COM instance
            self.dvbviewer.CurrentChannelNr
            com_IDVBViewerEvents = self.dvbviewer.Events
            self.comObj_IDVBViewerEvents = DispatchWithEvents(com_IDVBViewerEvents, self.plugin.EventHandler)
            plugin.TriggerEvent( "DVBViewerIsConnected" )
            plugin.InitAfterDVBViewerConnected()
        except Exception, exc:
            msg = 'Failed to initialize COM interface: ' + unicode(exc)
            eg.PrintError(msg)
            eg.PrintDebugNotice(msg)
            plugin.TriggerEvent('SevereError.COM', msg)
            return False
        else:
            return True
            

    @eg.LogIt
    def Finish(self):
        """
        This will be called inside the thread when it finishes. It will even
        be called if the thread exits through an exception.
        """
        if self.comObj_IDVBViewerEvents is not None :
            del self.comObj_IDVBViewerEvents
        if self.dvbviewer is not None:
            del self.dvbviewer
        self.plugin.workerThread = None
        return True


    @eg.LogItWithReturn
    def GetCurrentChannelNr( self ) :
        return self.dvbviewer.CurrentChannelNr


    @eg.LogItWithReturn
    def GetCurrentChannel( self ) :
        return self.dvbviewer.CurrentChannel


    @eg.LogItWithReturn
    def GetSetupValue( self, section, name, default ) :
        return self.dvbviewer.GetSetupValue( section, name, default )


    @eg.LogIt
    def GetChannelList( self ) :
        channelManager = self.dvbviewer.ChannelManager
        return channelManager.GetChannelList( )
    

    @eg.LogItWithReturn
    def GetChannelDetails(self, allChannels, currentChannel, channelID):
        result = None
        if allChannels:
            result = cpy(self.plugin.channelDetailsList) # return a deep copy, not the original
            channelID = None
        elif currentChannel:
            channelID = None
            channel = self.GetCurrentChannel()
            if channel:
                channelID = channel.ChannelID

        if channelID != None:
            try:
                channelID = unicode(channelID).split('|')[0]
                result = { channelID: cpy(self.plugin.channelDetailsList[channelID]) }
            except:
                eg.PrintError("No details for channel with ID '" + unicode(channelID) + "' found.")
                result = None
            
        return result


    @eg.LogIt
    def TuneChannel(self, channelID) :
        channelNr = self.dvbviewer.ChannelManager.GetNr(channelID)
        if channelNr >= 0:
            self.dvbviewer.CurrentChannelNr = channelNr
            return True
        else:
            eg.PrintError("Failed to tune channel with ID '" + str(channelID) + "'.")
            return False


    @eg.LogItWithReturn
    def TuneChannelIfNotRecording( self, channelNr, text="", time=0.0) :
        dvbviewer = self.dvbviewer
        timerCollection = dvbviewer.TimerManager

        if not timerCollection.Recording :
            dvbviewer.CurrentChannelNr = channelNr
            self.ShowInfoinTVPic( text, time + 2 )
            return True
        else :
            return False


    def ShowInfoinTVPic( self, text="", time=10.0 ) :
        dvbviewer = self.dvbviewer
        if text != "" :
            dvbviewer.OSD.ShowInfoinTVPic( text, time * 1000 )
        return True


    def ShowWindow( self, windowID ) :
        dvbviewer = self.dvbviewer
        dvbviewer.WindowManager.ShowWindow( windowID )
        return True


    @eg.LogIt
    def StopAllActiveRecordings( self ) :
        timerManager = self.dvbviewer.TimerManager
        timerlist = timerManager.GetTimerList( )
        for timer in timerlist[1] :
            #print "Record = ", timer
            #print "Recording: ", timer[ TI_11_RECORDING ]
            if timer[ TI_11_RECORDING ] :
                #print "Recording terminated"
                timerManager.StopRecording( timer[ TI_4_ID ] )
            else :
                #print "Recording not terminated"
                pass
        return True


    def IfMustInList( self, now, record, active, recordingsIDsService ) :
        """
        Returns True if timer is NOT in RS timer list and timer has not ended yet. 
        """
        if record[ TI_4_ID ] not in recordingsIDsService :
            tStart = toDateTime( record[ TI_5_DATE ], record[ TI_6_STARTTIME ] )
            if tStart <= now :
                tStop  = toDateTime( record[ TI_5_DATE ], record[ TI_7_ENDTIME ] )
                if tStart > tStop :
                    tStop = tStop + 24*60*60
                if tStop <= now :
                    return False
            if active and not record[ TI_11_RECORDING ] :
                return False
            return True
        return False


    @eg.LogIt
    def GetTimers( self, active=True, update=False ) :
        plugin = self.plugin
        recordingsIDsService = {}
        if plugin.useService and self.GetSetupValue( 'Service', 'Timerlist', '0' ) != '0' :
            recordingsIDsService = plugin.service.GetPseudoIDs( update )

        timerlist = self.dvbviewer.TimerManager.GetTimerList()[1]
        now = time()
        recordinglist = [ record for record in timerlist if self.IfMustInList( now, record, active, recordingsIDsService ) ]
        #print "active = ", active, "  recordinglist = ", recordinglist
        return recordinglist


    @eg.LogIt
    def GetTimerIDs( self, active=True, update=False ) :
        plugin = self.plugin
        recordingsIDsService = {}
        if plugin.useService and self.GetSetupValue( 'Service', 'Timerlist', '0' ) != '0' :
            recordingsIDsService = plugin.service.GetPseudoIDs( update )

        timerlist = self.dvbviewer.TimerManager.GetTimerList()[1]
        now = time()
        IDs = [ record[ TI_4_ID ] for record in timerlist if self.IfMustInList( now, record, active, recordingsIDsService ) ]
        return IDs

    @eg.LogIt
    def GetRecordings(self) :
        recordings = {}
        recMgr = self.dvbviewer.RecordManager
        if recMgr is not None:
            count = recMgr.Count
            for i in range(count):
                item = recMgr.Items(i)
                dtDate = dt.fromtimestamp(int(item.Date))
                timeInSecs = float(item.Duration) * 60 * 60 * 24
                dtDuration = td( seconds = timeInSecs ) # timedelta
                entry = toRecordingEntry(
                    recID = item.recID, 
                    channel = item.Channel, 
                    startDate = dtDate, 
                    description = item.Description, 
                    duration = dtDuration, 
                    filename = item.Filename, 
                    played = item.Played, 
                    title = item.Title,
                    series = "",
                    fromRS = False)
                recordings[entry[RE_RECID]] = entry
        return recordings

    @eg.LogIt
    def DeleteRecording(self, recID) :
        recMgr = self.dvbviewer.RecordManager
        if recMgr is not None:
            recMgr.DeleteEntry(recID)
            

    def ProcessMediaplayback( self ) :
        plugin = self.plugin
        last = plugin.playBackMode
        playBackIsStarted = self.dvbviewer.isMediaplayback()
        isDVD = self.dvbviewer.isDVD()
        if not playBackIsStarted :
            plugin.playBackMode = "NONE"
        elif isDVD :
            plugin.playBackMode = "DVD"
        else :
            plugin.playBackMode = "MEDIA"

        if last != plugin.playBackMode :
            if plugin.playBackMode != "NONE" :
                plugin.TriggerEvent("Playbackstart")
                plugin.UpdateDisplayMode()
            else :
                plugin.TriggerEvent("PlaybackEnd")
                plugin.UpdateDisplayMode()
        return True


    def IsDVD( self ) :
        return self.dvbviewer.isDVD


    @eg.LogIt
    def AddTimer( self,
        channelID,
        date,                 # dd.mm.yyyy
        startTime,            # hh:mm
        endTime,              # hh:mm
        description="",
        disableAV=False,
        enabled=True,
        recAction=0,          # intern = 0, tune only = 1,
                              # AudioPlugin = 2, Videoplugin = 3
        
        actionAfterRec=0,     # No action = 0, PowerOff = 1,
                              # Standby = 2, Hibernate = 3, Close = 4,
                              # Playlist = 5, Slumbermode: = 6
        days="-------"
    ) :
        pDate  = PyTime( strptime( date, "%d.%m.%Y" ) )
        pStart = PyTime( strptime( startTime, "%H:%M" ) ) #- PyTime( strptime( "00:00", "%H:%M" ) )
        pEnd   = PyTime( strptime( endTime,   "%H:%M" ) ) #- PyTime( strptime( "00:00", "%H:%M" ) )
        #count = self.dvbviewer.TimerManager.Count
        try :
            self.dvbviewer.TimerManager.AddItem( channelID, pDate, pStart, pEnd,
                                                 description, disableAV, enabled,
                                                 recAction, actionAfterRec, days )
        except :
            pass        #in case of "this is deprecated and will go away on"

        return True


    @eg.LogIt
    def GetCurrentShowDetails(self) :
        dataDict = {}
        dataDict['mode'] = self.plugin.actualDisplayMode
        if self.plugin.actualDisplayMode == 'TV':
            dataDict['title'] = self.DataManagerGetValue('#TV.Now.title')
            dataDict['channel'] = self.DataManagerGetValue('#channelname')
            dataDict['description'] = self.DataManagerGetValue('#TV.Now.description')
            dataDict['starttime'] = self.DataManagerGetValue('#TV.Now.start')
            dataDict['endtime'] = self.DataManagerGetValue('#TV.Now.stop')
            dataDict['duration'] = self.DataManagerGetValue('#TV.Now.duration')
            dataDict['remaining'] = self.DataManagerGetValue('#TV.Now.remain')
        elif self.plugin.actualDisplayMode == 'MEDIA':
            dataDict['title'] = self.DataManagerGetValue('#Media.title')
            dataDict['channel'] = self.DataManagerGetValue('#Media.Artist')
            dataDict['description'] = ''
            dataDict['starttime'] = ''
            dataDict['endtime'] = ''
            dataDict['duration'] = self.DataManagerGetValue('#duration')
            dataDict['remaining'] = self.DataManagerGetValue('#remain')
        elif self.plugin.actualDisplayMode == 'DVD':
            dataDict['title'] = self.DataManagerGetValue('#MR.MediaTitle')
            dataDict['channel'] = ''
            dataDict['description'] = ''
            dataDict['starttime'] = ''
            dataDict['endtime'] = ''
            dataDict['duration'] = self.DataManagerGetValue('#duration')
            dataDict['remaining'] = self.DataManagerGetValue('#remain')
        else:
            dataDict['title'] = ''
            dataDict['channel'] = ''
            dataDict['description'] = ''
            dataDict['starttime'] = ''
            dataDict['endtime'] = ''
            dataDict['duration'] = ''
            dataDict['remaining'] = ''

        return dataDict


    def DataManagerGetValue(self, keyName) :
        value = ''
        try:
            dataMgr = self.dvbviewer.DataManager
            if dataMgr is not None:
                value = dataMgr.Value(keyName)
        except Exception:
            pass  # silently ignore. DataManager no longer available after shutdown, however, we don't care.
        return value


    @eg.LogIt
    def DataManagerGetAllValues(self) :
        dataDict = {}
        try:
            dataMgr = self.dvbviewer.DataManager
            if dataMgr is not None:
                dataStr = dataMgr.GetAll()
                dataDict = dict(
                    (k.strip(), v.strip()) for k,v in (item.split('=') for item in dataStr.split(';'))
                )
        except Exception, exc:
            eg.PrintError('Failed dvbviewer.DataManager.DataManagerGetAllValues():', unicode(exc))
        return dataDict
            


class DVBViewerWatchDogThread( Thread ) :
    '''
    The watchdog thread is started by the plugin during startup. It monitors continuously the process list and waits
    for the dvbviewer.exe process. As soon as it detects the process, it starts the worker thread, which connects
    to DVBViewer's COM interface. As soon as it detects the process termination, it stops the worker thread
    and cleans up ressources.
    As a second task the watchdog thread periodically requests timers and recordings from Recording Service.
    '''

    def __init__( self, plugin, watchDogTime ) :

        Thread.__init__(self, name="DVBViewerWatchDogThread")
        self.plugin = plugin
        self.abort = False
        self.watchDogTime = watchDogTime
        self.started = False
        self.pauseEvent = Event()


    @eg.LogItWithReturn
    def run(self) :
        if self.watchDogTime == 0 :
            return False
        plugin = self.plugin

        try:
            self.pauseEvent.set()
            
            CoInitialize()
    
            queryTime = 5
            if queryTime > self.watchDogTime:
                queryTime = self.watchDogTime
    
            queryU = (
                    "SELECT * FROM __InstanceOperationEvent  WITHIN " + str( queryTime) +
                    " WHERE TargetInstance ISA 'Win32_Process' "
                    "AND TargetInstance.Name='dvbviewer.exe'" )
    
            queryA = ( "SELECT * FROM Win32_ProcessTrace WHERE ProcessName='dvbviewer.exe'" )
    
            WMI = GetObject('winmgmts:')
            
            eventSource = None
            eventsUsed = True
            
            try :
                eventSource = WMI.ExecNotificationQuery( queryA )
                START_EVENT = 'Win32_ProcessStartTrace'
                STOP_EVENT  = 'Win32_ProcessStopTrace'
                #print "Administrator rights"
            except :
                try :
                    eventSource = WMI.ExecNotificationQuery( queryU )
                    START_EVENT = '__InstanceCreationEvent'
                    STOP_EVENT  = '__InstanceDeletionEvent'
                    #print "User rights"
                except :
                    eventsUsed = False
                    eg.PrintDebugNotice('Getting WMI eventSource failed')
                    #print "Fallback"
    
            try:
                self.started = plugin.IsDVBViewerProcessRunning(WMI, eventOnException=False)
            except:
                self.started = False
    
            nextTimeViewer = time()
            timeout=True
    
            while not self.abort :
                try:
                    if ( eventsUsed ) :
                        try :
                            eventType = eventSource.NextEvent( 500 ).Path_.Class  # 500 = timeout in ms
                            if eventType == START_EVENT and not self.started :
                                #print "DVBViewer started"
                                eg.PrintDebugNotice("DVBViewer started")
                            elif eventType == STOP_EVENT and self.started:
                                #print "DVBViewer terminated"
                                eg.PrintDebugNotice("DVBViewer terminated")
                            elif time() < nextTimeViewer :
                                continue
                        except :
                            #print "Timeout"
                            if time() < nextTimeViewer :
                                continue
                            timeout = True

                    else :
                        while time() < nextTimeViewer and not self.abort :
                            started = plugin.IsDVBViewerProcessRunning(WMI, eventOnException=False)
                            if started and not self.started :
                                #print "DVBViewer started"
                                eg.PrintDebugNotice("DVBViewer started")
                                break
                            elif not started and self.started:
                                #print "DVBViewer terminated"
                                eg.PrintDebugNotice("DVBViewer terminated")
                                break
                            else :
                                timeCount = queryTime * 2
                                while ( timeCount > 0 and not self.abort ) :
                                    sleep( 0.5 )
                                    timeCount -= 1
                                    #print "wait"

                    # This event-object should be set before suspend (action: WaitUntilPluginIdle) and reset after resume
                    # It pauses background execution of DVBViewer- and RS-requests while system is going to suspend state.
                    # Otherwise acquired lock-objects would timeout after resume.
                    self.pauseEvent.wait(120) # timeout = 120 sec
                    self.pauseEvent.set()  # in case of timeout
                    
                    plugin.executionStatusChangeLock.acquire( timeout=TERMINATE_TIMEOUT )
        
                    try:
                        
                        if plugin.useService and timeout :
                            plugin.service.UpdateWithLock( UPDATE_ALL )
            
                        if plugin.closeWaitActive and plugin.eventHandlingIsAlive :
                            continue
            
                        started = plugin.IsDVBViewerProcessRunning(WMI, eventOnException=False)
                            
                        if self.started == started and not timeout :
                            continue
            
                        self.started = started
                        timeout=False
            
                        #print "started = ", self.started, "  workerThread = ", plugin.workerThread
            
                        nextTimeViewer = time() + self.watchDogTime
            
                        if not self.started and plugin.workerThread is not None :
                            #print "WatchDog: Disconnect"
                            eg.PrintDebugNotice( "Termination of DVBViewer detected by watch dog" )
                            if plugin.workerThread.Stop( TERMINATE_TIMEOUT ) :
                                eg.PrintError("Could not terminate DVBViewer worker thread")
                            plugin.workerThread = None
                            plugin.DVBViewerIsFinished()
            
                        elif self.started and plugin.workerThread is None :
                            #print "WatchDog: Connect"
                            eg.PrintDebugNotice( "DVBViewer will be connected by watch dog" )
                            plugin.Connect( CONNECT )
            
                        if plugin.workerThread is not None :
                            #print "WatchDog: Update recording timers"
                            updatedRecordings = 0
                            try :
                                plugin.checkEventHandlingLock.acquire( blocking = False, timeout = CALLWAIT_TIMEOUT )
                                if plugin.SendCommand( DUMMY_ACTION, lock = False, connectionMode = CHECK_CONNECT ) :
                                    updatedRecordings = plugin.UpdateDVTimers( lock = False, updateService = False )
                                else :
                                    plugin.checkEventHandlingLock.acquire( blocking = False )
                                    plugin.checkEventHandlingLock.release()
                                    plugin.eventHandlingIsAlive = True
            
                            except :
                                msg = "DVBViewer process running but could not accessed by the Watch Dog Thread. Restart of DVBViewer is suggested."
                                plugin.TriggerEvent( "SevereError.Connect" )
                                eg.PrintDebugNotice(msg)
                                eg.PrintError(msg)
                                plugin.checkEventHandlingLock.acquire( blocking = False )
                                plugin.checkEventHandlingLock.release()
                                plugin.eventHandlingIsAlive = True
                            else :
                                if updatedRecordings > 0 :
                                    eg.PrintDebugNotice(    "Number of recording timers ("
                                                          + str(updatedRecordings)
                                                          + ") was updated  by watch dog" )
                        
                    finally:
                        plugin.executionStatusChangeLock.release()

                except Exception, exc:
                    eg.PrintDebugNotice('Unexpected error: ', unicode(exc))

        finally:
            del eventSource
            del WMI
    
            # see also http://msdn.microsoft.com/en-us/library/ms688715%28VS.85%29.aspx
            # Closes the COM library on the current thread, unloads all DLLs loaded by the thread, 
            # frees any other resources that the thread maintains, and forces all RPC connections on the thread to close. 
            CoUninitialize()  
        return True


    @eg.LogIt
    def Finish( self ) :
        plugin = self.plugin
        plugin.checkEventHandlingLock.acquire( blocking = False )
        plugin.checkEventHandlingLock.release()
        plugin.eventHandlingIsAlive = True
        self.abort = True
        self.started = False
        return True



class DVBViewerTerminateThread( Thread ) :
    '''
    This thread is responsible for cleaning up allocated ressources when DVBViewer closes. 
    It is called by ComEventHandler.OnonDVBVClose()
    '''

    def __init__( self, plugin ) :

        Thread.__init__(self, name="DVBViewerTerminateThread")
        self.plugin = plugin


    @eg.LogItWithReturn
    def run(self) :
        plugin = self.plugin

        plugin.lockedByTerminate |= plugin.executionStatusChangeLock.acquire( blocking=False, timeout=TERMINATE_TIMEOUT )

        workerThread = plugin.workerThread

        if workerThread is None :
            plugin.executionStatusChangeLock.release()
            plugin.lockedByTerminate = False
            eg.PrintDebugNotice( "DVBViewer is not disconnected by the close event processing" )
            return True

        try:
            plugin.lockedByTerminate |= plugin.executionStatusChangeLock.acquire( blocking = False, timeout = TERMINATE_TIMEOUT )
            plugin.workerThread = None
            CoInitialize()

            queryA = "SELECT * FROM Win32_ProcessStopTrace WHERE ProcessName='dvbviewer.exe'"
            queryU = (
                       "SELECT * FROM __InstanceDeletionEvent  WITHIN 1 "
                       "WHERE TargetInstance ISA 'Win32_Process' "
                       "AND TargetInstance.Name='dvbviewer.exe'"
                     )
    
            WMI = GetObject('winmgmts:')
            
            plugin.lockedByTerminate |= plugin.executionStatusChangeLock.acquire( blocking = False, timeout = TERMINATE_TIMEOUT )

            events = None
            finished = True
            eventsUsed = True
    
            try :
                events = WMI.ExecNotificationQuery( queryA )
            except :
                try :
                    events = WMI.ExecNotificationQuery( queryU )
                except :
                    timeCounter = TERMINATE_TIMEOUT
                    finished   = False
                    while ( timeCounter > 0 ) :
                        if not plugin.IsDVBViewerProcessRunning(WMI, eventOnException=False):
                            finished = True
                            break
                        sleep( 1.0 )
                        timeCounter -= 1
                    eventsUsed = False
                    
            plugin.lockedByTerminate |= plugin.executionStatusChangeLock.acquire( blocking = False, timeout = TERMINATE_TIMEOUT )

            if plugin.IsDVBViewerProcessRunning(WMI, eventOnException=False) and eventsUsed :
                try :
                    events.NextEvent( TERMINATE_TIMEOUT * 1000 )
                except :
                    finished = False
                
            if not finished :
                eg.PrintDebugNotice("DVBViewer could not be terminated")
                plugin.TriggerEvent( "DVBViewerCouldNotBeTerminated" )
                
            if finished :
                plugin.DVBViewerIsFinished()
    
            plugin.lockedByTerminate |= plugin.executionStatusChangeLock.acquire( blocking = False, timeout = TERMINATE_TIMEOUT )

            if workerThread is not None :
                if workerThread.Stop( TERMINATE_TIMEOUT ) :
                    eg.PrintError("Could not terminate DVBViewer thread")
                    plugin.TriggerEvent( "DVBViewerCouldNotBeTerminated" )
    
            plugin.lockedByTerminate |= plugin.executionStatusChangeLock.acquire( blocking = False, timeout = TERMINATE_TIMEOUT )
            del events
    
        finally:
            del WMI
            CoUninitialize()
            plugin.terminateThread = None

            plugin.lockedByTerminate |= plugin.executionStatusChangeLock.acquire( blocking=False, timeout=TERMINATE_TIMEOUT )
            if plugin.lockedByTerminate :
                plugin.executionStatusChangeLock.release()
                plugin.lockedByTerminate = False

        return finished



class LockWithTimeout :

    def LockTimeout( self ) :
        '''This method is called whenever an acquired lock is not released within the specified time.
        Sounds good... but... what happens then? Release the lock, really?? 
        So we acquired a lock in order to avoid that multiple threads modify objects at the same time 
        (thread synchronization), but suddenly, when we are in trouble anyway, we allow unsynchronized access?
        Friends of the binary way of live, I think that wasn't a brilliant idea... :D
        Best we can do in this situation: Restart EventGhost, truly! That's why we trigger an event.
        '''
        try:
            msg = "Error: Timeout detected, DVBViewer plugin lock '" + self.name + "' force released. "
            eg.PrintDebugNotice(msg)
            eg.PrintError( msg )
            eg.PrintError( "Restart of EventGhost and DVBViewer is suggested." )
            self.printCaller('*** timeout!')
            if self.severeErrorEventOnTimeout:
                self.plugin.TriggerEvent( "SevereError.LockTimeout", msg )
            self.lock.release()
        except:
            pass
        return True

    def __init__( self, plugin, name, timeoutFunc=None, timeout=CALLWAIT_TIMEOUT-10, severeErrorEventOnTimeout=True) :  
        self.plugin = plugin
        if timeoutFunc is None:
            self.timeoutFunc = self.LockTimeout
        else:
            self.timeoutFunc = timeoutFunc
        self.timer = None
        self.lock = Lock()
        self.name = name
        self.timeout = timeout
        self.severeErrorEventOnTimeout = severeErrorEventOnTimeout


    def __del__(self) :
        if self.timer is not None :
            self.timer.cancel()
            del self.timer
            self.timer = None
            eg.PrintDebugNotice('Warning: Lock object "' + self.name + ' deleted while locked')
        del self.lock


    def printCaller(self, prefix, blocking=None):
        '''Debug helper function. Prints the calling classname, method and code line for
        acquire and release locks. Helpful in order to find dead locks.'''
        if False: #eg.debugLevel > 0: # TODO
            try:
                stack = inspect.stack()
                frame, lineno, funcname = 0, 2, 3
                lockName = self.name
                sl = stack[2]
                callingClass = sl[frame].f_locals['self'].__class__.__name__
                eg.PrintDebugNotice(callingClass, sl[funcname], sl[lineno], lockName, prefix, 'lock', "blocking="+str(blocking) if blocking is not None else "")
            except:
                pass
            finally:
                if stack is not None:
                    del stack

            
    def acquire( self, blocking=True, timeout=None ) :
        self.printCaller('+++ acquire', blocking)
        if timeout is None: 
            timeout = self.timeout
        blocked = self.lock.acquire( blocking )
        if blocked :
            self.printCaller('+++ ACQUIRE', blocking)
            self.timer = Timer( timeout, self.timeoutFunc )
            self.timer.start()
        return blocked


    def release( self ) :
        self.printCaller('--- RELEASE')
        ret = True
        try:
            if self.timer is not None :
                self.timer.cancel()
                del self.timer
                self.timer = None
                self.lock.release()
            else :
                eg.PrintDebugNotice('DVBViewer plugin unlocked lock "' + self.name + '" release detected')
                eg.PrintError( "Error: unlock lock '" + self.name + "' released" )
                ret = False
            #print "Released"
        except Exception, exc:
            eg.PrintError('Error releasing lock ' + self.name + ':', unicode(exc))
        return ret


class ActionPrototype(eg.ActionClass):
    def __call__(self):
        if self.value[1] :
            connectionMode = WAIT_CHECK_START_CONNECT
        else :
            connectionMode = CHECK_CONNECT
        return self.plugin.SendCommand(self.value[0], connectionMode = connectionMode )


class Start(eg.ActionClass):

    @eg.LogItWithReturn
    def __call__(self):
        self.plugin.Connect( WAIT_CHECK_START_CONNECT, lock = True )
        return True


class IsDVBViewerProcessRunning(eg.ActionClass):

    @eg.LogItWithReturn
    def __call__(self):
        return self.plugin.IsDVBViewerProcessRunning(eventOnException=False)


class WaitUntilPluginIdle(eg.ActionClass):
    name = "Prepare for Standby"
    description = u"""Prepares for Standby or Hibernate and waits until the plugin is idle. This action should be called before 
suspending the system (i.e. before calling Standby or Hibernate). See Description page for more info. 
    
This action waits until the plugin is idle and pauses background execution of the WatchDogThread. 
It should be called before suspending the system (i.e. before calling Standby or Hibernate). 
Otherwise lock-timeouts might occur, e.g. if the plugin gets interrupted in the middle of a background 
task, like a COM- or service-request. 
<p>
This action should be called as the <i>last</i> DVBViewer plugin action before suspending the system.
It should not be called without performing Standby / Hibernate immediately afterwards."""

    @eg.LogItWithReturn
    def __call__(self):
        plugin = self.plugin
        
        # This event-object pauses the WatchDogThread. Background requests to RS and DVBViewer (getting timer lists)
        # should be paused while system performs suspend. WatchDogThread continues after resume
        if plugin.watchDogThread is not None:
            plugin.watchDogThread.pauseEvent.clear()
        
        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.useService:
                plugin.serviceInUse.acquire()
                try:
                    pass
                finally:
                    plugin.serviceInUse.release()
        finally:
            plugin.executionStatusChangeLock.release()
        return True


class CloseDVBViewer( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, waitForTermination = False ) :
        plugin = self.plugin
        if plugin.workerThread is None :
            return False

        return plugin.WaitForTermination( sendCloseCommand = True, block = waitForTermination )


    def Configure(  self, waitForTermination = False ) :

        self.panel = eg.ConfigPanel()
        panel = self.panel

        checkBox = wx.CheckBox( panel, -1, self.text.checkBoxText )
        checkBox.SetValue( waitForTermination )

        panel.AddLine( checkBox )

        while panel.Affirmed():
            waitForTermination = checkBox.GetValue()
            panel.SetResult( waitForTermination )
        return True


class StopAllActiveRecordings( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self ) :
        plugin = self.plugin
        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( CHECK_CONNECT ) :
                plugin.workerThread.CallWait(
                    partial(plugin.workerThread.StopAllActiveRecordings ),
                    CALLWAIT_TIMEOUT
                )
        finally:
            plugin.executionStatusChangeLock.release()
        return True


class GetNumberOfActiveRecordings( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, enableDVBViewer = True, enableDVBService = False, updateDVBService = False ) :
        plugin = self.plugin

        count = 0
        if plugin.useService and enableDVBService :
            count = plugin.service.GetNumberOfActiveRecordings( updateDVBService )

        plugin.executionStatusChangeLock.acquire()
        try:
            if enableDVBViewer :
                if plugin.Connect( CHECK_CONNECT ) :
                    idList =  plugin.workerThread.CallWait(
                        partial(plugin.workerThread.GetTimerIDs,
                                True, not enableDVBService and updateDVBService ),
                        CALLWAIT_TIMEOUT
                    )
                    count += len( idList )
        finally:
            plugin.executionStatusChangeLock.release()
        return count


    def Configure(  self, enableDVBViewer = True, enableDVBService = False, updateDVBService = False ) :

        self.plugin.ServiceConfigure( enableDVBViewer, enableDVBService, updateDVBService )


class GetRecordingsIDs( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, active = True, enableDVBViewer = True, enableDVBService = False, updateDVBService = False ) :
        plugin = self.plugin

        idList = []

        if plugin.useService and enableDVBService :

            plugin.executionStatusChangeLock.acquire()
            try:
                timerIDs = plugin.service.GetTimerIDs( updateDVBService )
                if timerIDs is None :
                    timerIDs = {}
            finally:
                plugin.executionStatusChangeLock.release()

            for k, v in timerIDs.iteritems() :
                if v[0] or not active :
                    idList.append( v[2] )

        if enableDVBViewer :

            connectionMode = WAIT_CHECK_START_CONNECT
            if active :
                connectionMode = CHECK_CONNECT

            plugin.executionStatusChangeLock.acquire()
            try:
                if plugin.Connect( connectionMode ) :
                    idList.extend( plugin.workerThread.CallWait(
                         partial(plugin.workerThread.GetTimerIDs, active,
                                 not enableDVBService and updateDVBService ),
                         CALLWAIT_TIMEOUT
                     ) )
            finally:
                plugin.executionStatusChangeLock.release()
                
        return idList


    def Configure(  self, active = True, enableDVBViewer = True, enableDVBService = False, updateDVBService = False ) :

        plugin = self.plugin

        panel = eg.ConfigPanel()

        checkBox = wx.CheckBox( panel, -1, self.text.active )
        checkBox.SetValue( active )

        panel.sizer.Add( checkBox )
        panel.sizer.Add(wx.Size(0,10))

        getFlags = plugin.ServiceConfigure(  enableDVBViewer, enableDVBService, updateDVBService, affirmed = False, panel = panel )

        while panel.Affirmed():
            active      = checkBox.GetValue()
            enableDVBViewer, enableDVBService, updateDVBService = getFlags()
            panel.SetResult( active, enableDVBViewer, enableDVBService, updateDVBService )
        return True


class IsConnected( eg.ActionClass ) :

    #@eg.LogItWithReturn
    def __call__( self ) :
        plugin = self.plugin
        plugin.executionStatusChangeLock.acquire()
        try:
            return self.plugin.workerThread is not None
        finally:
            plugin.executionStatusChangeLock.release()


class IsRecording( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, enableDVBViewer = True, enableDVBService = False, updateDVBService = False ) :
        return eg.plugins.DVBViewer.GetNumberOfActiveRecordings(enableDVBViewer,
                                                        enableDVBService,
                                                        updateDVBService ) != 0

    def Configure(  self, enableDVBViewer = True, enableDVBService = False, updateDVBService = False ) :

        self.plugin.ServiceConfigure( enableDVBViewer, enableDVBService, updateDVBService )


class SendAction( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, action ) :
        if action != -1 :
            return self.plugin.SendCommand(action)
        else :
            return False

    def Configure( self, action=-1 ) :
        panel = eg.ConfigPanel()
        if action < 0 :
            action = 0
        actionCtrl = panel.SpinNumCtrl( action, min=0, max=999999, fractionWidth=0, integerWidth=6)
        panel.AddLine( self.text.action, actionCtrl )

        while panel.Affirmed() :
            action = actionCtrl.GetValue()

            panel.SetResult( action )
        return True


class ShowWindow( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, windowID ) :

        plugin = self.plugin

        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                plugin.workerThread.CallWait( partial( plugin.workerThread.ShowWindow, windowID ), CALLWAIT_TIMEOUT )
                return True
        finally:
            plugin.executionStatusChangeLock.release()

        return False


    def Configure( self, windowID=-1 ) :
        panel = eg.ConfigPanel()
        if windowID < 0 :
            windowID = 0
        windowIDCtrl = panel.SpinNumCtrl( windowID, min=0, max=999999, fractionWidth=0, integerWidth=6)
        panel.AddLine( self.text.windowID, windowIDCtrl )

        while panel.Affirmed() :
            windowID = windowIDCtrl.GetValue()

            panel.SetResult( windowID )
        return True


class ShowInfoinTVPic( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, text = "", timeout=15.0, force = False ) :

        connectMode = CHECK_CONNECT
        if force :
            connectMode = WAIT_CHECK_START_CONNECT

        plugin = self.plugin

        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( connectMode ) :
                plugin.workerThread.CallWait( partial( plugin.workerThread.ShowInfoinTVPic, " "+ text + " ", timeout ), CALLWAIT_TIMEOUT )
                plugin.infoInTVPicTimeout = time() + timeout
                return True
        finally:
            plugin.executionStatusChangeLock.release()

        return False


    def Configure( self, displayText="", timeout=15.0, force=False ) :

        plugin = self.plugin
        text = self.text

        panel = eg.ConfigPanel( resizable=True )

        textCtrl = panel.TextCtrl("\n", style=wx.TE_MULTILINE)
        w, h = textCtrl.GetBestSize()
        textCtrl.ChangeValue(displayText)
        textCtrl.SetMinSize((-1, h))

        timeCtrl = panel.SpinNumCtrl(timeout, min=0, max=999, fractionWidth=0, integerWidth=3)

        forceCheckBoxCtrl = wx.CheckBox(panel, -1, text.force)
        forceCheckBoxCtrl.SetValue( force )

        sizer = wx.GridBagSizer(5, 5)

        rowCount = 0
        sizer.Add(wx.StaticText(panel, -1, text.text), (rowCount, 0))
        sizer.Add(textCtrl,                            (rowCount, 1), flag=wx.EXPAND)

        rowCount += 1
        sizer.Add(wx.StaticText(panel, -1, text.time), (rowCount, 0), flag=wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(timeCtrl,                            (rowCount, 1) )

        rowCount += 1
        sizer.Add(forceCheckBoxCtrl,                   (rowCount, 0), (1,2), flag=wx.EXPAND)

        sizer.AddGrowableCol(1)
        panel.sizer.Add(sizer, 0, flag = wx.EXPAND)

        while panel.Affirmed():
            displayText = textCtrl.GetValue()
            timeout     = timeCtrl.GetValue()
            force       = forceCheckBoxCtrl.GetValue()

            panel.SetResult( displayText, timeout, force )


class DeleteInfoinTVPic( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self ) :

        plugin = self.plugin
        if plugin.infoInTVPicTimeout > time() + 0.01 :
            plugin.infoInTVPictimeout=0.0
            plugin.executionStatusChangeLock.acquire()
            try:
                if plugin.Connect( CHECK_CONNECT ) :
                    plugin.workerThread.CallWait( partial( plugin.workerThread.ShowInfoinTVPic, " ", 0 ), CALLWAIT_TIMEOUT )
                    return True
            finally:
                plugin.executionStatusChangeLock.release()
        return False


class UpdateEPG( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, timeBetweenChannelChange = 60.0, disableAVafterChannelChange = True, event = "EPGUpdateFinished" ) :

        plugin = self.plugin
        self.event = event

        if not plugin.frequencies :
            if not plugin.Connect( WAIT_CHECK_START_CONNECT, lock = True ) :
                plugin.TriggerEvent( event )
                return False

        plugin.tuneEPGThread = self.EPGTuneThread( plugin, timeBetweenChannelChange, event, disableAVafterChannelChange )
        plugin.tuneEPGThread.start()

        return True


    class EPGTuneThread( Thread ) :

        def __init__( self, plugin, timeBetweenChannelChange, eventText, disableAVafterChannelChange ) :

            Thread.__init__(self, name="DVBViewerEPGTuneThread")
            self.plugin = plugin
            self.timeBetweenChannelChange = timeBetweenChannelChange
            self.event = Event()
            self.eventText = eventText
            self.disableAVafterChannelChange = disableAVafterChannelChange

        @eg.LogItWithReturn
        def run( self ) :
            plugin = self.plugin
            abort = False

            saveDisableAV = plugin.disableAV

            CoInitialize()

            for frequency, channel in plugin.frequencies.iteritems() :
                changed = False
                while not changed and not abort:
                    if plugin.numberOfActiveTimers == 0 :
                        plugin.disableAV = self.disableAVafterChannelChange
                        changed =  plugin.TuneChannelIfNotRecording( channel[0], "Updating EPG", self.timeBetweenChannelChange )
                    self.event.wait( self.timeBetweenChannelChange )
                    plugin.disableAV = saveDisableAV
                    abort = self.event.isSet()
                if abort :
                    break

            plugin.SendCommand( 16383 )  #Stop Graph

            CoUninitialize()
            plugin.tuneEPGThread = None

            if not abort :
                plugin.TriggerEvent( self.eventText )

            return not abort

        def Finish( self ) :
            self.event.set()


    def Configure(  self, timeBetweenChannelChange=60.0, disableAVafterChannelChange=True, event="EPGUpdateFinished" ) :

        text = self.text

        panel = eg.ConfigPanel()

        disableAVCheckBoxCtrl = wx.CheckBox(panel, -1, text.disableAV)
        disableAVCheckBoxCtrl.SetValue( disableAVafterChannelChange )
        eventCtrl = panel.TextCtrl( event )

        epgTimeCtrl = panel.SpinNumCtrl(timeBetweenChannelChange, min=0, max=999, fractionWidth=0, integerWidth=3)

        panel.AddLine( disableAVCheckBoxCtrl )
        panel.AddLine( text.time,  epgTimeCtrl )
        panel.AddLine( text.event, eventCtrl )

        while panel.Affirmed():
            disableAVafterChannelChange                = disableAVCheckBoxCtrl.GetValue()
            timeBetweenChannelChange = epgTimeCtrl.GetValue()
            event                    = eventCtrl.GetValue()

            panel.SetResult( timeBetweenChannelChange, disableAVafterChannelChange, event )


class AddRecording( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__(self,
                 channelID,
                 date,                 # dd.mm.yyyy
                 startTime,            # hh:mm
                 endTime,              # hh:mm
                 description="",
                 disableAV=False,
                 enabled=True,
                 recAction=0,          # intern = 0, tune only = 1,
                                       # AudioPlugin = 2, Videoplugin = 3

                 actionAfterRec=0,     # No action = 0, PowerOff = 1,
                                       # Standby = 2, Hibernate = 3, Close = 4,
                                       # Playlist = 5, Slumbermode: = 6

                 days="-------"
                ) :
        plugin = self.plugin

        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                plugin.workerThread.CallWait(
                    partial( plugin.workerThread.AddTimer, 
                             channelID, date, startTime, endTime, description, disableAV, 
                             enabled, recAction, actionAfterRec, days 
                    ), 
                    CALLWAIT_TIMEOUT
                )
    
        finally:
            plugin.executionStatusChangeLock.release()
        return True


    def Configure(  self,
                 channelID   = "",
                 date        = "",     # dd.mm.yyyy
                 startTime   = "",     # hh:mm
                 endTime     = "",     # hh:mm
                 description = "",
                 disableAV   = False,
                 enabled     = True,
                 recAction   = 0,      # intern = 0, tune only = 1,
                                       # AudioPlugin = 2, Videoplugin = 3

                 actionAfterRec = 0,   # No action = 0, PowerOff = 1,
                                       # Standby = 2, Hibernate = 3, Close = 4,
                                       # Playlist = 5, Slumbermode: = 6

                 days = "-------"
                ) :

        plugin = self.plugin
        text = self.text

        if len(plugin.tvChannels) == 0 and len(plugin.radioChannels) == 0 :
            plugin.Connect( WAIT_CHECK_START_CONNECT, lock = True )

        ix = 0
        if plugin.IDbychannelIDList.has_key( channelID ) :

            key = plugin.IDbychannelIDList[ channelID ]
            self.tv = key[1]
            if ( self.tv ) :
                self.choices = plugin.tvChannels
            else :
                self.choices = plugin.radioChannels
            ix = key[0]
        else :
            self.tv = True
            self.choices = plugin.tvChannels


        def onRadioButton( event ) :

            ix = channelChoiceCtrl.GetSelection()

            if ix != wx.NOT_FOUND :
                if self.tv :
                    plugin.indexTV = ix
                else :
                    plugin.indexRadio = ix

            tvMode = tvCtrl.GetValue()

            if tvMode :
                ix = plugin.indexTV
                self.choices = plugin.tvChannels
            else :
                ix = plugin.indexRadio
                self.choices = plugin.radioChannels
            self.tv = tvMode
            channelChoiceCtrl.Clear()
            channelChoiceCtrl.SetItems( self.choices )
            channelChoiceCtrl.SetSelection( ix )
            event.Skip()


        self.panel = eg.ConfigPanel()
        panel = self.panel

        tvCtrl = wx.RadioButton( panel, -1, text.tvButton, style = wx.RB_GROUP )
        tvCtrl.SetValue( self.tv )
        tvCtrl.Bind(wx.EVT_RADIOBUTTON, onRadioButton)

        radioCtrl = wx.RadioButton( panel, -1, text.radioButton )
        radioCtrl.SetValue( not self.tv )
        radioCtrl.Bind(wx.EVT_RADIOBUTTON, onRadioButton )



        channelChoiceCtrl = panel.Choice(ix, choices=plugin.tvChannels + plugin.radioChannels)
        wSize = channelChoiceCtrl.GetSize()
        channelChoiceCtrl.Clear()
        channelChoiceCtrl.SetItems( self.choices )
        channelChoiceCtrl.SetSizeHintsSz( wSize )
        channelChoiceCtrl.SetSelection( ix )

        wxDate = wx.DateTime()
        if date == "" :
            wxDate = wx.DateTime.Now()
        else :
            wxDate.ParseFormat( date, "%d.%m.%Y" )
        dateCtrl = wx.DatePickerCtrl( panel, dt = wxDate, style=wx.DP_DEFAULT|wx.DP_DROPDOWN )#|wx.DP_ALLOWNONE )

        wxStartTime = wx.DateTime()
        if startTime == "" :
            wxStartTime = wx.DateTime.Now()
        else :
            wxStartTime.ParseFormat( startTime, "%H:%M" )
        startTimeCtrl = masked.timectrl.TimeCtrl( panel, format = "24HHMMSS", value = wxStartTime )

        wxEndTime = wx.DateTime()
        if endTime == "" :
            wxEndTime = wxStartTime
        else :
            wxEndTime.ParseFormat( endTime, "%H:%M" )
        endTimeCtrl = masked.timectrl.TimeCtrl( panel, format = "24HHMMSS", value = wxEndTime )

        descriptionCtrl = wx.TextCtrl( panel, size=(200,-1) )
        descriptionCtrl.SetValue( description )

        disableAVCheckBoxCtrl = wx.CheckBox(panel, -1, text.disableAV)
        disableAVCheckBoxCtrl.SetValue( disableAV )

        enabledCheckBoxCtrl = wx.CheckBox(panel, -1, text.enabled)
        enabledCheckBoxCtrl.SetValue( enabled )

        recActionChoiceCtrl = panel.Choice( recAction, choices=text.recActionChoices)
        actionAfterRecChoiceCtrl = panel.Choice( actionAfterRec, choices=text.actionAfterRecChoices)

        sb = wx.StaticBox( panel, -1, text.source )
        sBoxSizer = wx.StaticBoxSizer( sb, wx.HORIZONTAL )

        sizer = wx.GridSizer(2,2,5,5)
        sizer.Add( tvCtrl,    0, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
        sizer.Add( wx.StaticText(panel, -1, text.station), 0, flag = wx.ALIGN_BOTTOM)
        sizer.Add( radioCtrl, 0, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
        sizer.Add( channelChoiceCtrl, 0, flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)

        sBoxSizer.Add( sizer, 0 , flag = wx.EXPAND )

        panel.sizer.Add(sBoxSizer, 0, flag = wx.EXPAND )

        panel.sizer.Add(wx.Size(0,3))

        sb = wx.StaticBox( panel, -1, text.recordingDate )
        sBoxSizer = wx.StaticBoxSizer( sb, wx.VERTICAL )
        sBoxSizer.Add(wx.Size(0,3))

        sizer = wx.GridSizer(2,3,5,5)
        sizer.Add( wx.StaticText(panel, -1, text.date), 0, flag = wx.ALIGN_BOTTOM)
        sizer.Add( wx.StaticText(panel, -1, text.start), 0, flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL )
        sizer.Add(startTimeCtrl, 0, flag = wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(dateCtrl, 0, flag = wx.ALIGN_CENTER_VERTICAL)
        sizer.Add( wx.StaticText(panel, -1, text.end), 0, flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL )
        sizer.Add(endTimeCtrl, 0, flag = wx.ALIGN_CENTER_VERTICAL)

        sBoxSizer.Add( sizer, 0, flag = wx.EXPAND )

        sizer = wx.GridBagSizer( 0,3)

        dayCount = 0
        dayCheckBoxCtrl = []
        for day in text.days :
            dayCount += 1
            sizer.Add( wx.StaticText(panel, -1, day),( 0, dayCount ), flag = wx.LEFT | wx.ALIGN_CENTER_VERTICAL )
            checkBox = wx.CheckBox( panel, -1, "" )
            checkBox.SetValue( days[ dayCount-1 : dayCount ] != "-" )
            dayCheckBoxCtrl.append( checkBox )
            sizer.Add( checkBox,( 1, dayCount ), flag = wx.LEFT | wx.ALIGN_CENTER_VERTICAL )

        sBoxSizer.Add(wx.Size(0,5))
        sBoxSizer.Add( sizer, 0 )


        panel.sizer.Add(sBoxSizer, 0, flag = wx.EXPAND )

        panel.sizer.Add(wx.Size(0,3))

        sb = wx.StaticBox( panel, -1, "" )
        sBoxSizer = wx.StaticBoxSizer( sb, wx.VERTICAL )
        sBoxSizer.Add(wx.Size(0,10))

        sizer = wx.BoxSizer( wx.HORIZONTAL )
        sizer.Add( wx.StaticText(panel, -1, text.recordingDescription), 0, flag = wx.ALIGN_CENTER_VERTICAL )
        sizer.Add(descriptionCtrl, 0, flag = wx.EXPAND )
        sBoxSizer.Add( sizer, 0, flag = wx.EXPAND )

        sBoxSizer.Add(wx.Size(0,10))

        sizer = wx.GridSizer(2,3,5,5)

        sizer.Add(disableAVCheckBoxCtrl, 0 )
        sizer.Add( wx.StaticText(panel, -1, text.mode), 0, flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL )
        sizer.Add(recActionChoiceCtrl, 0 )

        sizer.Add(enabledCheckBoxCtrl, 0 )
        sizer.Add( wx.StaticText(panel, -1, text.afterRecording), 0, flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL )
        sizer.Add(actionAfterRecChoiceCtrl, 0 )

        sBoxSizer.Add( sizer, 0, flag = wx.EXPAND )

        panel.sizer.Add(sBoxSizer, 0, flag = wx.EXPAND )

        while panel.Affirmed():
            channelID       = plugin.channelIDbyIDList[ ( channelChoiceCtrl.GetValue(), self.tv ) ]
            date            = dateCtrl.GetValue().Format( "%d.%m.%Y" )
            startTime       = startTimeCtrl.GetValue( as_wxDateTime=True ).Format( "%H:%M" )
            endTime         = endTimeCtrl.GetValue( as_wxDateTime=True ).Format( "%H:%M" )
            description     = descriptionCtrl.GetValue()
            disableAV       = disableAVCheckBoxCtrl.GetValue()
            enabled         = enabledCheckBoxCtrl.GetValue()
            recAction       = recActionChoiceCtrl.GetValue()
            actionAfterRec  = actionAfterRecChoiceCtrl.GetValue()

            days = ""
            dayCount = -1
            for checkBox in dayCheckBoxCtrl :
                dayCount += 1
                if checkBox.GetValue() :
                    days += "T"
                else :
                    days += "-"

            panel.SetResult( channelID,
                             date,
                             startTime,
                             endTime,
                             description,
                             disableAV,
                             enabled,
                             recAction,
                             actionAfterRec,
                             days )


class GetSetupValue( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, section="", name="", default="" ) :

        plugin = self.plugin
        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                res = plugin.workerThread.CallWait(
                    partial( plugin.workerThread.GetSetupValue, section, name, default ),
                    CALLWAIT_TIMEOUT
                )
                return res
        finally:
            plugin.executionStatusChangeLock.release()
            
        return default


    def Configure(  self, section="", name="", default="" ) :

        self.panel = eg.ConfigPanel()
        panel = self.panel

        sectionCtrl = wx.TextCtrl( panel, size=(200,-1) )
        sectionCtrl.SetValue( section )

        nameCtrl = wx.TextCtrl( panel, size=(200,-1) )
        nameCtrl.SetValue( name )

        defaultCtrl = wx.TextCtrl( panel, size=(200,-1) )
        defaultCtrl.SetValue( default )

        panel.AddLine( self.text.section, sectionCtrl )
        panel.AddLine( self.text.setupName,    nameCtrl )
        panel.AddLine( self.text.default, defaultCtrl )

        while panel.Affirmed():
            section      = sectionCtrl.GetValue()
            name         =    nameCtrl.GetValue()
            default      = defaultCtrl.GetValue()
            
            panel.SetResult( section, name, default )


# TODO: re-implement using action 'GetTimerDetails' (simplifies a lot)
class GetDateOfRecordings( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self,
                  allRecordings=False,
                  enableDVBViewer=True,
                  enableDVBService=False,
                  updateDVBService=False
                ) :

        plugin = self.plugin

        readOutSuccessfull = plugin.useService and enableDVBService or enableDVBViewer
        timerDates = []
        if plugin.useService and enableDVBService :
            timerDates = plugin.service.GetTimerDates( active = False, update = updateDVBService )
            if timerDates is None :
                timerDates = []
                readOutSuccessfull = False

        if enableDVBViewer :
            recordingList = []
            plugin.executionStatusChangeLock.acquire()
            try:
                if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                    recordingList = plugin.workerThread.CallWait( 
                        partial( plugin.workerThread.GetTimers, False, updateDVBService and not enableDVBService ), 
                        CALLWAIT_TIMEOUT )
            finally:
                plugin.executionStatusChangeLock.release()
                
            readOutSuccessfull &= len(recordingList) > 0

            now = time()
            for record in recordingList :
                if record[TI_8_ENABLED] :
                    if record[TI_18_TIMERACTION] == 1 and record[TI_0_DESCRIPTION] == "EPG-Update by EventGhost" :
                        continue
                        
                    t = toDateTime( record[TI_5_DATE], record[TI_6_STARTTIME] )

                    if t < now :
                        continue
                    if not t in timerDates :
                        timerDates.append(t)
                    #print "date = ", ctime(t)
            

        timerDates.sort()

        if allRecordings :
            return ( readOutSuccessfull, timerDates )
        else :
            if len( timerDates ) == 0 :
                return ( readOutSuccessfull, -1 )
            else :
                return ( readOutSuccessfull, timerDates[ 0 ] )


    def Configure(  self, allRecordings=False, enableDVBViewer=True, enableDVBService=False, updateDVBService=False ) :

        plugin = self.plugin

        panel = eg.ConfigPanel()

        checkBox = wx.CheckBox( panel, -1, self.text.allDates )
        checkBox.SetValue( allRecordings )

        panel.sizer.Add( checkBox )
        panel.sizer.Add(wx.Size(0,10))

        getFlags = plugin.ServiceConfigure(  enableDVBViewer, enableDVBService, updateDVBService, affirmed = False, panel = panel )

        while panel.Affirmed():
            allRecordings      = checkBox.GetValue()
            enableDVBViewer, enableDVBService, updateDVBService = getFlags()
            panel.SetResult( allRecordings, enableDVBViewer, enableDVBService, updateDVBService )


class GetTimerDetails( eg.ActionClass ) :
    
    @eg.LogItWithReturn
    def __call__( self,
        allRecordings=False,
        enableDVBViewer=True,
        enableDVBService=True,
        updateDVBService=False,
        enabled=True,
        active=False
    ) :

        def mergeAndSort(rsTimerList, dvbvTimerList, enabled, active, now):
            resultList=[]
            for record in rsTimerList:
                if enabled and not record['enabled']:
                    continue
                if record['endDateTime'] < now:
                    continue
                if active and not record['recording']:
                    continue
                resultList.append(record)
                
            for record in dvbvTimerList:
                if record[TI_18_TIMERACTION] == 1 and record[TI_0_DESCRIPTION] == "EPG-Update by EventGhost" :
                    continue
                if enabled and not record[TI_8_ENABLED]:
                    continue
                startDatetime = toDateTime(record[TI_5_DATE], record[TI_6_STARTTIME])
                endDatetime = toDateTime(record[TI_5_DATE], record[TI_7_ENDTIME])
                if endDatetime < startDatetime:
                    endDatetime += 24 * 60 * 60
                if endDatetime < now:
                    continue
                if active and not record[TI_11_RECORDING]:
                    continue
                
                timerentry = toTimerEntry(
                    timerID = record[TI_4_ID],
                    channelID = record[TI_1_CHANNEL].split('|')[0], 
                    channelName = record[TI_1_CHANNEL].split('|')[1], 
                    dateStr = strftime("%d.%m.%Y", localtime(startDatetime)), 
                    startTimeStr = strftime("%H:%M:%S", localtime(startDatetime)),
                    endTimeStr = strftime("%H:%M:%S", localtime(endDatetime)), 
                    startDateTime = startDatetime, 
                    endDateTime = endDatetime, 
                    days = record[TI_15_DAYS], 
                    description = record[TI_0_DESCRIPTION], 
                    enabled = record[TI_8_ENABLED], 
                    recording = record[TI_11_RECORDING], 
                    action = record[TI_18_TIMERACTION],
                    fromRS = False
                )
                
                resultList.append(timerentry)
                
            resultList.sort(cmp=lambda x, y: cmp(x['startDateTime'], y['startDateTime']))
            return resultList
        
        
        plugin = self.plugin
        plugin.executionStatusChangeLock.acquire()
        try:
            readOutSuccessfull = True
    
            rsTimerList = []
            dvbvTimerList = []
            
            if plugin.useService and enableDVBService :
                rsTimerList = plugin.service.GetTimerList( update = updateDVBService )
                if rsTimerList is None :
                    rsTimerList = []
                    readOutSuccessfull = False
        finally:
            plugin.executionStatusChangeLock.release()

        if enableDVBViewer :
            plugin.executionStatusChangeLock.acquire()
            try:
                if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                    dvbvTimerList = plugin.workerThread.CallWait( 
                        partial( plugin.workerThread.GetTimers, active=active, update=(updateDVBService and not enableDVBService)),
                        CALLWAIT_TIMEOUT )
                else :
                    readOutSuccessfull = False
            finally:
                plugin.executionStatusChangeLock.release()

        resultList = mergeAndSort(rsTimerList, dvbvTimerList, enabled, active, time())
        #print "resultList=", resultList

        if allRecordings :
            return ( readOutSuccessfull, resultList )
        else :
            if not resultList or len( resultList ) == 0 :
                return ( readOutSuccessfull, [] )
            else :
                return ( readOutSuccessfull, resultList[ 0 ] )


    def Configure(  self, 
        allRecordings=False, 
        enableDVBViewer=True, 
        enableDVBService=True, 
        updateDVBService=True, 
        enabled=True, 
        active=False 
    ) :
        plugin=self.plugin
        panel = eg.ConfigPanel()

        allRecCB = wx.CheckBox( panel, -1, self.text.allRecordings )
        allRecCB.SetValue( allRecordings )
        enabledCB = wx.CheckBox( panel, -1, self.text.enabled )
        enabledCB.SetValue( enabled )
        activeCB = wx.CheckBox( panel, -1, self.text.active )
        activeCB.SetValue( active )

        panel.sizer.Add(wx.Size(0,5))
        panel.sizer.Add( allRecCB )
        panel.sizer.Add(wx.Size(0,5))
        panel.sizer.Add( enabledCB )
        panel.sizer.Add(wx.Size(0,5))
        panel.sizer.Add( activeCB )
        panel.sizer.Add(wx.Size(0,10))

        getFlags = plugin.ServiceConfigure(  enableDVBViewer, enableDVBService, updateDVBService, affirmed = False, panel = panel )

        while panel.Affirmed():
            allRecordings = allRecCB.GetValue()
            enabled = enabledCB.GetValue()
            active = activeCB.GetValue()
            enableDVBViewer, enableDVBService, updateDVBService = getFlags()
            panel.SetResult( allRecordings, enableDVBViewer, enableDVBService, updateDVBService, enabled, active )


class GetRecordingDetails( eg.ActionClass ) :
    name = "Get Recordings Details"
    description = """Returns recordings details like name, date, series, channel, play status etc. from DVBViewer 
as well as from Recording Service. 

The recording lists of both sources (DVBViewer and RS) are combined, however, 'playstatus' attribute is only available 
while DVBViewer is running and 'series' attribute is only supported by Recording Service.
The result is provided as a data dictionary in 'eg.result'."""

    @eg.LogIt
    def __call__( self,
        enableDVBViewer=True,
        enableDVBService=True,
        updateDVBService=False
    ) :
        def merge(rsList, dvbvList):
            """
            Merge result from DVBViewer and Recording Service.
            RS knows attribute 'series', but not 'played',
            DVBV knows attribute 'played', but not 'series'.
            """
            resultList = {}
            if rsList is not None and len(rsList) > 0 and dvbvList is not None and len(dvbvList) > 0:
                # copy the 'played' attribute from DVBViewer into the list from RS
                for rsKey, rsVal in rsList.items():
                    retVal = cpy(rsVal) # deep copy
                    dvbvVal = dvbvList.get(rsKey, None)
                    if dvbvVal is not None:
                        if (rsVal[RE_TITLE] == dvbvVal[RE_TITLE] # just to make sure that the recID key is the same...
                            #and rsVal[RE_STARTDATE] == dvbvVal[RE_STARTDATE]
                            and rsVal[RE_FILENAME] == dvbvVal[RE_FILENAME]
                        ):
                            retVal[RE_PLAYED] = dvbvVal[RE_PLAYED]
                        else:
                            eg.PrintError("Unexpected: rsVal is not equal to dvbvVal! rsVal=%s dvbvVal=%s" % (rsVal, dvbvVal))
                            
                    resultList[rsKey] = retVal
                    
                # copy missing DVBViewer recordings (direct recordings) into the list from RS
                for dvbvKey, dvbvVal in dvbvList.items():
                    if not dvbvKey in resultList:
                        resultList[dvbvKey] = cpy(dvbvVal)
                        
            elif rsList is not None and len(rsList) > 0:
                resultList = cpy(rsList)
            elif dvbvList is not None and len(dvbvList) > 0:
                resultList = cpy(dvbvList)
            return resultList

        plugin = self.plugin
        readOutSuccessfull = True
        rsRecordingList = {}
        dvbvRecordingList = {}

        if plugin.useService and enableDVBService :
            rsRecordingList = plugin.service.GetRecordingList( update = updateDVBService ) 
            if rsRecordingList is None :
                rsRecordingList = {}
                readOutSuccessfull = False

        if enableDVBViewer :
            plugin.executionStatusChangeLock.acquire()
            try:
                if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                    dvbvRecordingList = plugin.workerThread.CallWait( 
                        partial( plugin.workerThread.GetRecordings),
                        CALLWAIT_TIMEOUT )
                else :
                    readOutSuccessfull = False
            finally:
                plugin.executionStatusChangeLock.release()

        resultList = merge(rsRecordingList, dvbvRecordingList)

            
        return ( readOutSuccessfull, resultList )


    def Configure(  self, 
        enableDVBViewer=True, 
        enableDVBService=True, 
        updateDVBService=True
    ) :
        plugin = self.plugin
        panel = eg.ConfigPanel()

        getFlags = plugin.ServiceConfigure(  enableDVBViewer, enableDVBService, updateDVBService, affirmed = False, panel = panel )

        while panel.Affirmed():
            enableDVBViewer, enableDVBService, updateDVBService = getFlags()
            panel.SetResult( enableDVBViewer, enableDVBService, updateDVBService )


class DeleteRecordings( eg.ActionClass ) :
    name = "Delete Recordings"
    description = u'''Delete Recordings according to search criteria and filters. 
May be used to implement an automated housekeeping, especially for recorded TV series. 
IT'S POWERFUL, USE IT WITH CARE !

Suppose that you daily record some News program, or a weekly talk show or whatever. 
Tired of deleting old recordings? Let this action do the job for you.
<p>
Delete Recordings (aka 'series killer' or 'Putzfisch' ;)) is a powerful tool which lets you search and delete 
recordings with a variety of search options. 
Its intendeded usage is for automatic deletion of outdated recordings (housekeeping), 
especially for recorded TV series.
<p>
The configuration of this action should be self-explaining, however some hints:
<ul>
<li> If you want to implement an automated daily housekeeping, just call the 'Delete Recordings' 
macros from an appropriate event, I'd suggest to use 'DVBViewerService.AllRecordingsFinished'.
<li> ALWAYS TEST YOUR QUERY IN DRY MODE FIRST! 'Dry Mode' let's you play and try to see which recordings 
would be deleted, once the query is armed. As long as you are in Dry Mode, no recording will be deleted, never.
<li> It's recommended to specify the delete query as exact as possible, i.e. for example use not only 
the title of the recording, but also the channel name if you can etc.
As a thumb rule, the more options you specify, the smaller the risk that you delete something you regret afterwards.
<li> Partial strings search is supported, for example if you search for 'ind' you would 
find 'Indiana Jones', 'Gone with the Wind' etc.
<li> Series are only supported by Recording Service, not by DVBViewer. Series names can be configured 
in the timer configuration details of the Recording Service.
<li> The Played / Unplayed status is only supported by DVBViewer, not by the Recording Service. 
If you want to use it as a criteria in your search, DVBViewer must be running while the action is executed. 
If not, nothing bad will happen, it's just not working, but no recordings are deleted.
<li> The action can handle and delete 'Direct Recordings' from DVBViewer as well as Recordings 
from the Recording Service. This is done transparently, you don't have to care for.
</ul>
'''

    class Text:
        filterTitle = "Filter criteria (all filters are AND combined)"
        resultTitle = "Query result"

        deleteByAge = "Delete by age"
        minAgeDays1 = "Delete recordings older than"
        minAgeDays2 = "days"
        
        deleteBySeries = "Delete by series"
        seriesName = "Recording series name:"
        
        deleteByName = "Delete by title name"
        titleName = "Recording title:"
        
        deleteByChannel = "Delete by channel"
        channelName = "TV channel name:"
        
        deleteByPlaystatus = "Delete by play status (data only available while DVBViewer is running!)"
        onlyPlayed = "Delete only played recordings"
        onlyUnplayed = "Delete only unplayed recordings"
        
        keepMinimum = "Keep the youngest recordings"
        keepAtLeast1 = "Keep at least the last"
        keepAtLeast2 = "recordings that match the filters, regardless of age"

        dryMode = "Dry mode => do not really perform the delete operation but print the result to the log file"
        dryModeHint = "Hint: You're in dry mode. Simply press the 'Test' button to check the query results."
        
        deletePreview1 = "*** DryMode - Nothing will be deleted. ***"
        deletePreview2 = "*** This operation would delete %s records! ***"
        deletePreview3 = "Empty result, nothing to delete"
        
        dryPrefix = 'DryMode: '
        deletePrefix = 'Delete: '
        
        queryResult1 = "The current query results in"
        queryResult2 = "deletions!"
        
        errorDelAllRec = "ERROR: Current settings would delete %s recordings, i.e. ALL OF YOUR RECORDINGS! Refine your query!"
        errorDelTooManyRec = "ERROR: Current settings would delete %s recordings, that's MORE THAN HALF of your recordings! Refine your query!"

        dryMode_t = "[Dry Mode]: "
        realMode_t = "[Really!]: "
        delete_t = 'Delete all '
        played_t = 'played '
        unplayed_t = 'unplayed '
        any_t = 'any '
        recordings_t = 'recordings '
        title_t = 'with title "%s" '
        fromSeries_t = 'from series "%s" '
        onChannel_t = 'from channel "%s" '
        olderThan_t = 'which are older than %s days '
        keepAtLeast_t = 'but keep at least the last %s recordings '
    
    # class holding the parameters for the delete query
    class DeleteQuery:
        def __init__(self,
            dryMode=True,
            deleteByAge=False, minAgeDays=0,
            deleteBySeries=False, seriesName='',
            deleteByName=False, titleName='',
            deleteByChannel=False, channelName='',
            deleteByPlaystatus=False, onlyPlayed=True,
            keepMinimum=False, keepAtLeast=0
        ):
            self.dryMode = dryMode
            self.deleteByAge, self.minAgeDays = self.checkIntArg(deleteByAge, minAgeDays)
            self.deleteBySeries, self.seriesName = self.checkStringArg(deleteBySeries, seriesName)
            self.deleteByName, self.titleName = self.checkStringArg(deleteByName, titleName)
            self.deleteByChannel, self.channelName = self.checkStringArg(deleteByChannel, channelName)
            self.deleteByPlaystatus, self.onlyPlayed = deleteByPlaystatus, onlyPlayed
            self.keepMinimum, self.keepAtLeast = self.checkIntArg(keepMinimum, keepAtLeast)

        # workaround since GetLabel() does not accept custom objects as argument (I guess it's a bug in EG framework?)
        def ToTupel(self):
            return (self.dryMode, 
                self.deleteByAge, self.minAgeDays,
                self.deleteBySeries, self.seriesName,
                self.deleteByName, self.titleName,
                self.deleteByChannel, self.channelName,
                self.deleteByPlaystatus, self.onlyPlayed,
                self.keepMinimum, self.keepAtLeast
            )
        
        # workaround since GetLabel() does not accept custom objects as argument (I guess it's a bug in EG framework?)
        @classmethod
        def FromTupel(cls, tpl):
            return cls(tpl[0], tpl[1], tpl[2], tpl[3], tpl[4], tpl[5], tpl[6], tpl[7], tpl[8], tpl[9], tpl[10], tpl[11], tpl[12])
        
       
        # helper method: strips the string value and sets the flag to False if value is empty or None
        def checkIntArg(self, flag, value, default=0):
            if value is None or value == 0:
                return (False, default)
            else:
                return (flag, value)

            
        # helper method: strips the string value and sets the flag to False if value is empty or None
        def checkStringArg(self, flag, value, default=''):
            if value is not None:
                value = value.strip()
                if value == '':
                    value = None
            if value is None:
                return (False, default)
            else:
                return (flag, value)


        # constructs a prosa text from the query
        def QueryAsText(self):
            text = DeleteRecordings.Text
            
            label = ''
            if self.dryMode:
                label += text.dryMode_t
            else:
                label += text.realMode_t
                
            label += text.delete_t
    
            if self.deleteByPlaystatus:
                if self.onlyPlayed:
                    label += text.played_t
                else:
                    label += text.unplayed_t
            #elif not self.deleteByName:
            #    label += text.any_t
            
            label += text.recordings_t
                    
            if self.deleteByName:
                label += text.title_t % self.titleName
                
            if self.deleteBySeries:
                label += text.fromSeries_t % self.seriesName
                        
            if self.deleteByChannel:
                label += text.onChannel_t % self.channelName
                        
            if self.deleteByAge:
                label += text.olderThan_t % self.minAgeDays
                
            if self.keepMinimum:
                label += text.keepAtLeast_t % self.keepAtLeast
                    
            label = label.strip() + '. '
            
            return label
    

    # convenience method
    def LoadRecordingsData(self, deleteQueryTpl):
        if deleteQueryTpl is None:
            query = self.DeleteQuery() # new query with defaults
        else:
            query = self.DeleteQuery.FromTupel(deleteQueryTpl) # convert tupel into DeleteQuery object

        if self.plugin.workerThread is not None:
            enableDVBViewer = True
        else:
            enableDVBViewer = False
        
        # call action GetRecordingDetails
        recDetails = eg.plugins.DVBViewer.GetRecordingDetails(enableDVBViewer, enableDVBService=True, updateDVBService=True)
        
        recordings = {}
        if recDetails is not None:
            readoutSuccessful = recDetails[0]
            if readoutSuccessful:
                recordings = recDetails[1]
            else:
                eg.PrintError("GetRecordingDetails failed")
    
        return (recordings, query)


    # computes the query result
    # This code was hard to write, so it's OK if it's hard to read :)
    def ComputeQueryResult(self, recordings, deleteQuery, silent=False):
        emptyResult = (0, [])
        if recordings is None or len(recordings) == 0:
            return emptyResult
            
        if deleteQuery is None:
            return emptyResult
        else:
            q = deleteQuery
            
        numRecsBefore = len(recordings)
        
        dellist = recordings.values()
        dellist.sort(cmp=lambda x, y: cmp(y[RE_STARTDATE], x[RE_STARTDATE])) # sort by age in reverse order
        
        if q.deleteByName and q.titleName is not None:
            dellist = [ v for v in dellist if v[RE_TITLE].lower().find(q.titleName.lower()) >= 0 ]
        
        if q.deleteBySeries and q.seriesName is not None:
            dellist = [ v for v in dellist if v[RE_SERIES].lower().find(q.seriesName.lower()) >= 0 ]
            
        if q.deleteByChannel and q.channelName is not None:
            dellist = [ v for v in dellist if v[RE_CHANNEL].lower().find(q.channelName.lower()) >= 0 ]
            
        if q.deleteByPlaystatus:
            dellist = [ v for v in dellist 
                          if v[RE_PLAYED] is not None 
                            and (v[RE_PLAYED] >= 0 and q.onlyPlayed or v[RE_PLAYED] < 0 and not q.onlyPlayed) ]

        # Note: the 'keepAtLeast' logic IS different if combined with name searches (as above)
        # than when combined with the 'deleteByAge' logic - it's different because 
        # our natural expectation is different in these two situations.
        # Expl: "Delete name='denver clan' keepAtLeast=2"   -> keeps 2 records and deletes the rest
        #       "Delete older than (today+7) keepAtLeast=2" -> keeps *at least* 2 records and deletes the older ones. 
        if q.keepMinimum:
            if len(dellist) > q.keepAtLeast:
                dellist = dellist[q.keepAtLeast:] # remove 'keepAtLeast' elements from the beginning of the deletion list
            else:
                return emptyResult

        if q.deleteByAge:
            maxDate = dt.now() - td(days=q.minAgeDays) 
            dellist = [ v for v in dellist if v[RE_STARTDATE] < maxDate ] # records which are older than maxDays

        numRecsDelete = len(dellist)

        if numRecsDelete == numRecsBefore:
            if not silent:
                eg.PrintError(self.Text.errorDelAllRec % numRecsDelete)
            dellist = []
        elif float(numRecsDelete) / float(numRecsBefore) > 0.5:
            if not silent: 
                eg.PrintError(self.Text.errorDelTooManyRec % numRecsDelete)
            dellist = []
        
        return (numRecsDelete, dellist)
    
    
    @eg.LogItWithReturn
    def __call__( self,
        deleteQueryTpl=None
    ) :
        def printRecord(rec, prefix=''):
            delim  = ' | '
            print prefix + str(rec[RE_STARTDATE])  + delim + rec[RE_TITLE] + delim + rec[RE_CHANNEL] + delim + rec[RE_SERIES] + delim + rec[RE_FILENAME] + delim + str(rec[RE_PLAYED]) + delim + ('From RS' if rec[RE_FROMRS] else 'From DVBViewer')
            
        plugin = self.plugin
        text = self.Text

        recordings, q = self.LoadRecordingsData(deleteQueryTpl)

        dellist = self.ComputeQueryResult(recordings, q, False)[1]
            
        if q.dryMode:
            print text.deletePreview1
            if len(dellist) > 0:
                print text.deletePreview2 % len(dellist)
                for v in dellist:
                    printRecord(v, text.dryPrefix)
            else:
                print text.deletePreview3
            return False

        elif len(dellist) > 0:
            success = True
            for v in dellist:
                printRecord(v, text.deletePrefix)
                if v[RE_FROMRS]:
                    # recordings created by RS have to be deleted by the RS
                    success &= plugin.service.DeleteRecording(v[RE_RECID])
                else:
                    plugin.executionStatusChangeLock.acquire()
                    try:
                        # all other recordings have to be deleted by DVBViewer
                        if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                            success &= plugin.workerThread.CallWait( 
                                partial( plugin.workerThread.DeleteRecording, v[RE_RECID]), 
                                CALLWAIT_TIMEOUT 
                            )
                        else :
                            success = False
                    finally:
                        plugin.executionStatusChangeLock.release()
                if not success:
                    break
            return success
        else:
            return False
    
    
    @eg.LogIt
    def Configure(  self, 
        deleteQueryTpl=None
    ) :
        def UpdateQueryFromGuiSettings(q):
            q.deleteByName, q.titleName = q.checkStringArg(deleteByNameCB.GetValue(), titleNameComboCtrl.GetValue())
            q.deleteBySeries, q.seriesName = q.checkStringArg(deleteBySeriesCB.GetValue(), seriesNameComboCtrl.GetValue())
            q.deleteByChannel, q.channelName = q.checkStringArg(deleteByChannelCB.GetValue(), channelNameComboCtrl.GetValue())
            q.deleteByAge, q.minAgeDays = q.checkIntArg(deleteByAgeCB.GetValue(), minAgeNumCtrl.GetValue())
            q.deleteByPlaystatus, q.onlyPlayed = deleteByPlaystatusCB.GetValue(), onlyPlayedCtrl.GetValue()
            q.keepMinimum, q.keepAtLeast = q.checkIntArg(keepMinimumCB.GetValue(), keepAtLeastNumCtrl.GetValue())
            q.dryMode = dryModeCB.GetValue()
            return q
            
        #plugin = self.plugin
        text = self.Text
        panel = eg.ConfigPanel()
        dlgWindow = panel.GetTopLevelParent()

        # -------------- Get Data, Compute Query -------------------
        recordings, q = self.LoadRecordingsData(deleteQueryTpl)

        # -------------- Event Handlers -------------------
        def onGuiChange(event):
            def backupValues():
                backup = eg.Bunch()
                backup.titleNameStr = titleNameComboCtrl.GetValue()
                backup.titleNamePos = titleNameComboCtrl.GetInsertionPoint()
                backup.seriesNameStr = seriesNameComboCtrl.GetValue()
                backup.seriesNamePos = seriesNameComboCtrl.GetInsertionPoint()
                backup.channelNameStr = channelNameComboCtrl.GetValue()
                backup.channelNamePos = channelNameComboCtrl.GetInsertionPoint()
                return backup
            
            def restoreValues(backup):
                titleNameComboCtrl.SetValue(backup.titleNameStr)
                titleNameComboCtrl.SetInsertionPoint(backup.titleNamePos)
                seriesNameComboCtrl.SetValue(backup.seriesNameStr)
                seriesNameComboCtrl.SetInsertionPoint(backup.seriesNamePos)
                channelNameComboCtrl.SetValue(backup.channelNameStr)
                channelNameComboCtrl.SetInsertionPoint(backup.channelNamePos)

            # Workaround for an ugly behaviour of wx.ComboBox:
            # When calling 'Layout()' on an arbitrary component in the same frame, the ComboBox
            # performs some very strange autocomplete operation for which I didn't find a way
            # to turn it off (I'd say it's a bug in wx framework?)
            backup = backupValues()
            
            labelTxt = UpdateQueryFromGuiSettings(q).QueryAsText()
            proseQueryLabel.SetLabel(labelTxt)
            proseQueryLabel.Wrap(minwidth-10)
            hits = self.ComputeQueryResult(recordings, q, silent=True)[0]
            numDeletionsLabel.SetLabel(str(hits))
            numDeletionsLabel.SetInitialSize()
            numDeletionsLabel.GetParent().Layout() # give numDeletionsLabel enough space to grow
                # but this also causes improper autocomplete of wx.ComboBox
            dlgWindow.SetInitialSize()
            dlgWindow.Layout() # let the whole dialog dlgWindow shrink / enlarge
            
            restoreValues(backup)
                

        def onDeleteByNameCB(event):
            titleNameComboCtrl.Enable(deleteByNameCB.GetValue())
            if event is not None:
                onGuiChange(event)
        
        def onDeleteBySeriesCB(event):
            seriesNameComboCtrl.Enable(deleteBySeriesCB.GetValue())
            if event is not None:
                onGuiChange(event)
        
        def onDeleteByChannelCB(event):
            channelNameComboCtrl.Enable(deleteByChannelCB.GetValue())
            if event is not None:
                onGuiChange(event)
        
        def onDeleteByAgeCB(event):
            minAgeNumCtrl.Enable(deleteByAgeCB.GetValue())
            if event is not None:
                onGuiChange(event)
        
        def onDeleteByPlaystatusCB(event):
            onlyPlayedCtrl.Enable(deleteByPlaystatusCB.GetValue())
            onlyUnplayedCtrl.Enable(deleteByPlaystatusCB.GetValue())
            if event is not None:
                onGuiChange(event)
        
        def onKeepMinimumCB(event):
            keepAtLeastNumCtrl.Enable(keepMinimumCB.GetValue())
            if event is not None:
                onGuiChange(event)
        
        def onDryModeCB(event):
            if dryModeCB.GetValue():
                proseQueryLabel.SetForegroundColour((0,0,0))
                dryModeHintLabel.Show()
            else:
                proseQueryLabel.SetForegroundColour((255,0,0))
                dryModeHintLabel.Hide()
            if event is not None:
                onGuiChange(event)
        

        # -------------- GUI Components -------------------
        leftindent = 12
        blockgap = 5
        minwidth = 520

        deleteByNameCB = wx.CheckBox(panel, -1, text.deleteByName)
        deleteByNameCB.SetValue(q.deleteByName)
        deleteByNameCB.Bind(wx.EVT_CHECKBOX, onDeleteByNameCB)
        titleNames = [v[RE_TITLE] for v in recordings.values()]
        titleNames = list(set(titleNames)) # remove duplicates
        titleNames.sort(lambda a,b: cmp(a.lower(), b.lower()))
        titleNameComboCtrl = wx.ComboBox( panel, -1,
            value=q.titleName,
            choices=titleNames,
            size=(300,-1)
        )
        titleNameComboCtrl.Bind(wx.EVT_TEXT, onGuiChange)
        titleNameComboCtrl.Bind(wx.EVT_TEXT_ENTER, onGuiChange)
        titleNameComboCtrl.Bind(wx.EVT_COMBOBOX, onGuiChange)
        titleNameComboCtrl.Bind(wx.EVT_KILL_FOCUS, onGuiChange)
        
        deleteBySeriesCB = wx.CheckBox(panel, -1, text.deleteBySeries)
        deleteBySeriesCB.SetValue(q.deleteBySeries)
        deleteBySeriesCB.Bind(wx.EVT_CHECKBOX, onDeleteBySeriesCB)
        seriesNames = [v[RE_SERIES] for v in recordings.values()]
        seriesNames = list(set(seriesNames))
        seriesNames.sort(lambda a,b: cmp(a.lower(), b.lower()))
        seriesNameComboCtrl = wx.ComboBox( panel, -1,
            value=q.seriesName,
            choices=seriesNames,
            size=(300,-1)
        )
        seriesNameComboCtrl.Bind(wx.EVT_TEXT, onGuiChange)
        seriesNameComboCtrl.Bind(wx.EVT_TEXT_ENTER, onGuiChange)
        seriesNameComboCtrl.Bind(wx.EVT_COMBOBOX, onGuiChange)
        seriesNameComboCtrl.Bind(wx.EVT_KILL_FOCUS, onGuiChange)

        deleteByChannelCB = wx.CheckBox(panel, -1, text.deleteByChannel)
        deleteByChannelCB.SetValue(q.deleteByChannel)
        deleteByChannelCB.Bind(wx.EVT_CHECKBOX, onDeleteByChannelCB)
        channelNames = [v[RE_CHANNEL] for v in recordings.values()]
        channelNames = list(set(channelNames))
        channelNames.sort(lambda a,b: cmp(a.lower(), b.lower()))
        channelNameComboCtrl = wx.ComboBox( panel, -1,
            value=q.channelName,
            choices=channelNames,
            size=(300,-1)
        )
        channelNameComboCtrl.Bind(wx.EVT_TEXT, onGuiChange)
        channelNameComboCtrl.Bind(wx.EVT_TEXT_ENTER, onGuiChange)
        channelNameComboCtrl.Bind(wx.EVT_COMBOBOX, onGuiChange)
        channelNameComboCtrl.Bind(wx.EVT_KILL_FOCUS, onGuiChange)

        deleteByAgeCB = wx.CheckBox(panel, -1, text.deleteByAge)
        deleteByAgeCB.SetValue(q.deleteByAge)
        deleteByAgeCB.Bind(wx.EVT_CHECKBOX, onDeleteByAgeCB)
        minAgeNumCtrl = panel.SpinNumCtrl(q.minAgeDays, min=0, max=9999, fractionWidth=0, integerWidth=4)
        minAgeNumCtrl.Bind(wx.EVT_TEXT, onGuiChange)

        deleteByPlaystatusCB = wx.CheckBox(panel, -1, text.deleteByPlaystatus)
        deleteByPlaystatusCB.SetValue(q.deleteByPlaystatus)
        deleteByPlaystatusCB.Bind(wx.EVT_CHECKBOX, onDeleteByPlaystatusCB)

        onlyPlayedCtrl = wx.RadioButton( panel, -1, text.onlyPlayed, style = wx.RB_GROUP )
        onlyPlayedCtrl.SetValue( q.onlyPlayed )
        onlyPlayedCtrl.Bind(wx.EVT_RADIOBUTTON, onGuiChange)

        onlyUnplayedCtrl = wx.RadioButton( panel, -1, text.onlyUnplayed )
        onlyUnplayedCtrl.SetValue( not q.onlyPlayed )
        onlyUnplayedCtrl.Bind(wx.EVT_RADIOBUTTON, onGuiChange)

        keepMinimumCB = wx.CheckBox(panel, -1, text.keepMinimum)
        keepMinimumCB.SetValue(q.keepMinimum)
        keepMinimumCB.Bind(wx.EVT_CHECKBOX, onKeepMinimumCB)
        keepAtLeastNumCtrl = panel.SpinNumCtrl(q.keepAtLeast, min=0, max=9999, fractionWidth=0, integerWidth=4)
        keepAtLeastNumCtrl.Bind(wx.EVT_TEXT, onGuiChange)

        dryModeCB = wx.CheckBox(panel, -1, text.dryMode)
        dryModeCB.SetValue(q.dryMode)
        dryModeCB.Bind(wx.EVT_CHECKBOX, onDryModeCB)

        proseQueryLabel = wx.StaticText(panel, -1, "", style = wx.TE_READONLY | wx.TE_LEFT | wx.TE_LINEWRAP)

        numDeletionsLabel = wx.StaticText(panel, -1, "", style=wx.TE_READONLY | wx.TE_LEFT)
        numDeletionsLabel.SetForegroundColour((255,0,0))

        dryModeHintLabel = wx.StaticText(panel, -1, text.dryModeHint, style=wx.TE_READONLY | wx.TE_LEFT)
        
        # -------------- GUI Layout -------------------
        filterGBSizer = wx.GridBagSizer(hgap=5, vgap=3)
        filterGBrow = 0
        filterGBSizer.Add(wx.Size(0, 5), (filterGBrow, 0))
        filterGBrow += 1

        filterGBSizer.Add(deleteByNameCB, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(leftindent, 0), (filterGBrow, 0))
        filterGBSizer.Add(wx.StaticText(panel, -1, text.titleName), (filterGBrow, 1), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBSizer.Add(titleNameComboCtrl, (filterGBrow, 2), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(0, blockgap), (filterGBrow, 0))
        filterGBrow += 1
        
        filterGBSizer.Add(deleteBySeriesCB, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(leftindent, 0), (filterGBrow, 0))
        filterGBSizer.Add(wx.StaticText(panel, -1, text.seriesName), (filterGBrow, 1), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBSizer.Add(seriesNameComboCtrl, (filterGBrow, 2), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(0, blockgap), (filterGBrow, 0))
        filterGBrow += 1
        
        filterGBSizer.Add(deleteByChannelCB, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(leftindent, 0), (filterGBrow, 0))
        filterGBSizer.Add(wx.StaticText(panel, -1, text.channelName), (filterGBrow, 1), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBSizer.Add(channelNameComboCtrl, (filterGBrow, 2), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(0, blockgap), (filterGBrow, 0))
        filterGBrow += 1
        
        filterGBSizer.Add(deleteByPlaystatusCB, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        innerFGSizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5, vgap=5)
        innerFGSizer.Add(wx.Size(leftindent, 0))
        innerFGSizer.Add(onlyPlayedCtrl, flag = wx.ALIGN_CENTER_VERTICAL) 
        innerFGSizer.Add(wx.Size(leftindent, 0))
        innerFGSizer.Add(onlyUnplayedCtrl, flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBSizer.Add(innerFGSizer, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(0, blockgap), (filterGBrow, 0))
        filterGBrow += 1
        
        filterGBSizer.Add(deleteByAgeCB, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        innerFGSizer = wx.FlexGridSizer(rows=1, hgap=5, vgap=5)
        innerFGSizer.Add(wx.Size(leftindent, 0))
        innerFGSizer.Add(wx.StaticText(panel, -1, text.minAgeDays1), flag=wx.ALIGN_CENTER_VERTICAL)
        innerFGSizer.Add(minAgeNumCtrl, flag=wx.ALIGN_CENTER_VERTICAL)
        innerFGSizer.Add(wx.StaticText(panel, -1, text.minAgeDays2), flag=wx.ALIGN_CENTER_VERTICAL) 
        filterGBSizer.Add(innerFGSizer, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(0, blockgap), (filterGBrow, 0))
        filterGBrow += 1
        
        filterGBSizer.Add(keepMinimumCB, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        innerFGSizer = wx.FlexGridSizer(rows=1, hgap=5, vgap=5)
        innerFGSizer.Add(wx.Size(leftindent, 0))
        innerFGSizer.Add(wx.StaticText(panel, -1, text.keepAtLeast1), flag=wx.ALIGN_CENTER_VERTICAL)
        innerFGSizer.Add(keepAtLeastNumCtrl, flag=wx.ALIGN_CENTER_VERTICAL)
        innerFGSizer.Add(wx.StaticText(panel, -1, text.keepAtLeast2), flag=wx.ALIGN_CENTER_VERTICAL) 
        filterGBSizer.Add(innerFGSizer, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(0, blockgap), (filterGBrow, 0))
        filterGBrow += 1

        filterGBSizer.Add(dryModeCB, (filterGBrow, 0), span=(1, 4), flag=wx.ALIGN_CENTER_VERTICAL)
        filterGBrow += 1
        filterGBSizer.Add(wx.Size(0, blockgap), (filterGBrow, 0))
        filterGBrow += 1

        filterSBox = wx.StaticBox(panel, -1, text.filterTitle)
        filterSBoxSizer = wx.StaticBoxSizer(filterSBox, wx.VERTICAL)
        filterSBoxSizer.Add(filterGBSizer, proportion=1, flag=wx.EXPAND)

        # ---- Result Static Box
        resultGBSizer = wx.GridBagSizer(hgap=5, vgap=5)
        resultGBrow = 0

        resultGBSizer.Add(proseQueryLabel, (resultGBrow, 0), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL)
        resultGBrow += 1
        resultGBSizer.Add(wx.Size(0, blockgap), (resultGBrow, 0)) 
        resultGBrow += 1
        
        resultFGSizer = wx.FlexGridSizer(rows=1, hgap=5, vgap=5)
        resultFGSizer.Add(wx.StaticText(panel, -1, text.queryResult1), flag=wx.ALIGN_CENTER_VERTICAL)
        resultFGSizer.Add(numDeletionsLabel, flag=wx.ALIGN_CENTER_VERTICAL)
        resultFGSizer.Add(wx.StaticText(panel, -1, text.queryResult2), flag=wx.ALIGN_CENTER_VERTICAL)

        resultGBSizer.Add(resultFGSizer, (resultGBrow, 0), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL)
        resultGBrow += 1
        resultGBSizer.Add(wx.Size(0, blockgap), (resultGBrow, 0)) 
        resultGBrow += 1

        resultGBSizer.Add(dryModeHintLabel, (resultGBrow, 0), span=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL)
        resultGBrow += 1
        
        resultSBox = wx.StaticBox(panel, -1, text.resultTitle)
        resultSBoxSizer = wx.StaticBoxSizer(resultSBox, wx.VERTICAL)
        resultSBoxSizer.Add(resultGBSizer, proportion=1, flag=wx.EXPAND)

        mainGBSizer = wx.GridBagSizer(hgap=5, vgap=5)
        mainGBrow = 0
        mainGBSizer.Add(wx.Size(minwidth, blockgap), (mainGBrow, 0))
        mainGBrow += 1

        mainGBSizer.Add(filterSBoxSizer, (mainGBrow, 0), span=(1, 1), flag=wx.EXPAND)
        mainGBrow += 1
        mainGBSizer.Add(wx.Size(0, blockgap), (mainGBrow, 0))
        mainGBrow += 1

        mainGBSizer.Add(resultSBoxSizer, (mainGBrow, 0), span=(1, 1), flag=wx.EXPAND)
        mainGBrow += 1
        mainGBSizer.Add(wx.Size(0, blockgap), (mainGBrow, 0))
        mainGBrow += 1

        # -------------- Run & show -------------------
        panel.sizer.Add(mainGBSizer, proportion=1, flag=wx.EXPAND)

        onDeleteByNameCB(None)
        onDeleteBySeriesCB(None)
        onDeleteByChannelCB(None)
        onDeleteByAgeCB(None)
        onDeleteByPlaystatusCB(None)
        onKeepMinimumCB(None)
        onDryModeCB(None)
        onGuiChange(None)

        while panel.Affirmed():
            deleteQueryTpl = UpdateQueryFromGuiSettings(q).ToTupel()
            
            panel.SetResult( 
                deleteQueryTpl
            )


    def GetLabel(self, 
        deleteQueryTpl,
        *dummyArgs
    ):
        if deleteQueryTpl is None:
            q = self.DeleteQuery() # new query with defaults
        else:
            q = self.DeleteQuery.FromTupel(deleteQueryTpl) # convert tupel into DeleteQuery object

        return q.QueryAsText()
    


class TuneChannel( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__(self,
        channelID=None,      # 32bit or 64bit channelID
        fromVariable=False,  # get channelID from a variable
        variableName=None    # for example "eg.result"
    ) :
        plugin=self.plugin
        
        if fromVariable:
            channelID = eval(variableName)
        
        if channelID is None or channelID == '':
            eg.PrintError("Invalid arguments: missing channelID")
            return False

        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( WAIT_CHECK_START_CONNECT ) : 
                result = plugin.workerThread.CallWait(
                    partial(plugin.workerThread.TuneChannel, channelID ),
                    CALLWAIT_TIMEOUT
                )
        finally:
            plugin.executionStatusChangeLock.release()
        return result


    def Configure(  self,
        channelID = "",             # 32bit or 64bit channelID (see WIKI for description)
        fromVariable = False,       # get channelID from a variable
        variableName = "eg.result"  # for example "eg.result"
    ) :

        def OnModeSelect( event ) :
            fromVariable = fromVarRadioCtrl.GetValue()
            fromVariableTextCtrl.Enable(fromVariable)
            tvCtrl.Enable(not fromVariable)
            radioCtrl.Enable(not fromVariable)
            channelChoiceCtrl.Enable(not fromVariable)
            channelIDTextCtrl.Enable(not fromVariable)
            event.Skip()

        def OnTvRadioSelect( event ) :
            ix = channelChoiceCtrl.GetSelection()
            if ix != wx.NOT_FOUND :
                if self.tv :
                    plugin.indexTV = ix
                else :
                    plugin.indexRadio = ix

            self.tv = tvCtrl.GetValue()
            if self.tv:
                ix = plugin.indexTV
                self.choices = plugin.tvChannels
            else:
                ix = plugin.indexRadio
                self.choices = plugin.radioChannels
            channelChoiceCtrl.Clear()
            channelChoiceCtrl.SetItems( self.choices )
            channelChoiceCtrl.SetSelection( ix )
            OnChannelChoice(wx.CommandEvent())
            event.Skip()

        def OnChannelChoice(event):
            ix = channelChoiceCtrl.GetSelection()
            key = (ix, self.tv)
            channelID = plugin.channelIDbyIDList[key]
            channelIDTextCtrl.SetValue( channelID )
            event.Skip()

        self.panel = eg.ConfigPanel()
        panel = self.panel
        plugin = self.plugin
        text = self.text

        ix = -1
        if plugin.IDbychannelIDList.has_key( channelID ) :
            key = plugin.IDbychannelIDList[ channelID ]
            ix = key[0]
            self.tv = key[1]
            if ( self.tv ) :
                self.choices = plugin.tvChannels
            else :
                self.choices = plugin.radioChannels
        else :
            self.tv = True
            self.choices = plugin.tvChannels

        channelChoiceCtrl = panel.Choice(ix, choices=plugin.tvChannels + plugin.radioChannels)
        wSize = channelChoiceCtrl.GetSize()
        channelChoiceCtrl.Clear()
        channelChoiceCtrl.SetItems( self.choices )
        channelChoiceCtrl.SetSizeHintsSz( wSize )
        channelChoiceCtrl.SetSelection( ix )
        channelChoiceCtrl.Bind(wx.EVT_CHOICE, OnChannelChoice)

        tvCtrl = wx.RadioButton( panel, -1, text.tvButton, style = wx.RB_GROUP )
        tvCtrl.SetValue( self.tv )
        tvCtrl.SetMinSize((60, -1))
        tvCtrl.Bind(wx.EVT_RADIOBUTTON, OnTvRadioSelect)

        radioCtrl = wx.RadioButton( panel, -1, text.radioButton )
        radioCtrl.SetValue( not self.tv )
        radioCtrl.SetMinSize((60, -1))
        radioCtrl.Bind(wx.EVT_RADIOBUTTON, OnTvRadioSelect)

        chanIdRadioCtrl = wx.RadioButton( panel, -1, text.channelIDDescr, style = wx.RB_GROUP )
        chanIdRadioCtrl.SetValue( not fromVariable )
        chanIdRadioCtrl.Bind(wx.EVT_RADIOBUTTON, OnModeSelect)

        fromVarRadioCtrl = wx.RadioButton( panel, -1, text.fromVariableDescr )
        fromVarRadioCtrl.SetValue( fromVariable )
        fromVarRadioCtrl.Bind(wx.EVT_RADIOBUTTON, OnModeSelect)
        
        channelIDTextCtrl = wx.TextCtrl( panel, size=(200,-1) )
        channelIDTextCtrl.SetValue( channelID )

        fromVariableTextCtrl = wx.TextCtrl( panel, size=(200,-1) )
        fromVariableTextCtrl.SetValue( variableName )

        boxSizer = wx.BoxSizer(wx.HORIZONTAL)
        boxSizer.Add(tvCtrl, flag=wx.EXPAND)
        boxSizer.Add(radioCtrl, flag=wx.EXPAND)

        gridBagSizer = wx.GridBagSizer(5, 5)
        rowcount = 0
        gridBagSizer.Add(wx.Size(0, 5), (rowcount, 0))
        rowcount += 1

        # channelID from list
        gridBagSizer.Add(chanIdRadioCtrl, (rowcount, 0), span=(1, 3), flag=wx.ALIGN_CENTER_VERTICAL)
        rowcount += 1
        gridBagSizer.Add(wx.Size(20, 0), (rowcount, 0))
        gridBagSizer.Add(boxSizer, (rowcount, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(channelChoiceCtrl, (rowcount, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(wx.StaticText(panel, -1, text.channelID), (rowcount, 1), flag = wx.ALIGN_CENTER_VERTICAL) 
        gridBagSizer.Add(channelIDTextCtrl, (rowcount, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(wx.Size(0, 20), (rowcount, 0))
        rowcount += 1
        
        # channelID from variable
        gridBagSizer.Add(fromVarRadioCtrl, (rowcount, 0), span=(1, 3), flag = wx.ALIGN_CENTER_VERTICAL)
        rowcount += 1
        gridBagSizer.Add(wx.Size(20, 0), (rowcount, 0))
        gridBagSizer.Add(wx.StaticText(panel, -1, text.variableName), (rowcount, 1), flag = wx.ALIGN_CENTER_VERTICAL) 
        gridBagSizer.Add(fromVariableTextCtrl, (rowcount, 2), span=(1, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(wx.Size(0, 5), (rowcount, 0))
        rowcount += 1

        panel.sizer.Add(gridBagSizer, 1, flag=wx.EXPAND)
        
        OnModeSelect(wx.CommandEvent())
        
        while panel.Affirmed():
            panel.SetResult( channelIDTextCtrl.GetValue(),
                             fromVarRadioCtrl.GetValue(),
                             fromVariableTextCtrl.GetValue())


    def GetLabel(self, channelID, fromVariable, variableName, *dummyArgs):
        if fromVariable:
            return self.text.name + ": " + variableName
        else:
            return self.text.name + ": " + channelID


class GetChannelDetails( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__(self,
        allChannels=False,    # returns a list of all channels
        currentChannel=False, # returns just the current channel
        channelID=None,       # 32bit or 64bit channelID
        fromVariable=False,   # get channelID from a variable
        variableName=None     # for example "eg.result"
    ) :
        plugin = self.plugin
        
        if fromVariable:
            channelID = eval(variableName)

        if not allChannels and not currentChannel and (channelID is None or channelID == ""):
            eg.PrintError("Illegal argument combination: One of the arguments 'allChannels', 'currentChannel', 'fromVariable' or 'channelID' must be set.")
            return None
        
        if allChannels and currentChannel or allChannels and fromVariable or currentChannel and fromVariable:
            eg.PrintError("Illegal argument combination: Just one of the arguments 'allChannels', 'currentChannel' and 'fromVariable' must be True.")
            return None
        
        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( WAIT_CHECK_START_CONNECT ) : 
                channelDetails = plugin.workerThread.CallWait(
                    partial(plugin.workerThread.GetChannelDetails, allChannels, currentChannel, channelID ),
                    CALLWAIT_TIMEOUT
                )
        finally:
            plugin.executionStatusChangeLock.release()
        
        return channelDetails


    def Configure(  self,
        allChannels=False,        # returns a list of all channels
        currentChannel=True,      # returns just the current channel
        channelID="",             # 32bit or 64bit channelID (see WIKI for description)
        fromVariable=False,       # get channelID from a variable
        variableName="eg.result"  # for example "eg.result"
    ) :

        def OnModeSelect( event ) :
            fromChanID = chanIdRadioCtrl.GetValue()
            tvCtrl.Enable(fromChanID)
            radioCtrl.Enable(fromChanID)
            channelChoiceCtrl.Enable(fromChanID)
            channelIDTextCtrl.Enable(fromChanID)
            fromVariable = fromVarRadioCtrl.GetValue()
            fromVariableTextCtrl.Enable(fromVariable)
            event.Skip()

        def OnTvRadioSelect( event ) :
            ix = channelChoiceCtrl.GetSelection()
            if ix != wx.NOT_FOUND :
                if self.tv :
                    plugin.indexTV = ix
                else :
                    plugin.indexRadio = ix

            self.tv = tvCtrl.GetValue()
            if self.tv:
                ix = plugin.indexTV
                self.choices = plugin.tvChannels
            else:
                ix = plugin.indexRadio
                self.choices = plugin.radioChannels
            channelChoiceCtrl.Clear()
            channelChoiceCtrl.SetItems( self.choices )
            channelChoiceCtrl.SetSelection( ix )
            OnChannelChoice(wx.CommandEvent())
            event.Skip()

        def OnChannelChoice(event):
            ix = channelChoiceCtrl.GetSelection()
            key = (ix, self.tv)
            channelID = plugin.channelIDbyIDList[key]
            channelIDTextCtrl.SetValue( channelID )
            event.Skip()

        self.panel = eg.ConfigPanel()
        panel = self.panel
        plugin = self.plugin
        text = self.text

        ix = -1
        if plugin.IDbychannelIDList.has_key( channelID ) :
            key = plugin.IDbychannelIDList[ channelID ]
            ix = key[0]
            self.tv = key[1]
            if ( self.tv ) :
                self.choices = plugin.tvChannels
            else :
                self.choices = plugin.radioChannels
        else :
            self.tv = True
            self.choices = plugin.tvChannels

        channelChoiceCtrl = panel.Choice(ix, choices=plugin.tvChannels + plugin.radioChannels)
        wSize = channelChoiceCtrl.GetSize()
        channelChoiceCtrl.Clear()
        channelChoiceCtrl.SetItems( self.choices )
        channelChoiceCtrl.SetSizeHintsSz( wSize )
        channelChoiceCtrl.SetSelection( ix )
        channelChoiceCtrl.Bind(wx.EVT_CHOICE, OnChannelChoice)

        tvCtrl = wx.RadioButton( panel, -1, text.tvButton, style = wx.RB_GROUP )
        tvCtrl.SetValue( self.tv )
        tvCtrl.SetMinSize((60, -1))
        tvCtrl.Bind(wx.EVT_RADIOBUTTON, OnTvRadioSelect)

        radioCtrl = wx.RadioButton( panel, -1, text.radioButton )
        radioCtrl.SetValue( not self.tv )
        radioCtrl.SetMinSize((60, -1))
        radioCtrl.Bind(wx.EVT_RADIOBUTTON, OnTvRadioSelect)

        allChnlRadioCtrl = wx.RadioButton( panel, -1, text.allChannelsDescr, style = wx.RB_GROUP )
        allChnlRadioCtrl.SetValue( allChannels )
        allChnlRadioCtrl.Bind(wx.EVT_RADIOBUTTON, OnModeSelect)

        currChnlRadioCtrl = wx.RadioButton( panel, -1, text.currentChannelDescr )
        currChnlRadioCtrl.SetValue( currentChannel )
        currChnlRadioCtrl.Bind(wx.EVT_RADIOBUTTON, OnModeSelect)

        chanIdRadioCtrl = wx.RadioButton( panel, -1, text.channelIDDescr )
        chanIdRadioCtrl.SetValue( not allChannels and not currentChannel and not fromVariable )
        chanIdRadioCtrl.Bind(wx.EVT_RADIOBUTTON, OnModeSelect)

        fromVarRadioCtrl = wx.RadioButton( panel, -1, text.fromVariableDescr )
        fromVarRadioCtrl.SetValue( fromVariable )
        fromVarRadioCtrl.Bind(wx.EVT_RADIOBUTTON, OnModeSelect)
        
        channelIDTextCtrl = wx.TextCtrl( panel, size=(200,-1) )
        channelIDTextCtrl.SetValue( channelID )

        fromVariableTextCtrl = wx.TextCtrl( panel, size=(200,-1) )
        fromVariableTextCtrl.SetValue( variableName )

        boxSizer = wx.BoxSizer(wx.HORIZONTAL)
        boxSizer.Add(tvCtrl, flag=wx.EXPAND)
        boxSizer.Add(radioCtrl, flag=wx.EXPAND)

        gridBagSizer = wx.GridBagSizer(5, 5)
        rowcount = 0
        gridBagSizer.Add(wx.Size(0, 5), (rowcount, 0))
        rowcount += 1

        # all channels
        gridBagSizer.Add(allChnlRadioCtrl, (rowcount, 0), span=(1, 3), flag=wx.ALIGN_CENTER_VERTICAL)
        rowcount += 1
        gridBagSizer.Add(wx.Size(0, 10), (rowcount, 0))
        rowcount += 1

        # current channel
        gridBagSizer.Add(currChnlRadioCtrl, (rowcount, 0), span=(1, 3), flag=wx.ALIGN_CENTER_VERTICAL)
        rowcount += 1
        gridBagSizer.Add(wx.Size(0, 10), (rowcount, 0))
        rowcount += 1

        # channel by ID
        gridBagSizer.Add(chanIdRadioCtrl, (rowcount, 0), span=(1, 3), flag=wx.ALIGN_CENTER_VERTICAL)
        rowcount += 1
        gridBagSizer.Add(wx.Size(20, 0), (rowcount, 0))
        gridBagSizer.Add(boxSizer, (rowcount, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(channelChoiceCtrl, (rowcount, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(wx.StaticText(panel, -1, text.channelID), (rowcount, 1), flag = wx.ALIGN_CENTER_VERTICAL) 
        gridBagSizer.Add(channelIDTextCtrl, (rowcount, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(wx.Size(0, 10), (rowcount, 0))
        rowcount += 1
        
        # channelID from variable
        gridBagSizer.Add(fromVarRadioCtrl, (rowcount, 0), span=(1, 3), flag = wx.ALIGN_CENTER_VERTICAL)
        rowcount += 1
        gridBagSizer.Add(wx.Size(20, 0), (rowcount, 0))
        gridBagSizer.Add(wx.StaticText(panel, -1, text.variableName), (rowcount, 1), flag = wx.ALIGN_CENTER_VERTICAL) 
        gridBagSizer.Add(fromVariableTextCtrl, (rowcount, 2), span=(1, 2), flag = wx.ALIGN_CENTER_VERTICAL) 
        rowcount += 1
        gridBagSizer.Add(wx.Size(0, 5), (rowcount, 0))
        rowcount += 1

        panel.sizer.Add(gridBagSizer, 1, flag=wx.EXPAND)
        
        OnModeSelect(wx.CommandEvent())
        
        while panel.Affirmed():
            allChannels = allChnlRadioCtrl.GetValue()
            currentChannel = currChnlRadioCtrl.GetValue()
            channelID = channelIDTextCtrl.GetValue()
            fromVariable = fromVarRadioCtrl.GetValue()
            variableName = fromVariableTextCtrl.GetValue()
            panel.SetResult(
                allChannels,
                currentChannel,
                channelID,
                fromVariable,
                variableName
            )


    def GetLabel(self, allChannels, currentChannel, channelID, fromVariable, variableName, *dummyArgs):
        if allChannels:
            return self.text.name + ": " + self.text.allChannels
        elif currentChannel:
            return self.text.name + ": " + self.text.currentChannel
        elif fromVariable:
            return self.text.name + ": " + variableName
        else:
            return self.text.name + ": " + channelID


class GetCurrentShowDetails( eg.ActionClass ) :
    name = 'Get Details of Current Show'
    description = '''Gets information about the currently played show, either the live TV show or the media playback.
Returns a dictionary with attributes 'title', 'duration', 'remaining'
'''

    @eg.LogItWithReturn
    def __call__(self) :
        plugin = self.plugin

        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( CHECK_CONNECT ) : 
                showInfo = plugin.workerThread.CallWait(
                    partial(plugin.workerThread.GetCurrentShowDetails),
                    CALLWAIT_TIMEOUT
                )
            else:
                showInfo = {}
        finally:
            plugin.executionStatusChangeLock.release()
        
        return showInfo


class GetDataManagerValues( eg.ActionClass ) :
    name = 'Get all DVBViewer data manager values'
    description = '''Gets all available information about the currently played show, EGP, play times etc. 
Returns a dictionary 
'''

    @eg.LogItWithReturn
    def __call__(self) :
        plugin = self.plugin

        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( CHECK_CONNECT ) : 
                showInfo = plugin.workerThread.CallWait(
                    partial(plugin.workerThread.DataManagerGetAllValues),
                    CALLWAIT_TIMEOUT
                )
            else:
                showInfo = {}
        finally:
            plugin.executionStatusChangeLock.release()
        
        return showInfo



class TaskScheduler( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, enableDVBViewer=True, enableDVBService=False, updateDVBService=False ) :

        plugin = self.plugin

        leadTime = plugin.schedulerLeadTime * 60.0

        timerDates = eg.plugins.DVBViewer.GetDateOfRecordings(
                                                    allRecordings = plugin.scheduleAllRecordings,
                                                    enableDVBViewer = enableDVBViewer,
                                                    enableDVBService = enableDVBService,
                                                    updateDVBService = updateDVBService )

        if not timerDates[0] :
            eg.PrintError( "dates not valid" )
            return False

        dates = []

        if plugin.scheduleAllRecordings :
            dates = timerDates[1]

        elif timerDates[1] > 0 :
            dates = [ timerDates[1] ]

        ts = CoCreateInstance(taskscheduler.CLSID_CTaskScheduler,None,
                              CLSCTX_INPROC_SERVER,
                              taskscheduler.IID_ITaskScheduler)

        tasks=ts.Enum()

        if plugin.numberOfScheduledRecordings < 0 :
            for task in tasks:
                #print task, "    task[0:len(plugin.schedulerTaskNamePrefix)] = ", task[0:len(plugin.schedulerTaskNamePrefix)]
                if task[0:len(plugin.schedulerTaskNamePrefix)] == plugin.schedulerTaskNamePrefix :
                    ts.Delete( task )

            plugin.scheduledRecordings = []

            eg.PrintDebugNotice("All scheduled jobs deleted" )


        actuals = []
        now = time() + leadTime

        for date in plugin.scheduledRecordings :

            if date < 0 :
                continue

            if date not in dates :          # remove deleted recording timers
                ix = plugin.scheduledRecordings.index(date)
                name = plugin.schedulerTaskNamePrefix + "%03d" % ix + ".job"
                if name in tasks :
                    eg.PrintDebugNotice( name + " deleted" )
                    ts.Delete( name )
                plugin.scheduledRecordings[ ix ] = -1.0          #erased
                plugin.numberOfScheduledRecordings -= 1
                #print "deleted: ", no
            else :
                dates[ dates.index(date) ] = -1.0
                if date > now :
                    actuals.append( date )

        for date in dates :

            if date < 0 or date <= now :
                continue

            actuals.append( date )

            # get new index

            ix = 0

            if -1.0 in plugin.scheduledRecordings :
                ix = plugin.scheduledRecordings.index( -1.0 )
                plugin.scheduledRecordings[ ix ] = date
            else :
                ix = len(plugin.scheduledRecordings)
                plugin.scheduledRecordings.append( date )

            #print "added: ", ix
            plugin.numberOfScheduledRecordings += 1

            runTime = localtime(date - leadTime )

            taskName = plugin.schedulerTaskNamePrefix + "%03d" % ix + ".job"

            workItem = ts.NewWorkItem( taskName )

            workItem.SetApplicationName( sys.argv[0] )
            workItem.SetParameters( "-e " + plugin.schedulerEventName )
            workItem.SetPriority( taskscheduler.NORMAL_PRIORITY_CLASS )
            flags = taskscheduler.TASK_FLAG_SYSTEM_REQUIRED | taskscheduler.TASK_FLAG_DELETE_WHEN_DONE
            if plugin.schedulerEntryHidden :
                flags |= taskscheduler.TASK_FLAG_HIDDEN
            workItem.SetFlags( flags )
            workItem.SetAccountInformation(
                                plugin.accounts[INDEX_SCHEDULER][0],
                                plugin.accounts[INDEX_SCHEDULER][1]
                                )

            taskTrigger = workItem.CreateTrigger()[1]
            trigger = taskTrigger.GetTrigger()
            trigger.Flags = 0
            trigger.BeginYear =   runTime.tm_year
            trigger.BeginMonth =  runTime.tm_mon
            trigger.BeginDay =    runTime.tm_mday
            trigger.StartHour =   runTime.tm_hour
            trigger.StartMinute = runTime.tm_min

            trigger.TriggerType = int( taskscheduler.TASK_TIME_TRIGGER_ONCE )
            try :
                #print "SetTrigger"
                taskTrigger.SetTrigger( trigger )
                #print "QueryInterface"
                persistFile = workItem.QueryInterface( IID_IPersistFile )
                #print "Save"
                persistFile.Save( None, True )
            except Exception, exc:
                eg.PrintError( 'Error on adding a task scheduler entry:', unicode(exc) )
                try :
                    ts.Delete( taskName )
                except :
                    pass
                actuals = []
                break

        actuals.sort()

        if len( actuals ) > 0 :
            nextStartup = "Scheduled next wakeup at " + asctime( localtime( actuals[0] - leadTime ) )
            #print nextStartup
            eg.PrintDebugNotice( nextStartup )
            return True
        else :
            #print "No recording scheduled"
            eg.PrintDebugNotice( "No recording scheduled" )
            return False


    def Configure(  self, enableDVBViewer = True, enableDVBService = False, updateDVBService = False ) :

        self.plugin.ServiceConfigure( enableDVBViewer, enableDVBService, updateDVBService )


class GetDVBViewerObject( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self ) :
        plugin = self.plugin
        if plugin.Connect( WAIT_CHECK_START_CONNECT, lock = True ) :
            return plugin.workerThread.dvbviewer
        return None


class ExecuteDVBViewerCommandViaCOM( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, command, *args, **kwargs ) :
        plugin = self.plugin
        result = None
        plugin.executionStatusChangeLock.acquire()
        try:
            if plugin.Connect( WAIT_CHECK_START_CONNECT ) :
                result = plugin.workerThread.CallWait(
                    partial( command, *args, **kwargs ),
                    CALLWAIT_TIMEOUT
                )
        finally:
            plugin.executionStatusChangeLock.release()
        return result


class GetNumberOfClients( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, update=False ) :
        plugin = self.plugin

        if not plugin.useService :
            return -1

        return self.plugin.service.GetNumberOfClients( update )

    def Configure( self, updateDVBService=False ) :

        panel = eg.ConfigPanel()

        text = self.text

        updateCheckBoxCtrl = wx.CheckBox(panel, -1, text.serviceUpdate)
        updateCheckBoxCtrl.SetValue( updateDVBService )

        panel.sizer.Add( updateCheckBoxCtrl )


        while panel.Affirmed():

            panel.SetResult(updateCheckBoxCtrl.GetValue() )


class IsEPGUpdating( eg.ActionClass ) :

    @eg.LogItWithReturn
    def __call__( self, update=False ) :
        plugin = self.plugin

        if not plugin.useService :
            return False

        return self.plugin.service.IsEPGUpdating( update )


    def Configure( self, updateDVBService=False ) :

        panel = eg.ConfigPanel()

        text = self.text

        updateCheckBoxCtrl = wx.CheckBox(panel, -1, text.serviceUpdate)
        updateCheckBoxCtrl.SetValue( updateDVBService )

        panel.sizer.Add( updateCheckBoxCtrl )


        while panel.Affirmed():

            panel.SetResult(updateCheckBoxCtrl.GetValue() )


class DVBViewerService() :

    def __init__( self, plugin, serviceAddress='127.0.0.1:80',
                  account=( 'admin', 'admin' ),
                  serviceEvent='DVBViewerService') :

        self.serviceAddress = serviceAddress
        self.account = account

        self.timerIDs = {}       #Key: ID, Value: Timer
        self.pseudoIDs = {}
        self.recordingList = {}
        self.numberOfTimers = 0
        self.timerDates = []
        self.activeTimerDates = []

        self.versionDVBViewerService = None

        self.serviceEvent = serviceEvent

        self.failing = False

        self.plugin = plugin

        self.numberOfClients = -1
        self.updateEPG = False

        self.serviceInUse = plugin.serviceInUse


    def TriggerEvent( self, suffix, payload=None ):
            return eg.TriggerEvent( suffix, payload = payload, prefix=self.serviceEvent, source=self.plugin)


    def GetData( self, interface, params=None ) :

        def ErrorProcessing( e ) :
            self.failing = True
            if hasattr(e, 'code') and e.code == 401 :
                eg.PrintError( "Setup error: DVBViewer Service password not correct" )
                return None
            elif hasattr(e, 'errno') :
                if e.errno != 10054 :
                    eg.PrintError( "DVBViewer Service not alive or service address/port are not correct" )
                    self.TriggerEvent( "ServiceNotAlive" )
                    return None
                else :
                    print "DVBViewer Service errno: ", e.errno, ", http code: ", e.code, 
                    raise
            else :
                raise
            return

        theurl = 'http://' + self.serviceAddress + '/api/' + interface.lower() + '.html'
        
        if params is not None:
            first = True
            for k, v in params.iteritems():
                theurl += '?' if first else '&'
                first = False
                theurl += str(k) + '=' + str(v)
        
        #print "*** URL=", theurl
        req = Request(theurl)

        if self.account[0] != "" :
                authheader = "Basic " + encodestring64( self.account[0] + ':' + self.account[1])[:-1]
                req.add_header("Authorization", authheader)

        try :
            pageHandle = urlopen(req)
            self.failing = False
        except IOError, e :
            ErrorProcessing( e )
            return None

        xml = pageHandle.read()
        pageHandle.close()

        return xml


    @eg.LogIt
    def Update( self, updateMode=UPDATE_TIMERS ) :

        def GetID( *args ) :

            m = hashlib.md5()
            for arg in args:
                m.update(arg.encode("utf-8"))
            result = 0
            for c in m.hexdigest() :
                result = result * 251 + ord(c)

            return result


        def GetText( parent, key, default = '' ) :

            if parent is None :
                #print "Parent not found"
                return default

            element = parent
            if key is not None :
                element = parent.find( key )
            if element is None :
                #print "Key '", key, "' not found"
                return default
            else :
                return element.text

        def GetRSVersion():
            xmlData = self.GetData( 'version' )
            if xmlData is None :
                return False

            # EXAMPLE of xmldata:
            #<?xml version="1.0" encoding="utf-8" ?>
            #<version>DVBViewer Recording Service 1.5.0.2 (beta) (TOWER2008)</version>

            tree = ElementTree.fromstring( xmlData )
            matchObject = reSearch( r'(\d+\.\d+\.\d+\.\d+)', tree.text )
            self.versionDVBViewerService = matchObject.group(1)

        #@eg.LogIt
        def UpdateRSTimers():
            xmlData = self.GetData( 'timerlist' )

            if xmlData is None :
                xmlData = '<?xml version="1.0" encoding="iso-8859-1"?><Timers/>'

            self.timerDates = []
            self.activeTimerDates = []
            self.timerList = []

            #print xmlData

            """
             EXAMPLE of xmldata:

            <?xml version="1.0" encoding="iso-8859-1"?>
            <Timers>
                <Timer Type="1" ID="{FE6AA9F3-1A14-403F-9F3D-13B726A06624}" Enabled="-1" 
                    Priority="20" Charset="0" Date="01.03.2012" Start="20:55:00" Dur="85" End="22:20:00" Days="---T---" Action="0">
                  <Descr>Einstein</Descr>
                  <Options AdjustPAT="-1"/>
                  <Format>1</Format>
                  <Folder>Auto</Folder>
                  <NameScheme>%year_%date_%time_%station_%event</NameScheme>
                  <Series>Einstein</Series>
                  <Channel ID="3431745497999804388|SF 1 (deu)"/>
                  <Executeable>-1</Executeable>
                  <Recording>0</Recording>
                  <ID>3</ID>
                  <GUID>{FE6AA9F3-1A14-403F-9F3D-13B726A06624}</GUID>
                </Timer>
            </Timers>
            """

            tree = ElementTree.fromstring( xmlData )

            IDs = {}
            pseudoIDs = {}

            for timer in tree.findall("Timer"):

                enabled     = ( timer.get( "Enabled","-1" ) != '0')
                recording   = ( GetText( timer, "Recording" ) != '0' )
                action      = timer.get( "Action","0" )

                date        = timer.get( "Date","" )
                startTime   = timer.get( "Start","" )
                endTime     = timer.get( "End","" )
                days        = timer.get( "Days", "" )
                channelID   = timer.find( "Channel" ).get( "ID","")
                description = GetText( timer, "Descr" )
                pseudoID    = int( GetText( timer, "ID" ) )

                tStart = mktime( strptime( date + startTime,"%d.%m.%Y%H:%M:%S" ) )
                tEnd   = mktime( strptime( date + endTime,"%d.%m.%Y%H:%M:%S" ) )
                if tEnd < tStart:
                    tEnd += 24 * 60 * 60

                result = GetID( date, startTime, endTime, action, channelID, str( pseudoID ) )

                pseudoIDs[ pseudoID ] = result

                if result not in self.timerIDs :
                    if plugin.oldInterface :
                        self.TriggerEvent( "AddRecord:" + str(pseudoID) )
                    if plugin.newInterface :
                        self.TriggerEvent( "AddRecord", pseudoID )

                IDs[ result ] = ( recording, enabled, pseudoID )

                #print "ID = ", result, "  recording = ", recording, "  IDs[ result ] = ", IDs[ result ]

                timerentry = toTimerEntry(
                    timerID = pseudoID,
                    channelID = channelID.split('|')[0], 
                    channelName = channelID.split('|')[1], 
                    dateStr = date, 
                    startTimeStr = startTime,
                    endTimeStr = endTime, 
                    startDateTime = tStart, 
                    endDateTime = tEnd, 
                    days = days, 
                    description = description, 
                    enabled = enabled, 
                    recording = recording, 
                    action = action,
                    fromRS = True
                )
                self.timerList.append(timerentry)

                if enabled and not tStart in self.timerDates:
                    self.timerDates.append(tStart)
                    if recording :
                        self.activeTimerDates.append(tStart)
                    #print "date = ", ctime(tStart)

            numberOfTimers = self.numberOfTimers

            #print self.timerDates
            #print "isRecording = ", isRecording

            if self.timerIDs != IDs :

                for k, v in self.timerIDs.iteritems() :
                    g = IDs.get( k )
                    if v[0] and ( g is None or not g[0] ) :    # last query time recording but not now
                        numberOfTimers -= 1
                        if plugin.oldInterface :
                            self.TriggerEvent( "EndRecord" )
                        if plugin.newInterface :
                            self.TriggerEvent( "EndRecord", ( v[2], numberOfTimers ) )

                for k, v in IDs.iteritems() :
                    g = self.timerIDs.get( k )

                    if v[0] and ( g is None or not g[0] ) :    # last query time not recording but now
                        numberOfTimers += 1
                        if plugin.oldInterface :
                            self.TriggerEvent( "StartRecord" )
                        if plugin.newInterface :
                            self.TriggerEvent( "StartRecord", ( v[2], numberOfTimers ) )

                self.TriggerEvent( "TimerListUpdated" )

                #print "numberOfTimers = ", numberOfTimers
                if numberOfTimers == 0 and self.numberOfTimers != 0 :
                    self.TriggerEvent( "AllActiveRecordingsFinished" )
                self.numberOfTimers = numberOfTimers

                self.timerIDs = IDs
                self.pseudoIDs = pseudoIDs


        #@eg.LogIt
        def UpdateRSStream():
            page = 'status'

            xmlData = self.GetData( page )

            #print xmlData

            # EXAMPLE of xmldata:

            #<?xml version="1.0" encoding="utf-8" ?>
            #<status>
            #   <recordcount>0</recordcount>
            #   <clientcount>0</clientcount>
            #   <epgudate>0</epgudate>
            #</status>

            if xmlData is None :
                xmlData = '<?xml version="1.0" encoding="utf-8" ?><clientcount>0</clientcount>'

            tree = ElementTree.fromstring( xmlData )
            element = tree.find( 'clientcount' )
            numberOfClients = int( GetText( element, None, '0' ) ) - self.numberOfTimers

            if self.numberOfClients != numberOfClients :
                if numberOfClients != 0 :
                    self.TriggerEvent( "NumberOfClientsChanged", numberOfClients )
                else :
                    self.TriggerEvent( "NoClientActive" )
                self.numberOfClients = numberOfClients

            updateEPG = int( GetText( tree, 'epgudate', default = '0' ) ) != 0
            if self.updateEPG != updateEPG :
                if updateEPG :
                    self.TriggerEvent( "UpdateEPGstarted" )
                else :
                    self.TriggerEvent( "UpdateEPGfinished" )
                self.updateEPG = updateEPG



        #@eg.LogIt
        def UpdateRSRecordings():
            xmlData = self.GetData( 'recordings' )

            if xmlData is None :
                xmlData = '<?xml version="1.0" encoding="iso-8859-1"?><recordings/>'

            newRecordingList = {}

            #print xmlData

            """
             EXAMPLE of xmldata:
            <?xml version="1.0" encoding="iso-8859-1" ?>
            <!-- by DVBViewer Recording service -->
            <recordings Ver="1">
                <recording id="2218" idfile="" charset="1" content="32" minimumage="0" startStr="20120524210000" durStr="005000" runtime="000000" epgid="36948">
                    <channel>SF 1 HD (deu)</channel>
                    <file>d:\benutzer\videos\dvbviewer recording service\2012_05-24_20-55-01_sf 1 hd (deu)_einstein.ts</file>
                    <title>Einstein</title>
                    <desc>Moderation: Nicole Ulrich
                        [16:9] [H.264] [HD]
                        [stereo] [deu]
                        [stereo] [deu]
                        [AC-3] [mul]
                        [Teletext subtitles] [deu]</desc>
                    <series>Einstein</series>
                </recording>
            </recordings>

            """

            tree = ElementTree.fromstring( xmlData )

            for recording in tree.findall("recording"):
                recID       = recording.get("id")
                channel     = GetText(recording, "channel")
                startStr    = recording.get("start")
                durStr      = recording.get("duration")
                description = GetText(recording, "desc")
                filename    = GetText(recording, "file")
                title       = GetText(recording, "title")
                series      = GetText(recording, "series")
                played      = None
                
                startDate = dt.strptime(startStr, '%Y%m%d%H%M%S')
                durSecs = int(durStr[0:2]) * 60 * 60 + int(durStr[2:4]) * 60 + int(durStr[4:6])
                duration = td(seconds=durSecs)
                entry = toRecordingEntry(recID, channel, startDate, description, 
                                         duration, filename, played, title, series,
                                         fromRS=True)

                newRecordingList[entry[RE_RECID]] = entry
                
            if newRecordingList != self.recordingList:
                self.recordingList = newRecordingList
                self.TriggerEvent( "RecordingListUpdated" )


        plugin = self.plugin
        
        if self.versionDVBViewerService is None:
            GetRSVersion()

        if updateMode & UPDATE_TIMERS != 0 :
            UpdateRSTimers()

        if updateMode & UPDATE_STREAM != 0:
            UpdateRSStream()

        if updateMode & UPDATE_RECORDINGS != 0 :
            UpdateRSRecordings()

        return True


    def UpdateWithLock( self, updateType=UPDATE_TIMERS ) :
        self.serviceInUse.acquire()
        try:
            res = self.Update( updateType )
        finally:
            self.serviceInUse.release()
        return res


    def GetNumberOfClients( self, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_STREAM )
            res=self.numberOfClients
        finally:
            self.serviceInUse.release()
        return res


    def GetNumberOfActiveRecordings( self, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_TIMERS )
            res=self.numberOfTimers
        finally:
            self.serviceInUse.release()
        return res


    def IsRecording( self, update=True ) :
        return GetNumberOfActiveRecordings( update ) != 0


    def IsEPGUpdating( self, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_STREAM )
            res=self.updateEPG
        finally:
            self.serviceInUse.release()
        return res


    def GetTimerList( self, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_TIMERS )
            res =  self.timerList
        finally:
            self.serviceInUse.release()
        return res


    def GetTimerDates( self, active=True, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_TIMERS )
            if active :
                res =  self.activeTimerDates
            else :
                res =  self.timerDates
        finally:
            self.serviceInUse.release()
        return res


    def GetTimerIDs( self, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_TIMERS )
            res = self.timerIDs
        finally:
            self.serviceInUse.release()
        return res


    def GetPseudoIDs( self, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_TIMERS )
            res = self.pseudoIDs
        finally:
            self.serviceInUse.release()
        return res


    def GetRecordingList( self, update=True ) :
        self.serviceInUse.acquire()
        try:
            if update or self.failing :
                self.Update( UPDATE_RECORDINGS )
            res =  self.recordingList
        finally:
            self.serviceInUse.release()
        return res


    def DeleteRecording(self, recID) :
        self.serviceInUse.acquire()
        try:
            if not self.failing :
                params = { 'recid': recID, 'delfile': '1' }
                self.GetData('recdelete', params)
                return True
            else:
                return False
        finally:
            self.serviceInUse.release()
        return False # error case
    
    def GetVersion( self ) :
        self.serviceInUse.acquire()
        try:
            if self.versionDVBViewerService is None :
                self.Update( UPDATE_TIMERS )
            res = self.versionDVBViewerService
        finally:
            self.serviceInUse.release()
        return res

