[vdr-plugin-restfulapi] 01/02: Imported Upstream version 0.2.1.2

Tobias Grimm tiber-guest at moszumanska.debian.org
Thu Feb 12 22:36:37 UTC 2015


This is an automated email from the git hooks/post-receive script.

tiber-guest pushed a commit to branch master
in repository vdr-plugin-restfulapi.

commit 14eacc817128cd439e7a351243702ed6542859d3
Author: etobi <git at e-tobi.net>
Date:   Thu Feb 12 23:32:17 2015 +0100

    Imported Upstream version 0.2.1.2
---
 API.html                                           | 1302 +++
 COPYING                                            |  340 +
 HISTORY                                            |    6 +
 INSTALL                                            |   16 +
 Makefile                                           |  129 +
 Makefile-1.7.33pre                                 |  120 +
 README                                             |   22 +
 archlinux/cxxtools/PKGBUILD                        |   25 +
 archlinux/vdr-plugin-restfulapi-git/PKGBUILD       |   56 +
 .../plugin.restfulapi.conf                         |    8 +
 .../vdr-plugin-restfulapi-git.install              |   34 +
 archlinux/vdr-plugin-restfulapi-local/PKGBUILD     |   60 +
 .../vdr-plugin-restfulapi-local.install            |   34 +
 channels.cpp                                       |  372 +
 channels.h                                         |  133 +
 epgsearch.cpp                                      |  861 ++
 epgsearch.h                                        |  486 ++
 epgsearch/services.h                               |  195 +
 events.cpp                                         |  574 ++
 events.h                                           |  119 +
 examples/js_osd/index.html                         |  158 +
 examples/js_osd/jquery-1.6.2.js                    | 8981 ++++++++++++++++++++
 examples/js_osd/osd.css                            |  269 +
 femon.cpp                                          |   67 +
 femon.h                                            |   29 +
 femon/femonservice.h                               |   26 +
 info.cpp                                           |  222 +
 info.h                                             |   61 +
 jsonparser.cpp                                     |  416 +
 jsonparser.h                                       |  102 +
 osd.cpp                                            |  364 +
 osd.h                                              |  132 +
 patches/vdr-1.6.0-GetEvent-Patch.diff              |   15 +
 patches/vdr-1.6.0-LengthInSeconds-Patch.diff       |  122 +
 patches/vdr1721_eventdetails_v3.dpatch             |  115 +
 plugin.restfulapi.conf                             |    9 +
 po/de_DE.po                                        |   23 +
 recordings.cpp                                     |  577 ++
 recordings.h                                       |  116 +
 remote.cpp                                         |  180 +
 remote.h                                           |   44 +
 restfulapi.cpp                                     |  231 +
 scraper2vdr.cpp                                    |  609 ++
 scraper2vdr.h                                      |  106 +
 scraper2vdr/services.h                             |  197 +
 searchtimers.cpp                                   |  214 +
 searchtimers.h                                     |   80 +
 serverthread.cpp                                   |  116 +
 serverthread.h                                     |   56 +
 statusmonitor.cpp                                  |  344 +
 statusmonitor.h                                    |  165 +
 test/HTTPRequest.java                              |   57 +
 test/README                                        |    6 +
 test/httprequest.jar                               |  Bin 0 -> 1686 bytes
 test/restart.sh                                    |   16 +
 test/switch_channels_test.sh                       |   16 +
 timers.cpp                                         |  615 ++
 timers.h                                           |  136 +
 tools.cpp                                          | 1627 ++++
 tools.h                                            |  362 +
 utf8_checked.h                                     |  332 +
 utf8_core.h                                        |  359 +
 web/osd.css                                        |  267 +
 web/osd.js                                         |    5 +
 webapp.cpp                                         |  142 +
 webapp.h                                           |   26 +
 wirbelscan.cpp                                     |  408 +
 wirbelscan.h                                       |  108 +
 wirbelscan/wirbelscan_services.h                   |  266 +
 69 files changed, 23786 insertions(+)

diff --git a/API.html b/API.html
new file mode 100644
index 0000000..ee43d5c
--- /dev/null
+++ b/API.html
@@ -0,0 +1,1302 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>RESTFULAPI Documentation</title>
+
+<style type="text/css">
+html, body {
+    background-color: white;
+}
+.blurb {
+    font-style: italic;
+    font-weight: bold;
+    text-align: center;
+}
+.center {
+    text-align: center;
+}
+.code {
+    background-color: #F0F0F0;
+}
+.modified {
+    background-color: #FFDDDD;
+}
+</style>
+</head>
+<body>
+
+<div class="center">
+<h1>RESTFULAPI-Plugin for VDR</h1><br />
+<b>Version 0.1.1</b><br />
+Copyright © 2011 yavdr-Team, Michael Eiler<br /><br />
+Organization/Community: <a href="mailto:team at yavdr.org">team at yavdr.org</a><br />
+Developer: <a href="mailto:eiler.mike at gmail.com">eiler.mike at gmail.com</a> or <a href="mailto:aelo at yavdr.org">aelo at yavdr.org</a><br />
+<a href="http://www.yavdr.org">www.yavdr.org</a><br />
+<a href="http://github.com/yavdr/vdr-plugin-restfulapi">www.github.com/yavdr/vdr-plugin-restfulapi</a><br />
+</div>
+<br />
+<hr />
+
+<h1>Table Of Contents</h1>
+<ul>
+	<li><a href="#Preface">Preface</a>
+	<li><a href="#Requirements">Requirements</a>
+	<li><a href="#Configuration">Configuration</a>
+	<li><a href="#Channels">Channels</a>
+	<ul>
+		<li><a href="#ChannelGroups">Groups</a>
+    	<li><a href="#ChannelImages">Images / Channel-Logos</a>
+    </ul>
+	<li><a href="#Events">Events / EPG</a>
+	<ul>
+		<li><a href="#EventImages">Images</a>
+		<li><a href="#EventSearch">Search (EPGSEARCH)
+	</ul>
+	<li><a href="#Info">Info</a>
+	<li><a href="#Osd">Osd</a>
+	<li><a href="#Recordings">Recordings</a>
+	<ul>
+		<li><a href="#RecordingCut">Cut</a>
+		<li><a href="#RecordingsMarks">Marks</a>
+		<li><a href="#RecordingPlay">Play</a>
+	</ul>
+	<li><a href="#Remote">Remote</a>
+	<li><a href="#Timers">Timers</a>
+	<ul>
+		<li><a href="#ReadingTimers">Reading/Deleting Timers</a>
+		<li><a href="#CreatingTimers">Creating Timers</a>
+		<li><a href="#UpdatingTimers">Updating Timers</a>
+	</ul>
+	<li><a href="#SearchTimers">SearchTimers (EPGSEARCH)</a>
+	<ul>
+		<li><a href="#SearchTimersCreate">Create</a>
+		<li><a href="#SearchTimersDelete">Delete</a>
+		<li><a href="#SearchTimersShow">Show</a>
+		<li><a href="#SearchTimersSearch">Search</a>
+	</ul>
+	<li><a href="#ScraperImages">ScraperImages</a></li>
+	<li><a href="#Wirbelscan">Control the Wirbelscan plugin</a>
+	<ul>
+		<li><a href="#WirbelscanGetState">Get the current state</a>
+		<li><a href="#WirbelscanGetCountries">Get the list of countries</a>
+		<li><a href="#WirbelscanGetSatellites">Get the list of satellites</a>
+		<li><a href="#WirbelscanGetSetup">Get the current setup</a>
+		<li><a href="#WirbelscanSetSetup">Changes the current setup</a>
+		<li><a href="#WirbelscanDoCommand">Launches a command</a>
+	</ul>
+	<li><a href="#Webapp">Webapp</a></li>
+	<li><a href="#Femon">Femon</a></li>
+</ul>
+
+<hr />
+<h1 class="center"><a name="Preface">Preface</a></h1>
+<hr />
+<br />
+
+This plugin has been developed to offer a modern API for other developers to communicate with the VDR. <br />
+The plugin supports the following outputs formats: xml, json and html.<br />
+It also supports the folloing input formats: json and html.<br />
+<br />
+It would not exist without the help of the following people:<br />
+
+<ul>
+<li>Klaus Schmidinger: Of course because of the VDR itself. Thank you also for the nice and clean layout for the API-Documentation which I "borrowed" from your PLUGINS.html.
+<li>Gerald Dachs: Thank you for the intial idea and the initial jsonapi-Plugin.
+<li>Volker Richert: Thank you for fixing the UTF8-Support.
+<li>Tommi Mäkitalo: Thank you for implementing the Regex-Support in cxxtools which allows RESTful web services.
+<li>Holger Schvestka: Thank you for improving the package structure.
+<li>All other yaVDR-Members who I haven't already mentioned, just because you are who you are and do your best to improve the VDR experience.
+<li>The developers of the live and vnsisserver Plugins which helped me to learn a lot about the API of VDR.
+<li>The developers of the live plugin where I borrowed parts of the epgsearch-implementation :-)
+<li>Daniel Kuschny: Thank you for fixing my broken regular expressions. Will pay you a beer! :-)
+<li>Keine_Ahnung at vdr-portal for the VDR 1.6 compatibility patch
+</ul>
+<br /><br />
+<b>This document describes the API and how to use it. It contains a lot of examples for the different formats and services but there is still a lot more to discover if you simply try to make a few simple requests. </b>
+
+<br />
+
+<hr />
+<h1 class="center"><a name="Requirements">Requirements</a></h1>
+<hr />
+<br />
+
+Someone who wants to install the plugin on his/her VDR needs following applications and libraries:
+<br />
+<ul>
+<li>VDR 1.7.18
+<li>libcxxtools Rev. >= 1231, which is available as package for Ubuntu in the yavdr-PPA's
+</ul>
+
+Someone who wants to develop an application which uses this API:
+<br/>
+<ul>
+<li>XML or JSON Parser (Depends on which format you want to use! - You can also use the html-format, but that one is more a proof for the restful concept and does not show all information!)
+<li>URL Decoder or JSON Serializer (to send data to the webservice, required f.e. to create timers, searchtimers usw...) e.g. POSTMan REST Client. There is a wide variety of REST clients available on the web.
+</ul>
+
+<hr />
+<h1 class="center"><a name="Configuration">Configuration</a></h1>
+<hr />
+<br />
+
+Create a new file called plugin.restfulapi.conf in /etc/vdr/plugins/ .<br />
+<br />
+<div class="code"><code>
+#
+# Command line parameters for vdr-plugin-restfulapi
+#
+
+--port=8002
+--ip=0.0.0.0
+--epgimages=/var/cache/vdr/epgimages
+--channellogos=/usr/share/vdr/channel-logos
+--webapp=/var/lib/vdr/plugins/restfulapi/webapp
+</code></div>
+<br />
+<br />
+
+
+<hr />
+<h1 class="center"><a name="Channels">Channels</a></h1>
+<hr />
+<br />
+
+This service returns a list of channels.<br />
+<br />
+
+<b>The Request:</b>
+<br />
+<br />
+Method: <b>GET</b><br />
+<br />
+Examples: <br/>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/channels/.<format><br />
+GET http://<ip>:<port>/channels/<channelid>.<format><br />
+GET http://<ip>:<port>/channels/.<format>?start=<int>&limit=<int><br />
+</code></div>
+<br />
+<br />
+<b>Description of the Parameters:</b>
+<br />
+
+<ul>
+<li><format> - The requested format: json, xml or html.
+<li><channelid> - Id of the requested channel.
+<li>start, limit - These values are used to control the whole list. Start ist the first element you want to retrieve. Limit...
+</ul>
+
+<br />
+<br />
+
+<b>Example Result:</b>
+<br />
+
+<div class="code"><code>
+GET http://127.0.0.1:8002/channels.xml?start=0&limit=1<br /><br />
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><br />
+<channels xmlns="http://www.domain.org/restfulapi/2011/channels-xml"><br />
+ <channel><br />
+  <param name="name"<ORF1 HD</param><br />
+  <param name="number">1</param><br />
+  <param name="channel_id">C-71-71-61920</param<<br />
+  <param name="image">true</param><br />
+  <param name="group">hd</param><br />
+  <param name="transponder">330</param><br />
+  <param name="stream">C-71-71-61920.ts</param><br />
+  <param name="is_atsc">false</param><br />
+  <param name="is_cable">true</param><br />
+  <param name="is_sat">false</param><br />
+  <param name="is_terr">false</param><br />
+ </channel><br />
+ <count>1</count><br />
+ <total>259</total><br />
+</channels>
+</code></div>
+<br />
+
+<div class="code"><code>
+GET http://127.0.0.1:8002/channels.json?start=0&limit=1<br /><br />
+{ "channels": [<br />
+{<br />
+"name":"ORF1 HD",<br />
+"number":1,<br />
+"channel_id":"C-71-71-61920",<br />
+"image":true,<br />
+"group":"hd",<br />
+"transponder":330,<br />
+"stream":"C-71-71-61920.ts",<br />
+"is_atsc":false,<br />
+"is_cable":true,<br />
+"is_terr":false,<br />
+"is_sat":false<br />
+}],<br />
+"count":1,<br />
+"total":259<br />
+}
+</code></div>
+
+<h2><a name="ChannelGroups">Groups</a></h2>
+<br />
+
+This service will return a list containing all channel groups.<br />
+<br />
+<b>The Request:</b><br />
+<br />
+
+Method: <b>GET</b><br />
+<br />
+Examples: <br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/channels/groups<br />
+GET http://<ip>:<port>/channels/groups?start=<int>&limit=<int><br />
+GET http://<ip>:<port>/channels.<format>?group=<group>
+</code></div>
+<br />
+<br />
+
+<b>Description of the Parameters</b></br>
+<br />
+<ul>
+ <li><group> - returns the channels of the requested group
+</ul>
+<br />
+<br />
+
+<b>Example Result:</b><br />
+<br />
+
+<div class="code"><code>
+GET http://<ip>:<port>/channels/groups?start=0&limit=3<br />
+<br />
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><br />
+<groups xmlns="http://www.domain.org/restfulapi/2011/groups-xml"><br />
+ <group>hd</group><br />
+ <group>main</group><br />
+ <group>stuff</group><br />
+ <count>3</count><br />
+ <total>3</total><br />
+</groups>
+</code></div>
+<br />
+
+
+<div class="code"><code>
+GET http://<ip>:<port>/channels/groups?start=0&limit=3<br />
+<br />
+{<br />
+"groups":<br />
+[<br />
+"hd",<br />
+"main",<br />
+"stuff"<br />
+],<br />
+"count":3,<br />
+"total":3<br (>
+}
+</code></div>
+
+<h2><a name="ChannelImages">Images / Channel-Logos</a></h2>
+<br />
+
+This service returns the requested channel-logo.<br />
+<br />    XML or JSON Parser (Depends on which format you want to use!)
+    URL Decoder 
+Method: <b>GET</b><br />
+<br />
+Examples: <br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/channels/image/<channelid>
+</code></div>
+<br />
+<br />
+
+
+<hr />
+<h1 class="center"><a name="Events">Events / EPG</a></h1>
+<hr />
+<br />
+
+This service returns the epg information.<br />
+<br />
+Since Verions 0.2.0 the API returns additional media if a scraper plugin is available.
+The data is available within leaf additional_media and contains additional information about the event such as actors etc.
+Please have a look at the data to get an idea about the additional structure.
+Images can be retrieved using <a href="#ScraperImages">ScraperImages</a> service.
+<br />
+<br />
+Method: <b>GET</b><br />
+<br />
+Examples: <br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/events/<channelid>.<format>?timespan=<timespan><br />
+GET http://<ip>:<port>/events/<channelid>/<eventid>.<format><br />
+GET http://<ip>:<port>/events/<channelid>.<format>?timespan=<timespan><br />
+GET http://<ip>:<port>/events/<channelid>.<format>?timespan=<timespan>&from=<from>&start=<int>&limit=<int>
+</code></div>
+<br />
+<br />
+<b>Description of the Parameters:</b><br />
+<br />
+
+<ul>
+ <li><format> - The requested format: json, xml or html.
+ <li><channelid> - (optional) id of the channel, if not set the plugin will return the whole epg sorted by your channel list
+ <li><timespan> - the timespan from which you want to know the event-details, if set to 0 - all events in the future will be returned
+ <li><from> - (optional) time in the future when the requested events should end/start (default-value: now)
+ <li><eventid> - (optional) if you only want the details of a specific event
+ <li><chevents> - (optional) the count of events for each channel
+ <li><chfrom> - (optional) start number of the channel, from where events will be returned
+ <li><chto> - (optional) end number of the channel, through events will be returned 
+ <li><only_count> - (optional) if you just want to know the amount of available epg items
+</ul>
+
+<br />
+<br />
+<b>Example Result:</b><br />
+<br />
+
+<div class="code"><code>
+GET http://<ip>:<port>/events/C-1-1011-11110/3600.xml?start=0&limit=1<br />
+<br />
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><br />
+<events xmlns="http://www.domain.org/restfulapi/2011/events-xml"><br />
+ <event><br />
+  <param name="id">51514</param><br />
+  <param name="title">Eine Serie</param><br />
+  <param name="short_text">Der Episodentitel</param><br />
+  <param name="description">Die Beschreibung</param><br />
+  <param name="channel">C-1-1011-11110</param><br />
+  <param name="channel_name">ZDF HD</param><br />
+  <param name="start_time">1308845100</param><br />
+  <param name="duration">3300</param><br />
+  <param name="images">1</param><br />
+  <param name="components">
+   <component stream="5" type="11" language="deu" description="H.264/AVC high definition Video,  16:9 aspect ratio," />
+   <component stream="2" type="3" language="deu" description="stereo deutsch" />
+   <component stream="2" type="3" language="eng" description="stereo englisch" />
+   <component stream="2" type="5" language="deu" description="Dolby Digital 2.0" />
+  </param>
+  <param name="timer_exists">true</param>
+  <param name="timer_active">true</param>
+  <param name="timer_id">C-71-71-61920:0:1315173600:2100:2300</param>
+ </event><br />
+ <count>1</count><br />
+ <total>236</total><br />
+</events>
+</code></div>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/events/C-1-1011-11110/3600.json?start=0&limit=1<br />
+<br />
+{"events":[{<br />
+"id":51514,<br />
+"title":"Eine Serie",<br />
+"short_text":"Der Episodentitel",<br />
+"description":"Die Beschreibung",<br />
+"start_time":1308845100,<br />
+"channel":"C-1-1011-11110",<br />
+"channel_name":"ZDF HD",<br />
+"duration":3300, <br />
+"images":3,<br />
+"count":1,<br />
+"timer_exists":true,<br />
+"timer_active":true,<br />
+"timer_id":"C-71-71-61920:0:1315173600:2100:2300",<br />
+"components":[<br />
+{"stream":5, "type":11, "language":"deu", "description":"H.264\/AVC high definition Video,  16:9 aspect ratio,"},<br />
+{"stream":2,"type":3,"language":"deu","description":"stereo deutsch"},<br />
+{"stream":2,"type":3,"language":"eng","description":"stereo englisch"},<br />
+{"stream":2,"type":5,"language":"deu","description":"Dolby Digital 2.0"}],<br />
+"total":236}
+</code></div>
+<br />
+
+<h2><a name="EventImages">Images</a></h2>
+<br />
+
+This service returns the requested epg image.<br />
+<br />
+Method: <b>GET</b><br />
+<br />
+Examples: <br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/events/image/<eventid>/<imagenumber>
+</code></div>
+<br />
+<br />
+<b>Description of the Parameters:</b><br />
+<br />
+
+<ul>
+<li><eventid> - Id of the event
+<li><imagenumber> - the number of the image, in the above mentioned examples it would be 0,1 or 2
+</ul>
+
+<h2><a name="EventSearch">Search</a></h2>
+
+This service allows you to search for events.<br />
+<br />
+<b>The Request:</b>
+<br />
+<br />
+Method: <b>POST</b><br />
+<br />
+<b>Example URL:</b> <br />
+<br />
+<div class="code"><code>
+POST http://<ip>:<port>/events/search.<format>?limit=5&start=0;
+</code></div>
+
+<br />
+<b>HTTP-Body-Parameters:</b>
+<br />
+<ul>
+<li>query - (required)
+<li>mode - (required) 0=phrase, 1=and, 2=or, 3=regex
+<li>channelid - (optional) if id invalid, the plugin will search on every channel
+<li>use_title - (optional, default) search in the title
+<li>use_subtitle - (optional)
+<li>use_description - (optional)
+</ul>
+<br />
+<b>Example:</b>
+<div class="code"><code>
+{"query":"Asterix", "mode":0, "channelid":0,"use_title":true}
+</code></div>
+<br />
+<br />
+
+<hr />
+<h1 class="center"><a name="Info">Info</a></h1>
+<hr />
+
+General Information about the plugin and vdr.<br />
+<br />
+<b>Examples:</b><br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/info.<format>
+</code></div>
+<br />
+<b>Example Results:</b><br />
+<br />
+<div class="code"><code>
+<br />
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<info xmlns="http://www.domain.org/restfulapi/2011/info-xml">
+ <version>0.0.1</version>
+ <time>1311710291</time>
+ <services>
+  <service path="/info"  version="1" internal="true" />
+  <service path="/channels"  version="1" internal="true" />
+  <service path="/channels/groups"  version="1" internal="true" />
+  <service path="/channels/image"  version="1" internal="true" />
+  <service path="/events"  version="1" internal="true" />
+  <service path="/events/image"  version="1" internal="true" />
+  <service path="/events/search"  version="1" internal="false" />
+  <service path="/recordings"  version="1" internal="true" />
+  <service path="/remote"  version="1" internal="true" />
+  <service path="/timers"  version="1" internal="true" />
+  <service path="/osd"  version="1" internal="true" />
+  <service path="/searchtimers"  version="1" internal="false" />
+ </services>
+ <channel>C-71-71-61920</channel>
+ <vdr>
+  <plugins>
+   <plugin name="restfulapi" version="0.0.1" />
+   <plugin name="shutdown" version="0.0.2" />
+   <plugin name="vnsiserver" version="0.9.0" />
+   <plugin name="live" version="0.2.0" />
+   <plugin name="epgsearchonly" version="0.0.1" />
+   <plugin name="svdrposd" version="0.1.0" />
+   <plugin name="xineliboutput" version="1.0.90-cvs" />
+   <plugin name="streamdev-server" version="0.5.1-git" />
+   <plugin name="epgsearch" version="0.9.25.beta22" />
+   <plugin name="quickepgsearch" version="0.0.1" />
+   <plugin name="text2skin" version="1.3.1" />
+   <plugin name="conflictcheckonly" version="0.0.1" />
+  </plugins>
+ </vdr>
+</info>
+</code></div>
+<br />
+Instead of the channel it can also contain the currently playing video file:<br />
+<br />
+<div class="code"><code>
+<video name="Asterix in Amerika">/var/lib/video.00/Asterix_in_Amerika/2011-06-19.12.40.1-0.rec</video>
+</code></div>
+<br />
+<br />
+
+<hr />
+<h1 class="center"><a name="Osd">Osd</a></h1>
+<hr />
+
+OSD<br />
+<br />
+<b>Examples:</b><br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/osd.<format>
+</code></div>
+<br />
+<b>Example Results:</b><br />
+<br />
+<div class="code"><code>
+{"TextOsd":{"type":"TextOsd","title":"VDR  -  Disk 68%  -  57:53 free","message":"","red":"","green":"Audio","yellow":"","blue":"Stop","items":[{"content":"  1  Schedule","is_selected":true},{"content":"  2  Channels","is_selected":false},{"content":"  3  Timers","is_selected":false},{"content":"  4  Recordings","is_selected":false},{"content":"  5  Search","is_selected":false},{"content":"  6  Media Player","is_selected":false},{"content":"  7  Program guide","is_selected":false},{"cont [...]
+</code></div>
+<br />
+<br />
+<div class="code"><code>
+{"ProgrammeOsd":{"present_time":1311710264,"present_title":"Der letzte Bulle","present_subtitle":"Ich hab sie alle gehabt","following_time":1311713161,"following_title":"direkt - das magazin","following_subtitle":""}}
+</code></div>
+<br />
+<br />
+
+<hr />
+<h1 class="center"><a name="Recordings">Recordings</a></h1>
+<hr />
+<br />
+
+
+This service can move/delete recordings and can also return all the recordings as a list.<br />
+<br />
+Since Verions 0.2.0 the API returns additional media if a scraper plugin is available.
+The data is available within leaf additional_media and contains additional information about the event such as actors etc.
+Please have a look at the data to get an idea about the additional structure.
+Images can be retrieved using <a href="#ScraperImages">ScraperImages</a> service.
+<br />
+<br />
+
+<b>The Request:</b>
+<br />
+<br />
+Method: <b>GET, DELETE, POST</b><br />
+<br />
+Examples: <br/>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/recordings/.<format><br />
+GET http://<ip>:<port>/recordings/<recordingnumber>.<format>?marks=true<br />
+DELETE http://<ip>:<port>/recordings/<number><br />
+POST http://<ip>:<port>/recordings/move?<source>&<target>
+</code></div>
+<br />
+<br />
+<b>Description of the Parameters:</b>
+<br />
+
+<ul>
+<li><format> - The requested format: json, xml or html.
+<li><number> - The number of the recording which you want to delete. (The first recordings has the number 0.)
+<li><recordingnumber> - The number of the recording you want to retrieve.
+<li><source> - The full (url encoded) source path of the recording.
+<li><target> - The relative (url encoded) target path of the recording.
+<li>marks=true - add the cutting marks by reading the marks file from the recording directory
+<li>start, limit - These values are used to control the whole list. Start ist the first element you want to retrieve. Limit...
+</ul>
+
+<br />
+<br />
+
+<b>Example Result:</b>
+<br />
+
+<div class="code"><code>
+GET http://127.0.0.1:8002/recordings.xml?start=0&limit=1<br />
+<br />
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><br />
+<recordings xmlns="http://www.domain.org/restfulapi/2011/recordings-xml"><br />
+ <recording><br />
+  <param name="number">0</param><br />
+  <param name="name">Asterix in Amerika</param><br />
+  <param name="filename">/var/lib/video.00/Asterix_in_Amerika/2011-06-19.12.40.1-0.rec</param><br />
+  <param name="is_new">false</param><br />
+  <param name="is_edited">false</param><br />
+  <param name="is_pes_recording">false</param><br />
+  <param name="duration">-1</param><br />
+  <param name="frames_per_second">50</param><br />
+  <param name="marks"><0:03:17.10><1:43:07.24></param><br/>
+  <param name="event_title">Asterix in Amerika</param><br />
+  <param name="event_short_text"></param><br />
+  <param name="event_description">(Zeichentrickfilm, DEU 1994)</param><br />
+  <param name="event_start_time">1308480705</param><br />
+  <param name="event_duration">4611</param><br />
+ </recording><br />
+ <count>2</count><total>2</total><br />
+</recordings>
+</code></div>
+<br />
+
+<div class="code"><code>
+GET http://127.0.0.1:8002/recordings.json?start=0&limit=1i&marks=true<br />
+<br />
+{"recordings":[{<br />
+"number":0,<br />
+"name":"Asterix in Amerika",<br />
+"file_name":"\/var\/lib\/video.00\/Asterix_in_Amerika\/2011-06-19.12.40.1-0.rec",<br />
+"is_new":false,<br />
+"is_edited":false,<br />
+"is_pes_recording":false,<br />
+"duration":-1,<br />
+"frames_per_second":50,<br />
+"marks":["0:03:17.10", "1:43:07.24"],<br />
+"event_title":"Asterix in Amerika",<br />
+"event_short_text":"",<br />
+"event_description":"(Zeichentrickfilm, DEU 1994)",<br />
+"event_start_time":1308480705,<br />
+"event_duration":4611<br />
+}],"count":1,"total":2}
+</code></div>
+<br />
+<br />
+
+<div class="code"><code>
+POST "http://127.0.0.1:8002/recordings/move?source=/path/to/videodir/my_recording/YYYY-MM-DD.hh.mm.ss-0.rec&target=relative/path/to/renamed recording"
+</code></div>
+<br />
+<br />
+
+
+<h2><a name="RecordingCut">Cut</a></h2>
+
+This service tells your VDR to cut a recording or can tell the client if the vdr cutter is currently cutting a recording:<br />
+<br />
+Method: <b>GET, POST</b><br />
+<br />
+Examples: <br/>
+<br />
+<div class="code"><code>
+POST http://<ip>:<port>/recordings/cut/<recordingnumber>        # do it! (cut the recording)<br />
+GET http://<ip>:<port>/recordings/cut.<format>                  # this will return the status of the VDR Cutter
+</code></div>
+<br />
+
+
+<br />
+<h2><a name="RecordingsMarks">Marks</a></h2>
+
+This service can save and delete marks. To retrieve them use the parameter "marks=true" in the request url for the recordings.<br />
+<br />
+
+<b>Delete Cutting Marks: </b><br />
+<br />
+
+Method: <b>DELETE</b><br />
+<br />
+
+Examples: <br />
+<br />
+<div class="code"><code>
+DELETE http://<ip>:<port>/recordings/marks/<recordingnumber>
+</code></div>
+<br />
+
+
+<b>Create new Cutting Marks: </b><br />
+<br />
+
+Method: <b>POST</b><br />
+<br />
+
+Examples: <br />
+<br />
+<div class="code"><code>
+POST http://<ip>:<port>/recordings/marks/<recordingnumber>
+</code></div>
+<br />
+<br />
+
+Example Body-Content (required, Only JSON supported):<br />
+<br />
+<div class="code"><code>
+{'marks':['0:02:18.10','0:34:08.24']}
+</code></div>
+<br />
+<br />
+
+
+<h2><a name="RecordingPlay">Play</a></h2>
+
+This service tells your VDR to play or rewind a recording:<br />
+<br />
+Method: <b>GET, POST</b><br />
+<br />
+Examples: <br/>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/recordings/play/<number><br />
+POST http://<ip>:<port>/recordings/play/<number>
+</code></div>
+<br />
+<br />
+<b>Description of the Parameters:</b>
+<br />
+
+<ul>
+<li><number> - The number of the recording you want to play (GET).
+<li><number> - The number of the recording you want to rewind (POST).
+</ul>
+<br />
+<br />
+
+<hr />
+<h1 class="center"><a name="Remote">Remote</a></h1>
+<hr />
+<br />
+
+This service retrieves remote commands and sends them to VDR:<br />
+<br />
+Method: <b>POST</b><br />
+<br />
+<b>Syntax:</b><br />
+<br />
+<div class="code"><code>
+POST http://<ip>:<port>/remote/<key><br />
+POST http://<ip>:<port>/remote/switch/<channelid><br />
+POST http://<ip>:<port>/remote/kbd
+</code></div>
+<br />
+The parameter <key> can be set to the following remote control commands:<br /><br />
+Up, Down, Menu, Ok, Back, Left, Right, Red, Green, Yellow, Blue, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, Info, Play, Pause, Stop, Record, FastFwd, FastRew, Next, Prev, Power, ChanUp, ChanDn, ChanPrev, VolUp, VolDn, Mute, Audio, Subtitles, Schedule, Channels, Timers, Recordings, Setup, Commands, User0, User1, User2, User3, User4, User5, User6, User7, User8, User9, None, Kbd<br />
+<br />
+The second address allows to directly switch to a specific channel.<br />
+The third address allows to send strings that are interpreted as keyboard input for e.g. usage in search<br />
+The fourth address allows to send to send a sequence of keypresses<br /><br />
+<b>Send keyboard input:</b><br /><br />
+Method: <b>POST</b><br /><br />
+Expamle:<br /><br />
+<div class="code">
+    POST http://<ip>:<port>/remote/kbd
+</div><br />
+Example Body-Content (required, Only JSON supported):<br /><br />
+<div class="code">
+    {'kbd':'Asterix'}
+</div><br />
+<b>Send sequence:</b><br /><br />
+Method: <b>POST</b><br /><br />
+Expamle:<br /><br />
+<div class="code">
+    POST http://<ip>:<port>/remote/seq
+</div><br />
+Example Body-Content (required, Only JSON supported):<br /><br />
+<div class="code">
+    {'seq':'Down', 'Down', 'Down', 'Down', 'Right'}
+</div>
+<br />
+<br />
+
+<hr />
+<h1 class="center"><a name="Timers">Timers</a></h1>
+<hr />
+<br />
+
+<h2><a name="ReadingTimers">Reading/Deleting Timers</a></h2>
+<br />
+
+This service returns a list of timers.<br />
+<br />
+
+<b>The Request:</b>
+<br />
+<br />
+Method: <b>GET, DELETE</b><br />
+<br />
+Examples: <br/>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/timers.<format><br />
+GET http://<ip>:<port>/timers/<timerid>.<format><br />
+GET http://<ip>:<port>/timers.<format>?start=<int>&limit=<int><br />
+DELETE http://<ip>:<port>/timers/<timerid>
+</code></div>
+<br />
+<br />
+<b>Description of the Parameters:</b>
+<br />
+
+<ul>
+<li><format> - The requested format: json, xml or html.
+<li><timerid> - Id of the timer if you want to delete one.
+<li>start, limit - These values are used to control the whole list. Start ist the first element you want to retrieve. Limit...
+</ul>
+
+<br />
+<br />
+
+<b>Example Result:</b>
+<br />
+
+<div class="code"><code>
+GET http://127.0.0.1:8002/timers.xml?start=0&limit=1<br />
+<br />
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><br />
+<timers xmlns="http://www.domain.org/restfulapi/2011/timers-xml"><br />
+ <timer><br />
+  <param name="id">C-71-71-61920:0:1309039200:2013:2200</param><br />
+  <param name="start">2013</param><br />
+  <param name="stop">2200</param><br />
+  <param name="start_timestamp">2011-12-24 19:00:00</param>
+  <param name="stop_timestamp">2011-12-24 20:00:00</param>
+  <param name="priority">50</param><br />
+  <param name="lifetime">99</param><br />
+  <param name="event_id">27312</param><br />
+  <param name="weekdays">-------</param><br />
+  <param name="day">2011-06-26</param><br />
+  <param name="channel">C-71-71-61920</param><br />
+  <param name="is_recording">false</param><br />
+  <param name="is_pending">false>/param><br />
+  <param name="filename">Mad Money>/param><br />
+  <param name="channel_name">ORF1 HD>/param><br />
+  <param name="is_active">true>/param><br />
+ </timer><br />
+ <count>1</count><total>2</total><br />
+</timers>
+</code></div>
+<br />
+<div class="code"><code>
+GET http://127.0.0.1:8002/timers.json?start=0&limit=1<br />
+<br />
+
+</code></div>
+<br />
+
+<h2><a name="CreatingTimers">Creating Timers</a></h2>
+<br />
+
+This service can create new timers.<br />
+<br />
+Method: <b>POST</b><br />
+<br />
+
+Example:<br />
+<br />
+<div class="code"><code>
+POST http:/<ip>:<ip>/timers
+</code></div>
+<br />
+<b>Required Body Parameters:</b><br />
+
+<ul>
+<li>flags - set it to 1 for an active timer
+<li>file - name of the recording (use an url-encoder for this text if you special characters)
+<li>stop - the time when the recording should be start - in the 24 hour system, f.e. 2015 means quarter past ten in the evening
+<li>start - the time when the recording sholud be stoped, f.e. 2230 (half past ten in the evening)
+<li>day - YYYY-MM-DD, f.e. 2011-12-24 -> christmas :-)
+<li>channel - ID of the channel
+<li>weekdays - MTWTFSS, used for repeating timers -> just use '-' if you don't want the timer to run on the specific day
+<li>minpre, minpost can be used together with a channel and eventid to create a timer without giving the other information to the api
+</ul>
+<br />
+<b>Example Request (including HTTP-Header):</b><br />
+<br />
+<div class="code"><code>
+POST /timers HTTP/1.1<br />
+Content-Length: 98<br />
+Connection: close<br />
+<br />
+file=Ein%20Film&flags=1&start=1400&stop=1615&day=2011-12-24&channel=C-71-71-61920&weekdays=-------
+</code></div>
+<br />
+<b>Example Response (including HTTP-Header):</b><br />
+<br />
+<div class="code"><code>
+HTTP/1.1 200 OK<br />
+Content-Length: 0<br />
+Server: cxxtools-Http-Server 2.0<br />
+Connection: close<br />
+Date: Sun, 26 Jun 2011 19:03:48 GMT
+</code></div>
+<br />
+
+<h2><a name="UpdatingTimers">Updating Timers</a></h2>
+<br />
+
+This service can update the previously created timers.<br />
+<br />
+Method: <b>PUT</b><br />
+<br />
+Example:<br />
+<br />
+<div class="code"><code>
+PUT http://<ip>:<port>/timers
+</code></div>
+<br />
+
+<b>Required Body Parameters:</b><br />
+
+<ul>
+<li>timer_id - the id of the timer
+</ul>
+<br />
+
+<b>Optional Body Parameters are the ones you use to create the timer and now want to update.</b><br />
+<br />
+
+<b>Example Request (including HTTP-Header):</b><br />
+<br />
+<div class="code"><code>
+PUT /timers HTTP/1.1<br />
+Content-Length: 81<br />
+Connection: close<br />
+<br />
+timer_id=C-71-71-61920:0:1324681200:1400:1615&start=2015&stop=2230
+</code></div>
+<br />
+<b>Example Response (including HTTP-Header):</b><br />
+<br />
+<div class="code"><code>
+HTTP/1.1 200 OK<br />
+Content-Length: 0<br />
+Server: cxxtools-Http-Server 2.0<br />
+Connection: close<br />
+Date: Sun, 26 Jun 2011 19:20:03 GMT<br />
+</code></div>
+<br /><br />
+
+<hr />
+<h1 class="center"><a name="SearchTimers">SearchTimers</a></h1>
+<hr />
+<br />
+
+<h2><a name="SearchTimersCreate">Create a SearchTimer</a></h2>
+<br />
+<br />
+<div class="code"><code>
+POST http://<ip>:<port>/searchtimers
+</code></div>
+<br />
+Possible parameters:<br />
+<ul>
+ <li>search - (required)
+ <li>mode - (required) 0=phrase, 1=all words, 2=at least one word, 3=match exactly, 4=regex, 5=fuzzy
+ <li>id - used to edit an existing timer
+ <li>tolerance - used in fuzzy mode
+ <li>match_case - 0=false, 1=true
+ <li>use_time
+ <li>start_time
+ <li>stop_time
+ <li>use_title
+ <li>use_subtitle
+ <li>use_channel - 0=no, 1=interval, 2=channel group, 3=only FTA
+ <li>channel_min - id of the minimal channel (channelid)
+ <li>channel_max - id of the maximum channel (channelid)
+ <li>channels - f.e. All
+ <li>use_as_searchtimer - 0=no, 1=yes, 2=user defined
+ <li>use_duration - 0=false, 1=true
+ <li>duration_min
+ <li>duration_max
+ <li>use_dayofweek - 0=false, 1=true
+ <li>dayofweek - 7bits to represent the seven weekdays
+ <li>use_in_favorites
+ <li>directory
+ <li>del_recs_after_days
+ <li>keep_recs
+ <li>pause_on_recs
+ <li>blacklist_mode - always 0, blacklist currently not supported
+ <li>switch_min_before 
+ <li>avoid_repeats
+ <li>allowed_repeats
+ <li>repeats_within_days
+ <li>compare_title - 0=false, 1=true
+ <li>compare_subtitle
+ <li>compare_summary
+ <li>compare_categories
+</ul>
+<br />
+<br />
+
+<h2><a name="SearchTimersDelete">Delete a SearchTimer</a></h2>
+<br />
+<div class="code"><code>
+DELETE http://<ip>:<port>/searchtimers/<id>
+</code></div>
+<br />
+<br />
+
+<h2><a name="SearchTimersShow">Show one SearchTimer</a></h2>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/searchtimers.<format>
+</code></div>
+<br />
+<br />
+
+<h2><a name="SearchTimersSearch">Search Events of a SearchTimer</a></h2>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/searchtimers/search/<id>
+</code></div>
+<br />
+<br />
+<hr/>
+<h1 class="center"><a name="ScraperImages">ScraperImage</a></h1>
+<hr />
+<br />
+<h2>Retrieve Images from additional media.</h2>
+<div class="code"><code>
+GET http://<ip>:<port>/scraper/image/<path>/<provided>/<within>/<additional/<media>.jpg
+</code></div>
+<br />
+<br />
+<hr />
+<h1 class="center"><a name="Wirbelscan">Control the Wirbelscan plugin</a></h1>
+<hr />
+<br />
+
+<h2><a name="WirbelscanGetState">Get the state of the Wirbelscan plugin</a></h2>
+<br />
+This service gets the state of the Wirbelscan plugin.<br />
+<br />
+Method: <b>GET</b><br />
+<br />
+Example:<br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/wirbelscan/getStatus.json
+</code></div>
+<br />
+<br />
+Example Body-Content:<br /><br />
+<div class="code"><pre>
+{
+  "status":2
+}
+</pre></div>
+<br />
+Example Body-Content during scan:<br /><br />
+<div class="code"><pre>
+{
+  "status":1,
+  "currentDevice":"CXD2837 DVB-C DVB-T/T2",
+  "progress":1,"strength":0,
+  "transponder":"DVB-C 113.000MHz M2 SR6900",
+  "numChannels":552,"newChannels":0,"nextTransponder":0
+}</pre></div>
+<br />
+
+Description of the status values:<br />
+<ul>
+ <li>0 = no status information available, try again later.
+ <li>1 = scan in progress.
+ <li>2 = no scan in progress (not started, finished or stopped).
+ <li>3 = plugin is busy, try again later.
+</ul>
+<br />
+<br />
+
+<h2><a name="WirbelscanGetCountries">Retrieve the list of countries from the Wirbelscan plugin</a></h2>
+<br />
+This service retrieves the list of countries from the Wirbelscan plugin.<br />
+<br />
+Method: <b>GET</b><br />
+<br />
+Example:<br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/wirbelscan/countries.json
+</code></div>
+<br />
+<br />
+Example Body-Content:<br /><br />
+<div class="code"><pre>
+{"countries":
+  [
+    {"id":0,"shortName":"AF","fullName":"AFGHANISTAN"},
+    {"id":1,"shortName":"AX","fullName":"\ufffdLAND ISLANDS"},
+    {"id":2,"shortName":"AL","fullName":"ALBANIA"},
+    {"id":3,"shortName":"DZ","fullName":"ALGERIA"},
+    ...
+    {"id":246,"shortName":"ZM","fullName":"ZAMBIA"},
+    {"id":247,"shortName":"ZW","fullName":"ZIMBABWE"}
+  ],
+  "count":0,
+  "total":248
+}</pre></div>
+<br />
+<br />
+<br />
+
+<h2><a name="WirbelscanGetSatellites">Retrieve the list of satellites from the Wirbelscan plugin</a></h2>
+<br />
+This service retrieves the list of satellites from the Wirbelscan plugin.<br />
+<br />
+Method: <b>GET</b><br />
+<br />
+Example:<br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/wirbelscan/satellites.json
+</code></div>
+<br />
+<br />
+Example Body-Content:<br /><br />
+<div class="code"><pre>
+{"satellites":
+  [
+    {"id":0,"shortName":"S180E0","fullName":"180.0 east Intelsat 18"},
+    {"id":1,"shortName":"S172E0","fullName":"172.0 east GE 23"},
+    {"id":2,"shortName":"S169E0","fullName":"169.0 east Intelsat 5"},
+    {"id":3,"shortName":"S166E0","fullName":"166.0 east Intelsat 8"},
+    ...
+    {"id":131,"shortName":"S139W0","fullName":"135.0 west AMC 8"},
+    {"id":132,"shortName":"S177W0","fullName":"177.0 west NSS 9"}
+  ],
+  "count":0,
+  "total":133
+}
+</pre></div>
+<br />
+<br />
+<br />
+
+<h2><a name="WirbelscanGetSetup">Retrieve the current setup of the Wirbelscan plugin</a></h2>
+<br />
+This service retrieves the current setup of the Wirbelscan plugin.<br />
+<br />
+Method: <b>GET</b><br />
+<br />
+Example:<br />
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/wirbelscan/getSetup.json
+</code></div>
+<br />
+<br />
+Example Body-Content:<br /><br />
+<div class="code"><pre>
+{
+  "verbosity":5,
+  "logFile":2,
+  "DVB_Type":2,
+  "DVBT_Inversion":0,
+  "DVBC_Inversion":0,
+  "DVBC_Symbolrate":0,
+  "DVBC_QAM":0,
+  "CountryId":82,
+  "SatId":6,
+  "scanflags":31,
+  "ATSC_type":0
+}</pre></div>
+<br />
+Description of the setup values:<br />
+<ul>
+  <li>verbosity - 0 (errors only) .. 5 (extended debug); default = 3 (messages)
+  <li>logFile - 0 = off, 1 = stdout, 2 = syslog
+  <li>DVB_Type - DVB-T = 0, DVB-C = 1, DVB-S/S2 = 2, PVRINPUT = 3, PVRINPUT(FM Radio) = 4, ATSC = 5, TRANSPONDER = 999
+  <li>DVBT_Inversion - AUTO/OFF = 0, AUTO/ON = 1
+  <li>DVBC_Inversion - AUTO/OFF = 0, AUTO/ON = 1
+  <li>DVBC_Symbolrate - careful here - may change. AUTO = 0, 6900 = 1, 6875 = 2  (...)  14 = 5483, 15 = ALL
+  <li>DVBC_QAM - AUTO = 0,QAM64 = 1, QAM128 = 2, QAM256 = 3, ALL = 4
+  <li>CountryId - the id according to country, found in country list,   see wirbelscan_GetCountry
+  <li>SatId - the id according to satellite, found in list,         see wirbelscan_GetSat
+  <li>scanflags - bitwise flag of wanted channels: TV = (1 << 0), RADIO = (1 << 1), FTA = (1 << 2), SCRAMBLED = (1 << 4), HDTV = (1 << 5)
+  <li>ATSC_type - VSB = 0, QAM = 1, both VSB+QAM = 2
+</ul>
+<br />
+<br />
+
+<h2><a name="WirbelscanSetSetup">Changes the current setup of the Wirbelscan plugin</a></h2>
+<br />
+This service changes the current setup of the Wirbelscan plugin and returns the updated setup.<br />
+<br />
+Method: <b>PUT</b><br />
+<br />
+Example:<br />
+<br />
+<div class="code"><code>
+PUT http://<ip>:<port>/wirbelscan/setSetup.json
+</code></div>
+<br />
+For a description of the setup values see <a href="#WirbelscanGetSetup">Get the current setup</a>
+<br />
+Example Body-Content:<br /><br />
+<div class="code"><pre>
+{
+    "verbosity": 3,
+    "logFile": 1,
+    "DVB_Type": 3,
+    "DVBT_Inversion": 0,
+    "DVBC_Inversion": 0,
+    "DVBC_Symbolrate": 0,
+    "DVBC_QAM": 0,
+    "CountryId": 82,
+    "SatId": 67,
+    "scanflags": 31,
+    "ATSC_type": 0
+}
+</pre></div>
+<br />
+<br />
+
+<h2><a name="WirbelscanDoCommand">Launch a command with the Wirbelscan plugin</a></h2>
+<br />
+This service launches a command with the Wirbelscan plugin.<br />
+<br />
+Method: <b>POST</b><br />
+<br />
+Example:<br />
+<br />
+<div class="code"><code>
+POST http://<ip>:<port>/wirbelscan/doCommand.json
+</code></div>
+<br />
+<b>Required Body Parameters:</b><br />
+<ul>
+<li>command
+</ul>
+<br />
+Description of the command values:<br />
+<ul>
+  <li>0 = start scanning
+  <li>1 = stop scanning
+  <li>2 = store current setup
+</ul>
+<br />
+Example Body-Content:<br /><br />
+<div class="code"><pre>
+{
+  "replycode":true
+}
+</pre></div>
+<br />
+<br />
+<hr/>
+<h1 class="center"><a name="Webapp">Webapp</a></h1>
+<hr />
+<br />
+<h2>Retrieve Webapp.</h2>
+<br />
+
+This service can deliver files from configured folder.<br />
+Specify folder in plugin settings file (--webapp=/var/lib/vdr/plugins/restfulapi/webapp)<br /><br />
+<div class="code"><code>
+GET http://<ip>:<port>/webapp/<path>/<to>/<file>
+</code></div>
+<br />
+<hr />
+<h1 class="center"><a name="Femon">Femon</a></h1>
+<hr />
+<br />
+<h2>Retrieve Femon data</h2>
+<br />
+<div class="code"><code>
+GET http://<ip>:<port>/femon.json
+</code></div>
+<br />
+
+</body>
+</html>
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f90922e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..e344851
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,6 @@
+VDR Plugin 'restfulapi' Revision History
+----------------------------------------
+
+2011-05-16: Version 0.0.1
+
+- Initial revision.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..81ed4ee
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,16 @@
+Be sure you've installed an updated version of cxxtools (>= rev. 1232 or >= Release 2.1).
+On archlinux you'll find a PKGBUILD file in the appropriate folder.
+On Debian/Ubuntu you have to download the package-source from the yavdr-repository and rebuild it.
+
+How to build on Debian/Ubuntu:
+ Clone the git repository or download a copy of it and move to the root directory.
+ Install all dependencies.
+ Type: dpkg-buildpackage -tc -us -uc
+
+How to install on Arch-Linux:
+ Clone the git repository, move to archlinux/vdr-plugin-restfulapi-local/ and type "makepkg PKGBUILD".
+
+Alternate way on Arch-Linux:
+ Move to archlinux/vdr-plugin-resfulapi-git/ on github and download the PKGBUILD and vdr-plugin-restfulapi-git.install in it.
+ Move to the directory where you've downloaded them and execute "makepkg PKGBUILD".
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8f347dc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,129 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id$
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+
+PLUGIN = restfulapi
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).cpp | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The directory environment:
+
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+#
+TMPDIR ?= /tmp
+
+### The compiler options:
+
+export CFLAGS   = $(call PKGCFG,cflags)
+export CXXFLAGS = $(call PKGCFG,cxxflags)
+
+### The version number of VDR's plugin API:
+
+APIVERSION = $(call PKGCFG,apiversion)
+
+### Allow user defined options to overwrite defaults:
+
+-include $(PLGCFG)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
+### Includes and Defines (add further entries here):
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+LIBS    += $(shell cxxtools-config --libs) -lcxxtools-http
+CONFDIR  = $(call PKGCFG,configdir)
+PLGCONFDIR = $(CONFDIR)/plugins/$(PLUGIN)
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o serverthread.o tools.o info.o channels.o events.o recordings.o remote.o timers.o scraper2vdr.o statusmonitor.o osd.o jsonparser.o epgsearch.o searchtimers.o wirbelscan.o webapp.o femon.o
+CFGS = API.html
+
+### The main target:
+
+all: $(SOFILE) i18n
+
+### Implicit rules:
+
+%.o: %.cpp
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+	@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR     = po
+I18Npo    = $(wildcard $(PODIR)/*.po)
+I18Nmo    = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs  = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot   = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+	msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.cpp)
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ `ls $^`
+
+%.po: $(I18Npot)
+	msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
+	@touch $@
+
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
+
+install-i18n: $(I18Nmsgs)
+
+### Targets:
+
+$(SOFILE): $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) -o $@ -Wl,--no-whole-archive $(LIBS)
+
+install-lib: $(SOFILE)
+	install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install-cfg: $(CFGS)
+	install -D $^ $(DESTDIR)$(PLGCONFDIR)/$^
+
+install: install-lib install-i18n install-cfg
+
+dist: $(I18Npo) clean
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@mkdir $(TMPDIR)/$(ARCHIVE)
+	@cp -a * $(TMPDIR)/$(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)/debian
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+	@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
diff --git a/Makefile-1.7.33pre b/Makefile-1.7.33pre
new file mode 100644
index 0000000..7b65d69
--- /dev/null
+++ b/Makefile-1.7.33pre
@@ -0,0 +1,120 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id$
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+# IMPORTANT: the presence of this macro is important for the Make.config
+# file. So it must be defined, even if it is not used here!
+#
+PLUGIN = restfulapi
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).cpp | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+CXX      ?= g++
+CXXFLAGS ?= -g -fPIC -O2 -Wall -Woverloaded-virtual -Wno-parentheses
+LDFLAGS  ?= -fPIC -g
+
+CXXFLAGS += $(cxxtools-config --cxxflags)
+
+LIBS     += $(shell cxxtools-config --libs) -lcxxtools-http
+
+### The directory environment:
+
+VDRDIR = ../../..
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+### Make sure that necessary options are included:
+
+-include $(VDRDIR)/Make.global
+
+### Allow user defined options to overwrite defaults:
+
+-include $(VDRDIR)/Make.config
+
+### The version number of VDR's plugin API (taken from VDR's "config.h"):
+
+APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include
+
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o serverthread.o tools.o info.o channels.o events.o recordings.o remote.o timers.o statusmonitor.o osd.o jsonparser.o epgsearch.o searchtimers.o
+
+### The main target:
+
+all: libvdr-$(PLUGIN).so i18n
+
+### Implicit rules:
+
+%.o: %.cpp
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+	@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR     = po
+LOCALEDIR = $(VDRDIR)/locale
+I18Npo    = $(wildcard $(PODIR)/*.po)
+I18Nmsgs  = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+#I18Npot   = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+	msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.cpp)
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ $^
+
+%.po: $(I18Npot)
+	msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+	@touch $@
+
+$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	@mkdir -p $(dir $@)
+	cp $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmsgs) $(I18Npot)
+
+### Targets:
+
+libvdr-$(PLUGIN).so: $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) -o $@ -Wl,--no-whole-archive $(LIBS)
+	@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
+
+dist: $(I18Npo) clean
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@mkdir $(TMPDIR)/$(ARCHIVE)
+	@cp -a * $(TMPDIR)/$(ARCHIVE)
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot
diff --git a/README b/README
new file mode 100644
index 0000000..d64c6e9
--- /dev/null
+++ b/README
@@ -0,0 +1,22 @@
+yaVDR - vdr-plugin-restfulapi
+---------------------------------------
+
+"yet another VDR" (yaVDR) is a Linux distribution focussed on Klaus Schmidingers Video Disk Recorder and based on Ubuntu.
+
+yaVDR tries to let you:
+
+ * Watch and record TV easily and enjoy your media in a smart way: Quickly set up a digital video recorder (PVR) on your HTPC, receive SD and HD channels, manage it simply with a remote control. Furthermore, take advantage of the full blown media center software XBMC to listen to music, watch videos, check the weather.
+
+ * Enjoy High Definition without high CPU load: HDTV normally needs a strong CPU to be displayed flawlessly. If you own a Nvidia ION based nettop or a HTPC with a Nvidia GPU that supports VDPAU, your CPU will remain cool and your energy bill won't hurt you. yaVDR relies on VDPAU which is currently the only simple way to get GPU based HD video decoding on Linux.
+
+ * Start immediately after turning on the HTPC: yaVDR wants to compete with other living room devices as much as possible using upstart to speed up the boot. Besides that, the shutdown method S3 (Suspend to RAM) is enabled by default to bypass cold boot. It is possible to let the system automatically wake up on timers and go to sleep after a configurable timeout.
+
+Links:
+
+Installation: http://www.yavdr.org/installation/
+Configuration: http://www.yavdr.org/configuration/
+Features: http://www.yavdr.org/features/
+Issue tracker: https://bugs.yavdr.com/projects/yavdr/issues/new
+Package source: https://github.com/yavdr/vdr-plugin-restfulapi
+Team members: http://www.yavdr.org/developer-zone/team-members/
+
diff --git a/archlinux/cxxtools/PKGBUILD b/archlinux/cxxtools/PKGBUILD
new file mode 100644
index 0000000..a8fe3e0
--- /dev/null
+++ b/archlinux/cxxtools/PKGBUILD
@@ -0,0 +1,25 @@
+# Maintainer : <gimli at dark-green dot com>
+# Contributor: Jan Willies <jan at willies.info>
+
+pkgname="cxxtools"
+pkgver="2.1"
+pkgrel="3"
+pkgdesc="Cxxtools is a collection of general-purpose C++ classes"
+url="http://www.tntnet.org/cxxtools.hms"
+license=("LGPL")
+arch=("i686" "x86_64")
+source=(http://www.tntnet.org/download/$pkgname-$pkgver.tar.gz)
+md5sums=('e1b67eeec3f78bef9cce3c337076c120')
+depends=('gcc-libs' 'gcc')
+makedepends=('gcc')
+
+build() {
+  cd "$srcdir/$pkgname-$pkgver"
+
+  MAKEFLAGS="-j1"
+
+  ./configure --prefix=/usr
+  make || return 1
+  make DESTDIR="$pkgdir" install
+}
+
diff --git a/archlinux/vdr-plugin-restfulapi-git/PKGBUILD b/archlinux/vdr-plugin-restfulapi-git/PKGBUILD
new file mode 100644
index 0000000..df170bb
--- /dev/null
+++ b/archlinux/vdr-plugin-restfulapi-git/PKGBUILD
@@ -0,0 +1,56 @@
+# Maintainer  : Michael Eiler <eiler.mike at gmail.com>
+
+_pluginname=restfulapi
+pkgname=vdr-plugin-${_pluginname}-git
+pkgver=0.1.1.git
+pkgrel=1
+pkgdesc="RESTful api for VDR >1.7.21"
+arch=('i686' 'x86_64')
+url="http://github.com/yavdr/vdr-plugin-restfulapi"
+license=(GPL)
+depends=('gcc' 'vdr>=1.7.21' 'cxxtools>=2.1' 'git')
+optdepends=('vdr-plugin-epgsearch: Search in EPG data')
+backup=('etc/vdr/plugins/plugin.restfulapi.conf')
+install=vdr-plugin-${_pluginname}-git.install
+
+
+build() {
+  cd "$srcdir/"	|| return 1
+  
+  _githost="git://github.com/MichaelE1000"
+  _gitrepo="vdr-plugin-restfulapi.git"
+
+  if [ -e $srcdir/vdr-plugin-${_pluginname} ]; then	  
+    cd $srcdir/vdr-plugin-${_pluginname}
+    git rebase origin
+    git pull
+  else
+    git clone ${_githost}/${_gitrepo}
+    cd $srcdir/vdr-plugin-${_pluginname}
+    git pull
+  fi
+
+  if [ -e $srcdir/vdr-plugin-${_pluginname}-build ]; then
+    rm -rf $srcdir/vdr-plugin-${_pluginname}-build
+  fi
+
+  cp -r $srcdir/vdr-plugin-${_pluginname} $srcdir/vdr-plugin-${_pluginname}-build
+
+  cd $srcdir/vdr-plugin-${_pluginname}-build
+
+  #apply patches here
+
+  make VDRDIR="/usr/include/vdr"  LIBDIR="." \
+    LOCALEDIR="$pkgdir/usr/share/locale" all || return 1
+  
+  mkdir -p $pkgdir/etc/vdr/plugins						|| return 1
+  install -m 644 plugin.restfulapi.conf	$pkgdir/etc/vdr/plugins			|| return 1
+
+  mkdir -p $pkgdir/usr/lib/vdr/plugins						|| return 1
+  install -m 755 libvdr-*.so.*	$pkgdir/usr/lib/vdr/plugins/			|| return 1
+  
+  mkdir -p $pkgdir/var/lib/vdr/plugins/restfulapi				|| return 1
+  install -m 644 API.html       $pkgdir/var/lib/vdr/plugins/restfulapi/		|| return 1
+  install -m 644 web/osd.js     $pkgdir/var/lib/vdr/plugins/restfulapi/		|| return 1
+  install -m 644 web/osd.css    $pkgdir/var/lib/vdr/plugins/restfulapi/		|| return 1
+}
diff --git a/archlinux/vdr-plugin-restfulapi-git/plugin.restfulapi.conf b/archlinux/vdr-plugin-restfulapi-git/plugin.restfulapi.conf
new file mode 100644
index 0000000..5281c79
--- /dev/null
+++ b/archlinux/vdr-plugin-restfulapi-git/plugin.restfulapi.conf
@@ -0,0 +1,8 @@
+#
+# Command line parameters for vdr-plugin-restfulapi
+#
+
+--port=8002
+--ip=0.0.0.0
+--epgimages=/var/cache/vdr/epgimages
+--channellogos=/usr/share/vdr-channellogos
diff --git a/archlinux/vdr-plugin-restfulapi-git/vdr-plugin-restfulapi-git.install b/archlinux/vdr-plugin-restfulapi-git/vdr-plugin-restfulapi-git.install
new file mode 100644
index 0000000..47069e9
--- /dev/null
+++ b/archlinux/vdr-plugin-restfulapi-git/vdr-plugin-restfulapi-git.install
@@ -0,0 +1,34 @@
+# TODO: Is a fixed gid and uid good?
+
+_USER=vdr
+_GROUP=vdr
+_DIRS="/var/lib/vdr/plugins/restfulapi" # No whitespace in pathes allowed
+
+# arg 1:  the new package version
+post_install() {
+  local dir
+
+  echo ">>> - Set filesystem permissions for user \"$_USER\": "
+  for dir in $_DIRS; do
+    echo -n ">>>   $dir "
+    if [ -e "$dir" ] && chown -R $_USER:$_GROUP "$dir"; then
+      echo "done"
+    else
+      echo "failed"
+    fi
+  done
+  echo ">>>"
+}
+
+# arg 1:  the new package version
+# arg 2:  the old package version
+post_upgrade() {
+  post_install
+}
+
+# arg 1:  the old package version
+post_remove() {
+  id $_USER &>/dev/null
+}
+
+# vim:set ts=2 sw=2 et:
diff --git a/archlinux/vdr-plugin-restfulapi-local/PKGBUILD b/archlinux/vdr-plugin-restfulapi-local/PKGBUILD
new file mode 100644
index 0000000..238eebf
--- /dev/null
+++ b/archlinux/vdr-plugin-restfulapi-local/PKGBUILD
@@ -0,0 +1,60 @@
+# Maintainer  : Michael Eiler <eiler.mike at gmail.com>
+
+_pluginname=restfulapi
+pkgname=vdr-plugin-${_pluginname}-local
+pkgver=0.1.1.local
+pkgrel=1
+pkgdesc="RESTful api for VDR >1.7.21"
+arch=('i686' 'x86_64')
+url="http://github.com/yavdr/vdr-plugin-restfulapi"
+license=(GPL)
+depends=('gcc' 'vdr>=1.7.21' 'cxxtools>=2.1')
+optdepends=('vdr-plugin-epgsearch: Search in EPG data')
+backup=('etc/vdr/plugins/plugin.restfulapi.conf')
+install=vdr-plugin-${_pluginname}-local.install
+
+
+build() {
+  cd "$srcdir/"	|| return 1
+  
+  if [ -e $srcdir/vdr-plugin-${_pluginname} ]; then	  
+    cd $srcdir/vdr-plugin-${_pluginname}
+  else
+    mkdir $srcdir/vdr-plugin-${_pluginname}
+    cd $srcdir/vdr-plugin-${_pluginname}
+  fi
+
+  cp -u ../../../../*.cpp ./
+  cp -u ../../../../*.h ./
+  cp -u ../../../../Makefile ./
+  cp -u ../../../../plugin.restfulapi.conf ./
+  cp -u ../../../../HISTORY ./
+  cp -u ../../../../COPYING ./
+  cp -u ../../../../README ./
+  cp -u -a ../../../../epgsearch ./
+  cp -u -a ../../../../web ./
+
+  if [ -e $srcdir/vdr-plugin-${_pluginname}-build ]; then
+    rm -rf $srcdir/vdr-plugin-${_pluginname}-build
+  fi
+
+  cp -r $srcdir/vdr-plugin-${_pluginname} $srcdir/vdr-plugin-${_pluginname}-build
+
+  cd $srcdir/vdr-plugin-${_pluginname}-build
+
+  #apply patches here
+
+  make VDRDIR="/usr/include/vdr"  LIBDIR="." \
+    LOCALEDIR="$pkgdir/usr/share/locale" all || return 1
+  
+  mkdir -p $pkgdir/etc/vdr/plugins						|| return 1
+  install -m 644 plugin.restfulapi.conf	$pkgdir/etc/vdr/plugins			|| return 1
+
+  mkdir -p $pkgdir/usr/lib/vdr/plugins						|| return 1
+  install -m 755 libvdr-*.so.*	$pkgdir/usr/lib/vdr/plugins/			|| return 1
+  
+  mkdir -p $pkgdir/var/lib/vdr/plugins/restfulapi				|| return 1
+  install -m 644 API.html       $pkgdir/var/lib/vdr/plugins/restfulapi/		|| return 1
+  install -m 644 web/osd.js     $pkgdir/var/lib/vdr/plugins/restfulapi/		|| return 1
+  install -m 644 web/osd.css    $pkgdir/var/lib/vdr/plugins/restfulapi/		|| return 1
+}
diff --git a/archlinux/vdr-plugin-restfulapi-local/vdr-plugin-restfulapi-local.install b/archlinux/vdr-plugin-restfulapi-local/vdr-plugin-restfulapi-local.install
new file mode 100644
index 0000000..47069e9
--- /dev/null
+++ b/archlinux/vdr-plugin-restfulapi-local/vdr-plugin-restfulapi-local.install
@@ -0,0 +1,34 @@
+# TODO: Is a fixed gid and uid good?
+
+_USER=vdr
+_GROUP=vdr
+_DIRS="/var/lib/vdr/plugins/restfulapi" # No whitespace in pathes allowed
+
+# arg 1:  the new package version
+post_install() {
+  local dir
+
+  echo ">>> - Set filesystem permissions for user \"$_USER\": "
+  for dir in $_DIRS; do
+    echo -n ">>>   $dir "
+    if [ -e "$dir" ] && chown -R $_USER:$_GROUP "$dir"; then
+      echo "done"
+    else
+      echo "failed"
+    fi
+  done
+  echo ">>>"
+}
+
+# arg 1:  the new package version
+# arg 2:  the old package version
+post_upgrade() {
+  post_install
+}
+
+# arg 1:  the old package version
+post_remove() {
+  id $_USER &>/dev/null
+}
+
+# vim:set ts=2 sw=2 et:
diff --git a/channels.cpp b/channels.cpp
new file mode 100644
index 0000000..bfe9283
--- /dev/null
+++ b/channels.cpp
@@ -0,0 +1,372 @@
+#include "channels.h"
+using namespace std;
+
+void ChannelsResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler::addHeader(reply);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  if ( request.method() != "GET") {
+     reply.httpReturn(403, "To retrieve information use the GET method!");
+     return;
+  }
+  static cxxtools::Regex imageRegex("/channels/image/*");
+  static cxxtools::Regex groupsRegex("/channels/groups/*");
+
+  if ( imageRegex.match(request.url()) ) {
+     replyImage(out, request, reply);
+  } else if (groupsRegex.match(request.url()) ){
+     replyGroups(out, request, reply);
+  } else {
+     replyChannels(out, request, reply);
+  }
+}
+
+void ChannelsResponder::replyChannels(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/channels", request);
+  
+  ChannelList* channelList;
+
+  if ( q.isFormat(".json") ) {
+    reply.addHeader("Content-Type", "application/json; charset=utf-8");
+    channelList = (ChannelList*)new JsonChannelList(&out);
+  } else if ( q.isFormat(".html") ) {
+    reply.addHeader("Content-Type", "text/html; charset=utf-8");
+    channelList = (ChannelList*)new HtmlChannelList(&out);
+  } else if ( q.isFormat(".xml") ) {
+    reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+    channelList = (ChannelList*)new XmlChannelList(&out);
+  } else {
+    reply.httpReturn(403, "Resources are not available for the selected format. (Use: .json, .html or .xml)");
+    return;
+  }
+
+  string channel_details = q.getParamAsString(0);
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+  string group_filter = q.getOptionAsString("group");
+
+  if (channel_details.length() > 0) {
+     cChannel* channel = VdrExtension::getChannel(channel_details);
+     if (channel == NULL || channel->GroupSep()) {
+        reply.httpReturn(403, "The requested channel is not available.");
+        delete channelList;
+        return;        
+     } else {
+        channelList->init();
+        
+        string group = "";
+        int total = 0;
+        for (cChannel *channelIt = Channels.First(); channelIt; channelIt = Channels.Next(channelIt))
+        { 
+           if (!channelIt->GroupSep()) 
+              total++; 
+           else
+              if ( total < channel->Number())
+                 group = channelIt->Name();
+        }
+        channelList->setTotal(total);
+        string image = FileCaches::get()->searchChannelLogo(channel);
+        channelList->addChannel(channel, group, image.length() == 0);
+     }
+  } else {
+     if ( start_filter >= 0 && limit_filter >= 1 ) {
+        channelList->activateLimit(start_filter, limit_filter);
+     }
+     channelList->init();
+     int total = 0;
+     string group = "";
+     for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
+     {
+       if (!channel->GroupSep()) {
+          if ( group_filter.length() == 0 || group == group_filter ) {
+             string image = FileCaches::get()->searchChannelLogo(channel);
+             channelList->addChannel(channel, group, image.length() != 0);
+             total++;
+          }
+       } else {
+         group = channel->Name();
+       }
+     }
+     channelList->setTotal(total);
+  }
+
+  channelList->finish();
+  delete channelList;
+}
+
+void ChannelsResponder::replyImage(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  StreamExtension se(&out);
+  QueryHandler q("/channels/image/", request);
+  
+  string channelid = q.getParamAsString(0);
+  cChannel* channel = VdrExtension::getChannel(channelid);
+  string imageFolder = Settings::get()->ChannelLogoDirectory() + (string)"/";
+  double timediff = -1;
+  
+  if (channel == NULL) {
+     reply.httpReturn(502, "Channel not found!");
+     return;
+  }
+
+  string imageName = FileCaches::get()->searchChannelLogo(channel);
+
+  if (imageName.length() == 0 ) {
+     reply.httpReturn(502, "No image found!");
+     return;
+  }
+  
+  string absolute_path = imageFolder + imageName;
+
+  if (request.hasHeader("If-Modified-Since")) {
+      timediff = difftime(FileExtension::get()->getModifiedTime(absolute_path), FileExtension::get()->getModifiedSinceTime(request));
+  }
+
+  if (timediff > 0.0 || timediff < 0.0) {
+      string contenttype = (string)"image/" + imageName.substr( imageName.find_last_of('.') + 1 );
+      if ( se.writeBinary(absolute_path) ) {
+	  FileExtension::get()->addModifiedHeader(absolute_path, reply);
+         reply.addHeader("Content-Type", contenttype.c_str());
+      } else {
+        reply.httpReturn(502, "Binary Output failed");
+      }
+  } else {
+    reply.httpReturn(304, "Not-Modified");
+  }
+}
+
+void ChannelsResponder::replyGroups(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{ 
+  QueryHandler q("/channels/groups", request);
+  ChannelGroupList* channelGroupList;
+  
+  if ( q.isFormat(".json") ) {
+    reply.addHeader("Content-Type", "application/json; charset=utf-8");
+    channelGroupList = (ChannelGroupList*)new JsonChannelGroupList(&out);
+  } else if ( q.isFormat(".html") ) {
+    reply.addHeader("Content-Type", "text/html; charset=utf-8");
+    channelGroupList = (ChannelGroupList*)new HtmlChannelGroupList(&out);
+  } else if ( q.isFormat(".xml") ) {
+    reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+    channelGroupList = (ChannelGroupList*)new XmlChannelGroupList(&out);
+  } else {
+    reply.httpReturn(403, "Resources are not available for the selected format. (Use: .json, .html or .xml)");
+    return;
+  }
+
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+  if ( start_filter >= 0 && limit_filter >= 1 ) {
+     channelGroupList->activateLimit(start_filter, limit_filter);
+  }
+
+  channelGroupList->init();
+  int total = 0;
+  
+  for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
+  {
+      if (channel->GroupSep()) {
+         channelGroupList->addGroup((std::string)channel->Name());
+         total++;
+      }
+  }
+
+  channelGroupList->setTotal(total);
+  channelGroupList->finish();
+
+  delete channelGroupList;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerChannel& c)
+{
+  si.addMember("name") <<= c.Name;
+  si.addMember("number") <<= c.Number;
+  si.addMember("channel_id") <<= c.ChannelId;
+  si.addMember("image") <<= c.Image;
+  si.addMember("group") <<= c.Group;
+  si.addMember("transponder") <<= c.Transponder;
+  si.addMember("stream") <<= c.Stream;
+  si.addMember("is_atsc") <<= c.IsAtsc;
+  si.addMember("is_cable") <<= c.IsCable;
+  si.addMember("is_terr") <<= c.IsTerr;
+  si.addMember("is_sat") <<= c.IsSat;
+  si.addMember("is_radio") <<= c.IsRadio;
+}
+
+ChannelList::ChannelList(ostream* _out)
+{
+  s = new StreamExtension(_out);
+}
+
+ChannelList::~ChannelList()
+{
+  delete s;
+}
+
+void HtmlChannelList::init()
+{
+  s->writeHtmlHeader( "HtmlChannelList" );
+  s->write("<ul>");
+}
+
+void HtmlChannelList::addChannel(cChannel* channel, string group, bool image)
+{
+  if ( filtered() ) return;
+
+  s->write("<li>");
+  s->write((char*)channel->Name());
+  s->write("\n");
+}
+
+void HtmlChannelList::finish()
+{
+  s->write("</ul>");
+  s->write("</body></html>");
+}
+
+void JsonChannelList::addChannel(cChannel* channel, string group, bool image)
+{  
+  if ( filtered() ) return;
+
+  string suffix = (string) ".ts";
+
+  SerChannel serChannel;
+  serChannel.Name = StringExtension::UTF8Decode(channel->Name());
+  serChannel.Number = channel->Number();
+  serChannel.ChannelId = StringExtension::UTF8Decode((string)channel->GetChannelID().ToString());
+  serChannel.Image = image;
+  serChannel.Group = StringExtension::UTF8Decode(group);
+  serChannel.Transponder = channel->Transponder();
+  serChannel.Stream = StringExtension::UTF8Decode(((string)channel->GetChannelID().ToString() + (string)suffix).c_str());
+  // TODO: There is an atsc Patch
+  #if APIVERSNUM >= 10714
+  serChannel.IsAtsc = channel->IsAtsc();
+  #else
+  serChannel.IsAtsc = false;
+  #endif
+  serChannel.IsCable = channel->IsCable();
+  serChannel.IsSat = channel->IsSat();
+  serChannel.IsTerr = channel->IsTerr();
+  serChannel.IsRadio = VdrExtension::IsRadio(channel);
+  serChannels.push_back(serChannel);
+}
+
+void JsonChannelList::finish()
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(serChannels, "channels");
+  serializer.serialize(Count(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
+
+void XmlChannelList::init()
+{
+  s->writeXmlHeader();
+  s->write("<channels xmlns=\"http://www.domain.org/restfulapi/2011/channels-xml\">\n");
+}
+
+void XmlChannelList::addChannel(cChannel* channel, string group, bool image)
+{
+  if ( filtered() ) return;
+
+  string suffix = (string) ".ts";
+
+  s->write(" <channel>\n");
+  s->write(cString::sprintf("  <param name=\"name\">%s</param>\n", StringExtension::encodeToXml(channel->Name()).c_str()));
+  s->write(cString::sprintf("  <param name=\"number\">%i</param>\n", channel->Number()));
+  s->write(cString::sprintf("  <param name=\"channel_id\">%s</param>\n",  StringExtension::encodeToXml( (string)channel->GetChannelID().ToString()).c_str()));
+  s->write(cString::sprintf("  <param name=\"image\">%s</param>\n", (image ? "true" : "false")));
+  s->write(cString::sprintf("  <param name=\"group\">%s</param>\n", StringExtension::encodeToXml( group ).c_str()));
+  s->write(cString::sprintf("  <param name=\"transponder\">%i</param>\n", channel->Transponder()));
+  s->write(cString::sprintf("  <param name=\"stream\">%s</param>\n", StringExtension::encodeToXml( ((string)channel->GetChannelID().ToString() + (string)suffix).c_str()).c_str()));
+  // TODO: There is an atsc Patch
+  #if APIVERSNUM >= 10714
+  s->write(cString::sprintf("  <param name=\"is_atsc\">%s</param>\n", channel->IsAtsc() ? "true" : "false"));
+  #else
+  s->write(cString::sprintf("  <param name=\"is_atsc\">%s</param>\n", false ? "true" : "false"));
+  #endif
+  s->write(cString::sprintf("  <param name=\"is_cable\">%s</param>\n", channel->IsCable() ? "true" : "false"));
+  s->write(cString::sprintf("  <param name=\"is_sat\">%s</param>\n", channel->IsSat() ? "true" : "false"));
+  s->write(cString::sprintf("  <param name=\"is_terr\">%s</param>\n", channel->IsTerr() ? "true" : "false"));
+  bool is_radio = VdrExtension::IsRadio(channel);
+  s->write(cString::sprintf("  <param name=\"is_radio\">%s</param>\n", is_radio ? "true" : "false"));
+  s->write(" </channel>\n");
+}
+
+void XmlChannelList::finish()
+{
+  s->write(cString::sprintf(" <count>%i</count><total>%i</total>", Count(), total));
+  s->write("</channels>");
+}
+
+ChannelGroupList::ChannelGroupList(std::ostream* _out) 
+{
+  s = new StreamExtension(_out);
+}
+
+ChannelGroupList::~ChannelGroupList()
+{
+  delete s;
+}
+
+void HtmlChannelGroupList::init()
+{
+  s->writeHtmlHeader( "HtmlChannelGroupList" );
+  s->write("<ul>");
+}
+
+void HtmlChannelGroupList::addGroup(string group)
+{
+  if ( filtered() ) return;
+
+  s->write("<li>");
+  s->write(group.c_str());
+  s->write("\n");
+}
+
+void HtmlChannelGroupList::finish()
+{
+  s->write("</ul>");
+  s->write("</body></html>");
+}
+
+void JsonChannelGroupList::addGroup(string group)
+{
+  if ( filtered() ) return;
+  groups.push_back(StringExtension::UTF8Decode(group));
+}
+
+void JsonChannelGroupList::finish()
+{  
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(groups, "groups");
+  serializer.serialize(Count(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
+
+void XmlChannelGroupList::init()
+{
+  s->writeXmlHeader();
+  s->write("<groups xmlns=\"http://www.domain.org/restfulapi/2011/groups-xml\">\n");
+}
+
+void XmlChannelGroupList::addGroup(string group)
+{
+  if ( filtered() ) return;
+  s->write(cString::sprintf(" <group>%s</group>\n", StringExtension::encodeToXml( group ).c_str()));
+}
+
+void XmlChannelGroupList::finish()
+{
+  s->write(cString::sprintf(" <count>%i</count><total>%i</total>", Count(), total));
+  s->write("</groups>");
+}
diff --git a/channels.h b/channels.h
new file mode 100644
index 0000000..4c57e51
--- /dev/null
+++ b/channels.h
@@ -0,0 +1,133 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/arg.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include <cxxtools/utf8codec.h>
+#include <vdr/channels.h>
+#include "tools.h"
+#include "scraper2vdr.h"
+
+class ChannelsResponder : public cxxtools::http::Responder
+{
+  public:
+    explicit ChannelsResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service)
+      { }
+    virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void replyChannels(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void replyImage(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& replay);
+    virtual void replyGroups(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+};
+
+typedef cxxtools::http::CachedService<ChannelsResponder> ChannelsService;
+
+struct SerChannel
+{
+  cxxtools::String Name;
+  int Number;
+  cxxtools::String ChannelId;
+  int Transponder;
+  bool Image;
+  cxxtools::String Stream;
+  cxxtools::String Group;
+  bool IsAtsc;
+  bool IsCable;
+  bool IsTerr;
+  bool IsSat;
+  bool IsRadio;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerChannel& c);
+
+class ChannelList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+  public:
+    explicit ChannelList(std::ostream* _out);
+    virtual ~ChannelList();
+    virtual void init() { };
+    virtual void addChannel(cChannel* channel, std::string group, bool image) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; }
+};
+
+class HtmlChannelList : ChannelList
+{
+  public:
+    explicit HtmlChannelList(std::ostream* _out) : ChannelList(_out) { };
+    ~HtmlChannelList() { };
+    virtual void init();
+    virtual void addChannel(cChannel* channel, std::string group, bool image);
+    virtual void finish();
+};
+
+class JsonChannelList : ChannelList
+{
+  private:
+    std::vector < struct SerChannel > serChannels;
+  public:
+    explicit JsonChannelList(std::ostream* _out) : ChannelList(_out) { };
+    ~JsonChannelList() { };
+    virtual void addChannel(cChannel* channel, std::string group, bool image);
+    virtual void finish();
+};
+
+class XmlChannelList : ChannelList
+{
+  public:
+    explicit XmlChannelList(std::ostream* _out) : ChannelList(_out) { };
+    ~XmlChannelList() { };
+    virtual void init();
+    virtual void addChannel(cChannel* channel, std::string group, bool image);
+    virtual void finish();
+};
+
+class ChannelGroupList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+  public:
+    explicit ChannelGroupList(std::ostream* _out);
+    virtual ~ChannelGroupList();
+    virtual void init() { };
+    virtual void addGroup(std::string group) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; }
+};
+
+class HtmlChannelGroupList : ChannelGroupList
+{
+  public:
+    explicit HtmlChannelGroupList(std::ostream* _out) : ChannelGroupList(_out) { };
+    ~HtmlChannelGroupList() { };
+    virtual void init();
+    virtual void addGroup(std::string group);
+    virtual void finish();
+};
+
+class JsonChannelGroupList : ChannelGroupList
+{
+  private:
+    std::vector< cxxtools::String > groups;
+  public:
+    explicit JsonChannelGroupList(std::ostream* _out) : ChannelGroupList(_out) { };
+    ~JsonChannelGroupList() { };
+    virtual void init() { };
+    virtual void addGroup(std::string group);
+    virtual void finish();
+};
+
+class XmlChannelGroupList : ChannelGroupList
+{
+  public:
+    explicit XmlChannelGroupList(std::ostream* _out) : ChannelGroupList(_out) { };
+    ~XmlChannelGroupList() { };
+    virtual void init();
+    virtual void addGroup(std::string group);
+    virtual void finish();
+};
diff --git a/epgsearch.cpp b/epgsearch.cpp
new file mode 100644
index 0000000..26f4731
--- /dev/null
+++ b/epgsearch.cpp
@@ -0,0 +1,861 @@
+#include "epgsearch.h"
+
+using namespace std;
+
+void operator<<= (cxxtools::SerializationInfo& si, SerSearchTimerContainer s)
+{
+  si.addMember("id") <<= s.timer->Id();
+  si.addMember("search") <<= StringExtension::UTF8Decode(s.timer->Search());
+  si.addMember("mode") <<= s.timer->SearchMode();
+  si.addMember("tolerance") <<= s.timer->Tolerance();
+  si.addMember("match_case") <<= s.timer->MatchCase();
+  si.addMember("use_time") <<= s.timer->UseTime();
+  si.addMember("use_title") <<= s.timer->UseTitle();
+  si.addMember("use_subtitle") <<= s.timer->UseSubtitle();
+  si.addMember("use_description") <<= s.timer->UseDescription();
+  si.addMember("start_time") <<= s.timer->StartTime();
+  si.addMember("stop_time") <<= s.timer->StopTime();
+  si.addMember("use_channel") <<= s.timer->UseChannel();
+  si.addMember("channel_min") <<= (const char*)s.timer->ChannelMin().ToString();
+  si.addMember("channel_max") <<= (const char*)s.timer->ChannelMax().ToString();
+  si.addMember("channels") <<= s.timer->ChannelText();
+  si.addMember("use_as_searchtimer") <<= s.timer->UseAsSearchTimer();
+  si.addMember("use_duration") <<= s.timer->UseDuration();
+  si.addMember("duration_min") <<= s.timer->MinDuration();
+  si.addMember("duration_max") <<= s.timer->MaxDuration();
+  si.addMember("use_dayofweek") <<= s.timer->UseDayOfWeek();
+  si.addMember("dayofweek") <<= s.timer->DayOfWeek();
+  si.addMember("use_in_favorites") <<= s.timer->UseInFavorites();
+  si.addMember("search_timer_action") <<= s.timer->SearchTimerAction();
+  si.addMember("use_series_recording") <<= s.timer->UseSeriesRecording();
+  si.addMember("directory") <<= s.timer->Directory();
+  si.addMember("del_recs_after_days") <<= s.timer->DelRecsAfterDays();
+  si.addMember("keep_recs") <<= s.timer->KeepRecs();
+  si.addMember("pause_on_recs") <<= s.timer->PauseOnRecs();
+  si.addMember("blacklist_mode") <<= s.timer->BlacklistMode();
+  si.addMember("switch_min_before") <<= s.timer->SwitchMinBefore();
+  si.addMember("use_ext_epg_info") <<= s.timer->UseExtEPGInfo();
+  si.addMember("ext_epg_info") <<= s.timer->ExtEPGInfo();
+  si.addMember("avoid_repeats") <<= s.timer->AvoidRepeats();
+  si.addMember("allowed_repeats") <<= s.timer->AllowedRepeats();
+  si.addMember("repeats_within_days") <<= s.timer->RepeatsWithinDays();
+  si.addMember("compare_title") <<= s.timer->CompareTitle();
+  si.addMember("compare_subtitle") <<= s.timer->CompareSubtitle();
+  si.addMember("compare_summary") <<= s.timer->CompareSummary();
+  si.addMember("compare_categories") <<= s.timer->CompareCategories();
+  si.addMember("priority") <<= s.timer->Priority();
+  si.addMember("lifetime") <<= s.timer->Lifetime();
+  si.addMember("margin_start") <<= s.timer->MarginStart();
+  si.addMember("margin_stop") <<= s.timer->MarginStop();
+  si.addMember("use_vps") <<= s.timer->UseVPS();
+  si.addMember("del_mode") <<= s.timer->DelMode();
+  si.addMember("del_after_count_recs") <<= s.timer->DelAfterCountRecs();
+  si.addMember("del_after_days_of_first_rec") <<= s.timer->DelAfterDaysOfFirstRec();
+}
+
+namespace vdrlive {
+
+
+string SearchTimer::ToXml()
+{
+  ostringstream s;
+  s << "<searchtimer>\n"
+  << "<id>" << Id() << "</id>\n"
+  << "<search>" << StringExtension::encodeToXml(Search()) << "</search>\n"
+  << "<mode>" << SearchMode() << "</mode>\n"
+  << "<tolerance>" << Tolerance() << "</tolerance>\n"
+  << "<match_case>" << MatchCase() << "</match_case>\n"
+  << "<use_time>" << UseTime() << "</use_time>\n"
+  << "<use_title>" << UseTitle() << "</use_title>\n"
+  << "<use_subtitle>" << UseSubtitle() << "</use_subtitle>\n"
+  << "<start_time>" << StartTime() << "</start_time>\n"
+  << "<stop_time>" << StopTime() << "</stop_time>\n"
+  << "<use_channel>" << UseChannel() << "</use_channel>\n"
+  << "<channel_min>" << ChannelMin() << "</channel_min>\n"
+  << "<channel_max>" << ChannelMax() << "</channel_max>\n"
+  << "<channels>" << ChannelText() << "</channels>\n"
+  << "<use_as_searchtimer>" << UseAsSearchTimer() << "</use_as_searchtimer>\n"
+  << "<use_duration>" << UseDuration() << "</use_duration>\n"
+  << "<duration_min>" << MinDuration() << "</duration_min>\n"
+  << "<duration_max>" << MaxDuration() << "</duration_max>\n"
+  << "<use_dayofweek>" << UseDayOfWeek() << "</use_dayofweek>\n"
+  << "<dayofweek>" << DayOfWeek() << "</dayofweek>\n"
+  << "<use_in_favorites>" << UseInFavorites() << "</use_in_favorites>\n"
+  << "<directory>" << Directory() << "</directory>\n"
+  << "<del_recs_after_days>" << DelRecsAfterDays() << "</del_recs_after_days>\n"
+  << "<keep_recs>" << KeepRecs() << "</keep_recs>\n"
+  << "<pause_on_recs>" << PauseOnRecs() << "</pause_on_recs>\n"
+  << "<blacklist_mode>" << BlacklistMode() << "</blacklist_mode>\n"
+  << "<switch_min_before>" << SwitchMinBefore() << "</switch_min_before>\n"
+  << "<use_ext_epg_info>" << UseExtEPGInfo() << "</use_ext_epg_info>\n"
+  << "<ext_epg_info>\n";
+  vector<string > epg_infos = ExtEPGInfo();
+  for (int i=0;i<(int)epg_infos.size();i++)
+  {
+    s << " <info>" << epg_infos[i] << "</info>\n";
+  }
+  s << "</ext_epg_info>\n"
+  << "<avoid_repeats>" << AvoidRepeats() << "</avoid_repeats>\n"
+  << "<allowed_repeats>" << AllowedRepeats() << "</allowed_repeats>\n"
+  << "<repeats_within_days>" << RepeatsWithinDays() << "</repeats_within_days>\n"
+  << "<compare_title>" << CompareTitle() << "</compare_title>\n"
+  << "<compare_subtitle>" << CompareSubtitle() << "</compare_subtitle>\n"
+  << "<compare_summary>" << CompareSummary() << "</compare_summary>\n"
+  << "<compare_categories>" << CompareCategories() << "</compare_categories>\n"
+  << "<priority>" << Priority() << "</priority>\n"
+  << "<lifetime>" << Lifetime() << "</lifetime>\n"
+  << "<margin_start>" << MarginStart() << "</margin_start>\n"
+  << "<margin_stop>" << MarginStop() << "</margin_stop>\n"
+  << "<use_vps>" << UseVPS() << "</use_vps>\n"
+  << "<del_mode>" << DelMode() << "</del_mode>\n"
+  << "<del_after_count_recs>" << DelAfterCountRecs() << "</del_after_count_recs>\n"
+  << "<del_after_days_of_first_rec>" << DelAfterDaysOfFirstRec() << "</del_after_days_of_first_rec>\n"
+  << "</searchtimer>\n";
+  
+  return s.str();
+}
+
+string SearchTimer::ToHtml()
+{
+  return Search();
+}
+
+string SearchTimer::LoadFromQuery(QueryHandler& q)
+{
+  int searchtimer_id = q.getBodyAsInt("id");
+  if ( searchtimer_id >= 0 ) m_id = searchtimer_id;
+
+  string search = q.getBodyAsString("search");
+  if ( search.length() > 0 ) { m_search = search; } else { return "Search required."; }
+
+  m_useTime = q.getBodyAsBool("use_time");
+  if ( m_useTime ) {
+     m_startTime = q.getBodyAsInt("start_time");
+     m_stopTime = q.getBodyAsInt("stop_time");
+     if ( m_startTime < 0 || m_stopTime < 0 ) return "start or stop-time invalid";
+  }
+
+  int use_channel = q.getBodyAsInt("use_channel");
+  if ( use_channel >= 0 && use_channel <= 3 ) m_useChannel = use_channel; else return "use_channel invalid (0=no, 1=interval, 2=channel group, 3=only FTA";
+  if ( m_useChannel == Interval ) {
+     cChannel* minChannel = VdrExtension::getChannel(q.getBodyAsString("channel_min"));
+     cChannel* maxChannel = VdrExtension::getChannel(q.getBodyAsString("channel_max"));
+     if (minChannel == NULL || maxChannel == NULL) {
+        return "channel_min or channel_max invalid";
+     }
+     
+     m_channelMin = minChannel->GetChannelID();
+     m_channelMax = maxChannel->GetChannelID();
+  } else if(use_channel != 0) {
+     m_channels = q.getBodyAsString("channels");
+     if ( m_channels.length() == 0 ) { return "use_channels activated but no channel selected"; }
+  }
+  
+  m_useCase = q.getBodyAsBool("use_case");
+  m_mode = q.getBodyAsInt("mode");
+  if ( m_mode < 0 | m_mode > 5 ) return "mode invalid, (0=phrase, 1=all words, 2=at least one word, 3=match exactly, 4=regex, 5=fuzzy";
+  
+  m_useTitle = q.getBodyAsBool("use_title");
+  m_useSubtitle = q.getBodyAsBool("use_subtitle");
+  m_useDescription = q.getBodyAsBool("use_description");
+  if ( !m_useTitle && !m_useSubtitle && !m_useDescription ) return "You have to select at least one of the following three values: use_title, use_subtitle or/and use_description.";
+
+  m_useDuration = q.getBodyAsBool("use_duration");
+  if ( m_useDuration ) {
+     m_minDuration = q.getBodyAsInt("duration_min");
+     m_maxDuration = q.getBodyAsInt("duration_max");
+     if ( m_minDuration < 0 || m_maxDuration < 1 ) return "min/max-duration invalid";
+  }
+  
+  m_useDayOfWeek = q.getBodyAsBool("use_dayofweek");
+  if ( m_useDayOfWeek ) {
+     m_dayOfWeek = q.getBodyAsInt("dayofweek");
+     if (m_dayOfWeek < -127 || m_dayOfWeek > 6 ) return "day_of_week invalid (uses 7 bits for the seven days!)";
+  }
+
+  m_useEpisode = q.getBodyAsBool("use_series_recording");
+
+  int priority = q.getBodyAsInt("priority");
+  if (priority >= 0) m_priority = priority;
+
+  int lifetime = q.getBodyAsInt("lifetime");
+  if (lifetime >= 0) m_lifetime = lifetime;
+
+  //only required in fuzzy mode
+  int tolerance = q.getBodyAsInt("tolerance");  
+  if ( tolerance >= 0 && m_mode == 5 ) m_fuzzytolerance = tolerance;
+
+  m_useInFavorites = q.getBodyAsBool("use_in_favorites");
+
+  int useAsSearchtimer = q.getBodyAsInt("use_as_searchtimer");
+  if (useAsSearchtimer >= 0 && useAsSearchtimer <= 2) 
+     m_useAsSearchtimer = useAsSearchtimer;
+  else
+     return "use_as_searchtimer invalid (0=no, 1=yes, 2=user defined";
+
+  if (useAsSearchtimer == 2 /*user defined*/) {
+     //read time_t m_useAsSearchTimerFrom
+     //read time_t m_useAsSearchTimerTil
+     //to be implemented in the parser and in the webservice output // add methods to SearchTimer-class
+     return "use_as_searchtimer mode user defined not supported in restfulapi (at least currently)";
+  }
+
+  int action = q.getBodyAsInt("search_timer_action");
+  if ( action >= 0 ) m_action = action;
+
+  string directory = q.getBodyAsString("directory");
+  if ( directory.length() > 0 ) m_directory = directory;
+
+  int del_after_days = q.getBodyAsInt("del_recs_after_days");
+  if ( del_after_days > 0 ) m_delAfterDays = del_after_days;
+ 
+  int keep_recs = q.getBodyAsInt("keep_recs");
+  if ( keep_recs > 0 ) m_recordingsKeep = keep_recs;
+
+  int pause_on_recs = q.getBodyAsInt("pause_on_recs");
+  if ( pause_on_recs > 0 ) m_pauseOnNrRecordings = pause_on_recs;
+
+  int switch_min_before = q.getBodyAsInt("switch_min_before");
+  if ( switch_min_before >= 0 ) m_switchMinBefore = switch_min_before;
+ 
+  int marginstart = q.getBodyAsInt("margin_start");
+  int marginstop = q.getBodyAsInt("margin_stop");
+  if (marginstart >= 0) m_marginstart = marginstart;
+  if (marginstop >= 0) m_marginstop = marginstop;
+  
+  m_useVPS = q.getBodyAsBool("use_vps");
+  m_avoidrepeats = q.getBodyAsBool("avoid_repeats");
+  
+  if (m_avoidrepeats) {
+     int repeats = q.getBodyAsInt("allowed_repeats");
+     if (repeats < 0) return "allowed_repeats invalid";
+     m_allowedrepeats = repeats;
+  }
+  
+  m_compareTitle = q.getBodyAsBool("compare_title");
+  
+  int compareSubtitle = q.getBodyAsInt("compare_subtitle");
+  if (compareSubtitle > 0 ) m_compareSubtitle = compareSubtitle;
+  
+  m_compareSummary = q.getBodyAsBool("compare_summary");
+ 
+  int repeatsWithinDays = q.getBodyAsInt("repeats_within_days");
+  if (repeatsWithinDays > 0) m_repeatsWithinDays = repeatsWithinDays;
+
+  //int m_blacklistmode
+  //std::vecotr< std::string > m_blacklist_IDs;
+  //m_blacklistmode: 0=no, 1=Selection, 2=all
+  //to be implemented, requires array-support in QueryHandler for xml/html and json -> html param-parser has to be impelemented???
+  //and blacklist ids should be added to the webservice output 
+  int blacklistmode = q.getBodyAsInt("blackliste_mode");
+  if (blacklistmode > 0) return "blacklist currently not implemented";
+
+  int compareCategories = q.getBodyAsInt("compare_categories");
+  if (compareCategories >= 0) { m_catvaluesAvoidRepeat = compareCategories; }
+
+  int del_mode = q.getBodyAsInt("del_mode");
+  if (del_mode >= 0) m_delMode = del_mode;
+  
+  int delAfterCountRecs = q.getBodyAsInt("del_after_count_recs");
+  if (delAfterCountRecs >= 0) m_delAfterCountRecs = delAfterCountRecs;
+
+  int delAfterDaysOfFirstRec = q.getBodyAsInt("del_after_days_of_first_rec");
+  if (delAfterDaysOfFirstRec >= 0) m_delAfterDaysOfFirstRec = delAfterDaysOfFirstRec;
+
+  return ""; 
+} 
+
+istream& operator>>( istream& is, tChannelID& ret )
+{
+  string line;
+  if (!getline( is, line ) ) {
+    if (0 == is.gcount()) {
+      is.clear(is.rdstate() & ~ios::failbit);
+      return is;
+    }
+    if (!is.eof()) {
+      is.setstate( ios::badbit );
+      return is;
+    }
+  }
+
+  if ( !line.empty() && !( ret = tChannelID::FromString( line.c_str() ) ).Valid() )
+    is.setstate( ios::badbit );
+  return is;
+}
+
+template< typename To, typename From >
+To lexical_cast( From const& from )
+{
+   stringstream parser;
+   parser << from;
+   To result;
+   parser >> result;
+   if ( !parser )
+      throw bad_lexical_cast();
+   return result;
+}
+
+static char ServiceInterface[] = "Epgsearch-services-v1.0";
+
+bool operator<( SearchTimer const& left, SearchTimer const& right )
+{
+   string leftlower = left.m_search;
+   string rightlower = right.m_search;
+   transform(leftlower.begin(), leftlower.end(), leftlower.begin(), (int(*)(int)) tolower);
+   transform(rightlower.begin(), rightlower.end(), rightlower.begin(), (int(*)(int)) tolower);
+   return leftlower < rightlower;
+}
+
+SearchTimer::SearchTimer()
+{
+   Init();
+}
+
+void SearchTimer::Init()
+{
+	m_id = -1;
+	m_useTime = false;
+	m_startTime = 0;
+	m_stopTime = 0;
+	m_useChannel = NoChannel;
+	m_useCase = false;
+	m_mode = 0;
+	m_useTitle = true;
+	m_useSubtitle = true;
+	m_useDescription = true;
+	m_useDuration = false;
+	m_minDuration = 0;
+	m_maxDuration = 0;
+	m_useDayOfWeek = false;
+	m_dayOfWeek = 0;
+	m_useEpisode = false;
+	m_priority = 50;
+	m_lifetime = 99;
+	m_fuzzytolerance = 1;
+	m_useInFavorites = false;
+	m_useAsSearchtimer = 0;
+	m_action = 0;
+	m_delAfterDays = 0;
+	m_recordingsKeep = 0;
+	m_pauseOnNrRecordings = 0;
+	m_switchMinBefore = 1;
+	m_useExtEPGInfo = false;
+	m_useVPS = false;
+	m_marginstart = 10; //configuration possibility?
+	m_marginstop = 5; //configuration possibility?
+	m_avoidrepeats = false;
+	m_allowedrepeats = 0;
+	m_compareTitle = false;
+	m_compareSubtitle = 0;
+	m_compareSummary = false;
+	m_repeatsWithinDays = 0;
+	m_blacklistmode = 0;
+	m_menuTemplate = 0;
+	m_delMode = 0;
+	m_delAfterCountRecs = 0;
+	m_delAfterDaysOfFirstRec = 0;
+	m_useAsSearchTimerFrom = 0;
+	m_useAsSearchTimerTil = 0;	
+	m_catvaluesAvoidRepeat = 0;
+	m_ignoreMissingEPGCats = false;
+}
+
+SearchTimer::SearchTimer( string const& data )
+{
+   Init();
+   vector< string > parts = StringExtension::split( data, ":" );
+   try {
+      vector< string >::const_iterator part = parts.begin();
+      for ( int i = 0; part != parts.end(); ++i, ++part ) {
+			switch ( i ) {
+			case  0: m_id = lexical_cast< int >( *part ); break;
+			case  1: m_search = StringExtension::replace( StringExtension::replace( *part, "|", ":" ), "!^pipe^!", "|" ); break;
+			case  2: m_useTime = lexical_cast< bool >( *part ); break;
+			case  3: if ( m_useTime ) m_startTime = lexical_cast< int >( *part ); break;
+			case  4: if ( m_useTime ) m_stopTime = lexical_cast< int >( *part ); break;
+			case  5: m_useChannel = lexical_cast< int >( *part ); break;
+			case  6: ParseChannel( *part ); break;
+			case  7: m_useCase = lexical_cast< int >( *part ); break;
+			case  8: m_mode = lexical_cast< int >( *part ); break;
+			case  9: m_useTitle = lexical_cast< bool >( *part ); break;
+			case 10: m_useSubtitle = lexical_cast< bool >( *part ); break;
+			case 11: m_useDescription = lexical_cast< bool >( *part ); break;
+			case 12: m_useDuration = lexical_cast< bool >( *part ); break;
+			case 13: if ( m_useDuration ) m_minDuration = lexical_cast< int >( *part ); break;
+			case 14: if ( m_useDuration ) m_maxDuration = lexical_cast< int >( *part ); break;
+			case 15: m_useAsSearchtimer = lexical_cast< int >( *part ); break;
+			case 16: m_useDayOfWeek = lexical_cast< bool >( *part ); break;
+			case 17: m_dayOfWeek = lexical_cast< int >( *part ); break;
+			case 18: m_useEpisode = lexical_cast< bool >( *part ); break;
+			case 19: m_directory = StringExtension::replace( StringExtension::replace( *part, "|", ":" ), "!^pipe^!", "|" ); break;
+			case 20: m_priority = lexical_cast< int >( *part ); break;
+			case 21: m_lifetime = lexical_cast< int >( *part ); break;
+			case 22: m_marginstart = lexical_cast< int >( *part ); break;
+			case 23: m_marginstop = lexical_cast< int >( *part ); break;
+			case 24: m_useVPS = lexical_cast< bool >( *part ); break;
+			case 25: m_action = lexical_cast< int >( *part ); break;
+			case 26: m_useExtEPGInfo = lexical_cast< bool >( *part ); break;
+			case 27: ParseExtEPGInfo( *part ); break;
+			case 28: m_avoidrepeats = lexical_cast< bool >( *part ); break;
+			case 29: m_allowedrepeats = lexical_cast< int >( *part ); break;
+			case 30: m_compareTitle = lexical_cast< bool >( *part ); break;
+			case 31: m_compareSubtitle = lexical_cast< int >( *part ); break;
+			case 32: m_compareSummary = lexical_cast< bool >( *part ); break;
+			case 33: m_catvaluesAvoidRepeat = lexical_cast< long >( *part ); break;
+			case 34: m_repeatsWithinDays = lexical_cast< int >( *part ); break;
+			case 35: m_delAfterDays = lexical_cast< int >( *part ); break;
+			case 36: m_recordingsKeep = lexical_cast< int >( *part ); break;
+			case 37: m_switchMinBefore = lexical_cast< int >( *part ); break;
+			case 38: m_pauseOnNrRecordings = lexical_cast< int >( *part ); break;
+			case 39: m_blacklistmode = lexical_cast< int >( *part ); break;
+			case 40: ParseBlacklist( *part ); break;
+			case 41: m_fuzzytolerance = lexical_cast< int >( *part ); break;
+			case 42: m_useInFavorites = lexical_cast< bool >( *part ); break;
+			case 43: m_menuTemplate = lexical_cast< int >( *part ); break;
+			case 44: m_delMode = lexical_cast< int >( *part ); break;
+			case 45: m_delAfterCountRecs = lexical_cast< int >( *part ); break;
+			case 46: m_delAfterDaysOfFirstRec = lexical_cast< int >( *part ); break;
+			case 47: m_useAsSearchTimerFrom = lexical_cast< time_t >( *part ); break;
+			case 48: m_useAsSearchTimerTil = lexical_cast< time_t >( *part ); break;
+			case 49: m_ignoreMissingEPGCats = lexical_cast< bool >( *part ); break;
+			}
+		}
+	} catch ( bad_lexical_cast const& ex ) {
+	}
+}
+
+string SearchTimer::ToText()
+{
+   string tmp_Start;
+   string tmp_Stop;
+   string tmp_minDuration;
+   string tmp_maxDuration;
+   string tmp_chanSel;
+   string tmp_search;
+   string tmp_directory;
+   string tmp_catvalues;
+   string tmp_blacklists;
+
+   tmp_search = StringExtension::replace(StringExtension::replace(m_search, "|", "!^pipe^!"), ":", "|");
+   tmp_directory = StringExtension::replace(StringExtension::replace(m_directory, "|", "!^pipe^!"), ":", "|");
+
+   if (m_useTime)
+   {
+      ostringstream os;
+      os << setw(4) << setfill('0') << m_startTime;
+      tmp_Start = os.str();
+      os.str("");
+      os << setw(4) << setfill('0') << m_stopTime;
+      tmp_Stop = os.str();
+   }
+   if (m_useDuration)
+   {
+      ostringstream os;
+      os << setw(4) << setfill('0') << m_minDuration;
+      tmp_minDuration = os.str();
+      os.str("");
+      os << setw(4) << setfill('0') << m_maxDuration;
+      tmp_maxDuration = os.str();
+   }
+
+   if (m_useChannel==1)
+   {
+      cChannel const* channelMin = Channels.GetByChannelID( m_channelMin );
+      cChannel const* channelMax = Channels.GetByChannelID( m_channelMax );
+
+      if (channelMax && channelMin->Number() < channelMax->Number())
+         tmp_chanSel = *m_channelMin.ToString() + string("|") + *m_channelMax.ToString();
+      else
+         tmp_chanSel = *m_channelMin.ToString();
+   }
+   if (m_useChannel==2)
+      tmp_chanSel = m_channels;
+
+   if (m_useExtEPGInfo)
+   {
+      for(unsigned int i=0; i<m_ExtEPGInfo.size(); i++)
+         tmp_catvalues += (tmp_catvalues != ""?"|":"") + 
+            StringExtension::replace(StringExtension::replace(m_ExtEPGInfo[i], ":", "!^colon^!"), "|", "!^pipe^!");
+   }
+
+   if (m_blacklistmode == 1)
+   {
+      for(unsigned int i=0; i<m_blacklistIDs.size(); i++)
+         tmp_blacklists += (tmp_blacklists != ""?"|":"") +  m_blacklistIDs[i];
+   }
+
+   ostringstream os;
+   os << m_id << ":"
+      << tmp_search << ":"
+      << (m_useTime?1:0) << ":"
+      << tmp_Start << ":"
+      << tmp_Stop << ":"
+      << m_useChannel << ":"
+      << ((m_useChannel>0 && m_useChannel<3)?tmp_chanSel:"0") << ":"
+      << (m_useCase?1:0) << ":"
+      << m_mode << ":"
+      << (m_useTitle?1:0) << ":"
+      << (m_useSubtitle?1:0) << ":"
+      << (m_useDescription?1:0) << ":"
+      << (m_useDuration?1:0) << ":"
+      << tmp_minDuration << ":"
+      << tmp_maxDuration << ":"
+      << m_useAsSearchtimer << ":"
+      << (m_useDayOfWeek?1:0) << ":"
+      << m_dayOfWeek << ":"
+      << (m_useEpisode?1:0) << ":"
+      << tmp_directory << ":"
+      << m_priority << ":"
+      << m_lifetime << ":"
+      << m_marginstart << ":"
+      << m_marginstop << ":"
+      << (m_useVPS?1:0) << ":"
+      << m_action << ":"
+      << (m_useExtEPGInfo?1:0) << ":"
+      << tmp_catvalues << ":"
+      << (m_avoidrepeats?1:0) << ":"
+      << m_allowedrepeats << ":"
+      << (m_compareTitle?1:0) << ":"
+      << m_compareSubtitle << ":"
+      << (m_compareSummary?1:0) << ":"
+      << m_catvaluesAvoidRepeat << ":"
+      << m_repeatsWithinDays << ":"
+      << m_delAfterDays << ":"
+      << m_recordingsKeep << ":"
+      <<  m_switchMinBefore << ":"
+      << m_pauseOnNrRecordings << ":"
+      << m_blacklistmode << ":"
+      << tmp_blacklists << ":"
+      << m_fuzzytolerance << ":"
+      << (m_useInFavorites?1:0) << ":"
+      << m_menuTemplate << ":"
+      << m_delMode << ":"
+      << m_delAfterCountRecs << ":"
+      << m_delAfterDaysOfFirstRec << ":"
+      << (long) m_useAsSearchTimerFrom << ":"
+      << (long) m_useAsSearchTimerTil << ":"
+      << m_ignoreMissingEPGCats;
+
+   return os.str();
+}
+
+void SearchTimer::ParseChannel( string const& data )
+{
+	switch ( m_useChannel ) {
+	case NoChannel: m_channels = tr("All"); break;
+	case Interval: ParseChannelIDs( data ); break;
+	case Group: m_channels = data; break;
+	case FTAOnly: m_channels = tr("FTA"); break;
+	}
+}
+
+void SearchTimer::ParseChannelIDs( string const& data )
+{
+	vector< string > parts = StringExtension::split( data, "|" );
+	m_channelMin = lexical_cast< tChannelID >( parts[ 0 ] );
+
+	cChannel const* channel = Channels.GetByChannelID( m_channelMin );
+	if ( channel != 0 )
+		m_channels = channel->Name();
+
+	if ( parts.size() < 2 )
+		return;
+
+	m_channelMax = lexical_cast< tChannelID >( parts[ 1 ] );
+
+	channel = Channels.GetByChannelID( m_channelMax );
+	if ( channel != 0 )
+		m_channels += string( " - " ) + channel->Name();
+}
+
+void SearchTimer::ParseExtEPGInfo( string const& data )
+{
+   m_ExtEPGInfo = StringExtension::split( data, "|" );
+}
+
+void SearchTimer::ParseBlacklist( string const& data )
+{
+   m_blacklistIDs = StringExtension::split( data, "|" );
+}
+
+SearchTimers::SearchTimers()
+{
+	Reload();
+}
+
+bool SearchTimers::Reload()
+{
+	Epgsearch_services_v1_0 service;
+        cPluginManager::CallFirstService(ServiceInterface, &service);
+	ReadLock channelsLock( Channels, 0 );
+	list< string > timers = service.handler->SearchTimerList();
+	m_timers.assign( timers.begin(), timers.end() );
+	m_timers.sort();
+	return true;
+}
+
+bool SearchTimers::Save(SearchTimer* searchtimer)
+{
+	Epgsearch_services_v1_0 service;
+	cPluginManager::CallFirstService(ServiceInterface, &service);
+
+	if (!searchtimer) return false;
+	ReadLock channelsLock( Channels, 0 );
+	if (searchtimer->Id() >= 0)
+		return service.handler->ModSearchTimer(searchtimer->ToText());
+	else
+	{
+		searchtimer->SetId(0);
+		int id = service.handler->AddSearchTimer(searchtimer->ToText());
+		if (id >= 0)
+			searchtimer->SetId(id);
+		return (id >= 0);
+	}
+}
+
+SearchTimer* SearchTimers::GetByTimerId( string const& id )
+{
+   for (SearchTimers::iterator timer = m_timers.begin(); timer != m_timers.end(); ++timer) 
+      if (timer->Id() == lexical_cast< int >(id)) 
+         return &*timer;
+   return NULL;
+      
+}
+
+bool SearchTimers::ToggleActive(string const& id)
+{
+	SearchTimer* search = GetByTimerId( id );
+	if (!search) return false;
+	search->SetUseAsSearchTimer(search->UseAsSearchTimer()==1?0:1);
+	return Save(search);
+}
+
+bool SearchTimers::Delete(string const& id)
+{
+	SearchTimer* search = GetByTimerId( id );
+	if (!search) return false;
+
+	Epgsearch_services_v1_0 service;
+	cPluginManager::CallFirstService(ServiceInterface, &service);
+
+	if (service.handler->DelSearchTimer(lexical_cast< int >( id )))
+		return Reload();
+	return false;
+}
+
+void SearchTimers::TriggerUpdate()
+{
+	Epgsearch_updatesearchtimers_v1_0 service;
+	service.showMessage = true;
+	cPluginManager::CallFirstService("Epgsearch-updatesearchtimers-v1.0", &service);
+}
+
+bool SearchTimer::BlacklistSelected(int id) const
+{ 
+   for(unsigned int i=0; i<m_blacklistIDs.size(); i++) 
+      if (StringExtension::strtoi(m_blacklistIDs[i]) == id) return true; 
+   return false; 
+}
+
+ExtEPGInfo::ExtEPGInfo( string const& data )
+{
+   m_id = -1;
+   m_searchmode = 0;
+
+   vector< string > parts = StringExtension::split( data, "|" );
+   try {
+      vector< string >::const_iterator part = parts.begin();
+      for ( int i = 0; part != parts.end(); ++i, ++part ) {
+         switch ( i ) {
+			case  0: m_id = lexical_cast< int >( *part ); break;
+			case  1: m_name = *part; break;
+			case  2: m_menuname = *part; break;
+			case  3: ParseValues( *part ); break;
+			case  4: m_searchmode = lexical_cast< int >( *part ); break;
+         }
+      }
+   } catch ( bad_lexical_cast const& ex ) {
+   }
+}
+
+void ExtEPGInfo::ParseValues( string const& data )
+{
+   m_values = StringExtension::split( data, "," );
+}
+
+bool ExtEPGInfo::Selected(unsigned int index, string const& values)
+{
+   if (index >= m_values.size()) return false;
+   string extepgvalue = StringExtension::trim(m_values[index]);
+
+   vector< string > parts;
+   parts = StringExtension::split( values, "," );
+   for(unsigned int i=0; i<parts.size(); i++) if (StringExtension::trim(parts[i]) == extepgvalue) return true; 
+   parts = StringExtension::split( values, ";" );
+   for(unsigned int i=0; i<parts.size(); i++) if (StringExtension::trim(parts[i]) == extepgvalue) return true; 
+   parts = StringExtension::split( values, "|" );
+   for(unsigned int i=0; i<parts.size(); i++) if (StringExtension::trim(parts[i]) == extepgvalue) return true; 
+   parts = StringExtension::split( values, "~" );
+   for(unsigned int i=0; i<parts.size(); i++) if (StringExtension::trim(parts[i]) == extepgvalue) return true; 
+   return false;
+}
+
+ExtEPGInfos::ExtEPGInfos()
+{
+	Epgsearch_services_v1_0 service;
+	cPluginManager::CallFirstService(ServiceInterface, &service);
+
+	list< string > infos = service.handler->ExtEPGInfoList();
+	m_infos.assign( infos.begin(), infos.end() );
+}
+
+ChannelGroup::ChannelGroup( string const& data )
+{
+   vector< string > parts = StringExtension::split( data, "|" );
+   try {
+      vector< string >::const_iterator part = parts.begin();
+      for ( int i = 0; part != parts.end(); ++i, ++part ) {
+         switch ( i ) {
+			case  0: m_name = *part; break;
+         }
+      }
+   } catch ( bad_lexical_cast const& ex ) {
+   }
+}
+
+ChannelGroups::ChannelGroups()
+{
+   Epgsearch_services_v1_0 service;
+   cPluginManager::CallFirstService(ServiceInterface, &service);
+
+   list< string > list = service.handler->ChanGrpList();
+   m_list.assign( list.begin(), list.end() );
+}
+
+Blacklist::Blacklist( string const& data )
+{
+   vector< string > parts = StringExtension::split( data, ":" );
+   try {
+      vector< string >::const_iterator part = parts.begin();
+      for ( int i = 0; part != parts.end(); ++i, ++part ) {
+			switch ( i ) {
+			case  0: m_id = lexical_cast< int >( *part ); break;
+			case  1: m_search = StringExtension::replace( StringExtension::replace( *part, "|", ":" ), "!^pipe^!", "|" ); break;
+			}
+		}
+	} catch ( bad_lexical_cast const& ex ) {
+	}
+}
+
+Blacklists::Blacklists()
+{
+   Epgsearch_services_v1_0 service;
+   cPluginManager::CallFirstService(ServiceInterface, &service);
+   list< string > list = service.handler->BlackList();
+   m_list.assign( list.begin(), list.end() );
+   m_list.sort();
+}
+
+SearchResult::SearchResult( string const& data )
+{
+	vector< string > parts = StringExtension::split( data, ":" );
+	try {
+		vector< string >::const_iterator part = parts.begin();
+		for ( int i = 0; part != parts.end(); ++i, ++part ) {
+			switch ( i ) {
+			case  0: m_searchId = lexical_cast< int >( *part ); break;
+			case  1: m_eventId = lexical_cast< u_int32_t >( *part ); break;
+			case  2: m_title = StringExtension::replace( *part, "|", ":" ); break;
+			case  3: m_shorttext = StringExtension::replace( *part, "|", ":" ); break;
+			case  4: m_description = StringExtension::replace( *part, "|", ":" ); break;
+			case  5: m_starttime = lexical_cast< unsigned long >( *part ); break;
+			case  6: m_stoptime = lexical_cast< unsigned long >( *part ); break;
+			case  7: m_channel = lexical_cast< tChannelID >( *part ); break;
+			case  8: m_timerstart = lexical_cast< unsigned long >( *part ); break;
+			case  9: m_timerstop = lexical_cast< unsigned long >( *part ); break;
+			case 10: m_file = *part; break;
+			case 11: m_timerMode = lexical_cast< int >( *part ); break;
+			}
+		}
+	} catch ( bad_lexical_cast const& ex ) {
+	}
+}
+
+const cEvent* SearchResult::GetEvent()
+{
+	cSchedulesLock schedulesLock;
+	const cSchedules* Schedules = cSchedules::Schedules(schedulesLock);
+	if (!Schedules) return NULL;
+	const cChannel *Channel = GetChannel();
+	if (!Channel) return NULL;
+	const cSchedule *Schedule = Schedules->GetSchedule(Channel);
+	if (!Schedule) return NULL;
+	return Schedule->GetEvent(m_eventId);	
+}
+
+set<string> SearchResults::querySet;
+
+void SearchResults::GetByID(int id)
+{
+   Epgsearch_services_v1_0 service;
+   cPluginManager::CallFirstService(ServiceInterface, &service);
+
+   list< string > list = service.handler->QuerySearchTimer(id);
+   m_list.assign( list.begin(), list.end() );
+   m_list.sort();
+}
+
+void SearchResults::GetByQuery(string const& query)
+{
+   Epgsearch_services_v1_0 service;
+   cPluginManager::CallFirstService(ServiceInterface, &service); 
+
+   list< string > list = service.handler->QuerySearch(query);
+   m_list.assign( list.begin(), list.end() );
+   m_list.sort();
+}
+
+RecordingDirs::RecordingDirs(bool shortList)
+{
+  if (shortList)
+    {
+      Epgsearch_services_v1_2 service;
+      cPluginManager::CallFirstService(ServiceInterface, &service);
+      
+      m_set = service.handler->ShortDirectoryList();
+    }
+  else
+    {	
+      Epgsearch_services_v1_0 service;
+      cPluginManager::CallFirstService(ServiceInterface, &service);
+      
+      m_set = service.handler->DirectoryList();
+    }
+}
+
+string EPGSearchSetupValues::ReadValue(const string& entry)
+{
+   Epgsearch_services_v1_0 service;
+   cPluginManager::CallFirstService(ServiceInterface, &service);
+   return service.handler->ReadSetupValue(entry);
+}
+
+bool EPGSearchSetupValues::WriteValue(const string& entry, const string& value)
+{
+   Epgsearch_services_v1_0 service;
+   cPluginManager::CallFirstService(ServiceInterface, &service);
+
+   return service.handler->WriteSetupValue(entry, value);
+}
+
+string EPGSearchExpr::EvaluateExpr(const string& expr, const cEvent* event)
+{
+   Epgsearch_services_v1_2 service;
+   cPluginManager::CallFirstService(ServiceInterface, &service);
+   return service.handler->Evaluate(expr, event);
+}
+
+
+} // namespace vdrlive
+
diff --git a/epgsearch.h b/epgsearch.h
new file mode 100644
index 0000000..f0f4cda
--- /dev/null
+++ b/epgsearch.h
@@ -0,0 +1,486 @@
+#include <vector>
+#include <list>
+#include <set>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <iomanip>
+#include <ostream>
+#include <sstream>
+#include <istream>
+#include <stdexcept>
+
+#include <cxxtools/serializationinfo.h>
+#include <vdr/channels.h>
+#include <vdr/epg.h>
+#include <vdr/plugin.h>
+
+#include "epgsearch/services.h"
+#include "tools.h"
+
+#ifndef VDR_LIVE_EPGSEARCH_H
+#define VDR_LIVE_EPGSEARCH_H
+
+
+
+
+namespace vdrlive {
+
+
+// --- VDR-PLUGIN-LIVE-TOOLS ----------------------------
+
+std::istream& operator>>( std::istream& is, tChannelID& ret );
+
+inline
+std::ostream& operator<<( std::ostream& os, tChannelID const& id )
+{
+        return os << *id.ToString();
+}
+
+class ReadLock
+{
+   public:
+      ReadLock( cRwLock& lock, int timeout = 100 ): m_lock( lock ), m_locked( false ) { if ( m_lock.Lock( false, timeout ) ) m_locked = true; }
+      ~ReadLock() { if ( m_locked ) m_lock.Unlock(); }
+
+      operator bool() { return m_locked; }
+      bool operator!() { return !m_locked; }
+
+      private:
+         ReadLock( ReadLock const& );
+
+         cRwLock& m_lock;
+         bool m_locked;
+};
+
+struct bad_lexical_cast: std::runtime_error
+{
+   bad_lexical_cast(): std::runtime_error( "bad lexical cast" ) {}
+};
+
+
+template< typename To, typename From >
+To lexical_cast( From const& from );
+
+// --- Tools End ----------------------------------------
+
+class SearchTimer;
+
+bool operator<( SearchTimer const& left, SearchTimer const& right );
+
+class SearchTimer
+{
+public:
+	enum eUseChannel
+	{
+		NoChannel = 0,
+		Interval = 1,
+		Group = 2,
+		FTAOnly = 3
+	};
+	
+	SearchTimer();
+	SearchTimer( std::string const& data );
+	void Init();
+        std::string LoadFromQuery(QueryHandler& q);
+	std::string ToText();
+        std::string ToXml();
+        std::string ToHtml();
+	friend bool operator<( SearchTimer const& left, SearchTimer const& right );
+
+	int Id() const { return m_id; }
+	void SetId(int id) { m_id = id; }
+	std::string const& Search() const { return m_search; }
+	void SetSearch(std::string const& search) { m_search = search; }
+	int SearchMode() { return m_mode; }
+	void SetSearchMode(int mode) { m_mode = mode; }
+	int Tolerance() const { return m_fuzzytolerance; }
+	void SetTolerance(int tolerance) { m_fuzzytolerance = tolerance; }
+	bool MatchCase() const { return m_useCase; }
+	void SetMatchCase(bool useCase) { m_useCase = useCase; }
+	bool UseTime() const { return m_useTime; }
+	void SetUseTime(bool useTime) { m_useTime = useTime; }
+	bool UseTitle() const { return m_useTitle; }
+	void SetUseTitle(bool useTitle) { m_useTitle = useTitle; }
+	bool UseSubtitle() const { return m_useSubtitle; }
+	void SetUseSubtitle(bool useSubtitle) { m_useSubtitle = useSubtitle; }
+	bool UseDescription() const { return m_useDescription; }
+	void SetUseDescription(bool useDescription) { m_useDescription = useDescription; }
+	int StartTime() const { return m_startTime; }
+	std::string StartTimeFormatted();
+	void SetStartTime(int startTime) { m_startTime = startTime; }
+	int StopTime() const { return m_stopTime; }
+	std::string StopTimeFormatted();
+	void SetStopTime(int stopTime) { m_stopTime = stopTime; }
+	eUseChannel UseChannel() const { return static_cast< eUseChannel >( m_useChannel ); }
+	void SetUseChannel(eUseChannel useChannel) { m_useChannel = useChannel; }
+	tChannelID ChannelMin() const { return m_channelMin; }
+	void SetChannelMin(tChannelID channelMin) { m_channelMin = channelMin; }
+	tChannelID ChannelMax() const { return m_channelMax; }
+	void SetChannelMax(tChannelID channelMax) { m_channelMax = channelMax; }
+	std::string ChannelText() const { return m_channels; }
+	void SetChannelText(std::string channels) { m_channels = channels; }
+	int UseAsSearchTimer() const { return m_useAsSearchtimer; }
+	void SetUseAsSearchTimer(int useAsSearchtimer) { m_useAsSearchtimer = useAsSearchtimer; }
+	bool UseDuration() const { return m_useDuration; }
+	void SetUseDuration(bool useDuration) { m_useDuration = useDuration; }
+	int MinDuration() const { return m_minDuration; }
+	void SetMinDuration(int minDuration) { m_minDuration = minDuration; }
+	int MaxDuration() const { return m_maxDuration; }
+	void SetMaxDuration(int maxDuration) { m_maxDuration = maxDuration; }
+	bool UseDayOfWeek() const { return m_useDayOfWeek; }
+	void SetUseDayOfWeek(bool useDayOfWeek) { m_useDayOfWeek = useDayOfWeek; }
+	int DayOfWeek() const { return m_dayOfWeek; }
+	void SetDayOfWeek(int dayOfWeek) { m_dayOfWeek = dayOfWeek; }
+	bool UseInFavorites() const { return m_useInFavorites; }
+	void SetUseInFavorites(bool useInFavorites) { m_useInFavorites = useInFavorites; }
+	int SearchTimerAction() const { return m_action; }
+	void SetSearchTimerAction(int action) { m_action = action; }
+	bool UseSeriesRecording() const { return  m_useEpisode; }
+	void SetUseSeriesRecording(bool useEpisode) { m_useEpisode = useEpisode; }
+	std::string const& Directory() const { return m_directory; }
+	void SetDirectory(std::string const& directory) { m_directory = directory; }
+	int DelRecsAfterDays() const { return m_delAfterDays; }
+	void SetDelRecsAfterDays(int delAfterDays) { m_delAfterDays = delAfterDays; }
+	int KeepRecs() const { return m_recordingsKeep; }
+	void SetKeepRecs(int recordingsKeep) { m_recordingsKeep = recordingsKeep; }
+	int PauseOnRecs() const {return m_pauseOnNrRecordings; }
+	void SetPauseOnRecs(int pauseOnNrRecordings) { m_pauseOnNrRecordings = pauseOnNrRecordings; }
+	int BlacklistMode() const {return m_blacklistmode; }
+	void SetBlacklistMode(int blacklistmode) { m_blacklistmode = blacklistmode; }
+	bool BlacklistSelected(int id) const;
+	void ParseBlacklist( std::string const& data );
+	int SwitchMinBefore() const { return m_switchMinBefore; }
+	void SetSwitchMinBefore(int switchMinBefore) { m_switchMinBefore = switchMinBefore; }
+	bool UseExtEPGInfo() const { return m_useExtEPGInfo; }
+	void SetUseExtEPGInfo(bool useExtEPGInfo) { m_useExtEPGInfo = useExtEPGInfo; }
+	std::vector< std::string > ExtEPGInfo() const { return m_ExtEPGInfo; } 
+	void SetExtEPGInfo(std::vector< std::string > ExtEPGInfo) { m_ExtEPGInfo = ExtEPGInfo; } 
+	bool AvoidRepeats() const { return m_avoidrepeats; }
+	void SetAvoidRepeats(bool avoidrepeats) { m_avoidrepeats = avoidrepeats; }
+	int AllowedRepeats() const { return m_allowedrepeats; }
+	void SetAllowedRepeats(int allowedrepeats) { m_allowedrepeats = allowedrepeats; }
+	int RepeatsWithinDays() const { return m_repeatsWithinDays; }
+	void SetRepeatsWithinDays(int repeatsWithinDays) { m_repeatsWithinDays = repeatsWithinDays; }
+	bool CompareTitle() const { return m_compareTitle; }
+	void SetCompareTitle(bool compareTitle) { m_compareTitle = compareTitle; }
+	int CompareSubtitle() const { return m_compareSubtitle; }
+	void SetCompareSubtitle(int compareSubtitle) { m_compareSubtitle = compareSubtitle; }
+	bool CompareSummary() const { return m_compareSummary; }
+	void SetCompareSummary(bool compareSummary) { m_compareSummary = compareSummary; }
+	unsigned long CompareCategories() const { return m_catvaluesAvoidRepeat; }
+	void SetCompareCategories(unsigned long compareCategories) { m_catvaluesAvoidRepeat = compareCategories; }
+	int Priority() const { return m_priority; }
+	void SetPriority(int priority) { m_priority = priority; }
+	int Lifetime() const { return m_lifetime; }
+	void SetLifetime(int lifetime) { m_lifetime = lifetime; }
+	int MarginStart() const { return m_marginstart; }
+	void SetMarginStart(int marginstart) { m_marginstart = marginstart; }
+	int MarginStop() const { return m_marginstop; }
+	void SetMarginStop(int marginstop) { m_marginstop = marginstop; }
+	bool UseVPS() const { return m_useVPS; }
+	void SetUseVPS(bool useVPS) { m_useVPS = useVPS; }
+	int DelMode() const { return m_delMode; }
+	void SetDelMode(int delMode) { m_delMode = delMode; }
+	int DelAfterCountRecs() const { return m_delAfterCountRecs; }
+	void SetDelAfterCountRecs(int delAfterCountRecs) { m_delAfterCountRecs = delAfterCountRecs; }
+	int DelAfterDaysOfFirstRec() const { return m_delAfterDaysOfFirstRec; }
+	void SetDelAfterDaysOfFirstRec(int delAfterDaysOfFirstRec) { m_delAfterDaysOfFirstRec = delAfterDaysOfFirstRec; }
+
+private:
+	int m_id;
+	std::string m_search;
+	bool m_useTime;
+	int m_startTime;
+	int m_stopTime;
+	int m_useChannel;
+	tChannelID m_channelMin;
+	tChannelID m_channelMax;
+	std::string m_channels;
+	bool m_useCase;
+	int m_mode;
+	bool m_useTitle;
+	bool m_useSubtitle;
+	bool m_useDescription;
+	bool m_useDuration;
+	int m_minDuration;
+	int m_maxDuration;
+	bool m_useDayOfWeek;
+	int m_dayOfWeek;
+	bool m_useEpisode;
+	int m_priority;
+	int m_lifetime;
+	int m_fuzzytolerance;
+	bool m_useInFavorites;
+	int m_useAsSearchtimer;
+	int m_action;
+	std::string m_directory;
+	int m_delAfterDays;
+	int m_recordingsKeep;
+	int m_pauseOnNrRecordings;
+	int m_switchMinBefore;
+	int m_marginstart;
+	int m_marginstop;
+	bool m_useVPS;
+	bool m_useExtEPGInfo;
+	std::vector< std::string > m_ExtEPGInfo;
+	bool m_avoidrepeats;
+	int m_allowedrepeats;
+	bool m_compareTitle;
+	int m_compareSubtitle;
+	bool m_compareSummary;
+	int m_repeatsWithinDays;
+	int m_blacklistmode;
+	std::vector< std::string > m_blacklistIDs;
+	int m_menuTemplate;
+	unsigned long m_catvaluesAvoidRepeat;
+	int m_delMode;
+	int m_delAfterCountRecs;
+	int m_delAfterDaysOfFirstRec;
+	time_t m_useAsSearchTimerFrom;
+	time_t m_useAsSearchTimerTil;
+	bool m_ignoreMissingEPGCats;
+
+	void ParseChannel( std::string const& data );
+	void ParseChannelIDs( std::string const& data );
+	void ParseExtEPGInfo( std::string const& data );
+};
+
+class ExtEPGInfo
+{
+public:
+	ExtEPGInfo(std::string const& data );
+	int Id() const { return m_id; }
+	std::string Name() const { return m_menuname; }
+	std::vector< std::string > Values() const { return m_values; }
+	bool Selected(unsigned int index, std::string const& values);
+private:
+	int m_id;
+	std::string m_name;
+	std::string m_menuname;
+	std::vector< std::string > m_values;
+	int m_searchmode;
+
+	void ParseValues( std::string const& data );
+};
+
+class ExtEPGInfos
+{
+public:
+	typedef std::list< ExtEPGInfo > ExtEPGInfoList;
+	typedef ExtEPGInfoList::size_type size_type;
+	typedef ExtEPGInfoList::iterator iterator;
+	typedef ExtEPGInfoList::const_iterator const_iterator;
+
+	ExtEPGInfos();
+
+	size_type size() const { return m_infos.size(); }
+
+	iterator begin() { return m_infos.begin(); }
+	const_iterator begin() const { return m_infos.begin(); }
+	iterator end() { return m_infos.end(); }
+	const_iterator end() const { return m_infos.end(); }
+private:
+	ExtEPGInfoList m_infos;
+};
+
+class ChannelGroup
+{
+public:
+	ChannelGroup(std::string const& data );
+	std::string Name() { return m_name; }
+private:
+	std::string m_name;
+};
+
+class ChannelGroups
+{
+public:
+	typedef std::list< ChannelGroup > ChannelGroupList;
+	typedef ChannelGroupList::size_type size_type;
+	typedef ChannelGroupList::iterator iterator;
+	typedef ChannelGroupList::const_iterator const_iterator;
+
+	ChannelGroups();
+
+	size_type size() const { return m_list.size(); }
+
+	iterator begin() { return m_list.begin(); }
+	const_iterator begin() const { return m_list.begin(); }
+	iterator end() { return m_list.end(); }
+	const_iterator end() const { return m_list.end(); }
+private:
+	ChannelGroupList m_list;
+};
+
+class SearchTimers
+{
+public:
+	typedef std::list< SearchTimer > TimerList;
+	typedef TimerList::size_type size_type;
+	typedef TimerList::iterator iterator;
+	typedef TimerList::const_iterator const_iterator;
+
+	SearchTimers();
+	bool Save(SearchTimer* searchtimer);
+	bool Reload();
+
+	size_type size() const { return m_timers.size(); }
+
+	iterator begin() { return m_timers.begin(); }
+	const_iterator begin() const { return m_timers.begin(); }
+	iterator end() { return m_timers.end(); }
+	const_iterator end() const { return m_timers.end(); }
+	SearchTimer* GetByTimerId( std::string const& id );
+	bool ToggleActive(std::string const& id);
+	bool Delete(std::string const& id);
+	void TriggerUpdate();
+private:
+	TimerList m_timers;
+};
+
+class Blacklist
+{
+public:
+	Blacklist( std::string const& data );
+
+	std::string const& Search() const { return m_search; }
+	int Id() const { return m_id; }
+	bool operator<( Blacklist const& other ) const { return Search() < other.Search(); }
+
+private:
+	int m_id;
+	std::string m_search;
+};
+
+class Blacklists
+{
+public:
+	typedef std::list< Blacklist > blacklist;
+	typedef blacklist::size_type size_type;
+	typedef blacklist::iterator iterator;
+	typedef blacklist::const_iterator const_iterator;
+
+	Blacklists();
+
+	size_type size() const { return m_list.size(); }
+
+	iterator begin() { return m_list.begin(); }
+	const_iterator begin() const { return m_list.begin(); }
+	iterator end() { return m_list.end(); }
+	const_iterator end() const { return m_list.end(); }
+private:
+	blacklist m_list;
+};
+
+class SearchResult
+{
+public:
+	SearchResult( std::string const& data );
+
+	int SearchId() const { return m_searchId; }
+	tEventID EventId() const { return m_eventId; }
+	std::string const& Title() const { return m_title; }
+	std::string const& ShortText() const { return m_shorttext; }
+	std::string const& Description() const { return m_description; }
+	time_t StartTime() const { return m_starttime; }
+	time_t StopTime() const { return m_stoptime; }
+	tChannelID Channel() const { return m_channel; }
+	time_t TimerStartTime() const { return m_timerstart; }
+	time_t TimerStopTime() const { return m_timerstop; }
+	int TimerMode() const { return m_timerMode; }
+	bool operator<( SearchResult const& other ) const { return m_starttime <  other.m_starttime; }
+	const cEvent* GetEvent();
+	const cChannel* GetChannel() { return Channels.GetByChannelID(m_channel); }
+
+private:
+	int m_searchId;
+	tEventID m_eventId;
+	std::string m_title;
+	std::string m_shorttext;
+	std::string m_description;
+	time_t m_starttime;
+	time_t m_stoptime;
+	tChannelID m_channel;
+	time_t m_timerstart;
+	time_t m_timerstop;
+	std::string m_file;
+	int m_timerMode;
+};
+
+class SearchResults
+{
+	static std::set<std::string> querySet;
+public:
+	typedef std::list< SearchResult > searchresults;
+	typedef searchresults::size_type size_type;
+	typedef searchresults::iterator iterator;
+	typedef searchresults::const_iterator const_iterator;
+
+	SearchResults() {}
+	void GetByID(int id);
+	void GetByQuery(std::string const& query);
+
+	size_type size() const { return m_list.size(); }
+
+	iterator begin() { return m_list.begin(); }
+	const_iterator begin() const { return m_list.begin(); }
+	iterator end() { return m_list.end(); }
+	const_iterator end() const { return m_list.end(); }
+
+	void merge(SearchResults& r) {m_list.merge(r.m_list); m_list.sort();}
+private:
+	searchresults m_list;
+};
+
+class RecordingDirs
+{
+public:
+	typedef std::set< std::string > recordingdirs;
+	typedef recordingdirs::size_type size_type;
+	typedef recordingdirs::iterator iterator;
+	typedef recordingdirs::const_iterator const_iterator;
+	
+	RecordingDirs(bool shortList=false);
+
+	iterator begin() { return m_set.begin(); }
+	const_iterator begin() const { return m_set.begin(); }
+	iterator end() { return m_set.end(); }
+	const_iterator end() const { return m_set.end(); }
+
+private:
+	recordingdirs m_set;
+};
+
+class EPGSearchSetupValues
+{
+public:
+	static std::string ReadValue(const std::string& entry);	
+	static bool WriteValue(const std::string& entry, const std::string& value);	
+};
+
+class EPGSearchExpr
+{
+public:
+  static std::string EvaluateExpr(const std::string& expr, const cEvent* event);
+};
+
+
+
+
+}
+
+ // namespace vdrlive
+
+
+// --- JSON RELATED -------------------------------------
+struct SerSearchTimerContainer
+{
+  vdrlive::SearchTimer* timer;
+};
+
+
+void operator<<= (cxxtools::SerializationInfo& si, SerSearchTimerContainer s);
+
+
+#endif // VDR_LIVE_EPGSEARCH_H
diff --git a/epgsearch/services.h b/epgsearch/services.h
new file mode 100644
index 0000000..35cca9b
--- /dev/null
+++ b/epgsearch/services.h
@@ -0,0 +1,195 @@
+/*
+Copyright (C) 2004-2008 Christian Wieninger
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+
+The author can be reached at cwieninger at gmx.de
+
+The project's page is at http://winni.vdr-developer.org/epgsearch
+*/
+
+#ifndef EPGSEARCHSERVICES_INC
+#define EPGSEARCHSERVICES_INC
+
+#include <string>
+#include <list>
+#include <memory>
+#include <set>
+#include <vdr/osdbase.h>
+
+// Data structure for service "Epgsearch-search-v1.0"
+struct Epgsearch_search_v1_0
+{
+// in
+      char* query;               // search term
+      int mode;                  // search mode (0=phrase, 1=and, 2=or, 3=regular expression)
+      int channelNr;             // channel number to search in (0=any)
+      bool useTitle;             // search in title
+      bool useSubTitle;          // search in subtitle
+      bool useDescription;       // search in description
+// out
+      cOsdMenu* pResultMenu;   // pointer to the menu of results
+};
+
+// Data structure for service "Epgsearch-exttimeredit-v1.0"
+struct Epgsearch_exttimeredit_v1_0
+{
+// in
+      cTimer* timer;             // pointer to the timer to edit
+      bool bNew;                 // flag that indicates, if this is a new timer or an existing one
+      const cEvent* event;             // pointer to the event corresponding to this timer (may be NULL)
+// out
+      cOsdMenu* pTimerMenu;   // pointer to the menu of results
+};
+
+// Data structure for service "Epgsearch-updatesearchtimers-v1.0"
+struct Epgsearch_updatesearchtimers_v1_0
+{
+// in
+      bool showMessage;           // inform via osd when finished?
+};
+
+// Data structure for service "Epgsearch-osdmessage-v1.0"
+struct Epgsearch_osdmessage_v1_0
+{
+// in
+      char* message;             // the message to display
+      eMessageType type;
+};
+
+// Data structure for service "EpgsearchMenu-v1.0"
+struct EpgSearchMenu_v1_0
+{
+// in
+// out
+      cOsdMenu* Menu;   // pointer to the menu
+};
+
+// Data structure for service "Epgsearch-lastconflictinfo-v1.0"
+struct Epgsearch_lastconflictinfo_v1_0
+{
+// in
+// out
+      time_t nextConflict;       // next conflict date, 0 if none
+      int relevantConflicts;     // number of relevant conflicts
+      int totalConflicts;        // total number of conflicts
+};
+
+// Data structure for service "Epgsearch-searchresults-v1.0"
+struct Epgsearch_searchresults_v1_0
+{
+// in
+      char* query;               // search term
+      int mode;                  // search mode (0=phrase, 1=and, 2=or, 3=regular expression)
+      int channelNr;             // channel number to search in (0=any)
+      bool useTitle;             // search in title
+      bool useSubTitle;          // search in subtitle
+      bool useDescription;       // search in description
+// out
+
+      class cServiceSearchResult : public cListObject 
+      {
+        public:
+         const cEvent* event;
+         cServiceSearchResult(const cEvent* Event) : event(Event) {}
+      };
+
+      cList<cServiceSearchResult>* pResultList;   // pointer to the results
+};
+
+// Data structure for service "Epgsearch-switchtimer-v1.0"
+struct Epgsearch_switchtimer_v1_0
+{
+// in
+      const cEvent* event;
+      int mode;                  // mode (0=query existance, 1=add/modify, 2=delete)
+// in/out
+      int switchMinsBefore;
+      int announceOnly;
+// out   		
+      bool success;              // result
+};
+
+// Data structures for service "Epgsearch-services-v1.0"
+class cServiceHandler
+{
+  public:
+   virtual std::list<std::string> SearchTimerList() = 0;
+   // returns a list of search timer entries in the same format as used in epgsearch.conf 
+   virtual int AddSearchTimer(const std::string&) = 0;
+   // adds a new search timer and returns its ID (-1 on error)
+   virtual bool ModSearchTimer(const std::string&) = 0;
+   // edits an existing search timer and returns success
+   virtual bool DelSearchTimer(int) = 0;
+   // deletes search timer with given ID and returns success
+   virtual std::list<std::string> QuerySearchTimer(int) = 0;
+   // returns the search result of the searchtimer with given ID in the same format as used in SVDRP command 'QRYS' (->MANUAL)        
+   virtual std::list<std::string> QuerySearch(std::string) = 0;
+   // returns the search result of the searchtimer with given settings in the same format as used in SVDRP command 'QRYS' (->MANUAL)        
+   virtual std::list<std::string> ExtEPGInfoList() = 0;
+   // returns a list of extended EPG categories in the same format as used in epgsearchcats.conf 
+   virtual std::list<std::string> ChanGrpList() = 0;
+   // returns a list of channel groups maintained by epgsearch
+   virtual std::list<std::string> BlackList() = 0;
+   // returns a list of blacklists in the same format as used in epgsearchblacklists.conf
+   virtual std::set<std::string> DirectoryList() = 0;
+   // List of all recording directories used in recordings, timers, search timers or in epgsearchdirs.conf
+   virtual ~cServiceHandler() {}
+   // Read a setup value
+   virtual std::string ReadSetupValue(const std::string& entry) = 0;
+   // Write a setup value
+   virtual bool WriteSetupValue(const std::string& entry, const std::string& value) = 0;
+};
+
+struct Epgsearch_services_v1_0
+{
+// in/out
+      std::auto_ptr<cServiceHandler> handler;
+};
+
+// Data structures for service "Epgsearch-services-v1.1"
+class cServiceHandler_v1_1 : public cServiceHandler
+{
+  public:
+   // Get timer conflicts
+   virtual std::list<std::string> TimerConflictList(bool relOnly=false) = 0;    
+   // Check if a conflict check is advised
+   virtual bool IsConflictCheckAdvised() = 0;    
+};
+
+struct Epgsearch_services_v1_1
+{
+// in/out
+      std::auto_ptr<cServiceHandler_v1_1> handler;
+};
+
+// Data structures for service "Epgsearch-services-v1.2"
+class cServiceHandler_v1_2 : public cServiceHandler_v1_1
+{
+  public:
+  // List of all recording directories used in recordings, timers (and optionally search timers or in epgsearchdirs.conf)
+  virtual std::set<std::string> ShortDirectoryList() = 0;
+  // Evaluate an expression against an event
+  virtual std::string Evaluate(const std::string& expr, const cEvent* event) = 0;    
+};
+
+struct Epgsearch_services_v1_2
+{
+// in/out
+      std::auto_ptr<cServiceHandler_v1_2> handler;
+};
+
+#endif
diff --git a/events.cpp b/events.cpp
new file mode 100644
index 0000000..0d0cad9
--- /dev/null
+++ b/events.cpp
@@ -0,0 +1,574 @@
+#include "events.h"
+using namespace std;
+
+void EventsResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET, POST");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  QueryHandler::addHeader(reply);
+  if ( (int)request.url().find("/events/image/") == 0 ) {
+     replyImage(out, request, reply);
+  } else if ( (int)request.url().find("/events/search") == 0 ){
+     replySearchResult(out, request, reply);
+  } else {
+     replyEvents(out, request, reply);
+  }
+}
+
+void EventsResponder::replyEvents(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/events", request);
+
+  if ( request.method() != "GET") {
+
+     reply.httpReturn(403, "To retrieve information use the GET method!");
+     return;
+  }
+
+
+  EventList* eventList;
+
+  if ( q.isFormat(".json") ) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     eventList = (EventList*)new JsonEventList(&out);
+  } else if ( q.isFormat(".html") ) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     eventList = (EventList*)new HtmlEventList(&out);
+  } else if ( q.isFormat(".xml") ) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     eventList = (EventList*)new XmlEventList(&out);
+  } else {
+     reply.httpReturn(403, "Resources are not available for the selected format. (Use: .json or .html)");
+     return;
+  }
+
+  string channel_id = q.getParamAsString(0);
+  int timespan = q.getOptionAsInt("timespan");//q.getParamAsInt(1);
+  int from = q.getOptionAsInt("from");//q.getParamAsInt(2);
+
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+  
+  int event_id = q.getParamAsInt(1);//q.getOptionAsInt("eventid");
+
+  string onlyCount = q.getOptionAsString("only_count");
+
+  cChannel* channel = VdrExtension::getChannel(channel_id);
+  if ( channel == NULL ) { 
+     /*reply.addHeader("Content-Type", "application/octet-stream");
+     string error_message = (string)"Could not find channel with id: " + channel_id + (string)"!";
+     reply.httpReturn(404, error_message); 
+     return;*/
+  }
+
+  int channel_limit = q.getOptionAsInt("chevents");
+  if ( channel_limit <= -1 ) channel_limit = 0; // default channel events is 0 -> all
+  
+  int channel_from = q.getOptionAsInt("chfrom");
+  if ( channel_from <= -1 || channel != NULL ) channel_from = 0; // default channel number is 0
+  
+  int channel_to = q.getOptionAsInt("chto");
+  if ( channel_to <= 0 || channel != NULL ) channel_to = Channels.Count();
+ 
+  if ( from <= -1 ) from = time(NULL); // default time is now
+  if ( timespan <= -1 ) timespan = 0; // default timespan is 0, which means all entries will be returned
+  int to = from + timespan;
+
+  cSchedulesLock MutexLock;
+  const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
+
+  if( !Schedules ) {
+     reply.httpReturn(404, "Could not find schedules!");
+     return;
+  }
+
+
+  if ( start_filter >= 0 && limit_filter >= 1 ) {
+     eventList->activateLimit(start_filter, limit_filter);
+  }
+
+  bool initialized = false;
+  int total = 0;
+  for(int i=0; i<Channels.Count(); i++) {
+     const cSchedule *Schedule = Schedules->GetSchedule(Channels.Get(i)->GetChannelID());
+     
+     if ((channel == NULL || strcmp(channel->GetChannelID().ToString(), Channels.Get(i)->GetChannelID().ToString()) == 0) && (i >= channel_from && i <= channel_to)) {
+        if (!Schedule) {
+           if (channel != NULL) {
+              reply.httpReturn(404, "Could not find schedule!");
+              return;
+           }
+        } else {
+           if (!initialized) {
+              eventList->init();
+              initialized = true;
+           }
+
+           int old = 0;
+           int channel_events = 0;
+           for(cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event)) {
+              int ts = event->StartTime();
+              int te = ts + event->Duration();
+              if ((ts <= to && te > from) || (te > from && timespan == 0)) {
+                 if (channel_limit == 0 || channel_limit > channel_events) {
+                    if ((event_id < 0 || event_id == (int)event->EventID()) && onlyCount != "true") {
+                       eventList->addEvent(event);
+                       channel_events++;
+                    }
+                 }
+              } else {
+                 if (ts > to) break;
+                 if (te <= from) old++;
+              }
+           }
+           total += (Schedule->Events()->Count() - old);
+        }
+     }
+  }
+  eventList->setTotal(total);
+  eventList->finish();
+  delete eventList;
+}
+
+void EventsResponder::replyImage(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/events/image", request);
+  if ( request.method() != "GET") {
+     reply.httpReturn(403, "To retrieve information use the GET method!");
+     return;
+  }
+
+  StreamExtension se(&out);
+  int eventid = q.getParamAsInt(0);
+  int number = q.getParamAsInt(1);
+  double timediff = -1;
+  
+  vector< string > images;
+  
+  FileCaches::get()->searchEventImages(eventid, images);
+
+  if (number < 0 || number >= (int)images.size()) {
+     reply.httpReturn(404, "Could not find image because of invalid image number!");
+     return;
+  }
+
+  string image = images[number];
+  string type = image.substr(image.find_last_of(".")+1);
+  string contenttype = (string)"image/" + type;
+  string path = Settings::get()->EpgImageDirectory() + (string)"/" + image;
+
+  if (request.hasHeader("If-Modified-Since")) {
+      timediff = difftime(FileExtension::get()->getModifiedTime(path), FileExtension::get()->getModifiedSinceTime(request));
+  }
+
+  if (timediff > 0.0 || timediff < 0.0) {
+    if ( se.writeBinary(path) ) {
+       reply.addHeader("Content-Type", contenttype.c_str());
+       FileExtension::get()->addModifiedHeader(path, reply);
+    } else {
+       reply.httpReturn(404, "Could not find image!");
+    }
+  } else {
+      reply.httpReturn(304, "Not-Modified");
+  }
+}
+
+void EventsResponder::replySearchResult(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/events/search", request);
+
+  if ( request.method() != "POST") {
+     reply.httpReturn(403, "To search for information use the POST method!");
+     return;
+  }
+
+  StreamExtension se(&out);
+
+  string query = q.getBodyAsString("query");
+ 
+  int mode = q.getBodyAsInt("mode");// search mode (0=phrase, 1=and, 2=or, 3=regular expression)
+  string channelid = q.getBodyAsString("channel"); //id !!
+  bool use_title = q.getBodyAsString("use_title") == "true";
+  bool use_subtitle = q.getBodyAsString("use_subtitle") == "true";
+  bool use_description = q.getBodyAsString("use_description") == "true";
+
+  if ( query.length() == 0 ) {
+     reply.httpReturn(402, "Query required");
+     return;
+  }
+
+  int channel = 0;
+  cChannel* channelInstance = VdrExtension::getChannel(channelid);
+  if (channelInstance != NULL) {
+     channel = channelInstance->Number();
+  }
+
+  EventList* eventList;
+
+  if ( q.isFormat(".json") ) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     eventList = (EventList*)new JsonEventList(&out);
+  } else if ( q.isFormat(".html") ) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     eventList = (EventList*)new HtmlEventList(&out);
+  } else if ( q.isFormat(".xml") ) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     eventList = (EventList*)new XmlEventList(&out);
+  } else {
+     reply.httpReturn(403, "Resources are not available for the selected format. (Use: .json or .html)");
+     return;
+  }
+  eventList->init();
+  
+  if (!use_title && !use_subtitle && !use_description)
+     use_title = true;
+  if (mode < 0 || mode > 3) 
+     mode = 0;
+  if (channel < 0 || channel > Channels.Count())
+     channel = 0;
+  if (query.length() > 100)
+     query = query.substr(0,100); //don't allow more than 100 characters, NOTE: maybe I should add a limitation to the Responderclass?
+
+  struct Epgsearch_searchresults_v1_0* epgquery = new struct Epgsearch_searchresults_v1_0;
+  epgquery->query = (char*)query.c_str();
+  epgquery->mode = mode;
+  epgquery->channelNr = channel;
+  epgquery->useTitle = use_title;
+  epgquery->useSubTitle = use_subtitle;
+  epgquery->useDescription = use_description;
+ 
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+  if ( start_filter >= 0 && limit_filter >= 1 ) {
+     eventList->activateLimit(start_filter, limit_filter);
+  }
+
+  int total = 0; 
+
+  cPlugin *Plugin = cPluginManager::GetPlugin("epgsearch");
+  if (Plugin) {
+     if (Plugin->Service("Epgsearch-searchresults-v1.0", NULL)) {
+        if (Plugin->Service("Epgsearch-searchresults-v1.0", epgquery)) {
+           cList< Epgsearch_searchresults_v1_0::cServiceSearchResult>* result = epgquery->pResultList;
+           Epgsearch_searchresults_v1_0::cServiceSearchResult* item = NULL;
+           if (result != NULL) {
+              for(int i=0;i<result->Count();i++) {
+                 item = result->Get(i);
+                 eventList->addEvent(((cEvent*)item->event));
+                 total++;
+              }
+           }
+        } else {
+           reply.httpReturn(406, "Internal (epgsearch) error, check parameters.");
+        }
+     } else {
+        reply.httpReturn(405, "Plugin-service not available.");
+     }
+  } else {
+     reply.httpReturn(404, "Plugin not installed!");
+  }
+  eventList->setTotal(total);
+  eventList->finish();
+  delete eventList;
+  delete epgquery;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerEvent& e)
+{
+  si.addMember("id") <<= e.Id;
+  si.addMember("title") <<= e.Title;
+  si.addMember("short_text") <<= e.ShortText;
+  si.addMember("description") <<= e.Description;
+  si.addMember("start_time") <<= e.StartTime;
+  si.addMember("channel") <<= e.Channel;
+  si.addMember("channel_name") <<= e.ChannelName;
+  si.addMember("duration") <<= e.Duration;
+  si.addMember("table_id") <<= e.TableID;
+  si.addMember("version") <<= e.Version;
+  si.addMember("images") <<= e.Images;
+  si.addMember("timer_exists") <<= e.TimerExists;
+  si.addMember("timer_active") <<= e.TimerActive;
+  si.addMember("timer_id") <<= e.TimerId;
+#if APIVERSNUM > 10710 || EPGHANDLER
+  si.addMember("parental_rating") <<= e.ParentalRating;
+#endif
+  si.addMember("vps") <<= e.Vps;
+
+  vector< SerComponent > components;
+  if ( e.Instance->Components() != NULL ) {
+     for(int i=0;i<e.Instance->Components()->NumComponents();i++) {
+        tComponent* comp = e.Instance->Components()->Component(i);
+        SerComponent component;
+        component.Stream = (int)comp->stream;
+        component.Type = (int)comp->type;
+        component.Language = StringExtension::UTF8Decode("");
+        if(comp->language != NULL) component.Language = StringExtension::UTF8Decode(string(comp->language));
+        component.Description = StringExtension::UTF8Decode("");
+        if(comp->description != NULL) component.Description = StringExtension::UTF8Decode(string(comp->description));
+        components.push_back(component); 
+     }
+  }
+
+  si.addMember("components") <<= components;
+
+#if APIVERSNUM > 10710 || EPGHANDLER
+  vector< cxxtools::String > contents;
+  int counter = 0;
+  uchar content = e.Instance->Contents(counter);
+  while (content != 0) {
+     contents.push_back(StringExtension::UTF8Decode(cEvent::ContentToString(content)));
+     counter++;
+     content = e.Instance->Contents(counter);
+  }
+  si.addMember("contents") <<= contents;
+
+  vector< int > raw_contents;
+  counter = 0;
+  uchar raw_content = e.Instance->Contents(counter);
+  while (raw_content != 0) {
+     raw_contents.push_back(raw_content);
+     counter++;
+     raw_content = e.Instance->Contents(counter);
+  }
+  si.addMember("raw_contents") <<= raw_contents;
+#endif
+
+#ifdef EPG_DETAILS_PATCH
+  si.addMember("details") <<= *e.Details;
+#endif
+
+  si.addMember("additional_media") <<= e.AdditionalMedia;
+
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerComponent& c)
+{
+  si.addMember("stream") <<= c.Stream;
+  si.addMember("type") <<= c.Type;
+  si.addMember("language") <<= c.Language;
+  si.addMember("description") <<= c.Description;
+}
+
+#ifdef EPG_DETAILS_PATCH
+void operator<<= (cxxtools::SerializationInfo& si, const struct tEpgDetail& e)
+{
+  si.addMember("key") <<= StringExtension::UTF8Decode(e.key);
+  si.addMember("value") <<= StringExtension::UTF8Decode(e.value);
+}
+#endif
+
+EventList::EventList(ostream *_out) {
+  s = new StreamExtension(_out);
+  total = 0;
+  Scraper2VdrService sc;
+}
+
+EventList::~EventList()
+{
+  delete s;
+}
+
+void HtmlEventList::init()
+{
+  s->writeHtmlHeader( "HtmlEventList" );
+  s->write("<ul>");
+}
+
+void HtmlEventList::addEvent(cEvent* event)
+{
+  if ( filtered() ) return;
+  s->write("<li>");
+  s->write((char*)event->Title()); //TODO: add more infos
+  s->write("\n");
+}
+
+void HtmlEventList::finish()
+{
+  s->write("</ul>");
+  s->write("</body></html>");
+}
+
+void JsonEventList::addEvent(cEvent* event)
+{
+  if ( filtered() ) return;
+
+  cxxtools::String eventTitle;
+  cxxtools::String eventShortText;
+  cxxtools::String eventDescription;
+  cxxtools::String empty = StringExtension::UTF8Decode("");
+  cxxtools::String channelStr = StringExtension::UTF8Decode((const char*)event->ChannelID().ToString());
+  cxxtools::String channelName = StringExtension::UTF8Decode((const char*)Channels.GetByChannelID(event->ChannelID(), true)->Name());
+
+  SerEvent serEvent;
+
+  if( !event->Title() ) { eventTitle = empty; } else { eventTitle = StringExtension::UTF8Decode(event->Title()); }
+  if( !event->ShortText() ) { eventShortText = empty; } else { eventShortText = StringExtension::UTF8Decode(event->ShortText()); }
+  if( !event->Description() ) { eventDescription = empty; } else { eventDescription = StringExtension::UTF8Decode(event->Description()); }
+
+  SerAdditionalMedia am;
+  if (sc.getMedia(event, am)) {
+      serEvent.AdditionalMedia = am;
+  }
+
+  serEvent.Id = event->EventID();
+  serEvent.Title = eventTitle;
+  serEvent.ShortText = eventShortText;
+  serEvent.Description = eventDescription;
+  serEvent.Channel = channelStr;
+  serEvent.ChannelName = channelName;
+  serEvent.StartTime = event->StartTime();
+  serEvent.Duration = event->Duration();
+  serEvent.TableID = (int)event->TableID();
+  serEvent.Version = (int)event->Version();
+#if APIVERSNUM > 10710 || EPGHANDLER
+  serEvent.ParentalRating = event->ParentalRating();
+#endif
+  serEvent.Vps = event->Vps();
+  serEvent.Instance = event;
+
+  cTimer* timer = VdrExtension::TimerExists(event);
+  serEvent.TimerExists = timer != NULL ? true : false;
+  serEvent.TimerActive = false;
+  if ( timer != NULL ) {
+     serEvent.TimerActive = timer->Flags() & 0x01 == 0x01 ? true : false;
+     serEvent.TimerId = StringExtension::UTF8Decode(VdrExtension::getTimerID(timer));
+  }
+
+  vector< string > images;
+  FileCaches::get()->searchEventImages((int)event->EventID(), images);
+  serEvent.Images = images.size();
+
+#ifdef EPG_DETAILS_PATCH
+  serEvent.Details = (vector<tEpgDetail>*)&event->Details();
+#endif
+
+  serEvents.push_back(serEvent);
+}
+
+void JsonEventList::finish()
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.beautify();
+  serializer.serialize(serEvents, "events");
+  serializer.serialize(serEvents.size(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
+
+void XmlEventList::init()
+{
+  s->writeXmlHeader();  
+  s->write("<events xmlns=\"http://www.domain.org/restfulapi/2011/events-xml\">\n");
+}
+
+void XmlEventList::addEvent(cEvent* event)
+{
+  if ( filtered() ) return;
+
+  string eventTitle;
+  string eventShortText;
+  string eventDescription;
+
+  if ( event->Title() == NULL ) { eventTitle = ""; } else { eventTitle = event->Title(); }
+  if ( event->ShortText() == NULL ) { eventShortText = ""; } else { eventShortText = event->ShortText(); }
+  if ( event->Description() == NULL ) { eventDescription = ""; } else { eventDescription = event->Description(); }
+   
+  s->write(" <event>\n");
+  s->write(cString::sprintf("  <param name=\"id\">%i</param>\n", event->EventID()));
+  s->write(cString::sprintf("  <param name=\"title\">%s</param>\n", StringExtension::encodeToXml(eventTitle).c_str()));
+  s->write(cString::sprintf("  <param name=\"short_text\">%s</param>\n", StringExtension::encodeToXml(eventShortText).c_str()));
+  s->write(cString::sprintf("  <param name=\"description\">%s</param>\n", StringExtension::encodeToXml(eventDescription).c_str()));
+
+  s->write(cString::sprintf("  <param name=\"channel\">%s</param>\n", StringExtension::encodeToXml((const char*)event->ChannelID().ToString()).c_str()));
+  s->write(cString::sprintf("  <param name=\"channel_name\">%s</param>\n", StringExtension::encodeToXml((const char*)Channels.GetByChannelID(event->ChannelID(), true)->Name()).c_str()));
+
+  s->write(cString::sprintf("  <param name=\"start_time\">%i</param>\n", (int)event->StartTime()));
+  s->write(cString::sprintf("  <param name=\"duration\">%i</param>\n", event->Duration()));
+  s->write(cString::sprintf("  <param name=\"table_id\">%i</param>\n", (int)event->TableID()));
+  s->write(cString::sprintf("  <param name=\"version\">%i</param>\n", (int)event->Version()));
+#if APIVERSNUM > 10710 || EPGHANDLER
+  s->write(cString::sprintf("  <param name=\"parental_rating\">%i</param>\n", event->ParentalRating()));
+#endif
+  s->write(cString::sprintf("  <param name=\"vps\">%i</param>\n", (int)event->Vps()));
+  
+#ifdef EPG_DETAILS_PATCH
+  s->write("  <param name=\"details\">\n");
+  for(int i=0;i<(int)event->Details().size();i++) {
+     string key = event->Details()[i].key;
+     string value = event->Details()[i].value;
+     s->write(cString::sprintf("   <detail key=\"%s\">%s</detail>\n", StringExtension::encodeToXml(key.c_str()).c_str(), StringExtension::encodeToXml(value.c_str()).c_str()));
+  }
+  s->write("  </param>\n");
+#endif
+
+  vector< string > images;
+  FileCaches::get()->searchEventImages((int)event->EventID(), images);
+  s->write(cString::sprintf("  <param name=\"images\">%i</param>\n", (int)images.size()));
+
+  cTimer* timer = VdrExtension::TimerExists(event);
+  bool timer_exists = timer != NULL ? true : false;
+  bool timer_active = false;
+  string timer_id = "";
+  if ( timer_exists ) {
+     timer_active = timer->Flags() & 0x01 == 0x01 ? true : false;
+     timer_id = VdrExtension::getTimerID(timer);
+  }
+
+  s->write("  <param name=\"components\">\n");
+  if (event->Components() != NULL) {
+     cComponents* components = (cComponents*)event->Components();
+     for(int i=0;i<components->NumComponents();i++) {
+        tComponent* component = components->Component(i);
+
+        string language = ""; 
+        if (component->language != NULL) language = string(component->language);
+
+        string description = "";
+        if (component->description != NULL) description = string(component->description);
+
+        s->write(cString::sprintf("   <component stream=\"%i\" type=\"%i\" language=\"%s\" description=\"%s\" />\n", 
+                                  (int)component->stream, (int)component->type, language.c_str(), description.c_str()));
+     }
+  }
+  s->write("  </param>\n");
+
+#if APIVERSNUM > 10710 || EPGHANDLER
+  s->write("  <param name=\"contents\">\n");
+  int counter = 0;
+  uchar content = event->Contents(counter);
+  while(content != 0) {
+    counter++;
+    s->write(cString::sprintf("   <content name=\"%s\" />\n", cEvent::ContentToString(content)));
+    content = event->Contents(counter);
+  }
+  s->write("  </param>\n");
+
+  s->write("  <param name=\"raw_contents\">\n");
+  counter = 0;
+  content = event->Contents(counter);
+  while(content != 0) {
+    counter++;
+    s->write(cString::sprintf("   <raw_content name=\"%i\" />\n", content));
+    content = event->Contents(counter);
+  }
+  s->write("  </param>\n");
+#endif
+
+  s->write(cString::sprintf("  <param name=\"timer_exists\">%s</param>\n", (timer_exists ? "true" : "false")));
+  s->write(cString::sprintf("  <param name=\"timer_active\">%s</param>\n", (timer_active ? "true" : "false")));
+  s->write(cString::sprintf("  <param name=\"timer_id\">%s</param>\n", timer_id.c_str()));
+
+  sc.getMedia(event, s);
+
+  s->write(" </event>\n");
+}
+
+void XmlEventList::finish()
+{
+  s->write(cString::sprintf(" <count>%i</count><total>%i</total>\n", Count(), total));
+  s->write("</events>");
+}
diff --git a/events.h b/events.h
new file mode 100644
index 0000000..2e06750
--- /dev/null
+++ b/events.h
@@ -0,0 +1,119 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/arg.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include <cxxtools/utf8codec.h>
+#include <vdr/epg.h>
+#include <vdr/plugin.h>
+
+#include "tools.h"
+#include "epgsearch/services.h"
+#include "scraper2vdr.h"
+
+#ifndef __RESTFUL_EVENTS_H
+#define __RESTFUL_EVENTS_H
+
+class EventsResponder : public cxxtools::http::Responder
+{
+  public:
+    explicit EventsResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service)
+      { }
+    virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void replyEvents(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void replyImage(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void replySearchResult(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+};
+
+typedef cxxtools::http::CachedService<EventsResponder> EventsService;
+
+struct SerComponent
+{
+  int Stream;
+  int Type;
+  cxxtools::String Language;
+  cxxtools::String Description;
+};
+
+struct SerEvent
+{
+  int Id;
+  cxxtools::String Title;
+  cxxtools::String ShortText;
+  cxxtools::String Description;
+  cxxtools::String Channel;
+  cxxtools::String ChannelName;
+  int StartTime;
+  int Duration;
+  int TableID;
+  int Version;
+  int Images;
+  bool TimerExists;
+  bool TimerActive;
+#if APIVERSNUM > 10710 || EPGHANDLER
+  int ParentalRating;
+#endif
+  int Vps;
+  cxxtools::String TimerId;
+  cEvent* Instance;
+#ifdef EPG_DETAILS_PATCH
+  std::vector< tEpgDetail >* Details;
+#endif
+  struct SerAdditionalMedia AdditionalMedia;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerEvent& e);
+void operator<<= (cxxtools::SerializationInfo& si, const SerComponent& c);
+#ifdef EPG_DETAILS_PATCH
+void operator<<= (cxxtools::SerializationInfo& si, const struct tEpgDetail& e);
+#endif
+
+class EventList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+    Scraper2VdrService sc;
+  public:
+    explicit EventList(std::ostream* _out);
+    virtual ~EventList();
+    virtual void init() { };
+    virtual void addEvent(cEvent* event) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; }
+};
+
+class HtmlEventList : EventList
+{
+  public:
+    explicit HtmlEventList(std::ostream* _out) : EventList(_out) { };
+    ~HtmlEventList() { };
+    virtual void init();
+    virtual void addEvent(cEvent* event);
+    virtual void finish();
+};
+
+class JsonEventList : EventList
+{
+  private:
+    std::vector < struct SerEvent > serEvents;
+  public:
+    explicit JsonEventList(std::ostream* _out) : EventList(_out) { };
+    ~JsonEventList() { };
+    virtual void addEvent(cEvent* event);
+    virtual void finish();
+};
+
+class XmlEventList : EventList
+{
+  public:
+    explicit XmlEventList(std::ostream* _out) : EventList(_out) { };
+    ~XmlEventList() { };
+    virtual void init();
+    virtual void addEvent(cEvent* event);
+    virtual void finish();
+};
+
+#endif //__RESTFUL_EVENTS_H
diff --git a/examples/js_osd/index.html b/examples/js_osd/index.html
new file mode 100644
index 0000000..d5e42f0
--- /dev/null
+++ b/examples/js_osd/index.html
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <script type="text/javascript" src="jquery-1.6.2.js"></script>
+    <script type="text/javascript">
+    //<![CDATA[
+
+    function VdrOsd(){
+
+      //change restfulapiHost according to your needs
+      this.restfulapiHost = "http://127.0.0.1:8002/";
+      this.waitBeforeRefresh = 300; //milliseconds
+      this.oldOsdData = null;
+
+      this.refreshOsdData = function()
+      {
+        OSDobj = this;
+        $.getJSON( this.restfulapiHost + "osd.json", function(data){OSDobj.updateScreen(data)})
+        .error(function(data) { OSDobj.setScreenToBlank(); });
+      }
+
+      this.setScreenToBlank = function (data)
+      {
+        this.oldOsdData = null;
+        $("div#header").toggleClass("invisible", true);
+        $("div#color_buttons" ).toggleClass("invisible", true);
+        $("div#content" ).toggleClass("invisible", true);
+        $("div#message" ).toggleClass("invisible", true);
+        //trigger refresh x milliseconds after rendering
+        var x = setTimeout("OSDobj.refreshOsdData()", this.waitBeforeRefresh);
+      }
+
+      this.updateScreen = function (data)
+      {
+        var TextOsd = data.TextOsd;
+        //if (TextOsd.type !== "TextOsd") $("body").html(TextOsd.type);
+        if (JSON.stringify(this.oldOsdData) !== JSON.stringify(TextOsd)){
+
+          //update header
+          var headerObj = $("div#header");
+          if (typeof TextOsd.title == "string" && (TextOsd.title != "" || TextOsd.items.length > 0))
+          {
+              var title = (TextOsd.title == "") ? " " : TextOsd.title;
+              headerObj.html( title );
+              headerObj.toggleClass("invisible", false);
+          }
+          else
+          {
+              headerObj.html(" ");
+              headerObj.toggleClass("invisible", true);
+          }
+
+          //update content body
+          $( "ul" ).empty();
+          if (TextOsd.items.length > 0)
+          {
+            for (z = 0; z < TextOsd.items.length; z++)
+            {
+              var id = TextOsd.items[z].is_selected ? " id=\"selectedItem\"" : "";
+              $( "ul" ).append("<li"+id+" class=\"item\">"+ TextOsd.items[z].content +"</li>");
+            }
+            $("div#content" ).toggleClass("invisible", false);
+          }
+          else if (typeof TextOsd.title == "string" && TextOsd.title != "")
+          {
+            $( "ul" ).append("<li class=\"item\"> </li>");
+            $("div#content" ).toggleClass("invisible", false);
+          }
+
+          //update message
+          var messageObj = $("div#message");
+          if (typeof TextOsd.message == "string" && TextOsd.message != "")
+          {
+            messageObj.html( TextOsd.message );
+            messageObj.toggleClass("invisible", false);
+          }
+          else if ((typeof TextOsd.title == "string" && TextOsd.title != "") || TextOsd.items.length > 0)
+          {
+            messageObj.html(" ");
+            messageObj.toggleClass("invisible", false);
+          }
+          else
+          {
+            messageObj.html(" ");
+            messageObj.toggleClass("invisible", true);
+          }
+
+          //update color button area
+          var colorButtons = [ "red", "green", "yellow", "blue" ];
+          var showColorButtons = false;
+          var z= 0;
+          for (z = 0; z < colorButtons.length; z++)
+          {
+            var currentButtonName = "div#" + colorButtons[z];
+            var currentButtonObj = $( currentButtonName );
+            if (typeof (TextOsd[ colorButtons[z] ]) == "string" && TextOsd[ colorButtons[z] ] != "")
+            {
+              currentButtonObj.html(TextOsd[ colorButtons[z] ]);
+              currentButtonObj.toggleClass("active", true);
+              currentButtonObj.toggleClass("inactive", false);
+              showColorButtons = true;
+            }
+            else
+            {
+              currentButtonObj.html(" ");
+              currentButtonObj.toggleClass("active", false);
+              currentButtonObj.toggleClass("inactive", true);
+            }
+          }
+          if (TextOsd.items.length == 0 && typeof TextOsd.title == "string" && TextOsd.title == "" && !showColorButtons)
+            $("div#color_buttons" ).toggleClass("invisible", true);
+          else
+            $("div#color_buttons" ).toggleClass("invisible", false);
+
+          //keep in mind current osd status to compare later
+          this.oldOsdData = TextOsd;
+        }
+        //trigger refresh x milliseconds after rendering
+        var x = setTimeout("OSDobj.refreshOsdData()", this.waitBeforeRefresh);
+      }
+    }
+
+    $(document).ready(function()
+     {
+        /*$("body").ajaxError(function(event, xhr, settings, exception)
+        {
+          $(this).text( "Triggered ajaxError handler." +
+                xhr.responseText + "/" + xhr.status + "/" + settings.url );
+        });*/
+        var OSDobj = new VdrOsd();
+        OSDobj.restfulapiHost = prompt("Please change IP and port to match your running vdr-plugin-restfulapi within your local network", OSDobj.restfulapiHost);
+        OSDobj.refreshOsdData();
+    });
+
+    //]]>
+    </script>
+    <link rel="stylesheet" type="text/css" href="osd.css" />
+    <title>HTML based VDR OSD (passive slave)</title>
+  </head>
+  <body>
+  <div id="osd_container">
+    <div class="invisible" id="header"> </div>
+    <div class="invisible" id="content">
+    <ul type="none">
+    </ul>
+    </div><!-- closing content container -->
+    <div class="invisible" id="message"> </div>
+    <div class="invisible" id="color_buttons">
+      <div id="red" class="first inactive"> </div>
+      <div id="green" class="second inactive"> </div>
+      <div id="yellow" class="third inactive"> </div>
+      <div id="blue" class="fourth inactive"> </div>
+      <br class="clear">
+    </div><!-- closing color_buttons container -->
+  </div><!-- closing osd_container -->
+  </body>
+</html>
\ No newline at end of file
diff --git a/examples/js_osd/jquery-1.6.2.js b/examples/js_osd/jquery-1.6.2.js
new file mode 100644
index 0000000..f3201aa
--- /dev/null
+++ b/examples/js_osd/jquery-1.6.2.js
@@ -0,0 +1,8981 @@
+/*!
+ * jQuery JavaScript Library v1.6.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Jun 30 14:16:56 2011 -0400
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+	navigator = window.navigator,
+	location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context, rootjQuery );
+	},
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	// A central reference to the root jQuery(document)
+	rootjQuery,
+
+	// A simple way to check for HTML strings or ID strings
+	// (both of which we optimize for)
+	quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+	// Check if a string has a non-whitespace character in it
+	rnotwhite = /\S/,
+
+	// Used for trimming whitespace
+	trimLeft = /^\s+/,
+	trimRight = /\s+$/,
+
+	// Check for digits
+	rdigit = /\d/,
+
+	// Match a standalone tag
+	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+	// JSON RegExp
+	rvalidchars = /^[\],:{}\s]*$/,
+	rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+	rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+	// Useragent RegExp
+	rwebkit = /(webkit)[ \/]([\w.]+)/,
+	ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+	rmsie = /(msie) ([\w.]+)/,
+	rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+	// Matches dashed string for camelizing
+	rdashAlpha = /-([a-z])/ig,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return letter.toUpperCase();
+	},
+
+	// Keep a UserAgent string for use with jQuery.browser
+	userAgent = navigator.userAgent,
+
+	// For matching the engine and version of the browser
+	browserMatch,
+
+	// The deferred used on DOM ready
+	readyList,
+
+	// The ready event handler
+	DOMContentLoaded,
+
+	// Save a reference to some core methods
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	push = Array.prototype.push,
+	slice = Array.prototype.slice,
+	trim = String.prototype.trim,
+	indexOf = Array.prototype.indexOf,
+
+	// [[Class]] -> type pairs
+	class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		var match, elem, ret, doc;
+
+		// Handle $(""), $(null), or $(undefined)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this.context = this[0] = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// The body element only exists once, optimize finding it
+		if ( selector === "body" && !context && document.body ) {
+			this.context = document;
+			this[0] = document.body;
+			this.selector = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			// Are we dealing with HTML string or an ID?
+			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = quickExpr.exec( selector );
+			}
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] ) {
+					context = context instanceof jQuery ? context[0] : context;
+					doc = (context ? context.ownerDocument || context : document);
+
+					// If a single string is passed in and it's a single tag
+					// just do a createElement and skip the rest
+					ret = rsingleTag.exec( selector );
+
+					if ( ret ) {
+						if ( jQuery.isPlainObject( context ) ) {
+							selector = [ document.createElement( ret[1] ) ];
+							jQuery.fn.attr.call( selector, context, true );
+
+						} else {
+							selector = [ doc.createElement( ret[1] ) ];
+						}
+
+					} else {
+						ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+						selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
+					}
+
+					return jQuery.merge( this, selector );
+
+				// HANDLE: $("#id")
+				} else {
+					elem = document.getElementById( match[2] );
+
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id !== match[2] ) {
+							return rootjQuery.find( selector );
+						}
+
+						// Otherwise, we inject the element directly into the jQuery object
+						this.length = 1;
+						this[0] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return (context || rootjQuery).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return rootjQuery.ready( selector );
+		}
+
+		if (selector.selector !== undefined) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The current version of jQuery being used
+	jquery: "1.6.2",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+
+	toArray: function() {
+		return slice.call( this, 0 );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == null ?
+
+			// Return a 'clean' array
+			this.toArray() :
+
+			// Return just the object
+			( num < 0 ? this[ this.length + num ] : this[ num ] );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems, name, selector ) {
+		// Build a new jQuery matched element set
+		var ret = this.constructor();
+
+		if ( jQuery.isArray( elems ) ) {
+			push.apply( ret, elems );
+
+		} else {
+			jQuery.merge( ret, elems );
+		}
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		ret.context = this.context;
+
+		if ( name === "find" ) {
+			ret.selector = this.selector + (this.selector ? " " : "") + selector;
+		} else if ( name ) {
+			ret.selector = this.selector + "." + name + "(" + selector + ")";
+		}
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	ready: function( fn ) {
+		// Attach the listeners
+		jQuery.bindReady();
+
+		// Add the callback
+		readyList.done( fn );
+
+		return this;
+	},
+
+	eq: function( i ) {
+		return i === -1 ?
+			this.slice( i ) :
+			this.slice( i, +i + 1 );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	slice: function() {
+		return this.pushStack( slice.apply( this, arguments ),
+			"slice", slice.call(arguments).join(",") );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: push,
+	sort: [].sort,
+	splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+		target = {};
+	}
+
+	// extend jQuery itself if only one argument is passed
+	if ( length === i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ ) {
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null ) {
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray(src) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject(src) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	},
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+		// Either a released hold or an DOMready/load event and not yet ready
+		if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+			// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+			if ( !document.body ) {
+				return setTimeout( jQuery.ready, 1 );
+			}
+
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If a normal DOM Ready event fired, decrement, and wait if need be
+			if ( wait !== true && --jQuery.readyWait > 0 ) {
+				return;
+			}
+
+			// If there are functions bound, to execute
+			readyList.resolveWith( document, [ jQuery ] );
+
+			// Trigger any bound ready events
+			if ( jQuery.fn.trigger ) {
+				jQuery( document ).trigger( "ready" ).unbind( "ready" );
+			}
+		}
+	},
+
+	bindReady: function() {
+		if ( readyList ) {
+			return;
+		}
+
+		readyList = jQuery._Deferred();
+
+		// Catch cases where $(document).ready() is called after the
+		// browser event has already occurred.
+		if ( document.readyState === "complete" ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			return setTimeout( jQuery.ready, 1 );
+		}
+
+		// Mozilla, Opera and webkit nightlies currently support this event
+		if ( document.addEventListener ) {
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", jQuery.ready, false );
+
+		// If IE event model is used
+		} else if ( document.attachEvent ) {
+			// ensure firing before onload,
+			// maybe late but safe also for iframes
+			document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+			// A fallback to window.onload, that will always work
+			window.attachEvent( "onload", jQuery.ready );
+
+			// If IE and not a frame
+			// continually check to see if the document is ready
+			var toplevel = false;
+
+			try {
+				toplevel = window.frameElement == null;
+			} catch(e) {}
+
+			if ( document.documentElement.doScroll && toplevel ) {
+				doScrollCheck();
+			}
+		}
+	},
+
+	// See test/unit/core.js for details concerning isFunction.
+	// Since version 1.3, DOM methods and functions like alert
+	// aren't supported. They return false on IE (#2968).
+	isFunction: function( obj ) {
+		return jQuery.type(obj) === "function";
+	},
+
+	isArray: Array.isArray || function( obj ) {
+		return jQuery.type(obj) === "array";
+	},
+
+	// A crude way of determining if an object is a window
+	isWindow: function( obj ) {
+		return obj && typeof obj === "object" && "setInterval" in obj;
+	},
+
+	isNaN: function( obj ) {
+		return obj == null || !rdigit.test( obj ) || isNaN( obj );
+	},
+
+	type: function( obj ) {
+		return obj == null ?
+			String( obj ) :
+			class2type[ toString.call(obj) ] || "object";
+	},
+
+	isPlainObject: function( obj ) {
+		// Must be an Object.
+		// Because of IE, we also have to check the presence of the constructor property.
+		// Make sure that DOM nodes and window objects don't pass through, as well
+		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		// Not own constructor property must be Object
+		if ( obj.constructor &&
+			!hasOwn.call(obj, "constructor") &&
+			!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+			return false;
+		}
+
+		// Own properties are enumerated firstly, so to speed up,
+		// if last one is own, then all properties are own.
+
+		var key;
+		for ( key in obj ) {}
+
+		return key === undefined || hasOwn.call( obj, key );
+	},
+
+	isEmptyObject: function( obj ) {
+		for ( var name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	error: function( msg ) {
+		throw msg;
+	},
+
+	parseJSON: function( data ) {
+		if ( typeof data !== "string" || !data ) {
+			return null;
+		}
+
+		// Make sure leading/trailing whitespace is removed (IE can't handle it)
+		data = jQuery.trim( data );
+
+		// Attempt to parse using the native JSON parser first
+		if ( window.JSON && window.JSON.parse ) {
+			return window.JSON.parse( data );
+		}
+
+		// Make sure the incoming data is actual JSON
+		// Logic borrowed from http://json.org/json2.js
+		if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+			.replace( rvalidtokens, "]" )
+			.replace( rvalidbraces, "")) ) {
+
+			return (new Function( "return " + data ))();
+
+		}
+		jQuery.error( "Invalid JSON: " + data );
+	},
+
+	// Cross-browser xml parsing
+	// (xml & tmp used internally)
+	parseXML: function( data , xml , tmp ) {
+
+		if ( window.DOMParser ) { // Standard
+			tmp = new DOMParser();
+			xml = tmp.parseFromString( data , "text/xml" );
+		} else { // IE
+			xml = new ActiveXObject( "Microsoft.XMLDOM" );
+			xml.async = "false";
+			xml.loadXML( data );
+		}
+
+		tmp = xml.documentElement;
+
+		if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evaluates a script in a global context
+	// Workarounds based on findings by Jim Driscoll
+	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+	globalEval: function( data ) {
+		if ( data && rnotwhite.test( data ) ) {
+			// We use execScript on Internet Explorer
+			// We use an anonymous function so that context is window
+			// rather than jQuery in Firefox
+			( window.execScript || function( data ) {
+				window[ "eval" ].call( window, data );
+			} )( data );
+		}
+	},
+
+	// Converts a dashed string to camelCased string;
+	// Used by both the css and data modules
+	camelCase: function( string ) {
+		return string.replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		var name, i = 0,
+			length = object.length,
+			isObj = length === undefined || jQuery.isFunction( object );
+
+		if ( args ) {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.apply( object[ name ], args ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.apply( object[ i++ ], args ) === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return object;
+	},
+
+	// Use native String.trim function wherever possible
+	trim: trim ?
+		function( text ) {
+			return text == null ?
+				"" :
+				trim.call( text );
+		} :
+
+		// Otherwise use our own trimming functionality
+		function( text ) {
+			return text == null ?
+				"" :
+				text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+		},
+
+	// results is for internal usage only
+	makeArray: function( array, results ) {
+		var ret = results || [];
+
+		if ( array != null ) {
+			// The window, strings (and functions) also have 'length'
+			// The extra typeof function check is to prevent crashes
+			// in Safari 2 (See: #3039)
+			// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+			var type = jQuery.type( array );
+
+			if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+				push.call( ret, array );
+			} else {
+				jQuery.merge( ret, array );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, array ) {
+
+		if ( indexOf ) {
+			return indexOf.call( array, elem );
+		}
+
+		for ( var i = 0, length = array.length; i < length; i++ ) {
+			if ( array[ i ] === elem ) {
+				return i;
+			}
+		}
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		var i = first.length,
+			j = 0;
+
+		if ( typeof second.length === "number" ) {
+			for ( var l = second.length; j < l; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+
+		} else {
+			while ( second[j] !== undefined ) {
+				first[ i++ ] = second[ j++ ];
+			}
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [], retVal;
+		inv = !!inv;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			retVal = !!callback( elems[ i ], i );
+			if ( inv !== retVal ) {
+				ret.push( elems[ i ] );
+			}
+		}
+
+		return ret;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var value, key, ret = [],
+			i = 0,
+			length = elems.length,
+			// jquery objects are treated as arrays
+			isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+		// Go through the array, translating each of the items to their
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( key in elems ) {
+				value = callback( elems[ key ], key, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return ret.concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		if ( typeof context === "string" ) {
+			var tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		var args = slice.call( arguments, 2 ),
+			proxy = function() {
+				return fn.apply( context, args.concat( slice.call( arguments ) ) );
+			};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	// Mutifunctional method to get and set values to a collection
+	// The value/s can optionally be executed if it's a function
+	access: function( elems, key, value, exec, fn, pass ) {
+		var length = elems.length;
+
+		// Setting many attributes
+		if ( typeof key === "object" ) {
+			for ( var k in key ) {
+				jQuery.access( elems, k, key[k], exec, fn, value );
+			}
+			return elems;
+		}
+
+		// Setting one attribute
+		if ( value !== undefined ) {
+			// Optionally, function values get executed if exec is true
+			exec = !pass && exec && jQuery.isFunction(value);
+
+			for ( var i = 0; i < length; i++ ) {
+				fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+			}
+
+			return elems;
+		}
+
+		// Getting an attribute
+		return length ? fn( elems[0], key ) : undefined;
+	},
+
+	now: function() {
+		return (new Date()).getTime();
+	},
+
+	// Use of jQuery.browser is frowned upon.
+	// More details: http://docs.jquery.com/Utilities/jQuery.browser
+	uaMatch: function( ua ) {
+		ua = ua.toLowerCase();
+
+		var match = rwebkit.exec( ua ) ||
+			ropera.exec( ua ) ||
+			rmsie.exec( ua ) ||
+			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+			[];
+
+		return { browser: match[1] || "", version: match[2] || "0" };
+	},
+
+	sub: function() {
+		function jQuerySub( selector, context ) {
+			return new jQuerySub.fn.init( selector, context );
+		}
+		jQuery.extend( true, jQuerySub, this );
+		jQuerySub.superclass = this;
+		jQuerySub.fn = jQuerySub.prototype = this();
+		jQuerySub.fn.constructor = jQuerySub;
+		jQuerySub.sub = this.sub;
+		jQuerySub.fn.init = function init( selector, context ) {
+			if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+				context = jQuerySub( context );
+			}
+
+			return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+		};
+		jQuerySub.fn.init.prototype = jQuerySub.fn;
+		var rootjQuerySub = jQuerySub(document);
+		return jQuerySub;
+	},
+
+	browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+	jQuery.browser[ browserMatch.browser ] = true;
+	jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+	jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+	trimLeft = /^[\s\xA0]+/;
+	trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+	DOMContentLoaded = function() {
+		document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+		jQuery.ready();
+	};
+
+} else if ( document.attachEvent ) {
+	DOMContentLoaded = function() {
+		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+		if ( document.readyState === "complete" ) {
+			document.detachEvent( "onreadystatechange", DOMContentLoaded );
+			jQuery.ready();
+		}
+	};
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+	if ( jQuery.isReady ) {
+		return;
+	}
+
+	try {
+		// If IE is used, use the trick by Diego Perini
+		// http://javascript.nwbox.com/IEContentLoaded/
+		document.documentElement.doScroll("left");
+	} catch(e) {
+		setTimeout( doScrollCheck, 1 );
+		return;
+	}
+
+	// and execute any waiting functions
+	jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+var // Promise methods
+	promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
+	// Static reference to slice
+	sliceDeferred = [].slice;
+
+jQuery.extend({
+	// Create a simple deferred (one callbacks list)
+	_Deferred: function() {
+		var // callbacks list
+			callbacks = [],
+			// stored [ context , args ]
+			fired,
+			// to avoid firing when already doing so
+			firing,
+			// flag to know if the deferred has been cancelled
+			cancelled,
+			// the deferred itself
+			deferred  = {
+
+				// done( f1, f2, ...)
+				done: function() {
+					if ( !cancelled ) {
+						var args = arguments,
+							i,
+							length,
+							elem,
+							type,
+							_fired;
+						if ( fired ) {
+							_fired = fired;
+							fired = 0;
+						}
+						for ( i = 0, length = args.length; i < length; i++ ) {
+							elem = args[ i ];
+							type = jQuery.type( elem );
+							if ( type === "array" ) {
+								deferred.done.apply( deferred, elem );
+							} else if ( type === "function" ) {
+								callbacks.push( elem );
+							}
+						}
+						if ( _fired ) {
+							deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
+						}
+					}
+					return this;
+				},
+
+				// resolve with given context and args
+				resolveWith: function( context, args ) {
+					if ( !cancelled && !fired && !firing ) {
+						// make sure args are available (#8421)
+						args = args || [];
+						firing = 1;
+						try {
+							while( callbacks[ 0 ] ) {
+								callbacks.shift().apply( context, args );
+							}
+						}
+						finally {
+							fired = [ context, args ];
+							firing = 0;
+						}
+					}
+					return this;
+				},
+
+				// resolve with this as context and given arguments
+				resolve: function() {
+					deferred.resolveWith( this, arguments );
+					return this;
+				},
+
+				// Has this deferred been resolved?
+				isResolved: function() {
+					return !!( firing || fired );
+				},
+
+				// Cancel
+				cancel: function() {
+					cancelled = 1;
+					callbacks = [];
+					return this;
+				}
+			};
+
+		return deferred;
+	},
+
+	// Full fledged deferred (two callbacks list)
+	Deferred: function( func ) {
+		var deferred = jQuery._Deferred(),
+			failDeferred = jQuery._Deferred(),
+			promise;
+		// Add errorDeferred methods, then and promise
+		jQuery.extend( deferred, {
+			then: function( doneCallbacks, failCallbacks ) {
+				deferred.done( doneCallbacks ).fail( failCallbacks );
+				return this;
+			},
+			always: function() {
+				return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
+			},
+			fail: failDeferred.done,
+			rejectWith: failDeferred.resolveWith,
+			reject: failDeferred.resolve,
+			isRejected: failDeferred.isResolved,
+			pipe: function( fnDone, fnFail ) {
+				return jQuery.Deferred(function( newDefer ) {
+					jQuery.each( {
+						done: [ fnDone, "resolve" ],
+						fail: [ fnFail, "reject" ]
+					}, function( handler, data ) {
+						var fn = data[ 0 ],
+							action = data[ 1 ],
+							returned;
+						if ( jQuery.isFunction( fn ) ) {
+							deferred[ handler ](function() {
+								returned = fn.apply( this, arguments );
+								if ( returned && jQuery.isFunction( returned.promise ) ) {
+									returned.promise().then( newDefer.resolve, newDefer.reject );
+								} else {
+									newDefer[ action ]( returned );
+								}
+							});
+						} else {
+							deferred[ handler ]( newDefer[ action ] );
+						}
+					});
+				}).promise();
+			},
+			// Get a promise for this deferred
+			// If obj is provided, the promise aspect is added to the object
+			promise: function( obj ) {
+				if ( obj == null ) {
+					if ( promise ) {
+						return promise;
+					}
+					promise = obj = {};
+				}
+				var i = promiseMethods.length;
+				while( i-- ) {
+					obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
+				}
+				return obj;
+			}
+		});
+		// Make sure only one callback list will be used
+		deferred.done( failDeferred.cancel ).fail( deferred.cancel );
+		// Unexpose cancel
+		delete deferred.cancel;
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( firstParam ) {
+		var args = arguments,
+			i = 0,
+			length = args.length,
+			count = length,
+			deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+				firstParam :
+				jQuery.Deferred();
+		function resolveFunc( i ) {
+			return function( value ) {
+				args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+				if ( !( --count ) ) {
+					// Strange bug in FF4:
+					// Values changed onto the arguments object sometimes end up as undefined values
+					// outside the $.when method. Cloning the object into a fresh array solves the issue
+					deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
+				}
+			};
+		}
+		if ( length > 1 ) {
+			for( ; i < length; i++ ) {
+				if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
+					args[ i ].promise().then( resolveFunc(i), deferred.reject );
+				} else {
+					--count;
+				}
+			}
+			if ( !count ) {
+				deferred.resolveWith( deferred, args );
+			}
+		} else if ( deferred !== firstParam ) {
+			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+		}
+		return deferred.promise();
+	}
+});
+
+
+
+jQuery.support = (function() {
+
+	var div = document.createElement( "div" ),
+		documentElement = document.documentElement,
+		all,
+		a,
+		select,
+		opt,
+		input,
+		marginDiv,
+		support,
+		fragment,
+		body,
+		testElementParent,
+		testElement,
+		testElementStyle,
+		tds,
+		events,
+		eventName,
+		i,
+		isSupported;
+
+	// Preliminary tests
+	div.setAttribute("className", "t");
+	div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+	all = div.getElementsByTagName( "*" );
+	a = div.getElementsByTagName( "a" )[ 0 ];
+
+	// Can't get basic test support
+	if ( !all || !all.length || !a ) {
+		return {};
+	}
+
+	// First batch of supports tests
+	select = document.createElement( "select" );
+	opt = select.appendChild( document.createElement("option") );
+	input = div.getElementsByTagName( "input" )[ 0 ];
+
+	support = {
+		// IE strips leading whitespace when .innerHTML is used
+		leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+		// Make sure that tbody elements aren't automatically inserted
+		// IE will insert them into empty tables
+		tbody: !div.getElementsByTagName( "tbody" ).length,
+
+		// Make sure that link elements get serialized correctly by innerHTML
+		// This requires a wrapper element in IE
+		htmlSerialize: !!div.getElementsByTagName( "link" ).length,
+
+		// Get the style information from getAttribute
+		// (IE uses .cssText instead)
+		style: /top/.test( a.getAttribute("style") ),
+
+		// Make sure that URLs aren't manipulated
+		// (IE normalizes it by default)
+		hrefNormalized: ( a.getAttribute( "href" ) === "/a" ),
+
+		// Make sure that element opacity exists
+		// (IE uses filter instead)
+		// Use a regex to work around a WebKit issue. See #5145
+		opacity: /^0.55$/.test( a.style.opacity ),
+
+		// Verify style float existence
+		// (IE uses styleFloat instead of cssFloat)
+		cssFloat: !!a.style.cssFloat,
+
+		// Make sure that if no value is specified for a checkbox
+		// that it defaults to "on".
+		// (WebKit defaults to "" instead)
+		checkOn: ( input.value === "on" ),
+
+		// Make sure that a selected-by-default option has a working selected property.
+		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+		optSelected: opt.selected,
+
+		// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+		getSetAttribute: div.className !== "t",
+
+		// Will be defined later
+		submitBubbles: true,
+		changeBubbles: true,
+		focusinBubbles: false,
+		deleteExpando: true,
+		noCloneEvent: true,
+		inlineBlockNeedsLayout: false,
+		shrinkWrapBlocks: false,
+		reliableMarginRight: true
+	};
+
+	// Make sure checked status is properly cloned
+	input.checked = true;
+	support.noCloneChecked = input.cloneNode( true ).checked;
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as disabled)
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Test to see if it's possible to delete an expando from an element
+	// Fails in Internet Explorer
+	try {
+		delete div.test;
+	} catch( e ) {
+		support.deleteExpando = false;
+	}
+
+	if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+		div.attachEvent( "onclick", function() {
+			// Cloning a node shouldn't copy over any
+			// bound event handlers (IE does this)
+			support.noCloneEvent = false;
+		});
+		div.cloneNode( true ).fireEvent( "onclick" );
+	}
+
+	// Check if a radio maintains it's value
+	// after being appended to the DOM
+	input = document.createElement("input");
+	input.value = "t";
+	input.setAttribute("type", "radio");
+	support.radioValue = input.value === "t";
+
+	input.setAttribute("checked", "checked");
+	div.appendChild( input );
+	fragment = document.createDocumentFragment();
+	fragment.appendChild( div.firstChild );
+
+	// WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	div.innerHTML = "";
+
+	// Figure out if the W3C box model works as expected
+	div.style.width = div.style.paddingLeft = "1px";
+
+	body = document.getElementsByTagName( "body" )[ 0 ];
+	// We use our own, invisible, body unless the body is already present
+	// in which case we use a div (#9239)
+	testElement = document.createElement( body ? "div" : "body" );
+	testElementStyle = {
+		visibility: "hidden",
+		width: 0,
+		height: 0,
+		border: 0,
+		margin: 0
+	};
+	if ( body ) {
+		jQuery.extend( testElementStyle, {
+			position: "absolute",
+			left: -1000,
+			top: -1000
+		});
+	}
+	for ( i in testElementStyle ) {
+		testElement.style[ i ] = testElementStyle[ i ];
+	}
+	testElement.appendChild( div );
+	testElementParent = body || documentElement;
+	testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+	// Check if a disconnected checkbox will retain its checked
+	// value of true after appended to the DOM (IE6/7)
+	support.appendChecked = input.checked;
+
+	support.boxModel = div.offsetWidth === 2;
+
+	if ( "zoom" in div.style ) {
+		// Check if natively block-level elements act like inline-block
+		// elements when setting their display to 'inline' and giving
+		// them layout
+		// (IE < 8 does this)
+		div.style.display = "inline";
+		div.style.zoom = 1;
+		support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
+
+		// Check if elements with layout shrink-wrap their children
+		// (IE 6 does this)
+		div.style.display = "";
+		div.innerHTML = "<div style='width:4px;'></div>";
+		support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+	}
+
+	div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+	tds = div.getElementsByTagName( "td" );
+
+	// Check if table cells still have offsetWidth/Height when they are set
+	// to display:none and there are still other visible table cells in a
+	// table row; if so, offsetWidth/Height are not reliable for use when
+	// determining if an element has been hidden directly using
+	// display:none (it is still safe to use offsets if a parent element is
+	// hidden; don safety goggles and see bug #4512 for more information).
+	// (only IE 8 fails this test)
+	isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+	tds[ 0 ].style.display = "";
+	tds[ 1 ].style.display = "none";
+
+	// Check if empty table cells still have offsetWidth/Height
+	// (IE < 8 fail this test)
+	support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+	div.innerHTML = "";
+
+	// Check if div with explicit width and no margin-right incorrectly
+	// gets computed margin-right based on width of container. For more
+	// info see bug #3333
+	// Fails in WebKit before Feb 2011 nightlies
+	// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+	if ( document.defaultView && document.defaultView.getComputedStyle ) {
+		marginDiv = document.createElement( "div" );
+		marginDiv.style.width = "0";
+		marginDiv.style.marginRight = "0";
+		div.appendChild( marginDiv );
+		support.reliableMarginRight =
+			( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+	}
+
+	// Remove the body element we added
+	testElement.innerHTML = "";
+	testElementParent.removeChild( testElement );
+
+	// Technique from Juriy Zaytsev
+	// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+	// We only care about the case where non-standard event systems
+	// are used, namely in IE. Short-circuiting here helps us to
+	// avoid an eval call (in setAttribute) which can cause CSP
+	// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+	if ( div.attachEvent ) {
+		for( i in {
+			submit: 1,
+			change: 1,
+			focusin: 1
+		} ) {
+			eventName = "on" + i;
+			isSupported = ( eventName in div );
+			if ( !isSupported ) {
+				div.setAttribute( eventName, "return;" );
+				isSupported = ( typeof div[ eventName ] === "function" );
+			}
+			support[ i + "Bubbles" ] = isSupported;
+		}
+	}
+
+	// Null connected elements to avoid leaks in IE
+	testElement = fragment = select = opt = body = marginDiv = div = input = null;
+
+	return support;
+})();
+
+// Keep track of boxModel
+jQuery.boxModel = jQuery.support.boxModel;
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+	rmultiDash = /([a-z])([A-Z])/g;
+
+jQuery.extend({
+	cache: {},
+
+	// Please use with caution
+	uuid: 0,
+
+	// Unique for each copy of jQuery on the page
+	// Non-digits removed to match rinlinejQuery
+	expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+	// The following elements throw uncatchable exceptions if you
+	// attempt to add expando properties to them.
+	noData: {
+		"embed": true,
+		// Ban all objects except for Flash (which handle expandos)
+		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+		"applet": true
+	},
+
+	hasData: function( elem ) {
+		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+
+		return !!elem && !isEmptyDataObject( elem );
+	},
+
+	data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
+
+			// We have to handle DOM nodes and JS objects differently because IE6-7
+			// can't GC object references properly across the DOM-JS boundary
+			isNode = elem.nodeType,
+
+			// Only DOM nodes need the global jQuery cache; JS object data is
+			// attached directly to the object so GC can occur automatically
+			cache = isNode ? jQuery.cache : elem,
+
+			// Only defining an ID for JS objects if its cache already exists allows
+			// the code to shortcut on the same path as a DOM node with no cache
+			id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
+
+		// Avoid doing any more work than we need to when trying to get data on an
+		// object that has no data at all
+		if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
+			return;
+		}
+
+		if ( !id ) {
+			// Only DOM nodes need a new unique ID for each element since their data
+			// ends up in the global cache
+			if ( isNode ) {
+				elem[ jQuery.expando ] = id = ++jQuery.uuid;
+			} else {
+				id = jQuery.expando;
+			}
+		}
+
+		if ( !cache[ id ] ) {
+			cache[ id ] = {};
+
+			// TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+			// metadata on plain JS objects when the object is serialized using
+			// JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+		}
+
+		// An object can be passed to jQuery.data instead of a key/value pair; this gets
+		// shallow copied over onto the existing cache
+		if ( typeof name === "object" || typeof name === "function" ) {
+			if ( pvt ) {
+				cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
+			} else {
+				cache[ id ] = jQuery.extend(cache[ id ], name);
+			}
+		}
+
+		thisCache = cache[ id ];
+
+		// Internal jQuery data is stored in a separate object inside the object's data
+		// cache in order to avoid key collisions between internal data and user-defined
+		// data
+		if ( pvt ) {
+			if ( !thisCache[ internalKey ] ) {
+				thisCache[ internalKey ] = {};
+			}
+
+			thisCache = thisCache[ internalKey ];
+		}
+
+		if ( data !== undefined ) {
+			thisCache[ jQuery.camelCase( name ) ] = data;
+		}
+
+		// TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
+		// not attempt to inspect the internal events object using jQuery.data, as this
+		// internal data object is undocumented and subject to change.
+		if ( name === "events" && !thisCache[name] ) {
+			return thisCache[ internalKey ] && thisCache[ internalKey ].events;
+		}
+
+		return getByName ? 
+			// Check for both converted-to-camel and non-converted data property names
+			thisCache[ jQuery.camelCase( name ) ] || thisCache[ name ] :
+			thisCache;
+	},
+
+	removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var internalKey = jQuery.expando, isNode = elem.nodeType,
+
+			// See jQuery.data for more information
+			cache = isNode ? jQuery.cache : elem,
+
+			// See jQuery.data for more information
+			id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+		// If there is already no cache entry for this object, there is no
+		// purpose in continuing
+		if ( !cache[ id ] ) {
+			return;
+		}
+
+		if ( name ) {
+			var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
+
+			if ( thisCache ) {
+				delete thisCache[ name ];
+
+				// If there is no data left in the cache, we want to continue
+				// and let the cache object itself get destroyed
+				if ( !isEmptyDataObject(thisCache) ) {
+					return;
+				}
+			}
+		}
+
+		// See jQuery.data for more information
+		if ( pvt ) {
+			delete cache[ id ][ internalKey ];
+
+			// Don't destroy the parent cache unless the internal data object
+			// had been the only thing left in it
+			if ( !isEmptyDataObject(cache[ id ]) ) {
+				return;
+			}
+		}
+
+		var internalCache = cache[ id ][ internalKey ];
+
+		// Browsers that fail expando deletion also refuse to delete expandos on
+		// the window, but it will allow it on all other JS objects; other browsers
+		// don't care
+		if ( jQuery.support.deleteExpando || cache != window ) {
+			delete cache[ id ];
+		} else {
+			cache[ id ] = null;
+		}
+
+		// We destroyed the entire user cache at once because it's faster than
+		// iterating through each key, but we need to continue to persist internal
+		// data if it existed
+		if ( internalCache ) {
+			cache[ id ] = {};
+			// TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+			// metadata on plain JS objects when the object is serialized using
+			// JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+
+			cache[ id ][ internalKey ] = internalCache;
+
+		// Otherwise, we need to eliminate the expando on the node to avoid
+		// false lookups in the cache for entries that no longer exist
+		} else if ( isNode ) {
+			// IE does not allow us to delete expando properties from nodes,
+			// nor does it have a removeAttribute function on Document nodes;
+			// we must handle all of these cases
+			if ( jQuery.support.deleteExpando ) {
+				delete elem[ jQuery.expando ];
+			} else if ( elem.removeAttribute ) {
+				elem.removeAttribute( jQuery.expando );
+			} else {
+				elem[ jQuery.expando ] = null;
+			}
+		}
+	},
+
+	// For internal use only.
+	_data: function( elem, name, data ) {
+		return jQuery.data( elem, name, data, true );
+	},
+
+	// A method for determining if a DOM node can handle the data expando
+	acceptData: function( elem ) {
+		if ( elem.nodeName ) {
+			var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+			if ( match ) {
+				return !(match === true || elem.getAttribute("classid") !== match);
+			}
+		}
+
+		return true;
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var data = null;
+
+		if ( typeof key === "undefined" ) {
+			if ( this.length ) {
+				data = jQuery.data( this[0] );
+
+				if ( this[0].nodeType === 1 ) {
+			    var attr = this[0].attributes, name;
+					for ( var i = 0, l = attr.length; i < l; i++ ) {
+						name = attr[i].name;
+
+						if ( name.indexOf( "data-" ) === 0 ) {
+							name = jQuery.camelCase( name.substring(5) );
+
+							dataAttr( this[0], name, data[ name ] );
+						}
+					}
+				}
+			}
+
+			return data;
+
+		} else if ( typeof key === "object" ) {
+			return this.each(function() {
+				jQuery.data( this, key );
+			});
+		}
+
+		var parts = key.split(".");
+		parts[1] = parts[1] ? "." + parts[1] : "";
+
+		if ( value === undefined ) {
+			data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+			// Try to fetch any internally stored data first
+			if ( data === undefined && this.length ) {
+				data = jQuery.data( this[0], key );
+				data = dataAttr( this[0], key, data );
+			}
+
+			return data === undefined && parts[1] ?
+				this.data( parts[0] ) :
+				data;
+
+		} else {
+			return this.each(function() {
+				var $this = jQuery( this ),
+					args = [ parts[0], value ];
+
+				$this.triggerHandler( "setData" + parts[1] + "!", args );
+				jQuery.data( this, key, value );
+				$this.triggerHandler( "changeData" + parts[1] + "!", args );
+			});
+		}
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			jQuery.removeData( this, key );
+		});
+	}
+});
+
+function dataAttr( elem, key, data ) {
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+		var name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase();
+
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+				data === "false" ? false :
+				data === "null" ? null :
+				!jQuery.isNaN( data ) ? parseFloat( data ) :
+					rbrace.test( data ) ? jQuery.parseJSON( data ) :
+					data;
+			} catch( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			jQuery.data( elem, key, data );
+
+		} else {
+			data = undefined;
+		}
+	}
+
+	return data;
+}
+
+// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON
+// property to be considered empty objects; this property always exists in
+// order to make sure JSON.stringify does not expose internal metadata
+function isEmptyDataObject( obj ) {
+	for ( var name in obj ) {
+		if ( name !== "toJSON" ) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+	var deferDataKey = type + "defer",
+		queueDataKey = type + "queue",
+		markDataKey = type + "mark",
+		defer = jQuery.data( elem, deferDataKey, undefined, true );
+	if ( defer &&
+		( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) &&
+		( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) {
+		// Give room for hard-coded callbacks to fire first
+		// and eventually mark/queue something else on the element
+		setTimeout( function() {
+			if ( !jQuery.data( elem, queueDataKey, undefined, true ) &&
+				!jQuery.data( elem, markDataKey, undefined, true ) ) {
+				jQuery.removeData( elem, deferDataKey, true );
+				defer.resolve();
+			}
+		}, 0 );
+	}
+}
+
+jQuery.extend({
+
+	_mark: function( elem, type ) {
+		if ( elem ) {
+			type = (type || "fx") + "mark";
+			jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true );
+		}
+	},
+
+	_unmark: function( force, elem, type ) {
+		if ( force !== true ) {
+			type = elem;
+			elem = force;
+			force = false;
+		}
+		if ( elem ) {
+			type = type || "fx";
+			var key = type + "mark",
+				count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 );
+			if ( count ) {
+				jQuery.data( elem, key, count, true );
+			} else {
+				jQuery.removeData( elem, key, true );
+				handleQueueMarkDefer( elem, type, "mark" );
+			}
+		}
+	},
+
+	queue: function( elem, type, data ) {
+		if ( elem ) {
+			type = (type || "fx") + "queue";
+			var q = jQuery.data( elem, type, undefined, true );
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !q || jQuery.isArray(data) ) {
+					q = jQuery.data( elem, type, jQuery.makeArray(data), true );
+				} else {
+					q.push( data );
+				}
+			}
+			return q || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			fn = queue.shift(),
+			defer;
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+		}
+
+		if ( fn ) {
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift("inprogress");
+			}
+
+			fn.call(elem, function() {
+				jQuery.dequeue(elem, type);
+			});
+		}
+
+		if ( !queue.length ) {
+			jQuery.removeData( elem, type + "queue", true );
+			handleQueueMarkDefer( elem, type, "queue" );
+		}
+	}
+});
+
+jQuery.fn.extend({
+	queue: function( type, data ) {
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+		}
+
+		if ( data === undefined ) {
+			return jQuery.queue( this[0], type );
+		}
+		return this.each(function() {
+			var queue = jQuery.queue( this, type, data );
+
+			if ( type === "fx" && queue[0] !== "inprogress" ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	},
+	dequeue: function( type ) {
+		return this.each(function() {
+			jQuery.dequeue( this, type );
+		});
+	},
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://blindsignals.com/index.php/2009/07/jquery-delay/
+	delay: function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function() {
+			var elem = this;
+			setTimeout(function() {
+				jQuery.dequeue( elem, type );
+			}, time );
+		});
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, object ) {
+		if ( typeof type !== "string" ) {
+			object = type;
+			type = undefined;
+		}
+		type = type || "fx";
+		var defer = jQuery.Deferred(),
+			elements = this,
+			i = elements.length,
+			count = 1,
+			deferDataKey = type + "defer",
+			queueDataKey = type + "queue",
+			markDataKey = type + "mark",
+			tmp;
+		function resolve() {
+			if ( !( --count ) ) {
+				defer.resolveWith( elements, [ elements ] );
+			}
+		}
+		while( i-- ) {
+			if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+					( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+						jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+					jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {
+				count++;
+				tmp.done( resolve );
+			}
+		}
+		resolve();
+		return defer.promise();
+	}
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+	rspace = /\s+/,
+	rreturn = /\r/g,
+	rtype = /^(?:button|input)$/i,
+	rfocusable = /^(?:button|input|object|select|textarea)$/i,
+	rclickable = /^a(?:rea)?$/i,
+	rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+	rinvalidChar = /\:|^on/,
+	formHook, boolHook;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, name, value, true, jQuery.attr );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	},
+	
+	prop: function( name, value ) {
+		return jQuery.access( this, name, value, true, jQuery.prop );
+	},
+	
+	removeProp: function( name ) {
+		name = jQuery.propFix[ name ] || name;
+		return this.each(function() {
+			// try/catch handles cases where IE balks (such as removing a property on window)
+			try {
+				this[ name ] = undefined;
+				delete this[ name ];
+			} catch( e ) {}
+		});
+	},
+
+	addClass: function( value ) {
+		var classNames, i, l, elem,
+			setClass, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( value && typeof value === "string" ) {
+			classNames = value.split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 ) {
+					if ( !elem.className && classNames.length === 1 ) {
+						elem.className = value;
+
+					} else {
+						setClass = " " + elem.className + " ";
+
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+								setClass += classNames[ c ] + " ";
+							}
+						}
+						elem.className = jQuery.trim( setClass );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classNames, i, l, elem, className, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( (value && typeof value === "string") || value === undefined ) {
+			classNames = (value || "").split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 && elem.className ) {
+					if ( value ) {
+						className = (" " + elem.className + " ").replace( rclass, " " );
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							className = className.replace(" " + classNames[ c ] + " ", " ");
+						}
+						elem.className = jQuery.trim( className );
+
+					} else {
+						elem.className = "";
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value,
+			isBool = typeof stateVal === "boolean";
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					state = stateVal,
+					classNames = value.split( rspace );
+
+				while ( (className = classNames[ i++ ]) ) {
+					// check each className given, space seperated list
+					state = isBool ? state : !self.hasClass( className );
+					self[ state ? "addClass" : "removeClass" ]( className );
+				}
+
+			} else if ( type === "undefined" || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					jQuery._data( this, "__className__", this.className );
+				}
+
+				// toggle whole className
+				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ";
+		for ( var i = 0, l = this.length; i < l; i++ ) {
+			if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		var hooks, ret,
+			elem = this[0];
+		
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ? 
+					// handle most common string cases
+					ret.replace(rreturn, "") : 
+					// handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return undefined;
+		}
+
+		var isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var self = jQuery(this), val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, self.val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map(val, function ( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				// attributes.value is undefined in Blackberry 4.7 but
+				// uses .value. See #6932
+				var val = elem.attributes.value;
+				return !val || val.specified ? elem.value : elem.text;
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value,
+					index = elem.selectedIndex,
+					values = [],
+					options = elem.options,
+					one = elem.type === "select-one";
+
+				// Nothing was selected
+				if ( index < 0 ) {
+					return null;
+				}
+
+				// Loop through all the selected options
+				for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+					var option = options[ i ];
+
+					// Don't return options that are disabled or in a disabled optgroup
+					if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+							(!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				// Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+				if ( one && !values.length && options.length ) {
+					return jQuery( options[ index ] ).val();
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var values = jQuery.makeArray( value );
+
+				jQuery(elem).find("option").each(function() {
+					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+				});
+
+				if ( !values.length ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	},
+
+	attrFn: {
+		val: true,
+		css: true,
+		html: true,
+		text: true,
+		data: true,
+		width: true,
+		height: true,
+		offset: true
+	},
+	
+	attrFix: {
+		// Always normalize to ensure hook usage
+		tabindex: "tabIndex"
+	},
+	
+	attr: function( elem, name, value, pass ) {
+		var nType = elem.nodeType;
+		
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return undefined;
+		}
+
+		if ( pass && name in jQuery.attrFn ) {
+			return jQuery( elem )[ name ]( value );
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( !("getAttribute" in elem) ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		var ret, hooks,
+			notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		// Normalize the name if needed
+		if ( notxml ) {
+			name = jQuery.attrFix[ name ] || name;
+
+			hooks = jQuery.attrHooks[ name ];
+
+			if ( !hooks ) {
+				// Use boolHook for boolean attributes
+				if ( rboolean.test( name ) ) {
+
+					hooks = boolHook;
+
+				// Use formHook for forms and if the name contains certain characters
+				} else if ( formHook && name !== "className" &&
+					(jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) {
+
+					hooks = formHook;
+				}
+			}
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+				return undefined;
+
+			} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, "" + value );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+
+			ret = elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret === null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, name ) {
+		var propName;
+		if ( elem.nodeType === 1 ) {
+			name = jQuery.attrFix[ name ] || name;
+		
+			if ( jQuery.support.getSetAttribute ) {
+				// Use removeAttribute in browsers that support it
+				elem.removeAttribute( name );
+			} else {
+				jQuery.attr( elem, name, "" );
+				elem.removeAttributeNode( elem.getAttributeNode( name ) );
+			}
+
+			// Set corresponding property to false for boolean attributes
+			if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) {
+				elem[ propName ] = false;
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+					jQuery.error( "type property can't be changed" );
+				} else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+					// Setting the type on a radio button after the value resets the value in IE6-9
+					// Reset value to it's default in case type is set after value
+					// This is for element creation
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		},
+		tabIndex: {
+			get: function( elem ) {
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				var attributeNode = elem.getAttributeNode("tabIndex");
+
+				return attributeNode && attributeNode.specified ?
+					parseInt( attributeNode.value, 10 ) :
+					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+						0 :
+						undefined;
+			}
+		},
+		// Use the value property for back compat
+		// Use the formHook for button elements in IE6/7 (#1954)
+		value: {
+			get: function( elem, name ) {
+				if ( formHook && jQuery.nodeName( elem, "button" ) ) {
+					return formHook.get( elem, name );
+				}
+				return name in elem ?
+					elem.value :
+					null;
+			},
+			set: function( elem, value, name ) {
+				if ( formHook && jQuery.nodeName( elem, "button" ) ) {
+					return formHook.set( elem, value, name );
+				}
+				// Does not return so that setAttribute is also used
+				elem.value = value;
+			}
+		}
+	},
+
+	propFix: {
+		tabindex: "tabIndex",
+		readonly: "readOnly",
+		"for": "htmlFor",
+		"class": "className",
+		maxlength: "maxLength",
+		cellspacing: "cellSpacing",
+		cellpadding: "cellPadding",
+		rowspan: "rowSpan",
+		colspan: "colSpan",
+		usemap: "useMap",
+		frameborder: "frameBorder",
+		contenteditable: "contentEditable"
+	},
+	
+	prop: function( elem, name, value ) {
+		var nType = elem.nodeType;
+
+		// don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return undefined;
+		}
+
+		var ret, hooks,
+			notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				return (elem[ name ] = value);
+			}
+
+		} else {
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				return elem[ name ];
+			}
+		}
+	},
+	
+	propHooks: {}
+});
+
+// Hook for boolean attributes
+boolHook = {
+	get: function( elem, name ) {
+		// Align boolean attributes with corresponding properties
+		return jQuery.prop( elem, name ) ?
+			name.toLowerCase() :
+			undefined;
+	},
+	set: function( elem, value, name ) {
+		var propName;
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			// value is true since we know at this point it's type boolean and not false
+			// Set boolean attributes to the same name and set the DOM property
+			propName = jQuery.propFix[ name ] || name;
+			if ( propName in elem ) {
+				// Only set the IDL specifically if it already exists on the element
+				elem[ propName ] = true;
+			}
+
+			elem.setAttribute( name, name.toLowerCase() );
+		}
+		return name;
+	}
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !jQuery.support.getSetAttribute ) {
+
+	// propFix is more comprehensive and contains all fixes
+	jQuery.attrFix = jQuery.propFix;
+	
+	// Use this for any attribute on a form in IE6/7
+	formHook = jQuery.attrHooks.name = jQuery.attrHooks.title = jQuery.valHooks.button = {
+		get: function( elem, name ) {
+			var ret;
+			ret = elem.getAttributeNode( name );
+			// Return undefined if nodeValue is empty string
+			return ret && ret.nodeValue !== "" ?
+				ret.nodeValue :
+				undefined;
+		},
+		set: function( elem, value, name ) {
+			// Check form objects in IE (multiple bugs related)
+			// Only use nodeValue if the attribute node exists on the form
+			var ret = elem.getAttributeNode( name );
+			if ( ret ) {
+				ret.nodeValue = value;
+				return value;
+			}
+		}
+	};
+
+	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
+	// This is for removals
+	jQuery.each([ "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			set: function( elem, value ) {
+				if ( value === "" ) {
+					elem.setAttribute( name, "auto" );
+					return value;
+				}
+			}
+		});
+	});
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+	jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			get: function( elem ) {
+				var ret = elem.getAttribute( name, 2 );
+				return ret === null ? undefined : ret;
+			}
+		});
+	});
+}
+
+if ( !jQuery.support.style ) {
+	jQuery.attrHooks.style = {
+		get: function( elem ) {
+			// Return undefined in the case of empty string
+			// Normalize to lowercase since IE uppercases css property names
+			return elem.style.cssText.toLowerCase() || undefined;
+		},
+		set: function( elem, value ) {
+			return (elem.style.cssText = "" + value);
+		}
+	};
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+	jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+
+			if ( parent ) {
+				parent.selectedIndex;
+
+				// Make sure that it also works with optgroups, see #5701
+				if ( parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+			}
+		}
+	});
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+	jQuery.each([ "radio", "checkbox" ], function() {
+		jQuery.valHooks[ this ] = {
+			get: function( elem ) {
+				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+				return elem.getAttribute("value") === null ? "on" : elem.value;
+			}
+		};
+	});
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0);
+			}
+		}
+	});
+});
+
+
+
+
+var rnamespaces = /\.(.*)$/,
+	rformElems = /^(?:textarea|input|select)$/i,
+	rperiod = /\./g,
+	rspaces = / /g,
+	rescape = /[^\w\s.|`]/g,
+	fcleanup = function( nm ) {
+		return nm.replace(rescape, "\\$&");
+	};
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+	// Bind an event to an element
+	// Original by Dean Edwards
+	add: function( elem, types, handler, data ) {
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		if ( handler === false ) {
+			handler = returnFalse;
+		} else if ( !handler ) {
+			// Fixes bug #7229. Fix recommended by jdalton
+			return;
+		}
+
+		var handleObjIn, handleObj;
+
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+		}
+
+		// Make sure that the function being executed has a unique ID
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure
+		var elemData = jQuery._data( elem );
+
+		// If no elemData is found then we must be trying to bind to one of the
+		// banned noData elements
+		if ( !elemData ) {
+			return;
+		}
+
+		var events = elemData.events,
+			eventHandle = elemData.handle;
+
+		if ( !events ) {
+			elemData.events = events = {};
+		}
+
+		if ( !eventHandle ) {
+			elemData.handle = eventHandle = function( e ) {
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+					jQuery.event.handle.apply( eventHandle.elem, arguments ) :
+					undefined;
+			};
+		}
+
+		// Add elem as a property of the handle function
+		// This is to prevent a memory leak with non-native events in IE.
+		eventHandle.elem = elem;
+
+		// Handle multiple events separated by a space
+		// jQuery(...).bind("mouseover mouseout", fn);
+		types = types.split(" ");
+
+		var type, i = 0, namespaces;
+
+		while ( (type = types[ i++ ]) ) {
+			handleObj = handleObjIn ?
+				jQuery.extend({}, handleObjIn) :
+				{ handler: handler, data: data };
+
+			// Namespaced event handlers
+			if ( type.indexOf(".") > -1 ) {
+				namespaces = type.split(".");
+				type = namespaces.shift();
+				handleObj.namespace = namespaces.slice(0).sort().join(".");
+
+			} else {
+				namespaces = [];
+				handleObj.namespace = "";
+			}
+
+			handleObj.type = type;
+			if ( !handleObj.guid ) {
+				handleObj.guid = handler.guid;
+			}
+
+			// Get the current list of functions bound to this event
+			var handlers = events[ type ],
+				special = jQuery.event.special[ type ] || {};
+
+			// Init the event handler queue
+			if ( !handlers ) {
+				handlers = events[ type ] = [];
+
+				// Check for a special event handler
+				// Only use addEventListener/attachEvent if the special
+				// events handler returns false
+				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+					// Bind the global event handler to the element
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle, false );
+
+					} else if ( elem.attachEvent ) {
+						elem.attachEvent( "on" + type, eventHandle );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add the function to the element's handler list
+			handlers.push( handleObj );
+
+			// Keep track of which events have been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, pos ) {
+		// don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		if ( handler === false ) {
+			handler = returnFalse;
+		}
+
+		var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+			elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+			events = elemData && elemData.events;
+
+		if ( !elemData || !events ) {
+			return;
+		}
+
+		// types is actually an event object here
+		if ( types && types.type ) {
+			handler = types.handler;
+			types = types.type;
+		}
+
+		// Unbind all events for the element
+		if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
+			types = types || "";
+
+			for ( type in events ) {
+				jQuery.event.remove( elem, type + types );
+			}
+
+			return;
+		}
+
+		// Handle multiple events separated by a space
+		// jQuery(...).unbind("mouseover mouseout", fn);
+		types = types.split(" ");
+
+		while ( (type = types[ i++ ]) ) {
+			origType = type;
+			handleObj = null;
+			all = type.indexOf(".") < 0;
+			namespaces = [];
+
+			if ( !all ) {
+				// Namespaced event handlers
+				namespaces = type.split(".");
+				type = namespaces.shift();
+
+				namespace = new RegExp("(^|\\.)" +
+					jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
+			}
+
+			eventType = events[ type ];
+
+			if ( !eventType ) {
+				continue;
+			}
+
+			if ( !handler ) {
+				for ( j = 0; j < eventType.length; j++ ) {
+					handleObj = eventType[ j ];
+
+					if ( all || namespace.test( handleObj.namespace ) ) {
+						jQuery.event.remove( elem, origType, handleObj.handler, j );
+						eventType.splice( j--, 1 );
+					}
+				}
+
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+
+			for ( j = pos || 0; j < eventType.length; j++ ) {
+				handleObj = eventType[ j ];
+
+				if ( handler.guid === handleObj.guid ) {
+					// remove the given handler for the given type
+					if ( all || namespace.test( handleObj.namespace ) ) {
+						if ( pos == null ) {
+							eventType.splice( j--, 1 );
+						}
+
+						if ( special.remove ) {
+							special.remove.call( elem, handleObj );
+						}
+					}
+
+					if ( pos != null ) {
+						break;
+					}
+				}
+			}
+
+			// remove generic event handler if no more handlers exist
+			if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+				if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				ret = null;
+				delete events[ type ];
+			}
+		}
+
+		// Remove the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			var handle = elemData.handle;
+			if ( handle ) {
+				handle.elem = null;
+			}
+
+			delete elemData.events;
+			delete elemData.handle;
+
+			if ( jQuery.isEmptyObject( elemData ) ) {
+				jQuery.removeData( elem, undefined, true );
+			}
+		}
+	},
+	
+	// Events that are safe to short-circuit if no handlers are attached.
+	// Native DOM events should not be added, they may have inline handlers.
+	customEvent: {
+		"getData": true,
+		"setData": true,
+		"changeData": true
+	},
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+		// Event object or event type
+		var type = event.type || event,
+			namespaces = [],
+			exclusive;
+
+		if ( type.indexOf("!") >= 0 ) {
+			// Exclusive events trigger only for the exact event (no namespaces)
+			type = type.slice(0, -1);
+			exclusive = true;
+		}
+
+		if ( type.indexOf(".") >= 0 ) {
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split(".");
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+
+		if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+			// No jQuery handlers for this event type, and it can't have inline handlers
+			return;
+		}
+
+		// Caller can pass in an Event, Object, or just an event type string
+		event = typeof event === "object" ?
+			// jQuery.Event object
+			event[ jQuery.expando ] ? event :
+			// Object literal
+			new jQuery.Event( type, event ) :
+			// Just the event type (string)
+			new jQuery.Event( type );
+
+		event.type = type;
+		event.exclusive = exclusive;
+		event.namespace = namespaces.join(".");
+		event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)");
+		
+		// triggerHandler() and global events don't bubble or run the default action
+		if ( onlyHandlers || !elem ) {
+			event.preventDefault();
+			event.stopPropagation();
+		}
+
+		// Handle a global trigger
+		if ( !elem ) {
+			// TODO: Stop taunting the data cache; remove global events and always attach to document
+			jQuery.each( jQuery.cache, function() {
+				// internalKey variable is just used to make it easier to find
+				// and potentially change this stuff later; currently it just
+				// points to jQuery.expando
+				var internalKey = jQuery.expando,
+					internalCache = this[ internalKey ];
+				if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
+					jQuery.event.trigger( event, data, internalCache.handle.elem );
+				}
+			});
+			return;
+		}
+
+		// Don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		event.target = elem;
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data != null ? jQuery.makeArray( data ) : [];
+		data.unshift( event );
+
+		var cur = elem,
+			// IE doesn't like method names with a colon (#3533, #8272)
+			ontype = type.indexOf(":") < 0 ? "on" + type : "";
+
+		// Fire event on the current element, then bubble up the DOM tree
+		do {
+			var handle = jQuery._data( cur, "handle" );
+
+			event.currentTarget = cur;
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+
+			// Trigger an inline bound script
+			if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) {
+				event.result = false;
+				event.preventDefault();
+			}
+
+			// Bubble up to document, then to window
+			cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window;
+		} while ( cur && !event.isPropagationStopped() );
+
+		// If nobody prevented the default action, do it now
+		if ( !event.isDefaultPrevented() ) {
+			var old,
+				special = jQuery.event.special[ type ] || {};
+
+			if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) &&
+				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Can't use an .isFunction)() check here because IE6/7 fails that test.
+				// IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch.
+				try {
+					if ( ontype && elem[ type ] ) {
+						// Don't re-trigger an onFOO event when we call its FOO() method
+						old = elem[ ontype ];
+
+						if ( old ) {
+							elem[ ontype ] = null;
+						}
+
+						jQuery.event.triggered = type;
+						elem[ type ]();
+					}
+				} catch ( ieError ) {}
+
+				if ( old ) {
+					elem[ ontype ] = old;
+				}
+
+				jQuery.event.triggered = undefined;
+			}
+		}
+		
+		return event.result;
+	},
+
+	handle: function( event ) {
+		event = jQuery.event.fix( event || window.event );
+		// Snapshot the handlers list since a called handler may add/remove events.
+		var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0),
+			run_all = !event.exclusive && !event.namespace,
+			args = Array.prototype.slice.call( arguments, 0 );
+
+		// Use the fix-ed Event rather than the (read-only) native event
+		args[0] = event;
+		event.currentTarget = this;
+
+		for ( var j = 0, l = handlers.length; j < l; j++ ) {
+			var handleObj = handlers[ j ];
+
+			// Triggered event must 1) be non-exclusive and have no namespace, or
+			// 2) have namespace(s) a subset or equal to those in the bound event.
+			if ( run_all || event.namespace_re.test( handleObj.namespace ) ) {
+				// Pass in a reference to the handler function itself
+				// So that we can later remove it
+				event.handler = handleObj.handler;
+				event.data = handleObj.data;
+				event.handleObj = handleObj;
+
+				var ret = handleObj.handler.apply( this, args );
+
+				if ( ret !== undefined ) {
+					event.result = ret;
+					if ( ret === false ) {
+						event.preventDefault();
+						event.stopPropagation();
+					}
+				}
+
+				if ( event.isImmediatePropagationStopped() ) {
+					break;
+				}
+			}
+		}
+		return event.result;
+	},
+
+	props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// store a copy of the original event object
+		// and "clone" to set read-only properties
+		var originalEvent = event;
+		event = jQuery.Event( originalEvent );
+
+		for ( var i = this.props.length, prop; i; ) {
+			prop = this.props[ --i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Fix target property, if necessary
+		if ( !event.target ) {
+			// Fixes #1925 where srcElement might not be defined either
+			event.target = event.srcElement || document;
+		}
+
+		// check if target is a textnode (safari)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		// Add relatedTarget, if necessary
+		if ( !event.relatedTarget && event.fromElement ) {
+			event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+		}
+
+		// Calculate pageX/Y if missing and clientX/Y available
+		if ( event.pageX == null && event.clientX != null ) {
+			var eventDocument = event.target.ownerDocument || document,
+				doc = eventDocument.documentElement,
+				body = eventDocument.body;
+
+			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+			event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
+		}
+
+		// Add which for key events
+		if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
+			event.which = event.charCode != null ? event.charCode : event.keyCode;
+		}
+
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+		if ( !event.metaKey && event.ctrlKey ) {
+			event.metaKey = event.ctrlKey;
+		}
+
+		// Add which for click: 1 === left; 2 === middle; 3 === right
+		// Note: button is not normalized, so don't use it
+		if ( !event.which && event.button !== undefined ) {
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+		}
+
+		return event;
+	},
+
+	// Deprecated, use jQuery.guid instead
+	guid: 1E8,
+
+	// Deprecated, use jQuery.proxy instead
+	proxy: jQuery.proxy,
+
+	special: {
+		ready: {
+			// Make sure the ready event is setup
+			setup: jQuery.bindReady,
+			teardown: jQuery.noop
+		},
+
+		live: {
+			add: function( handleObj ) {
+				jQuery.event.add( this,
+					liveConvert( handleObj.origType, handleObj.selector ),
+					jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
+			},
+
+			remove: function( handleObj ) {
+				jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
+			}
+		},
+
+		beforeunload: {
+			setup: function( data, namespaces, eventHandle ) {
+				// We only want to do this special case on windows
+				if ( jQuery.isWindow( this ) ) {
+					this.onbeforeunload = eventHandle;
+				}
+			},
+
+			teardown: function( namespaces, eventHandle ) {
+				if ( this.onbeforeunload === eventHandle ) {
+					this.onbeforeunload = null;
+				}
+			}
+		}
+	}
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+	function( elem, type, handle ) {
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle, false );
+		}
+	} :
+	function( elem, type, handle ) {
+		if ( elem.detachEvent ) {
+			elem.detachEvent( "on" + type, handle );
+		}
+	};
+
+jQuery.Event = function( src, props ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !this.preventDefault ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
+			src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// timeStamp is buggy for some events on Firefox(#3843)
+	// So we won't rely on the native value
+	this.timeStamp = jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+	return false;
+}
+function returnTrue() {
+	return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	preventDefault: function() {
+		this.isDefaultPrevented = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+
+		// if preventDefault exists run it on the original event
+		if ( e.preventDefault ) {
+			e.preventDefault();
+
+		// otherwise set the returnValue property of the original event to false (IE)
+		} else {
+			e.returnValue = false;
+		}
+	},
+	stopPropagation: function() {
+		this.isPropagationStopped = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+		// if stopPropagation exists run it on the original event
+		if ( e.stopPropagation ) {
+			e.stopPropagation();
+		}
+		// otherwise set the cancelBubble property of the original event to true (IE)
+		e.cancelBubble = true;
+	},
+	stopImmediatePropagation: function() {
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	},
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse
+};
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function( event ) {
+
+	// Check if mouse(over|out) are still within the same parent element
+	var related = event.relatedTarget,
+		inside = false,
+		eventType = event.type;
+
+	event.type = event.data;
+
+	if ( related !== this ) {
+
+		if ( related ) {
+			inside = jQuery.contains( this, related );
+		}
+
+		if ( !inside ) {
+
+			jQuery.event.handle.apply( this, arguments );
+
+			event.type = eventType;
+		}
+	}
+},
+
+// In case of event delegation, we only need to rename the event.type,
+// liveHandler will take care of the rest.
+delegate = function( event ) {
+	event.type = event.data;
+	jQuery.event.handle.apply( this, arguments );
+};
+
+// Create mouseenter and mouseleave events
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		setup: function( data ) {
+			jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
+		},
+		teardown: function( data ) {
+			jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
+		}
+	};
+});
+
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+	jQuery.event.special.submit = {
+		setup: function( data, namespaces ) {
+			if ( !jQuery.nodeName( this, "form" ) ) {
+				jQuery.event.add(this, "click.specialSubmit", function( e ) {
+					var elem = e.target,
+						type = elem.type;
+
+					if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+						trigger( "submit", this, arguments );
+					}
+				});
+
+				jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+					var elem = e.target,
+						type = elem.type;
+
+					if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+						trigger( "submit", this, arguments );
+					}
+				});
+
+			} else {
+				return false;
+			}
+		},
+
+		teardown: function( namespaces ) {
+			jQuery.event.remove( this, ".specialSubmit" );
+		}
+	};
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+	var changeFilters,
+
+	getVal = function( elem ) {
+		var type = elem.type, val = elem.value;
+
+		if ( type === "radio" || type === "checkbox" ) {
+			val = elem.checked;
+
+		} else if ( type === "select-multiple" ) {
+			val = elem.selectedIndex > -1 ?
+				jQuery.map( elem.options, function( elem ) {
+					return elem.selected;
+				}).join("-") :
+				"";
+
+		} else if ( jQuery.nodeName( elem, "select" ) ) {
+			val = elem.selectedIndex;
+		}
+
+		return val;
+	},
+
+	testChange = function testChange( e ) {
+		var elem = e.target, data, val;
+
+		if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
+			return;
+		}
+
+		data = jQuery._data( elem, "_change_data" );
+		val = getVal(elem);
+
+		// the current data will be also retrieved by beforeactivate
+		if ( e.type !== "focusout" || elem.type !== "radio" ) {
+			jQuery._data( elem, "_change_data", val );
+		}
+
+		if ( data === undefined || val === data ) {
+			return;
+		}
+
+		if ( data != null || val ) {
+			e.type = "change";
+			e.liveFired = undefined;
+			jQuery.event.trigger( e, arguments[1], elem );
+		}
+	};
+
+	jQuery.event.special.change = {
+		filters: {
+			focusout: testChange,
+
+			beforedeactivate: testChange,
+
+			click: function( e ) {
+				var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : "";
+
+				if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) {
+					testChange.call( this, e );
+				}
+			},
+
+			// Change has to be called before submit
+			// Keydown will be called before keypress, which is used in submit-event delegation
+			keydown: function( e ) {
+				var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : "";
+
+				if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) ||
+					(e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+					type === "select-multiple" ) {
+					testChange.call( this, e );
+				}
+			},
+
+			// Beforeactivate happens also before the previous element is blurred
+			// with this event you can't trigger a change event, but you can store
+			// information
+			beforeactivate: function( e ) {
+				var elem = e.target;
+				jQuery._data( elem, "_change_data", getVal(elem) );
+			}
+		},
+
+		setup: function( data, namespaces ) {
+			if ( this.type === "file" ) {
+				return false;
+			}
+
+			for ( var type in changeFilters ) {
+				jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
+			}
+
+			return rformElems.test( this.nodeName );
+		},
+
+		teardown: function( namespaces ) {
+			jQuery.event.remove( this, ".specialChange" );
+
+			return rformElems.test( this.nodeName );
+		}
+	};
+
+	changeFilters = jQuery.event.special.change.filters;
+
+	// Handle when the input is .focus()'d
+	changeFilters.focus = changeFilters.beforeactivate;
+}
+
+function trigger( type, elem, args ) {
+	// Piggyback on a donor event to simulate a different one.
+	// Fake originalEvent to avoid donor's stopPropagation, but if the
+	// simulated event prevents default then we do the same on the donor.
+	// Don't pass args or remember liveFired; they apply to the donor event.
+	var event = jQuery.extend( {}, args[ 0 ] );
+	event.type = type;
+	event.originalEvent = {};
+	event.liveFired = undefined;
+	jQuery.event.handle.call( elem, event );
+	if ( event.isDefaultPrevented() ) {
+		args[ 0 ].preventDefault();
+	}
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0;
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
+			}
+		};
+
+		function handler( donor ) {
+			// Donor event is always a native one; fix it and switch its type.
+			// Let focusin/out handler cancel the donor focus/blur event.
+			var e = jQuery.event.fix( donor );
+			e.type = fix;
+			e.originalEvent = {};
+			jQuery.event.trigger( e, null, e.target );
+			if ( e.isDefaultPrevented() ) {
+				donor.preventDefault();
+			}
+		}
+	});
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+	jQuery.fn[ name ] = function( type, data, fn ) {
+		var handler;
+
+		// Handle object literals
+		if ( typeof type === "object" ) {
+			for ( var key in type ) {
+				this[ name ](key, data, type[key], fn);
+			}
+			return this;
+		}
+
+		if ( arguments.length === 2 || data === false ) {
+			fn = data;
+			data = undefined;
+		}
+
+		if ( name === "one" ) {
+			handler = function( event ) {
+				jQuery( this ).unbind( event, handler );
+				return fn.apply( this, arguments );
+			};
+			handler.guid = fn.guid || jQuery.guid++;
+		} else {
+			handler = fn;
+		}
+
+		if ( type === "unload" && name !== "one" ) {
+			this.one( type, data, fn );
+
+		} else {
+			for ( var i = 0, l = this.length; i < l; i++ ) {
+				jQuery.event.add( this[i], type, handler, data );
+			}
+		}
+
+		return this;
+	};
+});
+
+jQuery.fn.extend({
+	unbind: function( type, fn ) {
+		// Handle object literals
+		if ( typeof type === "object" && !type.preventDefault ) {
+			for ( var key in type ) {
+				this.unbind(key, type[key]);
+			}
+
+		} else {
+			for ( var i = 0, l = this.length; i < l; i++ ) {
+				jQuery.event.remove( this[i], type, fn );
+			}
+		}
+
+		return this;
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.live( types, data, fn, selector );
+	},
+
+	undelegate: function( selector, types, fn ) {
+		if ( arguments.length === 0 ) {
+			return this.unbind( "live" );
+
+		} else {
+			return this.die( types, null, fn, selector );
+		}
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function() {
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+
+	triggerHandler: function( type, data ) {
+		if ( this[0] ) {
+			return jQuery.event.trigger( type, data, this[0], true );
+		}
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments,
+			guid = fn.guid || jQuery.guid++,
+			i = 0,
+			toggler = function( event ) {
+				// Figure out which function to execute
+				var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+				jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+				// Make sure that clicks stop
+				event.preventDefault();
+
+				// and execute the function
+				return args[ lastToggle ].apply( this, arguments ) || false;
+			};
+
+		// link all the functions, so any of them can unbind this click handler
+		toggler.guid = guid;
+		while ( i < args.length ) {
+			args[ i++ ].guid = guid;
+		}
+
+		return this.click( toggler );
+	},
+
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	}
+});
+
+var liveMap = {
+	focus: "focusin",
+	blur: "focusout",
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+};
+
+jQuery.each(["live", "die"], function( i, name ) {
+	jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
+		var type, i = 0, match, namespaces, preType,
+			selector = origSelector || this.selector,
+			context = origSelector ? this : jQuery( this.context );
+
+		if ( typeof types === "object" && !types.preventDefault ) {
+			for ( var key in types ) {
+				context[ name ]( key, data, types[key], selector );
+			}
+
+			return this;
+		}
+
+		if ( name === "die" && !types &&
+					origSelector && origSelector.charAt(0) === "." ) {
+
+			context.unbind( origSelector );
+
+			return this;
+		}
+
+		if ( data === false || jQuery.isFunction( data ) ) {
+			fn = data || returnFalse;
+			data = undefined;
+		}
+
+		types = (types || "").split(" ");
+
+		while ( (type = types[ i++ ]) != null ) {
+			match = rnamespaces.exec( type );
+			namespaces = "";
+
+			if ( match )  {
+				namespaces = match[0];
+				type = type.replace( rnamespaces, "" );
+			}
+
+			if ( type === "hover" ) {
+				types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
+				continue;
+			}
+
+			preType = type;
+
+			if ( liveMap[ type ] ) {
+				types.push( liveMap[ type ] + namespaces );
+				type = type + namespaces;
+
+			} else {
+				type = (liveMap[ type ] || type) + namespaces;
+			}
+
+			if ( name === "live" ) {
+				// bind live handler
+				for ( var j = 0, l = context.length; j < l; j++ ) {
+					jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
+						{ data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
+				}
+
+			} else {
+				// unbind live handler
+				context.unbind( "live." + liveConvert( type, selector ), fn );
+			}
+		}
+
+		return this;
+	};
+});
+
+function liveHandler( event ) {
+	var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
+		elems = [],
+		selectors = [],
+		events = jQuery._data( this, "events" );
+
+	// Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
+	if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
+		return;
+	}
+
+	if ( event.namespace ) {
+		namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
+	}
+
+	event.liveFired = this;
+
+	var live = events.live.slice(0);
+
+	for ( j = 0; j < live.length; j++ ) {
+		handleObj = live[j];
+
+		if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
+			selectors.push( handleObj.selector );
+
+		} else {
+			live.splice( j--, 1 );
+		}
+	}
+
+	match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+	for ( i = 0, l = match.length; i < l; i++ ) {
+		close = match[i];
+
+		for ( j = 0; j < live.length; j++ ) {
+			handleObj = live[j];
+
+			if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
+				elem = close.elem;
+				related = null;
+
+				// Those two events require additional checking
+				if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+					event.type = handleObj.preType;
+					related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
+
+					// Make sure not to accidentally match a child element with the same selector
+					if ( related && jQuery.contains( elem, related ) ) {
+						related = elem;
+					}
+				}
+
+				if ( !related || related !== elem ) {
+					elems.push({ elem: elem, handleObj: handleObj, level: close.level });
+				}
+			}
+		}
+	}
+
+	for ( i = 0, l = elems.length; i < l; i++ ) {
+		match = elems[i];
+
+		if ( maxLevel && match.level > maxLevel ) {
+			break;
+		}
+
+		event.currentTarget = match.elem;
+		event.data = match.handleObj.data;
+		event.handleObj = match.handleObj;
+
+		ret = match.handleObj.origHandler.apply( match.elem, arguments );
+
+		if ( ret === false || event.isPropagationStopped() ) {
+			maxLevel = match.level;
+
+			if ( ret === false ) {
+				stop = false;
+			}
+			if ( event.isImmediatePropagationStopped() ) {
+				break;
+			}
+		}
+	}
+
+	return stop;
+}
+
+function liveConvert( type, selector ) {
+	return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&");
+}
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error").split(" "), function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		if ( fn == null ) {
+			fn = data;
+			data = null;
+		}
+
+		return arguments.length > 0 ?
+			this.bind( name, data, fn ) :
+			this.trigger( name );
+	};
+
+	if ( jQuery.attrFn ) {
+		jQuery.attrFn[ name ] = true;
+	}
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2011, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+	done = 0,
+	toString = Object.prototype.toString,
+	hasDuplicate = false,
+	baseHasDuplicate = true,
+	rBackslash = /\\/g,
+	rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+	baseHasDuplicate = false;
+	return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+	results = results || [];
+	context = context || document;
+
+	var origContext = context;
+
+	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+		return [];
+	}
+	
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	var m, set, checkSet, extra, ret, cur, pop, i,
+		prune = true,
+		contextXML = Sizzle.isXML( context ),
+		parts = [],
+		soFar = selector;
+	
+	// Reset the position of the chunker regexp (start from head)
+	do {
+		chunker.exec( "" );
+		m = chunker.exec( soFar );
+
+		if ( m ) {
+			soFar = m[3];
+		
+			parts.push( m[1] );
+		
+			if ( m[2] ) {
+				extra = m[3];
+				break;
+			}
+		}
+	} while ( m );
+
+	if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+			set = posProcess( parts[0] + parts[1], context );
+
+		} else {
+			set = Expr.relative[ parts[0] ] ?
+				[ context ] :
+				Sizzle( parts.shift(), context );
+
+			while ( parts.length ) {
+				selector = parts.shift();
+
+				if ( Expr.relative[ selector ] ) {
+					selector += parts.shift();
+				}
+				
+				set = posProcess( selector, set );
+			}
+		}
+
+	} else {
+		// Take a shortcut and set the context if the root selector is an ID
+		// (but not if it'll be faster if the inner selector is an ID)
+		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+			ret = Sizzle.find( parts.shift(), context, contextXML );
+			context = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set )[0] :
+				ret.set[0];
+		}
+
+		if ( context ) {
+			ret = seed ?
+				{ expr: parts.pop(), set: makeArray(seed) } :
+				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+			set = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set ) :
+				ret.set;
+
+			if ( parts.length > 0 ) {
+				checkSet = makeArray( set );
+
+			} else {
+				prune = false;
+			}
+
+			while ( parts.length ) {
+				cur = parts.pop();
+				pop = cur;
+
+				if ( !Expr.relative[ cur ] ) {
+					cur = "";
+				} else {
+					pop = parts.pop();
+				}
+
+				if ( pop == null ) {
+					pop = context;
+				}
+
+				Expr.relative[ cur ]( checkSet, pop, contextXML );
+			}
+
+		} else {
+			checkSet = parts = [];
+		}
+	}
+
+	if ( !checkSet ) {
+		checkSet = set;
+	}
+
+	if ( !checkSet ) {
+		Sizzle.error( cur || selector );
+	}
+
+	if ( toString.call(checkSet) === "[object Array]" ) {
+		if ( !prune ) {
+			results.push.apply( results, checkSet );
+
+		} else if ( context && context.nodeType === 1 ) {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+					results.push( set[i] );
+				}
+			}
+
+		} else {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+					results.push( set[i] );
+				}
+			}
+		}
+
+	} else {
+		makeArray( checkSet, results );
+	}
+
+	if ( extra ) {
+		Sizzle( extra, origContext, results, seed );
+		Sizzle.uniqueSort( results );
+	}
+
+	return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+	if ( sortOrder ) {
+		hasDuplicate = baseHasDuplicate;
+		results.sort( sortOrder );
+
+		if ( hasDuplicate ) {
+			for ( var i = 1; i < results.length; i++ ) {
+				if ( results[i] === results[ i - 1 ] ) {
+					results.splice( i--, 1 );
+				}
+			}
+		}
+	}
+
+	return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+	return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+	return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+	var set;
+
+	if ( !expr ) {
+		return [];
+	}
+
+	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+		var match,
+			type = Expr.order[i];
+		
+		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+			var left = match[1];
+			match.splice( 1, 1 );
+
+			if ( left.substr( left.length - 1 ) !== "\\" ) {
+				match[1] = (match[1] || "").replace( rBackslash, "" );
+				set = Expr.find[ type ]( match, context, isXML );
+
+				if ( set != null ) {
+					expr = expr.replace( Expr.match[ type ], "" );
+					break;
+				}
+			}
+		}
+	}
+
+	if ( !set ) {
+		set = typeof context.getElementsByTagName !== "undefined" ?
+			context.getElementsByTagName( "*" ) :
+			[];
+	}
+
+	return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+	var match, anyFound,
+		old = expr,
+		result = [],
+		curLoop = set,
+		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+	while ( expr && set.length ) {
+		for ( var type in Expr.filter ) {
+			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+				var found, item,
+					filter = Expr.filter[ type ],
+					left = match[1];
+
+				anyFound = false;
+
+				match.splice(1,1);
+
+				if ( left.substr( left.length - 1 ) === "\\" ) {
+					continue;
+				}
+
+				if ( curLoop === result ) {
+					result = [];
+				}
+
+				if ( Expr.preFilter[ type ] ) {
+					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+					if ( !match ) {
+						anyFound = found = true;
+
+					} else if ( match === true ) {
+						continue;
+					}
+				}
+
+				if ( match ) {
+					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+						if ( item ) {
+							found = filter( item, match, i, curLoop );
+							var pass = not ^ !!found;
+
+							if ( inplace && found != null ) {
+								if ( pass ) {
+									anyFound = true;
+
+								} else {
+									curLoop[i] = false;
+								}
+
+							} else if ( pass ) {
+								result.push( item );
+								anyFound = true;
+							}
+						}
+					}
+				}
+
+				if ( found !== undefined ) {
+					if ( !inplace ) {
+						curLoop = result;
+					}
+
+					expr = expr.replace( Expr.match[ type ], "" );
+
+					if ( !anyFound ) {
+						return [];
+					}
+
+					break;
+				}
+			}
+		}
+
+		// Improper expression
+		if ( expr === old ) {
+			if ( anyFound == null ) {
+				Sizzle.error( expr );
+
+			} else {
+				break;
+			}
+		}
+
+		old = expr;
+	}
+
+	return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+	throw "Syntax error, unrecognized expression: " + msg;
+};
+
+var Expr = Sizzle.selectors = {
+	order: [ "ID", "NAME", "TAG" ],
+
+	match: {
+		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+	},
+
+	leftMatch: {},
+
+	attrMap: {
+		"class": "className",
+		"for": "htmlFor"
+	},
+
+	attrHandle: {
+		href: function( elem ) {
+			return elem.getAttribute( "href" );
+		},
+		type: function( elem ) {
+			return elem.getAttribute( "type" );
+		}
+	},
+
+	relative: {
+		"+": function(checkSet, part){
+			var isPartStr = typeof part === "string",
+				isTag = isPartStr && !rNonWord.test( part ),
+				isPartStrNotTag = isPartStr && !isTag;
+
+			if ( isTag ) {
+				part = part.toLowerCase();
+			}
+
+			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+				if ( (elem = checkSet[i]) ) {
+					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+						elem || false :
+						elem === part;
+				}
+			}
+
+			if ( isPartStrNotTag ) {
+				Sizzle.filter( part, checkSet, true );
+			}
+		},
+
+		">": function( checkSet, part ) {
+			var elem,
+				isPartStr = typeof part === "string",
+				i = 0,
+				l = checkSet.length;
+
+			if ( isPartStr && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						var parent = elem.parentNode;
+						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+					}
+				}
+
+			} else {
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						checkSet[i] = isPartStr ?
+							elem.parentNode :
+							elem.parentNode === part;
+					}
+				}
+
+				if ( isPartStr ) {
+					Sizzle.filter( part, checkSet, true );
+				}
+			}
+		},
+
+		"": function(checkSet, part, isXML){
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+		},
+
+		"~": function( checkSet, part, isXML ) {
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+		}
+	},
+
+	find: {
+		ID: function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+				// Check parentNode to catch when Blackberry 4.6 returns
+				// nodes that are no longer in the document #6963
+				return m && m.parentNode ? [m] : [];
+			}
+		},
+
+		NAME: function( match, context ) {
+			if ( typeof context.getElementsByName !== "undefined" ) {
+				var ret = [],
+					results = context.getElementsByName( match[1] );
+
+				for ( var i = 0, l = results.length; i < l; i++ ) {
+					if ( results[i].getAttribute("name") === match[1] ) {
+						ret.push( results[i] );
+					}
+				}
+
+				return ret.length === 0 ? null : ret;
+			}
+		},
+
+		TAG: function( match, context ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( match[1] );
+			}
+		}
+	},
+	preFilter: {
+		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+			match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+			if ( isXML ) {
+				return match;
+			}
+
+			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+				if ( elem ) {
+					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+						if ( !inplace ) {
+							result.push( elem );
+						}
+
+					} else if ( inplace ) {
+						curLoop[i] = false;
+					}
+				}
+			}
+
+			return false;
+		},
+
+		ID: function( match ) {
+			return match[1].replace( rBackslash, "" );
+		},
+
+		TAG: function( match, curLoop ) {
+			return match[1].replace( rBackslash, "" ).toLowerCase();
+		},
+
+		CHILD: function( match ) {
+			if ( match[1] === "nth" ) {
+				if ( !match[2] ) {
+					Sizzle.error( match[0] );
+				}
+
+				match[2] = match[2].replace(/^\+|\s*/g, '');
+
+				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+				// calculate the numbers (first)n+(last) including if they are negative
+				match[2] = (test[1] + (test[2] || 1)) - 0;
+				match[3] = test[3] - 0;
+			}
+			else if ( match[2] ) {
+				Sizzle.error( match[0] );
+			}
+
+			// TODO: Move to normal caching system
+			match[0] = done++;
+
+			return match;
+		},
+
+		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+			var name = match[1] = match[1].replace( rBackslash, "" );
+			
+			if ( !isXML && Expr.attrMap[name] ) {
+				match[1] = Expr.attrMap[name];
+			}
+
+			// Handle if an un-quoted value was used
+			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+			if ( match[2] === "~=" ) {
+				match[4] = " " + match[4] + " ";
+			}
+
+			return match;
+		},
+
+		PSEUDO: function( match, curLoop, inplace, result, not ) {
+			if ( match[1] === "not" ) {
+				// If we're dealing with a complex expression, or a simple one
+				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+					match[3] = Sizzle(match[3], null, null, curLoop);
+
+				} else {
+					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+					if ( !inplace ) {
+						result.push.apply( result, ret );
+					}
+
+					return false;
+				}
+
+			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+				return true;
+			}
+			
+			return match;
+		},
+
+		POS: function( match ) {
+			match.unshift( true );
+
+			return match;
+		}
+	},
+	
+	filters: {
+		enabled: function( elem ) {
+			return elem.disabled === false && elem.type !== "hidden";
+		},
+
+		disabled: function( elem ) {
+			return elem.disabled === true;
+		},
+
+		checked: function( elem ) {
+			return elem.checked === true;
+		},
+		
+		selected: function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
+			
+			return elem.selected === true;
+		},
+
+		parent: function( elem ) {
+			return !!elem.firstChild;
+		},
+
+		empty: function( elem ) {
+			return !elem.firstChild;
+		},
+
+		has: function( elem, i, match ) {
+			return !!Sizzle( match[3], elem ).length;
+		},
+
+		header: function( elem ) {
+			return (/h\d/i).test( elem.nodeName );
+		},
+
+		text: function( elem ) {
+			var attr = elem.getAttribute( "type" ), type = elem.type;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 
+			// use getAttribute instead to test this case
+			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+		},
+
+		radio: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+		},
+
+		checkbox: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+		},
+
+		file: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+		},
+
+		password: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+		},
+
+		submit: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "submit" === elem.type;
+		},
+
+		image: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+		},
+
+		reset: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "reset" === elem.type;
+		},
+
+		button: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && "button" === elem.type || name === "button";
+		},
+
+		input: function( elem ) {
+			return (/input|select|textarea|button/i).test( elem.nodeName );
+		},
+
+		focus: function( elem ) {
+			return elem === elem.ownerDocument.activeElement;
+		}
+	},
+	setFilters: {
+		first: function( elem, i ) {
+			return i === 0;
+		},
+
+		last: function( elem, i, match, array ) {
+			return i === array.length - 1;
+		},
+
+		even: function( elem, i ) {
+			return i % 2 === 0;
+		},
+
+		odd: function( elem, i ) {
+			return i % 2 === 1;
+		},
+
+		lt: function( elem, i, match ) {
+			return i < match[3] - 0;
+		},
+
+		gt: function( elem, i, match ) {
+			return i > match[3] - 0;
+		},
+
+		nth: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		},
+
+		eq: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		}
+	},
+	filter: {
+		PSEUDO: function( elem, match, i, array ) {
+			var name = match[1],
+				filter = Expr.filters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+
+			} else if ( name === "contains" ) {
+				return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+			} else if ( name === "not" ) {
+				var not = match[3];
+
+				for ( var j = 0, l = not.length; j < l; j++ ) {
+					if ( not[j] === elem ) {
+						return false;
+					}
+				}
+
+				return true;
+
+			} else {
+				Sizzle.error( name );
+			}
+		},
+
+		CHILD: function( elem, match ) {
+			var type = match[1],
+				node = elem;
+
+			switch ( type ) {
+				case "only":
+				case "first":
+					while ( (node = node.previousSibling) )	 {
+						if ( node.nodeType === 1 ) { 
+							return false; 
+						}
+					}
+
+					if ( type === "first" ) { 
+						return true; 
+					}
+
+					node = elem;
+
+				case "last":
+					while ( (node = node.nextSibling) )	 {
+						if ( node.nodeType === 1 ) { 
+							return false; 
+						}
+					}
+
+					return true;
+
+				case "nth":
+					var first = match[2],
+						last = match[3];
+
+					if ( first === 1 && last === 0 ) {
+						return true;
+					}
+					
+					var doneName = match[0],
+						parent = elem.parentNode;
+	
+					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+						var count = 0;
+						
+						for ( node = parent.firstChild; node; node = node.nextSibling ) {
+							if ( node.nodeType === 1 ) {
+								node.nodeIndex = ++count;
+							}
+						} 
+
+						parent.sizcache = doneName;
+					}
+					
+					var diff = elem.nodeIndex - last;
+
+					if ( first === 0 ) {
+						return diff === 0;
+
+					} else {
+						return ( diff % first === 0 && diff / first >= 0 );
+					}
+			}
+		},
+
+		ID: function( elem, match ) {
+			return elem.nodeType === 1 && elem.getAttribute("id") === match;
+		},
+
+		TAG: function( elem, match ) {
+			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
+		},
+		
+		CLASS: function( elem, match ) {
+			return (" " + (elem.className || elem.getAttribute("class")) + " ")
+				.indexOf( match ) > -1;
+		},
+
+		ATTR: function( elem, match ) {
+			var name = match[1],
+				result = Expr.attrHandle[ name ] ?
+					Expr.attrHandle[ name ]( elem ) :
+					elem[ name ] != null ?
+						elem[ name ] :
+						elem.getAttribute( name ),
+				value = result + "",
+				type = match[2],
+				check = match[4];
+
+			return result == null ?
+				type === "!=" :
+				type === "=" ?
+				value === check :
+				type === "*=" ?
+				value.indexOf(check) >= 0 :
+				type === "~=" ?
+				(" " + value + " ").indexOf(check) >= 0 :
+				!check ?
+				value && result !== false :
+				type === "!=" ?
+				value !== check :
+				type === "^=" ?
+				value.indexOf(check) === 0 :
+				type === "$=" ?
+				value.substr(value.length - check.length) === check :
+				type === "|=" ?
+				value === check || value.substr(0, check.length + 1) === check + "-" :
+				false;
+		},
+
+		POS: function( elem, match, i, array ) {
+			var name = match[2],
+				filter = Expr.setFilters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+			}
+		}
+	}
+};
+
+var origPOS = Expr.match.POS,
+	fescape = function(all, num){
+		return "\\" + (num - 0 + 1);
+	};
+
+for ( var type in Expr.match ) {
+	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+	array = Array.prototype.slice.call( array, 0 );
+
+	if ( results ) {
+		results.push.apply( results, array );
+		return results;
+	}
+	
+	return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+	makeArray = function( array, results ) {
+		var i = 0,
+			ret = results || [];
+
+		if ( toString.call(array) === "[object Array]" ) {
+			Array.prototype.push.apply( ret, array );
+
+		} else {
+			if ( typeof array.length === "number" ) {
+				for ( var l = array.length; i < l; i++ ) {
+					ret.push( array[i] );
+				}
+
+			} else {
+				for ( ; array[i]; i++ ) {
+					ret.push( array[i] );
+				}
+			}
+		}
+
+		return ret;
+	};
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+			return a.compareDocumentPosition ? -1 : 1;
+		}
+
+		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+	};
+
+} else {
+	sortOrder = function( a, b ) {
+		// The nodes are identical, we can exit early
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// Fallback to using sourceIndex (in IE) if it's available on both nodes
+		} else if ( a.sourceIndex && b.sourceIndex ) {
+			return a.sourceIndex - b.sourceIndex;
+		}
+
+		var al, bl,
+			ap = [],
+			bp = [],
+			aup = a.parentNode,
+			bup = b.parentNode,
+			cur = aup;
+
+		// If the nodes are siblings (or identical) we can do a quick check
+		if ( aup === bup ) {
+			return siblingCheck( a, b );
+
+		// If no parents were found then the nodes are disconnected
+		} else if ( !aup ) {
+			return -1;
+
+		} else if ( !bup ) {
+			return 1;
+		}
+
+		// Otherwise they're somewhere else in the tree so we need
+		// to build up a full list of the parentNodes for comparison
+		while ( cur ) {
+			ap.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		cur = bup;
+
+		while ( cur ) {
+			bp.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		al = ap.length;
+		bl = bp.length;
+
+		// Start walking down the tree looking for a discrepancy
+		for ( var i = 0; i < al && i < bl; i++ ) {
+			if ( ap[i] !== bp[i] ) {
+				return siblingCheck( ap[i], bp[i] );
+			}
+		}
+
+		// We ended someplace up the tree so do a sibling check
+		return i === al ?
+			siblingCheck( a, bp[i], -1 ) :
+			siblingCheck( ap[i], b, 1 );
+	};
+
+	siblingCheck = function( a, b, ret ) {
+		if ( a === b ) {
+			return ret;
+		}
+
+		var cur = a.nextSibling;
+
+		while ( cur ) {
+			if ( cur === b ) {
+				return -1;
+			}
+
+			cur = cur.nextSibling;
+		}
+
+		return 1;
+	};
+}
+
+// Utility function for retreiving the text value of an array of DOM nodes
+Sizzle.getText = function( elems ) {
+	var ret = "", elem;
+
+	for ( var i = 0; elems[i]; i++ ) {
+		elem = elems[i];
+
+		// Get the text from text nodes and CDATA nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+			ret += elem.nodeValue;
+
+		// Traverse everything else, except comment nodes
+		} else if ( elem.nodeType !== 8 ) {
+			ret += Sizzle.getText( elem.childNodes );
+		}
+	}
+
+	return ret;
+};
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+	// We're going to inject a fake input element with a specified name
+	var form = document.createElement("div"),
+		id = "script" + (new Date()).getTime(),
+		root = document.documentElement;
+
+	form.innerHTML = "<a name='" + id + "'/>";
+
+	// Inject it into the root element, check its status, and remove it quickly
+	root.insertBefore( form, root.firstChild );
+
+	// The workaround has to do additional checks after a getElementById
+	// Which slows things down for other browsers (hence the branching)
+	if ( document.getElementById( id ) ) {
+		Expr.find.ID = function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+
+				return m ?
+					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+						[m] :
+						undefined :
+					[];
+			}
+		};
+
+		Expr.filter.ID = function( elem, match ) {
+			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+			return elem.nodeType === 1 && node && node.nodeValue === match;
+		};
+	}
+
+	root.removeChild( form );
+
+	// release memory in IE
+	root = form = null;
+})();
+
+(function(){
+	// Check to see if the browser returns only elements
+	// when doing getElementsByTagName("*")
+
+	// Create a fake element
+	var div = document.createElement("div");
+	div.appendChild( document.createComment("") );
+
+	// Make sure no comments are found
+	if ( div.getElementsByTagName("*").length > 0 ) {
+		Expr.find.TAG = function( match, context ) {
+			var results = context.getElementsByTagName( match[1] );
+
+			// Filter out possible comments
+			if ( match[1] === "*" ) {
+				var tmp = [];
+
+				for ( var i = 0; results[i]; i++ ) {
+					if ( results[i].nodeType === 1 ) {
+						tmp.push( results[i] );
+					}
+				}
+
+				results = tmp;
+			}
+
+			return results;
+		};
+	}
+
+	// Check to see if an attribute returns normalized href attributes
+	div.innerHTML = "<a href='#'></a>";
+
+	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+			div.firstChild.getAttribute("href") !== "#" ) {
+
+		Expr.attrHandle.href = function( elem ) {
+			return elem.getAttribute( "href", 2 );
+		};
+	}
+
+	// release memory in IE
+	div = null;
+})();
+
+if ( document.querySelectorAll ) {
+	(function(){
+		var oldSizzle = Sizzle,
+			div = document.createElement("div"),
+			id = "__sizzle__";
+
+		div.innerHTML = "<p class='TEST'></p>";
+
+		// Safari can't handle uppercase or unicode characters when
+		// in quirks mode.
+		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+			return;
+		}
+	
+		Sizzle = function( query, context, extra, seed ) {
+			context = context || document;
+
+			// Only use querySelectorAll on non-XML documents
+			// (ID selectors don't work in non-HTML documents)
+			if ( !seed && !Sizzle.isXML(context) ) {
+				// See if we find a selector to speed up
+				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+				
+				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+					// Speed-up: Sizzle("TAG")
+					if ( match[1] ) {
+						return makeArray( context.getElementsByTagName( query ), extra );
+					
+					// Speed-up: Sizzle(".CLASS")
+					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+						return makeArray( context.getElementsByClassName( match[2] ), extra );
+					}
+				}
+				
+				if ( context.nodeType === 9 ) {
+					// Speed-up: Sizzle("body")
+					// The body element only exists once, optimize finding it
+					if ( query === "body" && context.body ) {
+						return makeArray( [ context.body ], extra );
+						
+					// Speed-up: Sizzle("#ID")
+					} else if ( match && match[3] ) {
+						var elem = context.getElementById( match[3] );
+
+						// Check parentNode to catch when Blackberry 4.6 returns
+						// nodes that are no longer in the document #6963
+						if ( elem && elem.parentNode ) {
+							// Handle the case where IE and Opera return items
+							// by name instead of ID
+							if ( elem.id === match[3] ) {
+								return makeArray( [ elem ], extra );
+							}
+							
+						} else {
+							return makeArray( [], extra );
+						}
+					}
+					
+					try {
+						return makeArray( context.querySelectorAll(query), extra );
+					} catch(qsaError) {}
+
+				// qSA works strangely on Element-rooted queries
+				// We can work around this by specifying an extra ID on the root
+				// and working up from there (Thanks to Andrew Dupont for the technique)
+				// IE 8 doesn't work on object elements
+				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+					var oldContext = context,
+						old = context.getAttribute( "id" ),
+						nid = old || id,
+						hasParent = context.parentNode,
+						relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+					if ( !old ) {
+						context.setAttribute( "id", nid );
+					} else {
+						nid = nid.replace( /'/g, "\\$&" );
+					}
+					if ( relativeHierarchySelector && hasParent ) {
+						context = context.parentNode;
+					}
+
+					try {
+						if ( !relativeHierarchySelector || hasParent ) {
+							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+						}
+
+					} catch(pseudoError) {
+					} finally {
+						if ( !old ) {
+							oldContext.removeAttribute( "id" );
+						}
+					}
+				}
+			}
+		
+			return oldSizzle(query, context, extra, seed);
+		};
+
+		for ( var prop in oldSizzle ) {
+			Sizzle[ prop ] = oldSizzle[ prop ];
+		}
+
+		// release memory in IE
+		div = null;
+	})();
+}
+
+(function(){
+	var html = document.documentElement,
+		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+	if ( matches ) {
+		// Check to see if it's possible to do matchesSelector
+		// on a disconnected node (IE 9 fails this)
+		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+			pseudoWorks = false;
+
+		try {
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( document.documentElement, "[test!='']:sizzle" );
+	
+		} catch( pseudoError ) {
+			pseudoWorks = true;
+		}
+
+		Sizzle.matchesSelector = function( node, expr ) {
+			// Make sure that attribute selectors are quoted
+			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+			if ( !Sizzle.isXML( node ) ) {
+				try { 
+					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+						var ret = matches.call( node, expr );
+
+						// IE 9's matchesSelector returns false on disconnected nodes
+						if ( ret || !disconnectedMatch ||
+								// As well, disconnected nodes are said to be in a document
+								// fragment in IE 9, so check for that
+								node.document && node.document.nodeType !== 11 ) {
+							return ret;
+						}
+					}
+				} catch(e) {}
+			}
+
+			return Sizzle(expr, null, null, [node]).length > 0;
+		};
+	}
+})();
+
+(function(){
+	var div = document.createElement("div");
+
+	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+	// Opera can't find a second classname (in 9.6)
+	// Also, make sure that getElementsByClassName actually exists
+	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+		return;
+	}
+
+	// Safari caches class attributes, doesn't catch changes (in 3.2)
+	div.lastChild.className = "e";
+
+	if ( div.getElementsByClassName("e").length === 1 ) {
+		return;
+	}
+	
+	Expr.order.splice(1, 0, "CLASS");
+	Expr.find.CLASS = function( match, context, isXML ) {
+		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+			return context.getElementsByClassName(match[1]);
+		}
+	};
+
+	// release memory in IE
+	div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem.sizcache === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 && !isXML ){
+					elem.sizcache = doneName;
+					elem.sizset = i;
+				}
+
+				if ( elem.nodeName.toLowerCase() === cur ) {
+					match = elem;
+					break;
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+			
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem.sizcache === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 ) {
+					if ( !isXML ) {
+						elem.sizcache = doneName;
+						elem.sizset = i;
+					}
+
+					if ( typeof cur !== "string" ) {
+						if ( elem === cur ) {
+							match = true;
+							break;
+						}
+
+					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+						match = elem;
+						break;
+					}
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+if ( document.documentElement.contains ) {
+	Sizzle.contains = function( a, b ) {
+		return a !== b && (a.contains ? a.contains(b) : true);
+	};
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+	Sizzle.contains = function( a, b ) {
+		return !!(a.compareDocumentPosition(b) & 16);
+	};
+
+} else {
+	Sizzle.contains = function() {
+		return false;
+	};
+}
+
+Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833) 
+	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context ) {
+	var match,
+		tmpSet = [],
+		later = "",
+		root = context.nodeType ? [context] : context;
+
+	// Position selectors must be done after the filter
+	// And so must :not(positional) so we move all PSEUDOs to the end
+	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+		later += match[0];
+		selector = selector.replace( Expr.match.PSEUDO, "" );
+	}
+
+	selector = Expr.relative[selector] ? selector + "*" : selector;
+
+	for ( var i = 0, l = root.length; i < l; i++ ) {
+		Sizzle( selector, root[i], tmpSet );
+	}
+
+	return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+	rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+	// Note: This RegExp should be improved, or likely pulled from Sizzle
+	rmultiselector = /,/,
+	isSimple = /^.[^:#\[\.,]*$/,
+	slice = Array.prototype.slice,
+	POS = jQuery.expr.match.POS,
+	// methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var self = this,
+			i, l;
+
+		if ( typeof selector !== "string" ) {
+			return jQuery( selector ).filter(function() {
+				for ( i = 0, l = self.length; i < l; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			});
+		}
+
+		var ret = this.pushStack( "", "find", selector ),
+			length, n, r;
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			length = ret.length;
+			jQuery.find( selector, this[i], ret );
+
+			if ( i > 0 ) {
+				// Make sure that the results are unique
+				for ( n = length; n < ret.length; n++ ) {
+					for ( r = 0; r < length; r++ ) {
+						if ( ret[r] === ret[n] ) {
+							ret.splice(n--, 1);
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	has: function( target ) {
+		var targets = jQuery( target );
+		return this.filter(function() {
+			for ( var i = 0, l = targets.length; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[i] ) ) {
+					return true;
+				}
+			}
+		});
+	},
+
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector, false), "not", selector);
+	},
+
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector, true), "filter", selector );
+	},
+
+	is: function( selector ) {
+		return !!selector && ( typeof selector === "string" ?
+			jQuery.filter( selector, this ).length > 0 :
+			this.filter( selector ).length > 0 );
+	},
+
+	closest: function( selectors, context ) {
+		var ret = [], i, l, cur = this[0];
+		
+		// Array
+		if ( jQuery.isArray( selectors ) ) {
+			var match, selector,
+				matches = {},
+				level = 1;
+
+			if ( cur && selectors.length ) {
+				for ( i = 0, l = selectors.length; i < l; i++ ) {
+					selector = selectors[i];
+
+					if ( !matches[ selector ] ) {
+						matches[ selector ] = POS.test( selector ) ?
+							jQuery( selector, context || this.context ) :
+							selector;
+					}
+				}
+
+				while ( cur && cur.ownerDocument && cur !== context ) {
+					for ( selector in matches ) {
+						match = matches[ selector ];
+
+						if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) {
+							ret.push({ selector: selector, elem: cur, level: level });
+						}
+					}
+
+					cur = cur.parentNode;
+					level++;
+				}
+			}
+
+			return ret;
+		}
+
+		// String
+		var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			cur = this[i];
+
+			while ( cur ) {
+				if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+					ret.push( cur );
+					break;
+
+				} else {
+					cur = cur.parentNode;
+					if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+						break;
+					}
+				}
+			}
+		}
+
+		ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+		return this.pushStack( ret, "closest", selectors );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+		if ( !elem || typeof elem === "string" ) {
+			return jQuery.inArray( this[0],
+				// If it receives a string, the selector is used
+				// If it receives nothing, the siblings are used
+				elem ? jQuery( elem ) : this.parent().children() );
+		}
+		// Locate the position of the desired element
+		return jQuery.inArray(
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[0] : elem, this );
+	},
+
+	add: function( selector, context ) {
+		var set = typeof selector === "string" ?
+				jQuery( selector, context ) :
+				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+			all = jQuery.merge( this.get(), set );
+
+		return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+			all :
+			jQuery.unique( all ) );
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	}
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+	return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return jQuery.nth( elem, 2, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return jQuery.nth( elem, 2, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( elem.parentNode.firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return jQuery.nodeName( elem, "iframe" ) ?
+			elem.contentDocument || elem.contentWindow.document :
+			jQuery.makeArray( elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var ret = jQuery.map( this, fn, until ),
+			// The variable 'args' was introduced in
+			// https://github.com/jquery/jquery/commit/52a0238
+			// to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
+			// http://code.google.com/p/v8/issues/detail?id=1050
+			args = slice.call(arguments);
+
+		if ( !runtil.test( name ) ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			ret = jQuery.filter( selector, ret );
+		}
+
+		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+		if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+			ret = ret.reverse();
+		}
+
+		return this.pushStack( ret, name, args.join(",") );
+	};
+});
+
+jQuery.extend({
+	filter: function( expr, elems, not ) {
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 ?
+			jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+			jQuery.find.matches(expr, elems);
+	},
+
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			cur = elem[ dir ];
+
+		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+			if ( cur.nodeType === 1 ) {
+				matched.push( cur );
+			}
+			cur = cur[dir];
+		}
+		return matched;
+	},
+
+	nth: function( cur, result, dir, elem ) {
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] ) {
+			if ( cur.nodeType === 1 && ++num === result ) {
+				break;
+			}
+		}
+
+		return cur;
+	},
+
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				r.push( n );
+			}
+		}
+
+		return r;
+	}
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+	// Can't pass null or undefined to indexOf in Firefox 4
+	// Set to 0 to skip string check
+	qualifier = qualifier || 0;
+
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			var retVal = !!qualifier.call( elem, i, elem );
+			return retVal === keep;
+		});
+
+	} else if ( qualifier.nodeType ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			return (elem === qualifier) === keep;
+		});
+
+	} else if ( typeof qualifier === "string" ) {
+		var filtered = jQuery.grep(elements, function( elem ) {
+			return elem.nodeType === 1;
+		});
+
+		if ( isSimple.test( qualifier ) ) {
+			return jQuery.filter(qualifier, filtered, !keep);
+		} else {
+			qualifier = jQuery.filter( qualifier, filtered );
+		}
+	}
+
+	return jQuery.grep(elements, function( elem, i ) {
+		return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
+	});
+}
+
+
+
+
+var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+	rleadingWhitespace = /^\s+/,
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+	rtagName = /<([\w:]+)/,
+	rtbody = /<tbody/i,
+	rhtml = /<|&#?\w+;/,
+	rnocache = /<(?:script|object|embed|option|style)/i,
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /\/(java|ecma)script/i,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+	wrapMap = {
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+		legend: [ 1, "<fieldset>", "</fieldset>" ],
+		thead: [ 1, "<table>", "</table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+		area: [ 1, "<map>", "</map>" ],
+		_default: [ 0, "", "" ]
+	};
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+	wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+	text: function( text ) {
+		if ( jQuery.isFunction(text) ) {
+			return this.each(function(i) {
+				var self = jQuery( this );
+
+				self.text( text.call(this, i, self.text()) );
+			});
+		}
+
+		if ( typeof text !== "object" && text !== undefined ) {
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+		}
+
+		return jQuery.text( this );
+	},
+
+	wrapAll: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+			if ( this[0].parentNode ) {
+				wrap.insertBefore( this[0] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+					elem = elem.firstChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		return this.each(function() {
+			jQuery( this ).wrapAll( html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.insertBefore( elem, this.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this );
+			});
+		} else if ( arguments.length ) {
+			var set = jQuery(arguments[0]);
+			set.push.apply( set, this.toArray() );
+			return this.pushStack( set, "before", arguments );
+		}
+	},
+
+	after: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			});
+		} else if ( arguments.length ) {
+			var set = this.pushStack( this, "after", arguments );
+			set.push.apply( set, jQuery(arguments[0]).toArray() );
+			return set;
+		}
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+				if ( !keepData && elem.nodeType === 1 ) {
+					jQuery.cleanData( elem.getElementsByTagName("*") );
+					jQuery.cleanData( [ elem ] );
+				}
+
+				if ( elem.parentNode ) {
+					elem.parentNode.removeChild( elem );
+				}
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			// Remove element nodes and prevent memory leaks
+			if ( elem.nodeType === 1 ) {
+				jQuery.cleanData( elem.getElementsByTagName("*") );
+			}
+
+			// Remove any remaining nodes
+			while ( elem.firstChild ) {
+				elem.removeChild( elem.firstChild );
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		if ( value === undefined ) {
+			return this[0] && this[0].nodeType === 1 ?
+				this[0].innerHTML.replace(rinlinejQuery, "") :
+				null;
+
+		// See if we can take a shortcut and just use innerHTML
+		} else if ( typeof value === "string" && !rnocache.test( value ) &&
+			(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+			!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+			value = value.replace(rxhtmlTag, "<$1></$2>");
+
+			try {
+				for ( var i = 0, l = this.length; i < l; i++ ) {
+					// Remove element nodes and prevent memory leaks
+					if ( this[i].nodeType === 1 ) {
+						jQuery.cleanData( this[i].getElementsByTagName("*") );
+						this[i].innerHTML = value;
+					}
+				}
+
+			// If using innerHTML throws an exception, use the fallback method
+			} catch(e) {
+				this.empty().append( value );
+			}
+
+		} else if ( jQuery.isFunction( value ) ) {
+			this.each(function(i){
+				var self = jQuery( this );
+
+				self.html( value.call(this, i, self.html()) );
+			});
+
+		} else {
+			this.empty().append( value );
+		}
+
+		return this;
+	},
+
+	replaceWith: function( value ) {
+		if ( this[0] && this[0].parentNode ) {
+			// Make sure that the elements are removed from the DOM before they are inserted
+			// this can help fix replacing a parent with child elements
+			if ( jQuery.isFunction( value ) ) {
+				return this.each(function(i) {
+					var self = jQuery(this), old = self.html();
+					self.replaceWith( value.call( this, i, old ) );
+				});
+			}
+
+			if ( typeof value !== "string" ) {
+				value = jQuery( value ).detach();
+			}
+
+			return this.each(function() {
+				var next = this.nextSibling,
+					parent = this.parentNode;
+
+				jQuery( this ).remove();
+
+				if ( next ) {
+					jQuery(next).before( value );
+				} else {
+					jQuery(parent).append( value );
+				}
+			});
+		} else {
+			return this.length ?
+				this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+				this;
+		}
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, table, callback ) {
+		var results, first, fragment, parent,
+			value = args[0],
+			scripts = [];
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+			return this.each(function() {
+				jQuery(this).domManip( args, table, callback, true );
+			});
+		}
+
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				args[0] = value.call(this, i, table ? self.html() : undefined);
+				self.domManip( args, table, callback );
+			});
+		}
+
+		if ( this[0] ) {
+			parent = value && value.parentNode;
+
+			// If we're in a fragment, just use that instead of building a new one
+			if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+				results = { fragment: parent };
+
+			} else {
+				results = jQuery.buildFragment( args, this, scripts );
+			}
+
+			fragment = results.fragment;
+
+			if ( fragment.childNodes.length === 1 ) {
+				first = fragment = fragment.firstChild;
+			} else {
+				first = fragment.firstChild;
+			}
+
+			if ( first ) {
+				table = table && jQuery.nodeName( first, "tr" );
+
+				for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+					callback.call(
+						table ?
+							root(this[i], first) :
+							this[i],
+						// Make sure that we do not leak memory by inadvertently discarding
+						// the original fragment (which might have attached data) instead of
+						// using it; in addition, use the original fragment object for the last
+						// item instead of first because it can end up being emptied incorrectly
+						// in certain situations (Bug #8070).
+						// Fragments from the fragment cache must always be cloned and never used
+						// in place.
+						results.cacheable || (l > 1 && i < lastIndex) ?
+							jQuery.clone( fragment, true, true ) :
+							fragment
+					);
+				}
+			}
+
+			if ( scripts.length ) {
+				jQuery.each( scripts, evalScript );
+			}
+		}
+
+		return this;
+	}
+});
+
+function root( elem, cur ) {
+	return jQuery.nodeName(elem, "table") ?
+		(elem.getElementsByTagName("tbody")[0] ||
+		elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+		elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+		return;
+	}
+
+	var internalKey = jQuery.expando,
+		oldData = jQuery.data( src ),
+		curData = jQuery.data( dest, oldData );
+
+	// Switch to use the internal data object, if it exists, for the next
+	// stage of data copying
+	if ( (oldData = oldData[ internalKey ]) ) {
+		var events = oldData.events;
+				curData = curData[ internalKey ] = jQuery.extend({}, oldData);
+
+		if ( events ) {
+			delete curData.handle;
+			curData.events = {};
+
+			for ( var type in events ) {
+				for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
+					jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
+				}
+			}
+		}
+	}
+}
+
+function cloneFixAttributes( src, dest ) {
+	var nodeName;
+
+	// We do not need to do anything for non-Elements
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// clearAttributes removes the attributes, which we don't want,
+	// but also removes the attachEvent events, which we *do* want
+	if ( dest.clearAttributes ) {
+		dest.clearAttributes();
+	}
+
+	// mergeAttributes, in contrast, only merges back on the
+	// original attributes, not the events
+	if ( dest.mergeAttributes ) {
+		dest.mergeAttributes( src );
+	}
+
+	nodeName = dest.nodeName.toLowerCase();
+
+	// IE6-8 fail to clone children inside object elements that use
+	// the proprietary classid attribute value (rather than the type
+	// attribute) to identify the type of content to display
+	if ( nodeName === "object" ) {
+		dest.outerHTML = src.outerHTML;
+
+	} else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+		// IE6-8 fails to persist the checked state of a cloned checkbox
+		// or radio button. Worse, IE6-7 fail to give the cloned element
+		// a checked appearance if the defaultChecked value isn't also set
+		if ( src.checked ) {
+			dest.defaultChecked = dest.checked = src.checked;
+		}
+
+		// IE6-7 get confused and end up setting the value of a cloned
+		// checkbox/radio button to an empty string instead of "on"
+		if ( dest.value !== src.value ) {
+			dest.value = src.value;
+		}
+
+	// IE6-8 fails to return the selected option to the default selected
+	// state when cloning options
+	} else if ( nodeName === "option" ) {
+		dest.selected = src.defaultSelected;
+
+	// IE6-8 fails to set the defaultValue to the correct value when
+	// cloning other types of input fields
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+
+	// Event data gets referenced instead of copied if the expando
+	// gets copied too
+	dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+	var fragment, cacheable, cacheresults, doc;
+
+  // nodes may contain either an explicit document object,
+  // a jQuery collection or context object.
+  // If nodes[0] contains a valid object to assign to doc
+  if ( nodes && nodes[0] ) {
+    doc = nodes[0].ownerDocument || nodes[0];
+  }
+
+  // Ensure that an attr object doesn't incorrectly stand in as a document object
+	// Chrome and Firefox seem to allow this to occur and will throw exception
+	// Fixes #8950
+	if ( !doc.createDocumentFragment ) {
+		doc = document;
+	}
+
+	// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+	// Cloning options loses the selected state, so don't cache them
+	// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+	// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+	if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
+		args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+		cacheable = true;
+
+		cacheresults = jQuery.fragments[ args[0] ];
+		if ( cacheresults && cacheresults !== 1 ) {
+			fragment = cacheresults;
+		}
+	}
+
+	if ( !fragment ) {
+		fragment = doc.createDocumentFragment();
+		jQuery.clean( args, doc, fragment, scripts );
+	}
+
+	if ( cacheable ) {
+		jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
+	}
+
+	return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = [],
+			insert = jQuery( selector ),
+			parent = this.length === 1 && this[0].parentNode;
+
+		if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+			insert[ original ]( this[0] );
+			return this;
+
+		} else {
+			for ( var i = 0, l = insert.length; i < l; i++ ) {
+				var elems = (i > 0 ? this.clone(true) : this).get();
+				jQuery( insert[i] )[ original ]( elems );
+				ret = ret.concat( elems );
+			}
+
+			return this.pushStack( ret, name, insert.selector );
+		}
+	};
+});
+
+function getAll( elem ) {
+	if ( "getElementsByTagName" in elem ) {
+		return elem.getElementsByTagName( "*" );
+
+	} else if ( "querySelectorAll" in elem ) {
+		return elem.querySelectorAll( "*" );
+
+	} else {
+		return [];
+	}
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+	if ( elem.type === "checkbox" || elem.type === "radio" ) {
+		elem.defaultChecked = elem.checked;
+	}
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+	if ( jQuery.nodeName( elem, "input" ) ) {
+		fixDefaultChecked( elem );
+	} else if ( "getElementsByTagName" in elem ) {
+		jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+	}
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var clone = elem.cloneNode(true),
+				srcElements,
+				destElements,
+				i;
+
+		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+			// IE copies events bound via attachEvent when using cloneNode.
+			// Calling detachEvent on the clone will also remove the events
+			// from the original. In order to get around this, we use some
+			// proprietary methods to clear the events. Thanks to MooTools
+			// guys for this hotness.
+
+			cloneFixAttributes( elem, clone );
+
+			// Using Sizzle here is crazy slow, so we use getElementsByTagName
+			// instead
+			srcElements = getAll( elem );
+			destElements = getAll( clone );
+
+			// Weird iteration because IE will replace the length property
+			// with an element if you are cloning the body and one of the
+			// elements on the page has a name or id of "length"
+			for ( i = 0; srcElements[i]; ++i ) {
+				cloneFixAttributes( srcElements[i], destElements[i] );
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			cloneCopyEvent( elem, clone );
+
+			if ( deepDataAndEvents ) {
+				srcElements = getAll( elem );
+				destElements = getAll( clone );
+
+				for ( i = 0; srcElements[i]; ++i ) {
+					cloneCopyEvent( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		srcElements = destElements = null;
+
+		// Return the cloned set
+		return clone;
+	},
+
+	clean: function( elems, context, fragment, scripts ) {
+		var checkScriptType;
+
+		context = context || document;
+
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if ( typeof context.createElement === "undefined" ) {
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+		}
+
+		var ret = [], j;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( typeof elem === "number" ) {
+				elem += "";
+			}
+
+			if ( !elem ) {
+				continue;
+			}
+
+			// Convert html string into DOM nodes
+			if ( typeof elem === "string" ) {
+				if ( !rhtml.test( elem ) ) {
+					elem = context.createTextNode( elem );
+				} else {
+					// Fix "XHTML"-style tags in all browsers
+					elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+					// Trim whitespace, otherwise indexOf won't work as expected
+					var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+						wrap = wrapMap[ tag ] || wrapMap._default,
+						depth = wrap[0],
+						div = context.createElement("div");
+
+					// Go to html and back, then peel off extra wrappers
+					div.innerHTML = wrap[1] + elem + wrap[2];
+
+					// Move to the right depth
+					while ( depth-- ) {
+						div = div.lastChild;
+					}
+
+					// Remove IE's autoinserted <tbody> from table fragments
+					if ( !jQuery.support.tbody ) {
+
+						// String was a <table>, *may* have spurious <tbody>
+						var hasBody = rtbody.test(elem),
+							tbody = tag === "table" && !hasBody ?
+								div.firstChild && div.firstChild.childNodes :
+
+								// String was a bare <thead> or <tfoot>
+								wrap[1] === "<table>" && !hasBody ?
+									div.childNodes :
+									[];
+
+						for ( j = tbody.length - 1; j >= 0 ; --j ) {
+							if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+								tbody[ j ].parentNode.removeChild( tbody[ j ] );
+							}
+						}
+					}
+
+					// IE completely kills leading whitespace when innerHTML is used
+					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+						div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+					}
+
+					elem = div.childNodes;
+				}
+			}
+
+			// Resets defaultChecked for any radios and checkboxes
+			// about to be appended to the DOM in IE 6/7 (#8060)
+			var len;
+			if ( !jQuery.support.appendChecked ) {
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
+					for ( j = 0; j < len; j++ ) {
+						findInputs( elem[j] );
+					}
+				} else {
+					findInputs( elem );
+				}
+			}
+
+			if ( elem.nodeType ) {
+				ret.push( elem );
+			} else {
+				ret = jQuery.merge( ret, elem );
+			}
+		}
+
+		if ( fragment ) {
+			checkScriptType = function( elem ) {
+				return !elem.type || rscriptType.test( elem.type );
+			};
+			for ( i = 0; ret[i]; i++ ) {
+				if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+				} else {
+					if ( ret[i].nodeType === 1 ) {
+						var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
+
+						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+					}
+					fragment.appendChild( ret[i] );
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	cleanData: function( elems ) {
+		var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
+			deleteExpando = jQuery.support.deleteExpando;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+				continue;
+			}
+
+			id = elem[ jQuery.expando ];
+
+			if ( id ) {
+				data = cache[ id ] && cache[ id ][ internalKey ];
+
+				if ( data && data.events ) {
+					for ( var type in data.events ) {
+						if ( special[ type ] ) {
+							jQuery.event.remove( elem, type );
+
+						// This is a shortcut to avoid jQuery.event.remove's overhead
+						} else {
+							jQuery.removeEvent( elem, type, data.handle );
+						}
+					}
+
+					// Null the DOM reference to avoid IE6/7/8 leak (#7054)
+					if ( data.handle ) {
+						data.handle.elem = null;
+					}
+				}
+
+				if ( deleteExpando ) {
+					delete elem[ jQuery.expando ];
+
+				} else if ( elem.removeAttribute ) {
+					elem.removeAttribute( jQuery.expando );
+				}
+
+				delete cache[ id ];
+			}
+		}
+	}
+});
+
+function evalScript( i, elem ) {
+	if ( elem.src ) {
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+	} else {
+		jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+	}
+
+	if ( elem.parentNode ) {
+		elem.parentNode.removeChild( elem );
+	}
+}
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity=([^)]*)/,
+	// fixed for IE9, see #8346
+	rupper = /([A-Z]|^ms)/g,
+	rnumpx = /^-?\d+(?:px)?$/i,
+	rnum = /^-?\d/,
+	rrelNum = /^[+\-]=/,
+	rrelNumFilter = /[^+\-\.\de]+/g,
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssWidth = [ "Left", "Right" ],
+	cssHeight = [ "Top", "Bottom" ],
+	curCSS,
+
+	getComputedStyle,
+	currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+	// Setting 'undefined' is a no-op
+	if ( arguments.length === 2 && value === undefined ) {
+		return this;
+	}
+
+	return jQuery.access( this, name, value, true, function( elem, name, value ) {
+		return value !== undefined ?
+			jQuery.style( elem, name, value ) :
+			jQuery.css( elem, name );
+	});
+};
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity", "opacity" );
+					return ret === "" ? "1" : ret;
+
+				} else {
+					return elem.style.opacity;
+				}
+			}
+		}
+	},
+
+	// Exclude the following css properties to add px
+	cssNumber: {
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, origName = jQuery.camelCase( name ),
+			style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+		name = jQuery.cssProps[ origName ] || origName;
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( type === "number" && isNaN( value ) || value == null ) {
+				return;
+			}
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && rrelNum.test( value ) ) {
+				value = +value.replace( rrelNumFilter, "" ) + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+				// Fixes bug #5509
+				try {
+					style[ name ] = value;
+				} catch(e) {}
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra ) {
+		var ret, hooks;
+
+		// Make sure that we're working with the right name
+		name = jQuery.camelCase( name );
+		hooks = jQuery.cssHooks[ name ];
+		name = jQuery.cssProps[ name ] || name;
+
+		// cssFloat needs a special treatment
+		if ( name === "cssFloat" ) {
+			name = "float";
+		}
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+			return ret;
+
+		// Otherwise, if a way to get the computed value exists, use that
+		} else if ( curCSS ) {
+			return curCSS( elem, name );
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+	}
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			var val;
+
+			if ( computed ) {
+				if ( elem.offsetWidth !== 0 ) {
+					return getWH( elem, name, extra );
+				} else {
+					jQuery.swap( elem, cssShow, function() {
+						val = getWH( elem, name, extra );
+					});
+				}
+
+				return val;
+			}
+		},
+
+		set: function( elem, value ) {
+			if ( rnumpx.test( value ) ) {
+				// ignore negative width and height values #1599
+				value = parseFloat( value );
+
+				if ( value >= 0 ) {
+					return value + "px";
+				}
+
+			} else {
+				return value;
+			}
+		}
+	};
+});
+
+if ( !jQuery.support.opacity ) {
+	jQuery.cssHooks.opacity = {
+		get: function( elem, computed ) {
+			// IE uses filters for opacity
+			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+				( parseFloat( RegExp.$1 ) / 100 ) + "" :
+				computed ? "1" : "";
+		},
+
+		set: function( elem, value ) {
+			var style = elem.style,
+				currentStyle = elem.currentStyle;
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// Set the alpha filter to set the opacity
+			var opacity = jQuery.isNaN( value ) ?
+				"" :
+				"alpha(opacity=" + value * 100 + ")",
+				filter = currentStyle && currentStyle.filter || style.filter || "";
+
+			style.filter = ralpha.test( filter ) ?
+				filter.replace( ralpha, opacity ) :
+				filter + " " + opacity;
+		}
+	};
+}
+
+jQuery(function() {
+	// This hook cannot be added until DOM ready because the support test
+	// for it is not run until after DOM ready
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// Work around by temporarily setting element display to inline-block
+				var ret;
+				jQuery.swap( elem, { "display": "inline-block" }, function() {
+					if ( computed ) {
+						ret = curCSS( elem, "margin-right", "marginRight" );
+					} else {
+						ret = elem.style.marginRight;
+					}
+				});
+				return ret;
+			}
+		};
+	}
+});
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+	getComputedStyle = function( elem, name ) {
+		var ret, defaultView, computedStyle;
+
+		name = name.replace( rupper, "-$1" ).toLowerCase();
+
+		if ( !(defaultView = elem.ownerDocument.defaultView) ) {
+			return undefined;
+		}
+
+		if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+			ret = computedStyle.getPropertyValue( name );
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+		}
+
+		return ret;
+	};
+}
+
+if ( document.documentElement.currentStyle ) {
+	currentStyle = function( elem, name ) {
+		var left,
+			ret = elem.currentStyle && elem.currentStyle[ name ],
+			rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
+			style = elem.style;
+
+		// From the awesome hack by Dean Edwards
+		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+		// If we're not dealing with a regular pixel number
+		// but a number that has a weird ending, we need to convert it to pixels
+		if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+			// Remember the original values
+			left = style.left;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : (ret || 0);
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+
+	// Start with offset property
+	var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		which = name === "width" ? cssWidth : cssHeight;
+
+	if ( val > 0 ) {
+		if ( extra !== "border" ) {
+			jQuery.each( which, function() {
+				if ( !extra ) {
+					val -= parseFloat( jQuery.css( elem, "padding" + this ) ) || 0;
+				}
+				if ( extra === "margin" ) {
+					val += parseFloat( jQuery.css( elem, extra + this ) ) || 0;
+				} else {
+					val -= parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0;
+				}
+			});
+		}
+
+		return val + "px";
+	}
+
+	// Fall back to computed then uncomputed css if necessary
+	val = curCSS( elem, name, name );
+	if ( val < 0 || val == null ) {
+		val = elem.style[ name ] || 0;
+	}
+	// Normalize "", auto, and prepare for extra
+	val = parseFloat( val ) || 0;
+
+	// Add padding, border, margin
+	if ( extra ) {
+		jQuery.each( which, function() {
+			val += parseFloat( jQuery.css( elem, "padding" + this ) ) || 0;
+			if ( extra !== "padding" ) {
+				val += parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0;
+			}
+			if ( extra === "margin" ) {
+				val += parseFloat( jQuery.css( elem, extra + this ) ) || 0;
+			}
+		});
+	}
+
+	return val + "px";
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		var width = elem.offsetWidth,
+			height = elem.offsetHeight;
+
+		return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+
+
+
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rhash = /#.*$/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+	rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rquery = /\?/,
+	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+	rselectTextarea = /^(?:select|textarea)/i,
+	rspacesAjax = /\s+/,
+	rts = /([?&])_=[^&]*/,
+	rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Document location
+	ajaxLocation,
+
+	// Document location segments
+	ajaxLocParts;
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		if ( jQuery.isFunction( func ) ) {
+			var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+				i = 0,
+				length = dataTypes.length,
+				dataType,
+				list,
+				placeBefore;
+
+			// For each dataType in the dataTypeExpression
+			for(; i < length; i++ ) {
+				dataType = dataTypes[ i ];
+				// We control if we're asked to add before
+				// any existing element
+				placeBefore = /^\+/.test( dataType );
+				if ( placeBefore ) {
+					dataType = dataType.substr( 1 ) || "*";
+				}
+				list = structure[ dataType ] = structure[ dataType ] || [];
+				// then we add to the structure accordingly
+				list[ placeBefore ? "unshift" : "push" ]( func );
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+		dataType /* internal */, inspected /* internal */ ) {
+
+	dataType = dataType || options.dataTypes[ 0 ];
+	inspected = inspected || {};
+
+	inspected[ dataType ] = true;
+
+	var list = structure[ dataType ],
+		i = 0,
+		length = list ? list.length : 0,
+		executeOnly = ( structure === prefilters ),
+		selection;
+
+	for(; i < length && ( executeOnly || !selection ); i++ ) {
+		selection = list[ i ]( options, originalOptions, jqXHR );
+		// If we got redirected to another dataType
+		// we try there if executing only and not done already
+		if ( typeof selection === "string" ) {
+			if ( !executeOnly || inspected[ selection ] ) {
+				selection = undefined;
+			} else {
+				options.dataTypes.unshift( selection );
+				selection = inspectPrefiltersOrTransports(
+						structure, options, originalOptions, jqXHR, selection, inspected );
+			}
+		}
+	}
+	// If we're only executing or nothing was selected
+	// we try the catchall dataType if not done already
+	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+		selection = inspectPrefiltersOrTransports(
+				structure, options, originalOptions, jqXHR, "*", inspected );
+	}
+	// unnecessary when only executing (prefilters)
+	// but it'll be ignored by the caller in that case
+	return selection;
+}
+
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( typeof url !== "string" && _load ) {
+			return _load.apply( this, arguments );
+
+		// Don't do a request if no elements are being requested
+		} else if ( !this.length ) {
+			return this;
+		}
+
+		var off = url.indexOf( " " );
+		if ( off >= 0 ) {
+			var selector = url.slice( off, url.length );
+			url = url.slice( 0, off );
+		}
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params ) {
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = undefined;
+
+			// Otherwise, build a param string
+			} else if ( typeof params === "object" ) {
+				params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+				type = "POST";
+			}
+		}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			// Complete callback (responseText is used internally)
+			complete: function( jqXHR, status, responseText ) {
+				// Store the response as specified by the jqXHR object
+				responseText = jqXHR.responseText;
+				// If successful, inject the HTML into all the matched elements
+				if ( jqXHR.isResolved() ) {
+					// #4825: Get the actual response in case
+					// a dataFilter is present in ajaxSettings
+					jqXHR.done(function( r ) {
+						responseText = r;
+					});
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(responseText.replace(rscript, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						responseText );
+				}
+
+				if ( callback ) {
+					self.each( callback, [ responseText, status, jqXHR ] );
+				}
+			}
+		});
+
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+
+	serializeArray: function() {
+		return this.map(function(){
+			return this.elements ? jQuery.makeArray( this.elements ) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				( this.checked || rselectTextarea.test( this.nodeName ) ||
+					rinput.test( this.type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val, i ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+	jQuery.fn[ o ] = function( f ){
+		return this.bind( o, f );
+	};
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			type: method,
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	};
+});
+
+jQuery.extend({
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function ( target, settings ) {
+		if ( !settings ) {
+			// Only one parameter, we extend ajaxSettings
+			settings = target;
+			target = jQuery.extend( true, jQuery.ajaxSettings, settings );
+		} else {
+			// target was provided, we extend into it
+			jQuery.extend( true, target, jQuery.ajaxSettings, settings );
+		}
+		// Flatten fields we don't want deep extended
+		for( var field in { context: 1, url: 1 } ) {
+			if ( field in settings ) {
+				target[ field ] = settings[ field ];
+			} else if( field in jQuery.ajaxSettings ) {
+				target[ field ] = jQuery.ajaxSettings[ field ];
+			}
+		}
+		return target;
+	},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			text: "text/plain",
+			json: "application/json, text/javascript",
+			"*": "*/*"
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText"
+		},
+
+		// List of data converters
+		// 1) key format is "source_type destination_type" (a single space in-between)
+		// 2) the catchall symbol "*" can be used for source_type
+		converters: {
+
+			// Convert anything to text
+			"* text": window.String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		}
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events
+			// It's the callbackContext if one was provided in the options
+			// and if it's a DOM node or a jQuery collection
+			globalEventContext = callbackContext !== s &&
+				( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+						jQuery( callbackContext ) : jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery._Deferred(),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// ifModified key
+			ifModifiedKey,
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// transport
+			transport,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// The jqXHR state
+			state = 0,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Fake xhr
+			jqXHR = {
+
+				readyState: 0,
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( !state ) {
+						var lname = name.toLowerCase();
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match === undefined ? null : match;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					statusText = statusText || "abort";
+					if ( transport ) {
+						transport.abort( statusText );
+					}
+					done( 0, statusText );
+					return this;
+				}
+			};
+
+		// Callback for when everything is done
+		// It is defined here because jslint complains if it is declared
+		// at the end of the function (which would be more logical and readable)
+		function done( status, statusText, responses, headers ) {
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status ? 4 : 0;
+
+			var isSuccess,
+				success,
+				error,
+				response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+				lastModified,
+				etag;
+
+			// If successful, handle type chaining
+			if ( status >= 200 && status < 300 || status === 304 ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+
+					if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+						jQuery.lastModified[ ifModifiedKey ] = lastModified;
+					}
+					if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+						jQuery.etag[ ifModifiedKey ] = etag;
+					}
+				}
+
+				// If not modified
+				if ( status === 304 ) {
+
+					statusText = "notmodified";
+					isSuccess = true;
+
+				// If we have data
+				} else {
+
+					try {
+						success = ajaxConvert( s, response );
+						statusText = "success";
+						isSuccess = true;
+					} catch(e) {
+						// We have a parsererror
+						statusText = "parsererror";
+						error = e;
+					}
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if( !statusText || status ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = statusText;
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+						[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		// Attach deferreds
+		deferred.promise( jqXHR );
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+		jqXHR.complete = completeDeferred.done;
+
+		// Status-dependent callbacks
+		jqXHR.statusCode = function( map ) {
+			if ( map ) {
+				var tmp;
+				if ( state < 2 ) {
+					for( tmp in map ) {
+						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+					}
+				} else {
+					tmp = map[ jqXHR.status ];
+					jqXHR.then( tmp, tmp );
+				}
+			}
+			return this;
+		};
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+		// Determine if a cross-domain request is in order
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefiler, stop there
+		if ( state === 2 ) {
+			return false;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+			}
+
+			// Get ifModifiedKey before adding the anti-cache parameter
+			ifModifiedKey = s.url;
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+
+				var ts = jQuery.now(),
+					// try replacing _= if it is there
+					ret = s.url.replace( rts, "$1_=" + ts );
+
+				// if nothing was replaced, add timestamp to the end
+				s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			ifModifiedKey = ifModifiedKey || s.url;
+			if ( jQuery.lastModified[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+			}
+			if ( jQuery.etag[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+			}
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+				// Abort if not done already
+				jqXHR.abort();
+				return false;
+
+		}
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout( function(){
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch (e) {
+				// Propagate exception as error if not done
+				if ( status < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					jQuery.error( e );
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a, traditional ) {
+		var s = [],
+			add = function( key, value ) {
+				// If value is a function, invoke it and return its value
+				value = jQuery.isFunction( value ) ? value() : value;
+				s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+			};
+
+		// Set traditional to true for jQuery <= 1.3.2 behavior.
+		if ( traditional === undefined ) {
+			traditional = jQuery.ajaxSettings.traditional;
+		}
+
+		// If an array was passed in, assume that it is an array of form elements.
+		if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			});
+
+		} else {
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( var prefix in a ) {
+				buildParams( prefix, a[ prefix ], traditional, add );
+			}
+		}
+
+		// Return the resulting serialization
+		return s.join( "&" ).replace( r20, "+" );
+	}
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// If array item is non-scalar (array or object), encode its
+				// numeric index to resolve deserialization ambiguity issues.
+				// Note that rack (as of 1.0.0) can't currently deserialize
+				// nested arrays properly, and attempting to do so may cause
+				// a server error. Possible fixes are to modify rack's
+				// deserialization algorithm or to provide an option or flag
+				// to force array serialization to be shallow.
+				buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && obj != null && typeof obj === "object" ) {
+		// Serialize object item.
+		for ( var name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var contents = s.contents,
+		dataTypes = s.dataTypes,
+		responseFields = s.responseFields,
+		ct,
+		type,
+		finalDataType,
+		firstDataType;
+
+	// Fill responseXXX fields
+	for( type in responseFields ) {
+		if ( type in responses ) {
+			jqXHR[ responseFields[type] ] = responses[ type ];
+		}
+	}
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+	// Apply the dataFilter if provided
+	if ( s.dataFilter ) {
+		response = s.dataFilter( response, s.dataType );
+	}
+
+	var dataTypes = s.dataTypes,
+		converters = {},
+		i,
+		key,
+		length = dataTypes.length,
+		tmp,
+		// Current and previous dataTypes
+		current = dataTypes[ 0 ],
+		prev,
+		// Conversion expression
+		conversion,
+		// Conversion function
+		conv,
+		// Conversion functions (transitive conversion)
+		conv1,
+		conv2;
+
+	// For each dataType in the chain
+	for( i = 1; i < length; i++ ) {
+
+		// Create converters map
+		// with lowercased keys
+		if ( i === 1 ) {
+			for( key in s.converters ) {
+				if( typeof key === "string" ) {
+					converters[ key.toLowerCase() ] = s.converters[ key ];
+				}
+			}
+		}
+
+		// Get the dataTypes
+		prev = current;
+		current = dataTypes[ i ];
+
+		// If current is auto dataType, update it to prev
+		if( current === "*" ) {
+			current = prev;
+		// If no auto and dataTypes are actually different
+		} else if ( prev !== "*" && prev !== current ) {
+
+			// Get the converter
+			conversion = prev + " " + current;
+			conv = converters[ conversion ] || converters[ "* " + current ];
+
+			// If there is no direct converter, search transitively
+			if ( !conv ) {
+				conv2 = undefined;
+				for( conv1 in converters ) {
+					tmp = conv1.split( " " );
+					if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+						conv2 = converters[ tmp[1] + " " + current ];
+						if ( conv2 ) {
+							conv1 = converters[ conv1 ];
+							if ( conv1 === true ) {
+								conv = conv2;
+							} else if ( conv2 === true ) {
+								conv = conv1;
+							}
+							break;
+						}
+					}
+				}
+			}
+			// If we found no converter, dispatch an error
+			if ( !( conv || conv2 ) ) {
+				jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+			}
+			// If found converter is not an equivalence
+			if ( conv !== true ) {
+				// Convert with 1 or 2 converters accordingly
+				response = conv ? conv( response ) : conv2( conv1(response) );
+			}
+		}
+	}
+	return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+	jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		return jQuery.expando + "_" + ( jsc++ );
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
+		( typeof s.data === "string" );
+
+	if ( s.dataTypes[ 0 ] === "jsonp" ||
+		s.jsonp !== false && ( jsre.test( s.url ) ||
+				inspectData && jsre.test( s.data ) ) ) {
+
+		var responseContainer,
+			jsonpCallback = s.jsonpCallback =
+				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+			previous = window[ jsonpCallback ],
+			url = s.url,
+			data = s.data,
+			replace = "$1" + jsonpCallback + "$2";
+
+		if ( s.jsonp !== false ) {
+			url = url.replace( jsre, replace );
+			if ( s.url === url ) {
+				if ( inspectData ) {
+					data = data.replace( jsre, replace );
+				}
+				if ( s.data === data ) {
+					// Add callback manually
+					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+				}
+			}
+		}
+
+		s.url = url;
+		s.data = data;
+
+		// Install callback
+		window[ jsonpCallback ] = function( response ) {
+			responseContainer = [ response ];
+		};
+
+		// Clean-up function
+		jqXHR.always(function() {
+			// Set callback back to previous value
+			window[ jsonpCallback ] = previous;
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( previous ) ) {
+				window[ jsonpCallback ]( responseContainer[ 0 ] );
+			}
+		});
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( jsonpCallback + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Delegate to script
+		return "script";
+	}
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /javascript|ecmascript/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement( "script" );
+
+				script.async = "async";
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( head && script.parentNode ) {
+							head.removeChild( script );
+						}
+
+						// Dereference the script
+						script = undefined;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+				// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+				// This arises when a base node is used (#2709 and #4378).
+				head.insertBefore( script, head.firstChild );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( 0, 1 );
+				}
+			}
+		};
+	}
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+	xhrOnUnloadAbort = window.ActiveXObject ? function() {
+		// Abort all pending requests
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( 0, 1 );
+		}
+	} : false,
+	xhrId = 0,
+	xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+	} catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* Microsoft failed to properly
+	 * implement the XMLHttpRequest in IE7 (can't request local files),
+	 * so we use the ActiveXObject when it is available
+	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+	 * we need a fallback.
+	 */
+	function() {
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+	jQuery.extend( jQuery.support, {
+		ajax: !!xhr,
+		cors: !!xhr && ( "withCredentials" in xhr )
+	});
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// Get a new xhr
+					var xhr = s.xhr(),
+						handle,
+						i;
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
+					} catch( _ ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+
+						var status,
+							statusText,
+							responseHeaders,
+							responses,
+							xml;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occured
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									if ( xhrOnUnloadAbort ) {
+										delete xhrCallbacks[ handle ];
+									}
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+									responses = {};
+									xml = xhr.responseXML;
+
+									// Construct response list
+									if ( xml && xml.documentElement /* #4958 */ ) {
+										responses.xml = xml;
+									}
+									responses.text = xhr.responseText;
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
+							}
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
+							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
+						}
+					};
+
+					// if we're in sync mode or it's in cache
+					// and has been retrieved directly (IE6 & IE7)
+					// we need to manually fire the callback
+					if ( !s.async || xhr.readyState === 4 ) {
+						callback();
+					} else {
+						handle = ++xhrId;
+						if ( xhrOnUnloadAbort ) {
+							// Create the active xhrs callbacks list if needed
+							// and attach the unload handler
+							if ( !xhrCallbacks ) {
+								xhrCallbacks = {};
+								jQuery( window ).unload( xhrOnUnloadAbort );
+							}
+							// Add to list of active xhrs callbacks
+							xhrCallbacks[ handle ] = callback;
+						}
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback(0,1);
+					}
+				}
+			};
+		}
+	});
+}
+
+
+
+
+var elemdisplay = {},
+	iframe, iframeDoc,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+	timerId,
+	fxAttrs = [
+		// height animations
+		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+		// width animations
+		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+		// opacity animations
+		[ "opacity" ]
+	],
+	fxNow,
+	requestAnimationFrame = window.webkitRequestAnimationFrame ||
+		window.mozRequestAnimationFrame ||
+		window.oRequestAnimationFrame;
+
+jQuery.fn.extend({
+	show: function( speed, easing, callback ) {
+		var elem, display;
+
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("show", 3), speed, easing, callback);
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				elem = this[i];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					// Reset the inline display of this element to learn if it is
+					// being hidden by cascaded rules or not
+					if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+						display = elem.style.display = "";
+					}
+
+					// Set elements which have been overridden with display: none
+					// in a stylesheet to whatever the default browser style is
+					// for such an element
+					if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
+						jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
+					}
+				}
+			}
+
+			// Set the display of most of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				elem = this[i];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					if ( display === "" || display === "none" ) {
+						elem.style.display = jQuery._data(elem, "olddisplay") || "";
+					}
+				}
+			}
+
+			return this;
+		}
+	},
+
+	hide: function( speed, easing, callback ) {
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("hide", 3), speed, easing, callback);
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				if ( this[i].style ) {
+					var display = jQuery.css( this[i], "display" );
+
+					if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
+						jQuery._data( this[i], "olddisplay", display );
+					}
+				}
+			}
+
+			// Set the display of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				if ( this[i].style ) {
+					this[i].style.display = "none";
+				}
+			}
+
+			return this;
+		}
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+
+	toggle: function( fn, fn2, callback ) {
+		var bool = typeof fn === "boolean";
+
+		if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+			this._toggle.apply( this, arguments );
+
+		} else if ( fn == null || bool ) {
+			this.each(function() {
+				var state = bool ? fn : jQuery(this).is(":hidden");
+				jQuery(this)[ state ? "show" : "hide" ]();
+			});
+
+		} else {
+			this.animate(genFx("toggle", 3), fn, fn2, callback);
+		}
+
+		return this;
+	},
+
+	fadeTo: function( speed, to, easing, callback ) {
+		return this.filter(":hidden").css("opacity", 0).show().end()
+					.animate({opacity: to}, speed, easing, callback);
+	},
+
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed(speed, easing, callback);
+
+		if ( jQuery.isEmptyObject( prop ) ) {
+			return this.each( optall.complete, [ false ] );
+		}
+
+		// Do not change referenced properties as per-property easing will be lost
+		prop = jQuery.extend( {}, prop );
+
+		return this[ optall.queue === false ? "each" : "queue" ](function() {
+			// XXX 'this' does not always have a nodeName when running the
+			// test suite
+
+			if ( optall.queue === false ) {
+				jQuery._mark( this );
+			}
+
+			var opt = jQuery.extend( {}, optall ),
+				isElement = this.nodeType === 1,
+				hidden = isElement && jQuery(this).is(":hidden"),
+				name, val, p,
+				display, e,
+				parts, start, end, unit;
+
+			// will store per property easing and be used to determine when an animation is complete
+			opt.animatedProperties = {};
+
+			for ( p in prop ) {
+
+				// property name normalization
+				name = jQuery.camelCase( p );
+				if ( p !== name ) {
+					prop[ name ] = prop[ p ];
+					delete prop[ p ];
+				}
+
+				val = prop[ name ];
+
+				// easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+				if ( jQuery.isArray( val ) ) {
+					opt.animatedProperties[ name ] = val[ 1 ];
+					val = prop[ name ] = val[ 0 ];
+				} else {
+					opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+				}
+
+				if ( val === "hide" && hidden || val === "show" && !hidden ) {
+					return opt.complete.call( this );
+				}
+
+				if ( isElement && ( name === "height" || name === "width" ) ) {
+					// Make sure that nothing sneaks out
+					// Record all 3 overflow attributes because IE does not
+					// change the overflow attribute when overflowX and
+					// overflowY are set to the same value
+					opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+					// Set display property to inline-block for height/width
+					// animations on inline elements that are having width/height
+					// animated
+					if ( jQuery.css( this, "display" ) === "inline" &&
+							jQuery.css( this, "float" ) === "none" ) {
+						if ( !jQuery.support.inlineBlockNeedsLayout ) {
+							this.style.display = "inline-block";
+
+						} else {
+							display = defaultDisplay( this.nodeName );
+
+							// inline-level elements accept inline-block;
+							// block-level elements need to be inline with layout
+							if ( display === "inline" ) {
+								this.style.display = "inline-block";
+
+							} else {
+								this.style.display = "inline";
+								this.style.zoom = 1;
+							}
+						}
+					}
+				}
+			}
+
+			if ( opt.overflow != null ) {
+				this.style.overflow = "hidden";
+			}
+
+			for ( p in prop ) {
+				e = new jQuery.fx( this, opt, p );
+				val = prop[ p ];
+
+				if ( rfxtypes.test(val) ) {
+					e[ val === "toggle" ? hidden ? "show" : "hide" : val ]();
+
+				} else {
+					parts = rfxnum.exec( val );
+					start = e.cur();
+
+					if ( parts ) {
+						end = parseFloat( parts[2] );
+						unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+						// We need to compute starting value
+						if ( unit !== "px" ) {
+							jQuery.style( this, p, (end || 1) + unit);
+							start = ((end || 1) / e.cur()) * start;
+							jQuery.style( this, p, start + unit);
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] ) {
+							end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+						}
+
+						e.custom( start, end, unit );
+
+					} else {
+						e.custom( start, val, "" );
+					}
+				}
+			}
+
+			// For JS strict compliance
+			return true;
+		});
+	},
+
+	stop: function( clearQueue, gotoEnd ) {
+		if ( clearQueue ) {
+			this.queue([]);
+		}
+
+		this.each(function() {
+			var timers = jQuery.timers,
+				i = timers.length;
+			// clear marker counters if we know they won't be
+			if ( !gotoEnd ) {
+				jQuery._unmark( true, this );
+			}
+			while ( i-- ) {
+				if ( timers[i].elem === this ) {
+					if (gotoEnd) {
+						// force the next step to be the last
+						timers[i](true);
+					}
+
+					timers.splice(i, 1);
+				}
+			}
+		});
+
+		// start the next in the queue if the last step wasn't forced
+		if ( !gotoEnd ) {
+			this.dequeue();
+		}
+
+		return this;
+	}
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout( clearFxNow, 0 );
+	return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+	fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+	var obj = {};
+
+	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
+		obj[ this ] = type;
+	});
+
+	return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx("show", 1),
+	slideUp: genFx("hide", 1),
+	slideToggle: genFx("toggle", 1),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.extend({
+	speed: function( speed, easing, fn ) {
+		var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+		};
+
+		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+			opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
+
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function( noUnmark ) {
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+
+			if ( opt.queue !== false ) {
+				jQuery.dequeue( this );
+			} else if ( noUnmark !== false ) {
+				jQuery._unmark( this );
+			}
+		};
+
+		return opt;
+	},
+
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+
+	timers: [],
+
+	fx: function( elem, options, prop ) {
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		options.orig = options.orig || {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+	// Simple function for setting a style value
+	update: function() {
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+	},
+
+	// Get the current size
+	cur: function() {
+		if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
+			return this.elem[ this.prop ];
+		}
+
+		var parsed,
+			r = jQuery.css( this.elem, this.prop );
+		// Empty strings, null, undefined and "auto" are converted to 0,
+		// complex values such as "rotate(1rad)" are returned as is,
+		// simple values such as "10px" are parsed to Float.
+		return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+	},
+
+	// Start an animation from one number to another
+	custom: function( from, to, unit ) {
+		var self = this,
+			fx = jQuery.fx,
+			raf;
+
+		this.startTime = fxNow || createFxNow();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+		this.now = this.start;
+		this.pos = this.state = 0;
+
+		function t( gotoEnd ) {
+			return self.step(gotoEnd);
+		}
+
+		t.elem = this.elem;
+
+		if ( t() && jQuery.timers.push(t) && !timerId ) {
+			// Use requestAnimationFrame instead of setInterval if available
+			if ( requestAnimationFrame ) {
+				timerId = true;
+				raf = function() {
+					// When timerId gets set to null at any point, this stops
+					if ( timerId ) {
+						requestAnimationFrame( raf );
+						fx.tick();
+					}
+				};
+				requestAnimationFrame( raf );
+			} else {
+				timerId = setInterval( fx.tick, fx.interval );
+			}
+		}
+	},
+
+	// Simple 'show' function
+	show: function() {
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		// Make sure that we start at a small width/height to avoid any
+		// flash of content
+		this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
+
+		// Start by showing the element
+		jQuery( this.elem ).show();
+	},
+
+	// Simple 'hide' function
+	hide: function() {
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom(this.cur(), 0);
+	},
+
+	// Each step of an animation
+	step: function( gotoEnd ) {
+		var t = fxNow || createFxNow(),
+			done = true,
+			elem = this.elem,
+			options = this.options,
+			i, n;
+
+		if ( gotoEnd || t >= options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			options.animatedProperties[ this.prop ] = true;
+
+			for ( i in options.animatedProperties ) {
+				if ( options.animatedProperties[i] !== true ) {
+					done = false;
+				}
+			}
+
+			if ( done ) {
+				// Reset the overflow
+				if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+					jQuery.each( [ "", "X", "Y" ], function (index, value) {
+						elem.style[ "overflow" + value ] = options.overflow[index];
+					});
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( options.hide ) {
+					jQuery(elem).hide();
+				}
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( options.hide || options.show ) {
+					for ( var p in options.animatedProperties ) {
+						jQuery.style( elem, p, options.orig[p] );
+					}
+				}
+
+				// Execute the complete function
+				options.complete.call( elem );
+			}
+
+			return false;
+
+		} else {
+			// classical easing cannot be used with an Infinity duration
+			if ( options.duration == Infinity ) {
+				this.now = t;
+			} else {
+				n = t - this.startTime;
+				this.state = n / options.duration;
+
+				// Perform the easing function, defaults to swing
+				this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, options.duration );
+				this.now = this.start + ((this.end - this.start) * this.pos);
+			}
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+};
+
+jQuery.extend( jQuery.fx, {
+	tick: function() {
+		for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) {
+			if ( !timers[i]() ) {
+				timers.splice(i--, 1);
+			}
+		}
+
+		if ( !timers.length ) {
+			jQuery.fx.stop();
+		}
+	},
+
+	interval: 13,
+
+	stop: function() {
+		clearInterval( timerId );
+		timerId = null;
+	},
+
+	speeds: {
+		slow: 600,
+		fast: 200,
+		// Default speed
+		_default: 400
+	},
+
+	step: {
+		opacity: function( fx ) {
+			jQuery.style( fx.elem, "opacity", fx.now );
+		},
+
+		_default: function( fx ) {
+			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+				fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
+			} else {
+				fx.elem[ fx.prop ] = fx.now;
+			}
+		}
+	}
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+	if ( !elemdisplay[ nodeName ] ) {
+
+		var body = document.body,
+			elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+			display = elem.css( "display" );
+
+		elem.remove();
+
+		// If the simple way fails,
+		// get element's real default display by attaching it to a temp iframe
+		if ( display === "none" || display === "" ) {
+			// No iframe to use yet, so create it
+			if ( !iframe ) {
+				iframe = document.createElement( "iframe" );
+				iframe.frameBorder = iframe.width = iframe.height = 0;
+			}
+
+			body.appendChild( iframe );
+
+			// Create a cacheable copy of the iframe document on first call.
+			// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+			// document to it; WebKit & Firefox won't allow reusing the iframe document.
+			if ( !iframeDoc || !iframe.createElement ) {
+				iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+				iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
+				iframeDoc.close();
+			}
+
+			elem = iframeDoc.createElement( nodeName );
+
+			iframeDoc.body.appendChild( elem );
+
+			display = jQuery.css( elem, "display" );
+
+			body.removeChild( iframe );
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+	rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0], box;
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		try {
+			box = elem.getBoundingClientRect();
+		} catch(e) {}
+
+		var doc = elem.ownerDocument,
+			docElem = doc.documentElement;
+
+		// Make sure we're not dealing with a disconnected DOM node
+		if ( !box || !jQuery.contains( docElem, elem ) ) {
+			return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+		}
+
+		var body = doc.body,
+			win = getWindow(doc),
+			clientTop  = docElem.clientTop  || body.clientTop  || 0,
+			clientLeft = docElem.clientLeft || body.clientLeft || 0,
+			scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
+			scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+			top  = box.top  + scrollTop  - clientTop,
+			left = box.left + scrollLeft - clientLeft;
+
+		return { top: top, left: left };
+	};
+
+} else {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0];
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		jQuery.offset.initialize();
+
+		var computedStyle,
+			offsetParent = elem.offsetParent,
+			prevOffsetParent = elem,
+			doc = elem.ownerDocument,
+			docElem = doc.documentElement,
+			body = doc.body,
+			defaultView = doc.defaultView,
+			prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+			top = elem.offsetTop,
+			left = elem.offsetLeft;
+
+		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+			if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+				break;
+			}
+
+			computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+			top  -= elem.scrollTop;
+			left -= elem.scrollLeft;
+
+			if ( elem === offsetParent ) {
+				top  += elem.offsetTop;
+				left += elem.offsetLeft;
+
+				if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+					top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+					left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+				}
+
+				prevOffsetParent = offsetParent;
+				offsetParent = elem.offsetParent;
+			}
+
+			if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+				top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+				left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+			}
+
+			prevComputedStyle = computedStyle;
+		}
+
+		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+			top  += body.offsetTop;
+			left += body.offsetLeft;
+		}
+
+		if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+			top  += Math.max( docElem.scrollTop, body.scrollTop );
+			left += Math.max( docElem.scrollLeft, body.scrollLeft );
+		}
+
+		return { top: top, left: left };
+	};
+}
+
+jQuery.offset = {
+	initialize: function() {
+		var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
+			html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+
+		jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
+
+		container.innerHTML = html;
+		body.insertBefore( container, body.firstChild );
+		innerDiv = container.firstChild;
+		checkDiv = innerDiv.firstChild;
+		td = innerDiv.nextSibling.firstChild.firstChild;
+
+		this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+		this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+		checkDiv.style.position = "fixed";
+		checkDiv.style.top = "20px";
+
+		// safari subtracts parent border width here which is 5px
+		this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
+		checkDiv.style.position = checkDiv.style.top = "";
+
+		innerDiv.style.overflow = "hidden";
+		innerDiv.style.position = "relative";
+
+		this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+		this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
+
+		body.removeChild( container );
+		jQuery.offset.initialize = jQuery.noop;
+	},
+
+	bodyOffset: function( body ) {
+		var top = body.offsetTop,
+			left = body.offsetLeft;
+
+		jQuery.offset.initialize();
+
+		if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
+			top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+			left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+		}
+
+		return { top: top, left: left };
+	},
+
+	setOffset: function( elem, options, i ) {
+		var position = jQuery.css( elem, "position" );
+
+		// set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		var curElem = jQuery( elem ),
+			curOffset = curElem.offset(),
+			curCSSTop = jQuery.css( elem, "top" ),
+			curCSSLeft = jQuery.css( elem, "left" ),
+			calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+			props = {}, curPosition = {}, curTop, curLeft;
+
+		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if (options.top != null) {
+			props.top = (options.top - curOffset.top) + curTop;
+		}
+		if (options.left != null) {
+			props.left = (options.left - curOffset.left) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+	position: function() {
+		if ( !this[0] ) {
+			return null;
+		}
+
+		var elem = this[0],
+
+		// Get *real* offsetParent
+		offsetParent = this.offsetParent(),
+
+		// Get correct offsets
+		offset       = this.offset(),
+		parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+		// Subtract element margins
+		// note: when an element has margin: auto the offsetLeft and marginLeft
+		// are the same in Safari causing offset.left to incorrectly be 0
+		offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+		offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+		// Add offsetParent borders
+		parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+		parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+		// Subtract the two offsets
+		return {
+			top:  offset.top  - parentOffset.top,
+			left: offset.left - parentOffset.left
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || document.body;
+			while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+			return offsetParent;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+	var method = "scroll" + name;
+
+	jQuery.fn[ method ] = function( val ) {
+		var elem, win;
+
+		if ( val === undefined ) {
+			elem = this[ 0 ];
+
+			if ( !elem ) {
+				return null;
+			}
+
+			win = getWindow( elem );
+
+			// Return the scroll offset
+			return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+				jQuery.support.boxModel && win.document.documentElement[ method ] ||
+					win.document.body[ method ] :
+				elem[ method ];
+		}
+
+		// Set the scroll offset
+		return this.each(function() {
+			win = getWindow( this );
+
+			if ( win ) {
+				win.scrollTo(
+					!i ? val : jQuery( win ).scrollLeft(),
+					 i ? val : jQuery( win ).scrollTop()
+				);
+
+			} else {
+				this[ method ] = val;
+			}
+		});
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+	var type = name.toLowerCase();
+
+	// innerHeight and innerWidth
+	jQuery.fn[ "inner" + name ] = function() {
+		var elem = this[0];
+		return elem && elem.style ?
+			parseFloat( jQuery.css( elem, type, "padding" ) ) :
+			null;
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn[ "outer" + name ] = function( margin ) {
+		var elem = this[0];
+		return elem && elem.style ?
+			parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+			null;
+	};
+
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		var elem = this[0];
+		if ( !elem ) {
+			return size == null ? null : this;
+		}
+
+		if ( jQuery.isFunction( size ) ) {
+			return this.each(function( i ) {
+				var self = jQuery( this );
+				self[ type ]( size.call( this, i, self[ type ]() ) );
+			});
+		}
+
+		if ( jQuery.isWindow( elem ) ) {
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			// 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+			var docElemProp = elem.document.documentElement[ "client" + name ];
+			return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+				elem.document.body[ "client" + name ] || docElemProp;
+
+		// Get document width or height
+		} else if ( elem.nodeType === 9 ) {
+			// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+			return Math.max(
+				elem.documentElement["client" + name],
+				elem.body["scroll" + name], elem.documentElement["scroll" + name],
+				elem.body["offset" + name], elem.documentElement["offset" + name]
+			);
+
+		// Get or set width or height on the element
+		} else if ( size === undefined ) {
+			var orig = jQuery.css( elem, type ),
+				ret = parseFloat( orig );
+
+			return jQuery.isNaN( ret ) ? orig : ret;
+
+		// Set the width or height on the element (default to pixels if value is unitless)
+		} else {
+			return this.css( type, typeof size === "string" ? size : size + "px" );
+		}
+	};
+
+});
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+})(window);
diff --git a/examples/js_osd/osd.css b/examples/js_osd/osd.css
new file mode 100644
index 0000000..3de7a9f
--- /dev/null
+++ b/examples/js_osd/osd.css
@@ -0,0 +1,269 @@
+body
+{
+  font-family: Arial, sans-serif;
+  margin: 0px;
+  padding: 0px;
+  overflow: hidden;
+  height: 100%;
+  width: 100%;
+  font-size: 120%;
+}
+
+/* osd_container - main wrapper element */
+
+div#osd_container
+{
+  position: absolute;
+  width:90%;
+  height: 90%;
+  margin: 0%;
+  padding: 5%;
+  overflow: hidden;
+  background: #d8e0de; /* Old browsers */
+  background: -moz-linear-gradient(top, #d8e0de 0%, #aebfbc 22%, #99afab 33%, #8ea6a2 50%, #829d98 67%, #4e5c5a 82%, #0e0e0e 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d8e0de), color-stop(22%,#aebfbc), color-stop(33%,#99afab), color-stop(50%,#8ea6a2), color-stop(67%,#829d98), color-stop(82%,#4e5c5a), color-stop(100%,#0e0e0e)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#d8e0de', endColorstr='#0e0e0e',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* W3C */
+}
+
+/* header */
+
+div#header
+{
+  background-color:#222266;
+  font-size: 1.4em;
+  font-weight: bold;
+  color:#CDCDCD;
+  white-space: pre;
+  overflow: hidden;
+  padding: 2%;
+  -moz-border-radius-topleft:25px;
+  -moz-border-radius-topright:25px;
+  border-top-left-radius:25px;
+  border-top-right-radius:25px;
+}
+
+/* content - between header and colored buttons */
+
+div#content
+{
+  height: 50%;
+  padding: 1%;
+  margin: 0px;
+  overflow: auto;
+
+  background: rgb(222,239,255); /* Old browsers */
+  background: -moz-linear-gradient(top, rgba(222,239,255,1) 0%, rgba(152,190,222,1) 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(222,239,255,1)), color-stop(100%,rgba(152,190,222,1))); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#deefff', endColorstr='#98bede',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* W3C */
+}
+
+div#content ul
+{
+  padding: 0px;
+  margin: 0px;
+  list-style: none;
+}
+
+div#content ul li.item
+{
+  padding: 0% 1% 0% 1%;
+  margin: 0% 4% 0.5% 4%;
+  font-weight:bold;
+  font-size: 1em;
+  white-space: nowrap;
+}
+
+div#content ul li#selectedItem,
+div#content ul li.item:hover
+{
+  -moz-border-radius:25px;
+  -moz-border-radius:25px;
+}
+
+div#content ul li#selectedItem
+{
+  color: white;
+  background: #b5bdc8; /* Old browsers */
+  background: -moz-linear-gradient(top, #b5bdc8 0%, #828c95 36%, #28343b 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b5bdc8), color-stop(36%,#828c95), color-stop(100%,#28343b)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b5bdc8', endColorstr='#28343b',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* W3C */}
+
+/*div#content ul li.item:hover
+{
+  border-radius:25px;
+  border-radius:25px;
+  background: rgb(246,248,249);
+  background: -moz-linear-gradient(top, rgba(246,248,249,1) 0%, rgba(229,235,238,1) 50%, rgba(215,222,227,1) 51%, rgba(245,247,249,1) 100%);
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(246,248,249,1)), color-stop(50%,rgba(229,235,238,1)), color-stop(51%,rgba(215,222,227,1)), color-stop(100%,rgba(245,247,249,1)));
+  background: -webkit-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%);
+  background: -o-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%);
+  background: -ms-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f8f9', endColorstr='#f5f7f9',GradientType=0 );
+  background: linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%);
+}*/
+
+#content2
+{
+  position:absolute;
+  top: 75%;
+  bottom:5%;
+  left:4%;
+  right:4%;
+  -moz-border-radius:25px;
+  border-radius:25px;
+  color:#CDCDCD;
+  background-color:#222266;
+}
+
+#innercontent
+{
+  padding: 25px;
+}
+
+#eventtitle
+{
+  font-size: 25pt;
+}
+
+#eventsubtitle
+{
+  font-size: 15pt;
+}
+
+#eventtime
+{
+  font-size:15pt;
+  right: 7%;
+  top: 78%;
+  width: 20%;
+}
+
+div#color_buttons{
+  width: 96%;
+  padding: 0% 2% 0% 2%;
+  -moz-border-radius-bottomleft:25px;
+  -moz-border-radius-bottomright:25px;
+  border-bottom-left-radius:25px;
+  border-bottom-right-radius:25px;
+}
+
+div#message
+{
+  font-size: 0.8em;
+  color:#CDCDCD;
+  white-space: pre;
+  overflow: hidden;
+  padding: 2%;
+}
+
+
+div#header,
+div#message,
+div#color_buttons
+{
+  background: rgb(76,76,76); /* Old browsers */
+  background: -moz-linear-gradient(top, rgba(76,76,76,1) 0%, rgba(89,89,89,1) 12%, rgba(102,102,102,1) 25%, rgba(71,71,71,1) 39%, rgba(44,44,44,1) 50%, rgba(0,0,0,1) 51%, rgba(17,17,17,1) 60%, rgba(43,43,43,1) 76%, rgba(28,28,28,1) 91%, rgba(19,19,19,1) 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(76,76,76,1)), color-stop(12%,rgba(89,89,89,1)), color-stop(25%,rgba(102,102,102,1)), color-stop(39%,rgba(71,71,71,1)), color-stop(50%,rgba(44,44,44,1)), color-stop(51%,rgba(0,0,0,1)), color-stop(60%,rgba(17,17,17,1)), color-stop(76%,rgba(43,43,43,1)), color-stop(91%,rgba(28,28,28,1)), color-stop(100%,rgba(19,19,19,1))); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* W3C */
+}
+
+div#color_buttons div.active,
+div#color_buttons div.inactive
+{
+  float: left;
+  padding: 0%;
+  margin: 1%;
+  font-size: 1.2em;
+  width: 23%;
+  /*height: 60%;*/
+  overflow: hidden;
+  text-align: center;
+  -moz-border-radius:25px;
+  -moz-border-radius:25px;
+  border-radius:25px;
+  border-radius:25px;
+}
+
+div#color_buttons div.inactive
+{
+  background: rgb(149,149,149); /* Old browsers */
+  background: -moz-linear-gradient(top, rgba(149,149,149,1) 0%, rgba(13,13,13,1) 46%, rgba(1,1,1,1) 50%, rgba(10,10,10,1) 53%, rgba(78,78,78,1) 76%, rgba(56,56,56,1) 87%, rgba(27,27,27,1) 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(149,149,149,1)), color-stop(46%,rgba(13,13,13,1)), color-stop(50%,rgba(1,1,1,1)), color-stop(53%,rgba(10,10,10,1)), color-stop(76%,rgba(78,78,78,1)), color-stop(87%,rgba(56,56,56,1)), color-stop(100%,rgba(27,27,27,1))); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#959595', endColorstr='#1b1b1b',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* W3C */
+}
+
+div#color_buttons div#red.active
+{
+  background: #feccb1; /* Old browsers */
+  background: -moz-linear-gradient(top, #feccb1 0%, #f17432 50%, #ea5507 51%, #fb955e 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#feccb1), color-stop(50%,#f17432), color-stop(51%,#ea5507), color-stop(100%,#fb955e)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#feccb1', endColorstr='#fb955e',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* W3C */
+}
+
+div#color_buttons div#green.active
+{
+  background: #bfd255; /* Old browsers */
+  background: -moz-linear-gradient(top, #bfd255 0%, #8eb92a 50%, #72aa00 51%, #9ecb2d 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bfd255), color-stop(50%,#8eb92a), color-stop(51%,#72aa00), color-stop(100%,#9ecb2d)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#bfd255', endColorstr='#9ecb2d',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* W3C */
+}
+
+div#color_buttons div#yellow.active
+{
+  background: #fefcea; /* Old browsers */
+  background: -moz-linear-gradient(top, #fefcea 0%, #f1da36 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fefcea), color-stop(100%,#f1da36)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #fefcea 0%,#f1da36 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #fefcea 0%,#f1da36 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #fefcea 0%,#f1da36 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fefcea', endColorstr='#f1da36',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #fefcea 0%,#f1da36 100%); /* W3C */
+}
+
+div#color_buttons div#blue.active
+{
+  background: #ebf1f6; /* Old browsers */
+  background: -moz-linear-gradient(top, #ebf1f6 0%, #abd3ee 50%, #89c3eb 51%, #d5ebfb 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ebf1f6), color-stop(50%,#abd3ee), color-stop(51%,#89c3eb), color-stop(100%,#d5ebfb)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ebf1f6', endColorstr='#d5ebfb',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* W3C */
+}
+
+div#color_buttons br.clear{
+  clear: both;
+}
+
+div.invisible{
+  visibility: hidden;
+}
diff --git a/femon.cpp b/femon.cpp
new file mode 100644
index 0000000..dee5f02
--- /dev/null
+++ b/femon.cpp
@@ -0,0 +1,67 @@
+#include "femon.h"
+using namespace std;
+
+void FemonResponder::reply (std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply) {
+
+  QueryHandler::addHeader(reply);
+  QueryHandler q("/femon", request);
+
+  if ((femon = cPluginManager::GetPlugin("femon")) == NULL) {
+      reply.httpReturn(403U, "Femon Plugin is not available. Please install first");
+      return;
+  }
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET");
+      reply.httpReturn(200U, "OK");
+      return;
+  }
+
+  if (request.method() != "GET") {
+     reply.httpReturn(403U, "Only GET method is supported by the femon service");
+     return;
+  }
+
+  FemonService_v1_0 fe;
+  StreamExtension se(&out);
+  femon->Service("FemonService-v1.0", &fe);
+
+  if (q.isFormat(".json")) {
+    reply.addHeader("Content-Type", "application/json; charset=utf-8");
+    replyJson(se, fe);
+  } else {
+    reply.httpReturn(403U, "Supported formats: JSON");
+  }
+};
+
+void FemonResponder::replyJson(StreamExtension se, FemonService_v1_0& fe) {
+
+  esyslog("restfulapi: Reply JSON");
+  cxxtools::JsonSerializer serializer(*se.getBasicStream());
+  serializer.serialize(fe, "femonData");
+  serializer.finish();
+
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const FemonService_v1_0& fe)
+{
+  const char *name = fe.fe_name;
+  const char *status = fe.fe_status;
+
+  if (!name) {
+      name = "None";
+  }
+  if (!status) {
+      status = "Not available";
+  }
+
+  si.addMember("name") <<= name;
+  si.addMember("status") <<= status;
+  si.addMember("snr") <<= fe.fe_snr;
+  si.addMember("signal") <<= fe.fe_signal;
+  si.addMember("ber") <<= fe.fe_ber;
+  si.addMember("unc") <<= fe.fe_unc;
+  si.addMember("audio_bitrate") <<= fe.audio_bitrate;
+  si.addMember("video_bitrate") <<= fe.video_bitrate;
+  si.addMember("dolby_bitrate") <<= fe.dolby_bitrate;
+}
diff --git a/femon.h b/femon.h
new file mode 100644
index 0000000..15c5054
--- /dev/null
+++ b/femon.h
@@ -0,0 +1,29 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include <vdr/tools.h>
+#include "femon/femonservice.h"
+#include "tools.h"
+
+
+#ifndef FEMONRESTFULAPI_H
+#define FEMONRESTFULAPI_H
+
+class FemonResponder : public cxxtools::http::Responder {
+private:
+  cPlugin *femon;
+public:
+  explicit FemonResponder(cxxtools::http::Service& service)
+        : cxxtools::http::Responder(service) { };
+  virtual ~FemonResponder() {};
+  virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+  virtual void replyJson(StreamExtension se, FemonService_v1_0& fe);
+};
+
+typedef cxxtools::http::CachedService<FemonResponder> FemonService;
+
+void operator<<= (cxxtools::SerializationInfo& si, const FemonService_v1_0& fe);
+
+#endif
diff --git a/femon/femonservice.h b/femon/femonservice.h
new file mode 100644
index 0000000..a960b0a
--- /dev/null
+++ b/femon/femonservice.h
@@ -0,0 +1,26 @@
+/*
+ * Frontend Status Monitor plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __FEMONSERVICE_H
+#define __FEMONSERVICE_H
+
+#include <linux/dvb/frontend.h>
+
+struct FemonService_v1_0 {
+  cString fe_name;
+  cString fe_status;
+  uint16_t fe_snr;
+  uint16_t fe_signal;
+  uint32_t fe_ber;
+  uint32_t fe_unc;
+  double video_bitrate;
+  double audio_bitrate;
+  double dolby_bitrate;
+  };
+
+#endif //__FEMONSERVICE_H
+
diff --git a/info.cpp b/info.cpp
new file mode 100644
index 0000000..cdb20d3
--- /dev/null
+++ b/info.cpp
@@ -0,0 +1,222 @@
+#include "info.h"
+#include <vdr/videodir.h>
+using namespace std;
+
+void InfoResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler::addHeader(reply);
+  QueryHandler q("/info", request);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  if (request.method() != "GET") {
+     reply.httpReturn(403, "Only GET method is support by the remote control");
+     return;
+  }
+  StreamExtension se(&out);
+
+  if (q.isFormat(".xml")) {
+    reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+    replyXml(se);
+  } else if (q.isFormat(".json")) {
+    reply.addHeader("Content-Type", "application/json; charset=utf-8");
+    replyJson(se);
+  } else if (q.isFormat(".html")) { 
+    reply.addHeader("Content-Type", "text/html; charset=utf-8");
+    replyHtml(se);
+  }else {
+    reply.httpReturn(403, "Support formats: xml, json and html!");
+  }
+}
+
+void InfoResponder::replyHtml(StreamExtension& se)
+{
+  if ( !se.writeBinary(DOCUMENT_ROOT "API.html") ) {
+     se.write("Copy API.html to " DOCUMENT_ROOT);
+  }
+}
+
+void InfoResponder::replyJson(StreamExtension& se)
+{
+  time_t now = time(0);
+  StatusMonitor* statm = StatusMonitor::get();
+
+  cxxtools::JsonSerializer serializer(*se.getBasicStream());
+  serializer.serialize("0.0.1", "version");
+  serializer.serialize((int)now, "time");
+  
+  vector< struct SerService > services;
+  vector< RestfulService* > restful_services = RestfulServices::get()->Services(true, true);
+  struct SerService s;
+  for (size_t i = 0; i < restful_services.size(); i++) {
+     s.Path = StringExtension::UTF8Decode(restful_services[i]->Path());
+     s.Version = restful_services[i]->Version();
+     s.Internal = restful_services[i]->Internal();
+     services.push_back(s);
+  }
+
+  struct SerPluginList pl;
+
+  cPlugin* p = NULL;
+  int counter = 0;
+  while ( (p = cPluginManager::GetPlugin(counter)) != NULL ) {
+     struct SerPlugin sp;
+     sp.Name = StringExtension::UTF8Decode(p->Name());
+     sp.Version = StringExtension::UTF8Decode(p->Version());
+     pl.plugins.push_back(sp);
+     counter++;
+  }
+
+  serializer.serialize(services, "services");
+
+  if ( statm->getRecordingName().length() > 0 || statm->getRecordingFile().length() > 0 ) {
+     SerPlayerInfo pi;
+     pi.Name = StringExtension::UTF8Decode(statm->getRecordingName());
+     pi.FileName = StringExtension::UTF8Decode(statm->getRecordingFile());
+     serializer.serialize(pi, "video");
+  } else {
+     string channelid = "";
+     cChannel* channel = Channels.GetByNumber(statm->getChannel());
+     if (channel != NULL) { 
+        channelid = (const char*)channel->GetChannelID().ToString();
+        serializer.serialize(channelid, "channel");
+        cEvent* event = VdrExtension::getCurrentEventOnChannel(channel);
+                
+        string eventTitle = "";
+        int start_time = -1;
+        int duration = -1;
+        int eventId = -1;
+
+        if ( event != NULL) {
+           eventTitle = event->Title();
+           start_time = event->StartTime();
+           duration = event->Duration(),
+           eventId = (int)event->EventID();	   
+        }
+
+        serializer.serialize(eventId, "eventid");
+        serializer.serialize(start_time, "start_time");
+        serializer.serialize(duration, "duration");
+        serializer.serialize(StringExtension::UTF8Decode(eventTitle), "title");
+     }
+  }
+
+  SerDiskSpaceInfo ds;
+  ds.Description = cVideoDiskUsage::String(); //description call goes first, it calls HasChanged
+  ds.UsedPercent = cVideoDiskUsage::UsedPercent();
+  ds.FreeMB      = cVideoDiskUsage::FreeMB();
+  ds.FreeMinutes = cVideoDiskUsage::FreeMinutes();
+  serializer.serialize(ds, "diskusage");
+
+
+  serializer.serialize(pl, "vdr");
+  serializer.finish();  
+}
+
+void InfoResponder::replyXml(StreamExtension& se)
+{
+  time_t now = time(0);
+  StatusMonitor* statm = StatusMonitor::get();
+
+
+  se.writeXmlHeader();
+  se.write("<info xmlns=\"http://www.domain.org/restfulapi/2011/info-xml\">\n");
+  se.write(" <version>0.0.1</version>\n");
+  se.write(cString::sprintf(" <time>%i</time>\n", (int)now)); 
+  se.write(" <services>\n");
+  
+  vector< RestfulService* > restful_services = RestfulServices::get()->Services(true, true);
+  for (size_t i = 0; i < restful_services.size(); i++) {
+    se.write(cString::sprintf("  <service path=\"%s\"  version=\"%i\" internal=\"%s\" />\n", 
+              restful_services[i]->Path().c_str(),
+              restful_services[i]->Version(),
+              restful_services[i]->Internal() ? "true" : "false"));
+  }
+  se.write(" </services>\n");
+
+  
+  if ( statm->getRecordingName().length() > 0 || statm->getRecordingFile().length() > 0 ) {
+     se.write(cString::sprintf(" <video name=\"%s\">%s</video>\n", StringExtension::encodeToXml(statm->getRecordingName()).c_str(), StringExtension::encodeToXml(statm->getRecordingFile()).c_str()));
+  } else {
+     cChannel* channel = Channels.GetByNumber(statm->getChannel());
+     string channelid = "";
+     cEvent* event = NULL;
+     if (channel != NULL) { 
+        channelid = (const char*)channel->GetChannelID().ToString();
+        event = VdrExtension::getCurrentEventOnChannel(channel);  
+     }
+
+     se.write(cString::sprintf(" <channel>%s</channel>\n", channelid.c_str()));
+     if ( event != NULL) {
+        string eventTitle = "";
+        if ( event->Title() != NULL ) { eventTitle = event->Title(); }
+
+        se.write(cString::sprintf(" <eventid>%i</eventid>\n", event->EventID()));
+        se.write(cString::sprintf(" <start_time>%i</start_time>\n", (int)event->StartTime()));
+        se.write(cString::sprintf(" <duration>%i</duration>\n", (int)event->Duration()));
+        se.write(cString::sprintf(" <title>%s</title>\n", StringExtension::encodeToXml(eventTitle).c_str()));
+     }
+  }
+  SerDiskSpaceInfo ds;
+  ds.Description = cVideoDiskUsage::String(); //description call goes first, it calls HasChanged
+  ds.UsedPercent = cVideoDiskUsage::UsedPercent();
+  ds.FreeMB      = cVideoDiskUsage::FreeMB();
+  ds.FreeMinutes = cVideoDiskUsage::FreeMinutes();
+
+  se.write(" <diskusage>\n");
+  se.write(cString::sprintf("  <free_mb>%i</free_mb>\n", ds.FreeMB));
+  se.write(cString::sprintf("  <free_minutes>%i</free_minutes>\n", ds.FreeMinutes));
+  se.write(cString::sprintf("  <used_percent>%i</used_percent>\n", ds.UsedPercent));
+  se.write(cString::sprintf("  <description_localized>%s</description_localized>\n", ds.Description.c_str()));
+  se.write(" </diskusage>\n");
+
+  se.write(" <vdr>\n");
+  se.write("  <plugins>\n");
+ 
+  cPlugin* p = NULL; 
+  int counter = 0;
+  while ( (p = cPluginManager::GetPlugin(counter) ) != NULL ) {
+     se.write(cString::sprintf("   <plugin name=\"%s\" version=\"%s\" />\n", p->Name(), p->Version()));
+     counter++;
+  }
+
+  se.write("  </plugins>\n");
+  se.write(" </vdr>\n");
+
+  se.write("</info>");
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerService& s)
+{
+  si.addMember("path") <<= s.Path;
+  si.addMember("version") <<= s.Version;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerPlugin& p)
+{
+  si.addMember("name") <<= p.Name;
+  si.addMember("version") <<= p.Version;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerPluginList& pl)
+{
+  si.addMember("plugins") <<= pl.plugins;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerPlayerInfo& pi)
+{
+  si.addMember("name") <<= pi.Name;
+  si.addMember("filename") <<= pi.FileName;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerDiskSpaceInfo& ds)
+{
+  si.addMember("free_mb") <<= ds.FreeMB;
+  si.addMember("used_percent") <<= ds.UsedPercent;
+  si.addMember("free_minutes") <<= ds.FreeMinutes;
+  si.addMember("description_localized") <<= ds.Description;
+}
diff --git a/info.h b/info.h
new file mode 100644
index 0000000..fbd3ab3
--- /dev/null
+++ b/info.h
@@ -0,0 +1,61 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include "tools.h"
+#include <time.h>
+#include <vector>
+#include "statusmonitor.h"
+
+struct SerService
+{
+  cxxtools::String Path;
+  int Version;
+  bool Internal;
+};
+
+struct SerPlugin
+{
+  cxxtools::String Name;
+  cxxtools::String Version;
+};
+
+struct SerPluginList
+{
+  std::vector< struct SerPlugin > plugins;
+};
+
+struct SerPlayerInfo
+{
+  cxxtools::String Name;
+  cxxtools::String FileName;
+};
+
+struct SerDiskSpaceInfo
+{
+  int FreeMB;
+  int UsedPercent;
+  int FreeMinutes;
+  std::string Description;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerService& s);
+void operator<<= (cxxtools::SerializationInfo& si, const SerPlugin& p);
+void operator<<= (cxxtools::SerializationInfo& si, const SerPluginList& pl);
+void operator<<= (cxxtools::SerializationInfo& si, const SerPlayerInfo& pi);
+void operator<<= (cxxtools::SerializationInfo& si, const SerDiskSpaceInfo& ds);
+
+class InfoResponder : public cxxtools::http::Responder
+{
+  public:
+    explicit InfoResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service) { };
+    virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void replyHtml(StreamExtension& se);
+    virtual void replyJson(StreamExtension& se);
+    virtual void replyXml(StreamExtension& se);
+};
+
+typedef cxxtools::http::CachedService<InfoResponder> InfoService;
+
diff --git a/jsonparser.cpp b/jsonparser.cpp
new file mode 100644
index 0000000..b486930
--- /dev/null
+++ b/jsonparser.cpp
@@ -0,0 +1,416 @@
+#include "jsonparser.h"
+using namespace std;
+
+// --- JsonBase ----------------------------------------------------------
+
+JsonBase::JsonBase(int type)
+{
+  _type = type;
+}
+
+JsonBase::~JsonBase()
+{
+
+}
+
+bool JsonBase::IsObject()
+{
+  return _type == 0x01;
+}
+
+bool JsonBase::IsValue()
+{
+  return _type == 0x02;
+}
+ 
+bool JsonBase::IsBasicValue()
+{
+  return _type == 0x03;
+}
+
+bool JsonBase::IsArray()
+{
+  return _type == 0x04;
+}
+
+// --- JsonObject --------------------------------------------------------
+
+JsonObject::JsonObject() : JsonBase(0x01)
+{
+  
+}
+
+JsonObject::~JsonObject()
+{
+  while(_items.size() != 0)
+  {
+    JsonBase* last = _items.back();
+    _items.pop_back();
+    delete last;
+  }
+}
+
+void JsonObject::AddItem(JsonValue* item)
+{
+  _items.push_back(item);
+}
+
+JsonValue* JsonObject::GetItem(int i)
+{
+  if (i >= 0 && i < (int)_items.size()){
+     return (JsonValue*)_items[i];
+  }
+  return NULL;
+}
+
+JsonValue* JsonObject::GetItem(string str)
+{
+  for(int i=0;i<(int)_items.size();i++) {
+    if (_items[i]->IsValue()) {
+       JsonValue* jsonValue = _items[i];
+       if(jsonValue->Identifier() == str) {
+          return jsonValue;
+       }
+    }
+  }
+  return NULL;
+}
+
+JsonValue* JsonObject::GetItem(const char* str)
+{
+  return GetItem((string)str);
+}
+
+int JsonObject::CountItem()
+{
+  return (int)_items.size();
+}
+
+// --- JsonValue ---------------------------------------------------------
+
+JsonValue::JsonValue(string identifier) : JsonBase(0x02)
+{
+  _identifier = identifier;
+  _value = NULL;
+}
+
+JsonValue::~JsonValue()
+{
+  if (_value != NULL)
+  {
+     delete _value;
+  }
+}
+
+string JsonValue::Identifier()
+{
+  return _identifier;
+}
+
+void JsonValue::Value(JsonBase* value)
+{
+  _value = value;
+}
+
+JsonBase* JsonValue::Value()
+{
+  return _value;
+}
+
+// --- JsonBasicValue ----------------------------------------------------
+
+JsonBasicValue::JsonBasicValue(string value) : JsonBase(0x03)
+{
+  _str = value;
+  _type = 0x11;
+}
+
+JsonBasicValue::JsonBasicValue(double value) : JsonBase(0x03)
+{
+  _double = value;
+  _type = 0x12;
+}
+
+JsonBasicValue::JsonBasicValue(bool value) : JsonBase(0x03)
+{
+  _bool = value;
+  _type = 0x13;
+}
+
+JsonBasicValue::~JsonBasicValue()
+{
+
+}
+
+bool JsonBasicValue::IsString()
+{
+  return _type == 0x11;
+}
+
+bool JsonBasicValue::IsBool()
+{
+  return _type == 0x13;
+}
+
+bool JsonBasicValue::IsDouble()
+{
+  return _type == 0x12;
+}
+
+string JsonBasicValue::ValueAsString()
+{
+  return _str;
+}
+
+double JsonBasicValue::ValueAsDouble()
+{
+  return _double;
+}
+
+bool JsonBasicValue::ValueAsBool()
+{
+  return _bool;
+}
+
+// --- JsonArray --------------------------------------------------------
+
+JsonArray::JsonArray() : JsonBase(0x04)
+{
+
+}
+
+JsonArray::~JsonArray()
+{
+
+}
+
+void JsonArray::AddItem(JsonBase* item)
+{
+  _items.push_back(item);
+}
+
+JsonBase* JsonArray::GetItem(int i)
+{
+  if (i >= 0 && i < (int)_items.size())
+  {
+    return _items[i];
+  }
+  return NULL;
+}
+
+int JsonArray::CountItem()
+{
+  return (int)_items.size();
+}
+
+// --- JsonParser -------------------------------------------------------
+
+JsonParser::JsonParser()
+{
+  
+}
+
+JsonParser::~JsonParser()
+{
+
+}
+
+JsonObject* JsonParser::Parse(string str)
+{
+  bool found = false;
+  long position = 0;
+
+  QUOTATIONCHAR='"';
+
+  while(!found && (size_t)position < str.length()) {
+     if ( str[position] == '\'') {
+        QUOTATIONCHAR = '\'';
+        found = true;
+     } else if ( str[position] == '"' ){
+        found = true;
+     }
+     position++;
+  }
+
+  position = 0;
+  return ParseJsonObject(str.c_str(), str.length(), &position);
+}
+
+
+bool JsonParser::SkipEmpty(const char* data, long size, long* position)
+{
+  while(*position < size) {
+     if ( data[*position] != '\t' && 
+          data[*position] != '\n' && 
+          data[*position] != '\r' &&
+          data[*position] != ' ' && 
+          data[*position] != ':' && 
+          data[*position] != ',' ) 
+     {
+        return true;
+     } else {
+        (*position)++;
+     }
+  }
+  return false;
+}
+
+string JsonParser::ParseString(const char* data, long size, long* position)
+{
+  (*position)++; //ignore QUOTATIONMARK
+  bool escaped = false;
+  ostringstream str;
+
+  while(!(data[*position] == QUOTATIONCHAR && !escaped) && *position < size) {
+     switch(data[*position]) {
+       case '"':
+       case '\'': if ( QUOTATIONCHAR == data[*position]) 
+                  { if(escaped) str << data[*position]; }
+                  else { str << data[*position]; } 
+                  escaped = false; break;
+       case '\\': if(escaped) str << '\\'; else escaped = true; break;
+       default: str << data[*position]; escaped = false; break;
+     }
+     (*position)++;
+  }
+  (*position)++; //ignore QUOTATIONMARK again
+ 
+  return str.str();
+}
+
+JsonObject* JsonParser::ParseJsonObject(const char* data, long size, long* position)
+{
+  SkipEmpty(data, size, position);
+  if ( data[*position] != '{' ) return NULL;
+  (*position)++; //skip '{'
+  bool finished = false;
+  string error = "";
+  JsonObject* jsonObject = new JsonObject();
+  
+  while (!finished && error.length() == 0 && *position < size) {
+    SkipEmpty(data, size, position);
+    if ( data[*position] == '}' || data[*position] == 0 ) {
+       finished = true;
+       (*position)++; // skip '}'
+    } else {
+       string name = ParseString(data, size, position);
+      SkipEmpty(data, size, position);
+      JsonBase* item = NULL;
+
+      switch(data[*position]) {
+        case '{': item = ParseJsonObject(data, size, position); 
+                  break;
+        case '[': item = (JsonBase*)ParseArray(data, size, position);
+                  break;
+        case 't': 
+        case 'f': item = (JsonBase*)ParseBool(data, size, position);
+                  break;
+        case 'n': if ( data[(*position)+1] == 'u' && data[(*position)+2] == 'l' && data[(*position)+3] == 'l') {
+                     item = NULL; (*position) = (*position)+4;
+                     break;
+                  }
+        case '"':
+        case '\'': if ( data[*position] == QUOTATIONCHAR) {
+                      item = (JsonBase*)new JsonBasicValue(ParseString(data, size, position));
+                      break;
+                   }
+                   //go to default if char isn't the QUOTATIONCHAR
+        default:  if((int)data[*position] == 45 || ((int)data[*position] >= 48 && (int)data[*position] <= 57))
+                  {
+                    item = (JsonBase*)ParseDouble(data, size, position);
+                  } else {
+                    error = "parsing failed";
+     		    esyslog("restfulapi, jsonparser, error: %s, char: %c, pos: %ld", error.c_str(), data[*position], *position);
+                  }
+                  break;
+      }
+      if (error.length() == 0) {
+         JsonValue* jsonValue = new JsonValue(name);
+         jsonValue->Value(item);
+         jsonObject->AddItem(jsonValue);
+      } 
+    }
+  }
+  if ( error.length() > 0 ) {
+     delete jsonObject;
+     jsonObject = NULL;
+  }
+  return jsonObject;
+}
+
+JsonBasicValue* JsonParser::ParseBool(const char* data, long size, long* position)
+{
+  //dont check size because at least t/f are available which is enough until the next loop in ParseObject or ParseArray will check the length again :-)
+  if ( data[*position] == 't' ) {
+     (*position) += 4;
+     return new JsonBasicValue(true);
+  } else {
+     (*position) += 5;
+     return new JsonBasicValue(false);
+  }
+}
+
+JsonBasicValue* JsonParser::ParseDouble(const char* data, long size, long* position)
+{
+  ostringstream str;
+  while(((data[(*position)] >= 48 && data[(*position)] <= 57) || data[(*position)] == 45 || data[(*position)] == '.') && *position < size) {
+    str << data[(*position)];
+    (*position)++;
+  }
+  double nr = atof(str.str().c_str());
+  return new JsonBasicValue(nr);
+}
+
+JsonArray* JsonParser::ParseArray(const char* data, long size, long* position)
+{
+  SkipEmpty(data, size, position); 
+  if ( data[*position] != '[' ) return NULL;
+  (*position)++; //skip '[';
+  SkipEmpty(data, size, position);
+  JsonArray* jsonArray = new JsonArray();
+  bool finished = false;
+  string error = "";
+
+  while (!finished && *position < size && error == "") {
+    SkipEmpty(data, size, position);
+    JsonBase* item = NULL;
+    if (data[*position] != ']' && data[*position] != '\0') {
+       switch(data[*position]) {
+         case '{': item = (JsonBase*)ParseJsonObject(data, size, position);
+                   break;
+         case 'f': 
+         case 't': item = (JsonBase*)ParseBool(data, size, position);
+                   break;
+         case 'n': if ( data[(*position)+1] == 'u' && data[(*position)+2] == 'l' && data[(*position)+3] == 'l') {
+                      item = NULL; (*position) = (*position)+4;
+                      break;
+                   }
+         case '\'': 
+         case '"': if ( data[*position] == QUOTATIONCHAR ) {
+                      item = (JsonBase*)new JsonBasicValue(ParseString(data, size, position));
+                      break;
+                   }
+                   // go to default if it's not the QUOTATIONCHAR                   
+         default:  {
+                      if (data[*position] >= 48 && data[*position] <= 57) {
+                         item = (JsonBase*)ParseDouble(data, size, position);      
+                      } else {
+                         error = "Parsing array failed!";
+                         esyslog("restfulapi, jsonparser, error: %s, char: %c, pos: %ld", error.c_str(), data[*position], *position);
+                      }
+                   }
+                   break;
+       }
+       if ( error.length() == 0 && item != NULL ) {
+          jsonArray->AddItem(item);
+       } else if (item != NULL) {
+          delete item;
+       }
+    } else {
+       finished = true;
+    }
+  }
+
+  (*position)++; //skip ']'
+  return jsonArray;
+}
diff --git a/jsonparser.h b/jsonparser.h
new file mode 100644
index 0000000..bc36f25
--- /dev/null
+++ b/jsonparser.h
@@ -0,0 +1,102 @@
+#include <string>
+#include <vector>
+#include <sstream>
+#include <stdlib.h>
+#include <stdio.h>
+#include <vdr/plugin.h>
+
+#ifndef __JSONPARSER_H
+#define __JSONPARSER_H
+
+class JsonBase
+{
+  private:
+   int _type;
+  public:
+    JsonBase(int type);
+    ~JsonBase();
+    bool IsObject();
+    bool IsValue();
+    bool IsBasicValue();
+    bool IsArray();
+};
+
+
+class JsonValue : public JsonBase
+{
+  private:
+    std::string _identifier;
+    JsonBase* _value;
+  public:
+    JsonValue(std::string identifier);
+    ~JsonValue();
+    std::string Identifier();
+    void Value(JsonBase* value);
+    JsonBase* Value();
+};
+
+class JsonBasicValue : public JsonBase
+{
+  private:
+    std::string _str;
+    double _double;
+    bool _bool;
+    int _type;
+  public:
+    JsonBasicValue(std::string value);
+    JsonBasicValue(double value);
+    JsonBasicValue(bool value);
+    ~JsonBasicValue();
+    bool IsString();
+    bool IsBool();
+    bool IsDouble();
+    std::string ValueAsString();
+    double ValueAsDouble();
+    bool ValueAsBool();
+};
+
+class JsonArray : public JsonBase
+{
+  private:
+    std::vector< JsonBase* > _items;
+  public:
+    JsonArray();
+    ~JsonArray();
+    void AddItem(JsonBase* item);
+    JsonBase* GetItem(int i);
+    int CountItem();
+};
+
+class JsonObject : public JsonBase
+{
+  private:
+    std::vector< JsonValue* > _items;
+  public:
+    JsonObject();
+    ~JsonObject();
+    void AddItem(JsonValue* item);
+    JsonValue* GetItem(int i);
+    JsonValue* GetItem(std::string str);
+    JsonValue* GetItem(const char* str);
+    int CountItem();
+};
+
+class JsonParser
+{
+  public:
+    JsonParser();
+    ~JsonParser();
+    JsonObject* Parse(std::string str);
+  private:
+    char QUOTATIONCHAR;
+    bool SkipEmpty(const char* data, long size, long* position);
+    std::string ParseString(const char* data, long size, long* position);
+    JsonObject* ParseJsonObject(const char* data, long size, long* position);
+    JsonBasicValue* ParseBool(const char* data, long size, long* position);
+    JsonBasicValue* ParseDouble(const char* data, long size, long* position);
+    JsonArray* ParseArray(const char* data, long size, long* position);
+};
+
+#endif
+
+
diff --git a/osd.cpp b/osd.cpp
new file mode 100644
index 0000000..8139739
--- /dev/null
+++ b/osd.cpp
@@ -0,0 +1,364 @@
+#include "osd.h"
+using namespace std;
+
+void OsdResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler::addHeader(reply);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  QueryHandler q("/osd", request);
+
+  if ( request.method() != "GET" ) {
+     reply.httpReturn(403, "Only GET-method is supported!");
+     return;
+  }
+
+  BasicOsd* osd = StatusMonitor::get()->getOsd();
+
+  if ( osd == NULL ) {
+     if ( q.isFormat(".html") ) {
+        reply.addHeader("Content-Type", "text /html; charset=utf-8");
+        printEmptyHtml(out);
+        return;
+     } else {
+        reply.httpReturn(404, "No OSD opened!");
+        return;
+     }
+  }
+
+  string format = "";
+  if ( q.isFormat(".json") ) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     format = ".json";
+  } else if ( q.isFormat(".html") ) {
+     format = ".html";
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+  } else if ( q.isFormat(".xml") ) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     format = ".xml";
+  } else {
+     reply.httpReturn(403, "Resources are not available for the selected format. (Use: .json, .html or .xml)");
+     return;
+  }
+
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+
+  switch(osd->Type())
+  {
+     case 0x01: printTextOsd(out, (TextOsd*)osd, format, start_filter, limit_filter);
+                break;
+     case 0x02: { ChannelOsdWrapper* w = new ChannelOsdWrapper(&out);
+                  w->print((ChannelOsd*)osd, format);
+                  delete w; }
+                break;
+     case 0x03: { ProgrammeOsdWrapper* w = new ProgrammeOsdWrapper(&out);
+                  w->print((ProgrammeOsd*)osd, format);
+                  delete w; }
+                break;
+  }
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerTextOsdItem& o)
+{
+  si.addMember("content") <<= o.Content;
+  si.addMember("is_selected") <<= o.Selected;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerTextOsd& o)
+{
+  si.addMember("type") <<= "TextOsd";
+  si.addMember("title") <<= o.Title;
+  si.addMember("message") <<= o.Message;
+  si.addMember("red") <<= o.Red;
+  si.addMember("green") <<= o.Green;
+  si.addMember("yellow") <<= o.Yellow;
+  si.addMember("blue") <<= o.Blue;
+
+  si.addMember("items") <<= o.ItemContainer->items;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerProgrammeOsd& o)
+{
+  si.addMember("present_time") <<= o.PresentTime;
+  si.addMember("present_title") <<= o.PresentTitle;
+  si.addMember("present_subtitle") <<= o.PresentSubtitle;
+  si.addMember("following_time") <<= o.FollowingTime;
+  si.addMember("following_title") <<= o.FollowingTitle;
+  si.addMember("following_subtitle") <<= o.FollowingSubtitle;
+}
+
+void OsdResponder::printEmptyHtml(ostream& out)
+{
+  StreamExtension se(&out);
+
+  HtmlHeader htmlHeader;
+  htmlHeader.Title("VDR Restfulapi: No OSD opened!");
+  htmlHeader.Stylesheet(DOCUMENT_ROOT "osd.css");
+  htmlHeader.Script(DOCUMENT_ROOT "osd.js");
+  htmlHeader.MetaTag("<meta http-equiv=\"refresh\" content=\"1\" />");
+  htmlHeader.ToStream(&se);
+
+  se.write("\n<div id=\"osd_bg\"><div id=\"osd_container\"> </div></div>\n");
+  se.write("</body></html>");
+}
+
+void OsdResponder::printTextOsd(ostream& out, TextOsd* osd, string format, int start_filter, int limit_filter)
+{
+  TextOsdList* osdList = NULL;
+
+  if ( format == ".json" ) {
+     osdList = (TextOsdList*)new JsonTextOsdList(&out);
+  } else if ( format == ".xml" ) {
+     osdList = (TextOsdList*)new XmlTextOsdList(&out);
+  } else if ( format == ".html" ) {
+     osdList = (TextOsdList*)new HtmlTextOsdList(&out);
+  }
+
+  if ( osdList != NULL ) {
+     if (start_filter >= 0 && limit_filter >= 1 ) {
+        osdList->activateLimit(start_filter, limit_filter);
+     }
+     osdList->printTextOsd(osd);
+  }
+}
+
+// --- XmlTextOsdList --------------------------------------------------------------------------------
+
+void XmlTextOsdList::printTextOsd(TextOsd* osd)
+{
+  s->writeXmlHeader();
+  s->write("<TextOsd xmlns=\"http://www.domain.org/restfulapi/2011/TextOsd-xml\">\n");
+  s->write(cString::sprintf(" <title>%s</title>\n", StringExtension::encodeToXml(osd->Title()).c_str()));
+  s->write(cString::sprintf(" <message>%s</message>\n", StringExtension::encodeToXml(osd->Message()).c_str()));
+  s->write(cString::sprintf(" <red>%s</red>\n", StringExtension::encodeToXml(osd->Red()).c_str()));
+  s->write(cString::sprintf(" <green>%s</green>\n", StringExtension::encodeToXml(osd->Green()).c_str()));
+  s->write(cString::sprintf(" <yellow>%s</yellow>\n", StringExtension::encodeToXml(osd->Yellow()).c_str()));
+  s->write(cString::sprintf(" <blue>%s</blue>\n", StringExtension::encodeToXml(osd->Blue()).c_str()));
+
+  list<TextOsdItem*>::iterator it;
+  list<TextOsdItem*> items = osd->GetItems();
+
+  s->write(" <items>\n");
+  for( it = items.begin(); it != items.end(); ++it ) {
+    if (!filtered()) {
+       const char* selected = (*it) == osd->Selected() ? "true" : "false";
+       TextOsdItem* item = *it;
+       s->write(cString::sprintf(" <item selected=\"%s\">%s</item>\n", selected, StringExtension::encodeToXml(item->Text()).c_str()));
+    }
+  }
+  s->write(" </items>\n");
+  s->write("</TextOsd>\n");
+}
+
+// --- JsonTextOsdList -------------------------------------------------------------------------------
+
+void JsonTextOsdList::printTextOsd(TextOsd* textOsd)
+{
+  SerTextOsd t;
+
+  t.Title = StringExtension::UTF8Decode(textOsd->Title());
+  t.Message = StringExtension::UTF8Decode(textOsd->Message());
+  t.Red = StringExtension::UTF8Decode(textOsd->Red());
+  t.Green = StringExtension::UTF8Decode(textOsd->Green());
+  t.Yellow = StringExtension::UTF8Decode(textOsd->Yellow());
+  t.Blue = StringExtension::UTF8Decode(textOsd->Blue());
+
+  SerTextOsdItemContainer* itemContainer = new SerTextOsdItemContainer();
+
+  list<TextOsdItem*>::iterator it;
+  list<TextOsdItem*> items = textOsd->GetItems();
+
+  for(it = items.begin(); it != items.end(); ++it)
+  {
+    if (!filtered()) {
+       SerTextOsdItem sitem;
+       sitem.Content = cxxtools::String(StringExtension::UTF8Decode((*it)->Text()));
+       sitem.Selected = (*it) == textOsd->Selected() ? true : false;
+       itemContainer->items.push_back(sitem);
+    }
+  }
+
+  t.ItemContainer = itemContainer;;
+
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(t, "TextOsd");
+  serializer.finish();
+
+  //delete itemsArray;
+  delete itemContainer;
+}
+
+// --- HtmlTextOsdList -------------------------------------------------------------------------------
+
+void HtmlTextOsdList::printTextOsd(TextOsd* textOsd)
+{
+  HtmlHeader htmlHeader;
+  htmlHeader.Title("HtmlTextOsdList");
+  htmlHeader.Stylesheet(DOCUMENT_ROOT "osd.css");
+  htmlHeader.Script(DOCUMENT_ROOT "osd.js");
+  htmlHeader.MetaTag("<meta http-equiv=\"refresh\" content=\"1\" />");
+  htmlHeader.ToStream(s);
+
+  s->write("\n<div id=\"osd_bg\"><div id=\"osd_container\">");
+  s->write("\n<div id=\"header\">");
+    if (textOsd->Title().length() > 0)
+       s->write(textOsd->Title().c_str());
+    if (textOsd->Message().length() > 0) {
+       s->write("\n");
+       s->write(textOsd->Message().c_str());
+    }
+
+  s->write("</div><!-- closing header container -->\n");
+
+  s->write("<div id=\"content\">\n");
+    list< TextOsdItem* > items = textOsd->GetItems();
+    list< TextOsdItem* >::iterator it;
+    s->write("<ul type=\"none\">\n");
+    for(it = items.begin(); it != items.end(); ++it) {
+       if (!filtered()) {
+          s->write("<li class=\"item\"");
+          s->write((*it) == textOsd->Selected() ? " id=\"selectedItem\">" : ">" );
+          s->write((*it)->Text().c_str());
+          s->write("</li>\n");
+       }
+    }
+  s->write("</ul>\n");
+  s->write("</div><!-- closing content container -->\n");
+
+  s->write("<div id=\"color_buttons\">\n");
+  if (textOsd->Red().length() > 0)
+     s->write(cString::sprintf("<div id=\"red\" class=\"first active\">%s</div>\n", textOsd->Red().c_str()));
+  else
+     s->write("<div id=\"red\" class=\"first inactive\"> </div>\n");
+
+  if (textOsd->Green().length() > 0)
+     s->write(cString::sprintf("<div id=\"green\" class=\"second active\">%s</div>\n", textOsd->Green().c_str()));
+  else
+     s->write("<div id=\"green\" class=\"second inactive\"> </div>\n");
+
+  if (textOsd->Yellow().length() > 0)
+     s->write(cString::sprintf("<div id=\"yellow\" class=\"third active\">%s</div>\n", textOsd->Yellow().c_str()));
+  else
+     s->write("<div id=\"yellow\" class=\"third inactive\"> </div>\n");
+
+  if (textOsd->Blue().length() > 0)
+     s->write(cString::sprintf("<div id=\"blue\" class=\"fourth active\">%s</div>\n", textOsd->Blue().c_str()));
+  else
+     s->write("<div id=\"blue\" class=\"fourth inactive\"> </div>\n");
+
+  s->write("<br class=\"clear\">\n</div><!-- closing color_buttons container -->\n");
+  s->write("</div></div><!-- closing osd_container -->\n");
+  s->write("</body></html>");
+}
+
+// --- ProgrammeOsdWrapper ---------------------------------------------------------------------------
+
+void ProgrammeOsdWrapper::print(ProgrammeOsd* osd, string format)
+{
+  if ( format == ".json" ) {
+     printJson(osd);
+  } else if ( format == ".html" ) {
+     printHtml(osd);
+  } else if ( format == ".xml") {
+     printXml(osd);
+  }
+}
+
+void ProgrammeOsdWrapper::printXml(ProgrammeOsd* osd)
+{
+  s->writeXmlHeader();
+  s->write("<ProgrammeOsd xmlns=\"http://www.domain.org/restfulapi/2011/ProgrammeOsd-xml\">\n");
+  s->write(cString::sprintf(" <presenttime>%i</presenttime>\n", (int)osd->PresentTime()));
+  s->write(cString::sprintf(" <presenttitle>%s</presenttitle>\n", StringExtension::encodeToXml(osd->PresentTitle()).c_str()));
+  s->write(cString::sprintf(" <presentsubtitle>%s</presentsubtitle>\n", StringExtension::encodeToXml(osd->PresentSubtitle()).c_str()));
+  s->write(cString::sprintf(" <followingtime>%i</followingtime>\n", (int)osd->FollowingTime()));
+  s->write(cString::sprintf(" <followingtitle>%s</followingtitle>\n", StringExtension::encodeToXml(osd->FollowingTitle()).c_str()));
+  s->write(cString::sprintf(" <followingsubtitle>%s</followingsubtitle>\n", StringExtension::encodeToXml(osd->FollowingSubtitle()).c_str()));
+  s->write("</ProgrammeOsd>\n");
+}
+
+void ProgrammeOsdWrapper::printJson(ProgrammeOsd* osd)
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  SerProgrammeOsd p;
+  p.PresentTime = osd->PresentTime();
+  p.PresentTitle = StringExtension::UTF8Decode(osd->PresentTitle());
+  p.PresentSubtitle = StringExtension::UTF8Decode(osd->PresentSubtitle());
+  p.FollowingTime = osd->FollowingTime();
+  p.FollowingTitle = StringExtension::UTF8Decode(osd->FollowingTitle());
+  p.FollowingSubtitle = StringExtension::UTF8Decode(osd->FollowingSubtitle());
+  serializer.serialize(p, "ProgrammeOsd");
+  serializer.finish();
+}
+
+void ProgrammeOsdWrapper::printHtml(ProgrammeOsd* osd)
+{
+  HtmlHeader htmlHeader;
+  htmlHeader.Title("ProgrammeOsdWrapper");
+  htmlHeader.Stylesheet(DOCUMENT_ROOT "osd.css");
+  htmlHeader.Script(DOCUMENT_ROOT "osd.js");
+  htmlHeader.MetaTag("<meta http-equiv=\"refresh\" content=\"1\" />");
+  htmlHeader.ToStream(s);
+  s->write("<div id=\"osd_bg\"><div id=\"osd_container\">\n");
+  s->write("<div id=\"content2\"><div id=\"innercontent\">");
+  s->write(cString::sprintf("<div id=\"eventtitle\">%s</div>", osd->PresentTitle().c_str()));
+  s->write(cString::sprintf("<div id=\"eventsubtitle\">%s - %s</div>",
+osd->PresentSubtitle().c_str(),
+StringExtension::timeToString(osd->PresentTime()).c_str()));
+  s->write(cString::sprintf("<div id=\"eventtitle\">%s</div>", osd->FollowingTitle().c_str()));
+  s->write(cString::sprintf("<div id=\"eventsubtitle\">%s - %s</div>",
+osd->FollowingSubtitle().c_str(),
+StringExtension::timeToString(osd->FollowingTime()).c_str()));
+  s->write("</div></div>\n");
+  s->write("</div></div>\n");
+  s->write("</body></html>\n");
+}
+
+// --- ChannelOsdWrapper -----------------------------------------------------------------------------
+
+void ChannelOsdWrapper::print(ChannelOsd* osd, string format)
+{
+  if ( format == ".json" ) {
+     printJson(osd);
+  } else if ( format == ".html" ) {
+     printHtml(osd);
+  } else if ( format == ".xml" ) {
+     printXml(osd);
+  }
+}
+
+void ChannelOsdWrapper::printXml(ChannelOsd* osd)
+{
+  s->writeXmlHeader();
+  s->write("<ChannelOsd xmlns=\"http://www.domain.org/restfulapi/2011/ChannelOsd-xml\">\n");
+  s->write(cString::sprintf(" <Text>%s</Text>\n", StringExtension::encodeToXml(osd->Channel()).c_str()));
+  s->write("</ChannelOsd>\n");
+}
+
+void ChannelOsdWrapper::printJson(ChannelOsd* osd)
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(StringExtension::UTF8Decode(osd->Channel()), "ChannelOsd");
+  serializer.finish();
+}
+
+void ChannelOsdWrapper::printHtml(ChannelOsd* osd)
+{
+  HtmlHeader htmlHeader;
+  htmlHeader.Title("ChannelOsdWrapper");
+  htmlHeader.Stylesheet(DOCUMENT_ROOT "osd.css");
+  htmlHeader.Script(DOCUMENT_ROOT "osd.js");
+  htmlHeader.MetaTag("<meta http-equiv=\"refresh\" content=\"1\" />");
+  htmlHeader.ToStream(s);
+
+  s->write("<div id=\"header\">");
+  s->write(StringExtension::encodeToXml(osd->Channel()).c_str());
+  s->write("</div>\n");
+  s->write("</body></html>");
+}
diff --git a/osd.h b/osd.h
new file mode 100644
index 0000000..ad6c922
--- /dev/null
+++ b/osd.h
@@ -0,0 +1,132 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/arg.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include <cxxtools/utf8codec.h>
+#include <cxxtools/string.h>
+#include "statusmonitor.h"
+#include <list>
+#include "tools.h"
+
+class OsdResponder : public cxxtools::http::Responder
+{
+  public:
+    explicit OsdResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service)
+      { }
+
+    virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void printEmptyHtml(std::ostream& out);
+    virtual void printTextOsd(std::ostream& out, TextOsd* osd, std::string format, int start_filter, int limit_filter);
+};
+
+typedef cxxtools::http::CachedService<OsdResponder> OsdService;
+
+struct SerTextOsdItem
+{
+  cxxtools::String Content;
+  bool Selected;
+};
+
+class SerTextOsdItemContainer
+{
+  public:
+    std::vector< struct SerTextOsdItem > items;
+};
+
+struct SerTextOsd
+{
+  cxxtools::String Title;
+  cxxtools::String Message;
+  cxxtools::String Red;
+  cxxtools::String Green;
+  cxxtools::String Yellow;
+  cxxtools::String Blue;
+  SerTextOsdItemContainer* ItemContainer;
+};
+
+//SerChannelOsd not required because it only contains a string
+
+struct SerProgrammeOsd
+{
+  int PresentTime;
+  cxxtools::String PresentTitle;
+  cxxtools::String PresentSubtitle;
+  int FollowingTime;
+  cxxtools::String FollowingTitle;
+  cxxtools::String FollowingSubtitle;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerTextOsdItem& o);
+void operator<<= (cxxtools::SerializationInfo& si, const SerTextOsd& o);
+void operator<<= (cxxtools::SerializationInfo& si, const SerProgrammeOsd& o);
+
+class TextOsdList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+  public:
+    TextOsdList(std::ostream* _out) { s = new StreamExtension(_out); total = 0; };
+    ~TextOsdList() { delete s; }
+    void setTotal(int _total) { total = _total; };
+    virtual void printTextOsd(TextOsd* textOsd) { };
+};
+
+class XmlTextOsdList : TextOsdList
+{
+  public:
+    XmlTextOsdList(std::ostream* _out) : TextOsdList(_out) { };
+    ~XmlTextOsdList() { };
+    virtual void printTextOsd(TextOsd* textOsd);
+};
+
+class JsonTextOsdList : TextOsdList
+{
+  public:
+    JsonTextOsdList(std::ostream* _out) : TextOsdList(_out) { };
+    ~JsonTextOsdList() { };
+    virtual void printTextOsd(TextOsd* textOsd);
+};
+
+class HtmlTextOsdList : TextOsdList
+{
+  public:
+    HtmlTextOsdList(std::ostream* _out) : TextOsdList(_out) { };
+    ~HtmlTextOsdList() { };
+    virtual void printTextOsd(TextOsd* textOsd);
+};
+
+class OsdWrapper
+{
+  protected:
+    StreamExtension *s;
+  public:
+    OsdWrapper(std::ostream* _out) { s = new StreamExtension(_out); };
+    ~OsdWrapper() { delete s; };
+};
+
+class ProgrammeOsdWrapper : OsdWrapper
+{
+  public:
+    ProgrammeOsdWrapper(std::ostream* _out) : OsdWrapper(_out) { };
+    ~ProgrammeOsdWrapper() { };
+    void print(ProgrammeOsd* osd, std::string format);
+    void printXml(ProgrammeOsd* osd);
+    void printJson(ProgrammeOsd* osd);
+    void printHtml(ProgrammeOsd* osd);
+};
+
+class ChannelOsdWrapper : OsdWrapper
+{
+  public:
+    ChannelOsdWrapper(std::ostream* _out) : OsdWrapper(_out) { };
+    ~ChannelOsdWrapper() { };
+    void print(ChannelOsd* osd, std::string format);
+    void printXml(ChannelOsd* osd);
+    void printJson(ChannelOsd* osd);
+    void printHtml(ChannelOsd* osd);
+};
+
diff --git a/patches/vdr-1.6.0-GetEvent-Patch.diff b/patches/vdr-1.6.0-GetEvent-Patch.diff
new file mode 100644
index 0000000..8b193e5
--- /dev/null
+++ b/patches/vdr-1.6.0-GetEvent-Patch.diff
@@ -0,0 +1,15 @@
+If you use the VDR Extensions Patch activate STREAMDEVEXT
+http://www.zulu-entertainment.de/content.php?f=VDR-Patches&sub=Extensions
+
+If you dont't want use the VDR Extensions Patch use the following patch
+
+--- recording.h.old	2012-05-31 22:47:30.091729100 +0200
++++ recording.h	2012-05-31 22:47:50.013433934 +0200
+@@ -56,6 +56,7 @@
+   const char *Title(void) const { return event->Title(); }
+   const char *ShortText(void) const { return event->ShortText(); }
+   const char *Description(void) const { return event->Description(); }
++  const cEvent *GetEvent(void) const { return event; }
+   const cComponents *Components(void) const { return event->Components(); }
+   const char *Aux(void) const { return aux; }
+   bool Read(FILE *f);
diff --git a/patches/vdr-1.6.0-LengthInSeconds-Patch.diff b/patches/vdr-1.6.0-LengthInSeconds-Patch.diff
new file mode 100644
index 0000000..27911e0
--- /dev/null
+++ b/patches/vdr-1.6.0-LengthInSeconds-Patch.diff
@@ -0,0 +1,122 @@
+--- recording.c.org	2011-09-06 21:24:53.162929000 +0200
++++ recording.c	2011-09-06 22:41:25.034516000 +0200
+@@ -53,6 +53,7 @@
+ #define DELETEDLIFETIME   300 // seconds after which a deleted recording will be actually removed
+ #define DISKCHECKDELTA    100 // seconds between checks for free disk space
+ #define REMOVELATENCY      10 // seconds to wait until next check after removing a file
++#define MININDEXAGE      3600 // seconds before an index file is considered no longer to be written
+ 
+ #define TIMERMACRO_TITLE    "TITLE"
+ #define TIMERMACRO_EPISODE  "EPISODE"
+@@ -495,6 +496,7 @@
+   fileName = NULL;
+   name = NULL;
+   fileSizeMB = -1; // unknown
++  numFrames = -1;
+   deleted = 0;
+   // set up the actual name:
+   const char *Title = Event ? Event->Title() : NULL;
+@@ -546,6 +548,7 @@
+ {
+   resume = RESUME_NOT_INITIALIZED;
+   fileSizeMB = -1; // unknown
++  numFrames = -1;
+   deleted = 0;
+   titleBuffer = NULL;
+   sortBuffer = NULL;
+@@ -864,6 +867,25 @@
+   resume = RESUME_NOT_INITIALIZED;
+ }
+ 
++int cRecording::NumFrames(void) const
++{
++  if (numFrames < 0) {
++     int nf = cIndexFile::GetLength(FileName());
++     if (time(NULL) - LastModifiedTime(FileName()) < MININDEXAGE)
++        return nf; // check again later for ongoing recordings
++     numFrames = nf;
++     }
++  return numFrames;
++}
++
++int cRecording::LengthInSeconds(void) const
++{
++  int nf = NumFrames();
++  if (nf >= 0)
++     return int((nf / FRAMESPERSEC + 30) / 60) * 60;
++  return -1;
++}
++
+ // --- cRecordings -----------------------------------------------------------
+ 
+ cRecordings Recordings;
+@@ -935,6 +957,7 @@
+                  if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
+                     cRecording *r = new cRecording(buffer);
+                     if (r->Name()) {
++                       r->NumFrames(); // initializes the numFrames member
+                        Lock();
+                        Add(r);
+                        ChangeState();
+@@ -1180,9 +1203,6 @@
+ // The maximum time to wait before giving up while catching up on an index file:
+ #define MAXINDEXCATCHUP   8 // seconds
+ 
+-// The minimum age of an index file for considering it no longer to be written:
+-#define MININDEXAGE    3600 // seconds
+-
+ cIndexFile::cIndexFile(const char *FileName, bool Record)
+ :resumeFile(FileName)
+ {
+@@ -1349,6 +1369,20 @@
+   return false;
+ }
+ 
++cString cIndexFile::IndexFileName(const char *FileName)
++{
++  return cString::sprintf("%s%s", FileName, INDEXFILESUFFIX);
++}
++
++int cIndexFile::GetLength(const char *FileName)
++{
++  struct stat buf;
++  cString s = IndexFileName(FileName);
++  if (*s && stat(s, &buf) == 0)
++     return buf.st_size / sizeof(tIndex);
++  return -1;
++}
++
+ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd)
+ {
+   if (CatchUp()) {
+--- recording.h.org	2007-10-14 12:11:34.000000000 +0200
++++ recording.h	2011-09-06 22:50:20.453868000 +0200
+@@ -71,6 +71,7 @@
+   mutable char *fileName;
+   mutable char *name;
+   mutable int fileSizeMB;
++  mutable int numFrames;
+   cRecordingInfo *info;
+   cRecording(const cRecording&); // can't copy cRecording
+   cRecording &operator=(const cRecording &); // can't assign cRecording
+@@ -93,6 +94,11 @@
+   const char *PrefixFileName(char Prefix);
+   int HierarchyLevels(void) const;
+   void ResetResume(void) const;
++  int NumFrames(void) const;
++       ///< Returns the number of frames in this recording.
++       ///< If the number of frames is unknown, -1 will be returned.
++  int LengthInSeconds(void) const;
++       ///< Returns the length (in seconds) of this recording, or -1 in case of error.
+   bool IsNew(void) const { return GetResume() <= 0; }
+   bool IsEdited(void) const;
+   bool WriteInfo(void);
+@@ -218,6 +224,8 @@
+   int Last(void) { CatchUp(); return last; }
+   int GetResume(void) { return resumeFile.Read(); }
+   bool StoreResume(int Index) { return resumeFile.Save(Index); }
++  static cString IndexFileName(const char *FileName);
++  static int GetLength(const char *FileName);
+   bool IsStillRecording(void);
+   };
+ 
diff --git a/patches/vdr1721_eventdetails_v3.dpatch b/patches/vdr1721_eventdetails_v3.dpatch
new file mode 100644
index 0000000..e700c25
--- /dev/null
+++ b/patches/vdr1721_eventdetails_v3.dpatch
@@ -0,0 +1,115 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## eventdetails.dpatch
+##
+## Michael Eiler <eiler.mike at gmail.com>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+
+ at DPATCH@
+diff -Naur vdr-1.7.21/epg.c vdrng/epg.c
+--- vdr-1.7.21/epg.c	2011-02-25 16:16:05.000000000 +0100
++++ vdrng/epg.c	2011-10-22 21:48:04.774816379 +0200
+@@ -428,6 +428,21 @@
+   return buf;
+ }
+ 
++void cEvent::AddDetail(char* value)
++{
++  std::string strValue = std::string(value);
++  int delim = strValue.find_first_of(' ');
++  AddDetail(strValue.substr(0, delim), strValue.substr(delim+1));
++}
++
++void cEvent::AddDetail(std::string key, std::string value)
++{
++  tEpgDetail detail;
++  detail.key = key;
++  detail.value = value;
++  details.push_back(detail);
++}
++
+ void cEvent::Dump(FILE *f, const char *Prefix, bool InfoOnly) const
+ {
+   if (InfoOnly || startTime + duration + Setup.EPGLinger * 60 >= time(NULL)) {
+@@ -441,6 +456,12 @@
+         fprintf(f, "%sD %s\n", Prefix, description);
+         strreplace(description, '|', '\n');
+         }
++     for(int i=0;i<(int)details.size();i++) {
++        char* value = (char*)details[i].value.c_str();
++        strreplace(value, '\n', '|');
++        fprintf(f, "K %s %s\n", details[i].key.c_str(), value);
++        strreplace(value, '|', '\n');
++     }
+      if (contents[0]) {
+         fprintf(f, "%sG", Prefix);
+         for (int i = 0; Contents(i); i++)
+@@ -473,6 +494,9 @@
+     case 'D': strreplace(t, '|', '\n');
+               SetDescription(t);
+               break;
++    case 'K': strreplace(t, '|', '\n');
++              AddDetail(t);
++              break;
+     case 'G': {
+                 memset(contents, 0, sizeof(contents));
+                 for (int i = 0; i < MaxEventContents; i++) {
+@@ -537,6 +561,7 @@
+                                 }
+                              }
+                           }
++                          if (Event != NULL) { Event->ClearDetails(); }
+                        break;
+              case 'e': if (Event && !Event->Title())
+                           Event->SetTitle(tr("No title"));
+diff -Naur vdr-1.7.21/epg.h vdrng/epg.h
+--- vdr-1.7.21/epg.h	2011-02-25 15:14:38.000000000 +0100
++++ vdrng/epg.h	2011-10-22 21:48:26.750815544 +0200
+@@ -16,6 +16,8 @@
+ #include "channels.h"
+ #include "thread.h"
+ #include "tools.h"
++#include <string>
++#include <vector>
+ 
+ #define MAXEPGBUGFIXLEVEL 3
+ 
+@@ -63,6 +65,12 @@
+                                                                  // In case of an audio stream the 'type' check actually just distinguishes between "normal" and "Dolby Digital"
+   };
+ 
++#define EPG_DETAILS_PATCH
++struct tEpgDetail {
++  std::string key;
++  std::string value;
++ };
++
+ class cSchedule;
+ 
+ typedef u_int32_t tEventID;
+@@ -86,6 +94,7 @@
+   int duration;            // Duration of this event in seconds
+   time_t vps;              // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL)
+   time_t seen;             // When this event was last seen in the data stream
++  std::vector< struct tEpgDetail > details; // additional information provided by epg source
+ public:
+   cEvent(tEventID EventID);
+   ~cEvent();
+@@ -116,6 +125,7 @@
+   cString GetTimeString(void) const;
+   cString GetEndTimeString(void) const;
+   cString GetVpsString(void) const;
++  const std::vector< struct tEpgDetail >& Details(void) const { return details; };
+   void SetEventID(tEventID EventID);
+   void SetTableID(uchar TableID);
+   void SetVersion(uchar Version);
+@@ -130,6 +140,9 @@
+   void SetDuration(int Duration);
+   void SetVps(time_t Vps);
+   void SetSeen(void);
++  void AddDetail(char* value);
++  void AddDetail(std::string key, std::string value);
++  void ClearDetails() { details.erase(details.begin(), details.end()); };
+   cString ToDescr(void) const;
+   void Dump(FILE *f, const char *Prefix = "", bool InfoOnly = false) const;
+   bool Parse(char *s);
diff --git a/plugin.restfulapi.conf b/plugin.restfulapi.conf
new file mode 100644
index 0000000..0d9f464
--- /dev/null
+++ b/plugin.restfulapi.conf
@@ -0,0 +1,9 @@
+#
+# Command line parameters for vdr-plugin-restfulapi
+#
+
+--port=8002
+--ip=0.0.0.0
+--epgimages=/var/cache/vdr/epgimages
+--channellogos=/usr/share/vdr-channellogos
+--webapp=/var/lib/vdr/plugins/restfulapi/webapp
diff --git a/po/de_DE.po b/po/de_DE.po
new file mode 100644
index 0000000..990a28f
--- /dev/null
+++ b/po/de_DE.po
@@ -0,0 +1,23 @@
+# vdr-restfulapi plugin language source file
+# Copyright (C) 2013
+# This file is distributed under the same license as the vdr-restfulapi package.
+# Lars Hanisch <dvb at flensrocker.de>, 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-restfulapi 0.1.0\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2013-02-09 11:11+0100\n"
+"PO-Revision-Date: 2013-02-09 11:11+0100\n"
+"Last-Translator: Lars Hanisch <dvb at flensrocker.de>\n"
+"Language-Team: German <vdr at linuxtv.org>\n"
+"Language: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "All"
+msgstr "alle"
+
+msgid "FTA"
+msgstr "freie"
diff --git a/recordings.cpp b/recordings.cpp
new file mode 100644
index 0000000..17c7f36
--- /dev/null
+++ b/recordings.cpp
@@ -0,0 +1,577 @@
+#include "recordings.h"
+using namespace std;
+
+void RecordingsResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler::addHeader(reply);
+  bool found = false;
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET, POST, DELETE");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  if ((int)request.url().find("/recordings/play") == 0 ) {
+     if ( request.method() == "GET" ) {
+        playRecording(out, request, reply);
+        reply.addHeader("Content-Type", "text/plain; charset=utf-8");
+     } else if (request.method() == "POST") {
+        rewindRecording(out, request, reply);
+        reply.addHeader("Content-Type", "text/plain; charset=utf-8");
+     } else {
+        reply.httpReturn(501, "Only GET and POST method is supported by the /recordings/play service.");
+     }
+     found = true;
+  }
+
+  else if ((int)request.url().find("/recordings/cut") == 0 ) {
+     if (request.method() == "GET") {
+        showCutterStatus(out, request, reply);
+     } else if (request.method() == "POST") {
+        cutRecording(out, request, reply); 
+     } else {
+        reply.httpReturn(501, "Only GET and POST methods are supported by the /recordings/cut service.");
+     }
+     found = true;
+  }
+
+  else if ((int)request.url().find("/recordings/marks") == 0 ) {
+     if (request.method() == "DELETE") {
+        deleteMarks(out, request, reply);
+     } else if (request.method() == "POST") {
+        saveMarks(out, request, reply);
+     } else {
+        reply.httpReturn(501, "Only DELETE and POST methods are supported by the /recordings/marks service.");
+     }
+     found = true;
+  }
+
+  else if ((int)request.url().find("/recordings/move") == 0 ) {
+     if (request.method() == "POST") {
+        moveRecording(out, request, reply);
+     } else {
+        reply.httpReturn(501, "Only POST method is supported by the /recordings/move service.");
+     }
+     found = true;
+  }
+  
+  else if ((int) request.url().find("/recordings/delete") == 0 ) {
+     if (request.method() == "POST") {
+        deleteRecordingByName(out, request, reply);
+     } else if (request.method() == "DELETE") {
+        deleteRecordingByName(out, request, reply);
+     } else {
+        reply.httpReturn(501, "Only POST and DELETE methods are supported by the /recordings/delete service.");
+     }
+     found = true;
+  }
+
+  // original /recordings service
+  else if ((int) request.url().find("/recordings") == 0 ) {
+     if (request.method() == "GET") {
+        showRecordings(out, request, reply);
+     } else if (request.method() == "DELETE") {
+        deleteRecording(out, request, reply);
+     } else if (request.method() == "POST") {
+        deleteRecordingByName(out, request, reply);
+     } else {
+        reply.httpReturn(501, "Only GET, POST and DELETE methods are supported by the /recordings service.");
+     }
+     found = true;
+  }
+
+  if (!found) {
+     reply.httpReturn(403, "Service not found");
+  }
+}
+
+void RecordingsResponder::playRecording(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings/play", request);
+  int recording_number = q.getParamAsInt(0);
+  cThreadLock RecordingsLock(&Recordings);
+  if ( recording_number < 0 || recording_number >= Recordings.Count() ) {
+     reply.httpReturn(404, "Wrong recording number!");
+  } else {
+     cRecording* recording = Recordings.Get(recording_number);
+     if ( recording != NULL ) {
+        TaskScheduler::get()->SwitchableRecording(recording);
+     } else {
+        reply.httpReturn(404, "Wrong recording number!");
+     }
+  }
+}
+
+void RecordingsResponder::rewindRecording(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings/play", request);
+  int recording_number = q.getParamAsInt(0);
+  cThreadLock RecordingsLock(&Recordings);
+  if ( recording_number < 0 || recording_number >= Recordings.Count() ) {
+     reply.httpReturn(404, "Wrong recording number!");
+  } else {
+     cRecording* recording = Recordings.Get(recording_number);
+     if ( recording != NULL ) {
+        cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
+        cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
+        ResumeFile.Delete();
+        TaskScheduler::get()->SwitchableRecording(recording);
+     } else {
+        reply.httpReturn(404, "Wrong recording number!");
+     }
+  }
+}
+
+/* move or copy recording */
+void RecordingsResponder::moveRecording(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+   QueryHandler q("/recordings/move", request);
+   string source = q.getBodyAsString("source");
+   string target = q.getBodyAsString("target");
+   bool copy_only = q.getBodyAsBool("copy_only");
+
+   if (!copy_only) {
+      cThreadLock RecordingsLock(&Recordings);
+   }
+
+   if (source.length() <= 0 || target.length() <= 0) {
+      reply.httpReturn(404, "Missing file name!");
+      return;
+   } else if (access(source.c_str(), F_OK) != 0) {
+      reply.httpReturn(504, "Path is invalid!");
+      return;
+   }
+
+   cRecording* recording = Recordings.GetByName(source.c_str());
+   if (!recording) {
+      reply.httpReturn(504, "Recording not found!");
+      return;
+   }
+
+   string newname = VdrExtension::MoveRecording(recording, VdrExtension::FileSystemExchangeChars(target.c_str(), true), copy_only);
+
+   if (newname.length() <= 0) {
+      LOG_ERROR_STR(source.c_str());
+      reply.httpReturn(503, "File copy failed!");
+      return;
+   }
+
+   cRecording* new_recording = Recordings.GetByName(newname.c_str());
+   if (!new_recording) {
+      LOG_ERROR_STR(newname.c_str());
+      reply.httpReturn(504, "Recording not found, after moving!");
+      return;
+   }
+
+   replyRecordingMoved(out, request, reply, new_recording);
+}
+
+void RecordingsResponder::replyRecordingMoved(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply, cRecording* recording) {
+  QueryHandler q("/recordings/move", request);
+  StreamExtension s(&out);
+  RecordingList* recordingList;
+  bool read_marks = false;
+
+  if ( q.isFormat(".json") ) {
+    reply.addHeader("Content-Type", "application/json; charset=utf-8");
+    recordingList = (RecordingList*)new JsonRecordingList(&out, read_marks);
+  } else if ( q.isFormat(".html") ) {
+    reply.addHeader("Content-Type", "text/html; charset=utf-8");
+    recordingList = (RecordingList*)new HtmlRecordingList(&out, read_marks);
+    recordingList->init();
+  } else if ( q.isFormat(".xml") )  {
+    reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+    recordingList = (RecordingList*)new XmlRecordingList(&out, read_marks);
+    recordingList->init();
+  } else {
+    reply.httpReturn(502, "Resources are not available for the selected format. (Use: .json, .xml or .html)");
+    return;
+  }
+
+  cThreadLock RecordingsLock(&Recordings);
+  for (int i = 0; i < Recordings.Count(); i++) {
+     cRecording* tmp_recording = Recordings.Get(i);
+     if (strcmp(recording->FileName(), tmp_recording->FileName()) == 0) {
+        recordingList->addRecording(tmp_recording, i);
+     }
+  }
+  recordingList->setTotal(Recordings.Count());
+  recordingList->finish();
+  delete recordingList;
+}
+
+/* delete recording by file name */
+void RecordingsResponder::deleteRecordingByName(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings/delete", request);
+  string recording_file = q.getBodyAsString("file");
+  cThreadLock RecordingsLock(&Recordings);
+  if (recording_file.length() > 0) {
+     cRecording* delRecording = Recordings.GetByName(recording_file.c_str());
+     if (delRecording->Delete()) {
+        Recordings.DelByName(delRecording->FileName());
+     }
+  } else {
+     reply.httpReturn(404, "No recording file!");
+  }
+}
+
+void RecordingsResponder::deleteRecording(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings", request);
+  int recording_number = q.getParamAsInt(0);
+  cThreadLock RecordingsLock(&Recordings);
+  if ( recording_number < 0 || recording_number >= Recordings.Count() ) { 
+     reply.httpReturn(404, "Wrong recording number!");
+  } else {
+     cRecording* delRecording = Recordings.Get(recording_number);
+     if ( delRecording->Delete() ) {
+        Recordings.DelByName(delRecording->FileName());
+     }
+  }
+}
+
+void RecordingsResponder::showRecordings(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings", request);
+  RecordingList* recordingList;
+  bool read_marks = q.getOptionAsString("marks") == "true";
+
+  if ( q.isFormat(".json") ) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     recordingList = (RecordingList*)new JsonRecordingList(&out, read_marks);
+  } else if ( q.isFormat(".html") ) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     recordingList = (RecordingList*)new HtmlRecordingList(&out, read_marks);
+  } else if ( q.isFormat(".xml") )  {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     recordingList = (RecordingList*)new XmlRecordingList(&out, read_marks);
+  } else {
+     reply.httpReturn(404, "Resources are not available for the selected format. (Use: .json or .html)");
+     return;
+  }
+
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+  
+  int requested_item = q.getParamAsInt(0);
+
+  if ( start_filter >= 0 && limit_filter >= 1 ) {
+     recordingList->activateLimit(start_filter, limit_filter);
+  }
+
+  recordingList->init();
+
+  cThreadLock RecordingsLock(&Recordings);
+  if ( requested_item < 0 ) {
+     for (int i = 0; i < Recordings.Count(); i++)
+        recordingList->addRecording(Recordings.Get(i), i);
+  } else if ( requested_item < Recordings.Count() )
+     recordingList->addRecording(Recordings.Get(requested_item), requested_item);
+  recordingList->setTotal(Recordings.Count());
+
+  recordingList->finish();
+  delete recordingList;
+}
+
+void RecordingsResponder::saveMarks(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings/marks", request);
+  int recording = q.getParamAsInt(0);
+  JsonArray* jsonArray = q.getBodyAsArray("marks");
+
+  if (jsonArray == NULL) {
+     reply.httpReturn(503, "Marks in HTTP-Body are missing.");
+  }
+
+  cThreadLock RecordingsLock(&Recordings);
+  if (recording < 0 || recording >= Recordings.Count()) {
+     reply.httpReturn(504, "Recording number missing or invalid.");
+  } else {
+     vector< string > marks;
+
+     for (int i = 0; i < jsonArray->CountItem(); i++) {
+        JsonBase* jsonBase = jsonArray->GetItem(i);
+        if (jsonBase->IsBasicValue()) {
+           JsonBasicValue* jsonBasicValue = (JsonBasicValue*)jsonBase;
+           if (jsonBasicValue->IsString()) {
+              marks.push_back(jsonBasicValue->ValueAsString());
+           }
+        }
+     }
+
+     VdrMarks::get()->saveMarks(Recordings.Get(recording), marks);
+  }
+}
+
+void RecordingsResponder::deleteMarks(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings/marks", request);
+  int rec_number = q.getParamAsInt(0);
+  cThreadLock RecordingsLock(&Recordings);
+  if (rec_number >= 0 && rec_number < Recordings.Count()) {
+     cRecording* recording = Recordings.Get(rec_number);
+     if (VdrMarks::get()->deleteMarks(recording)) {
+        return;
+     }
+  }
+  reply.httpReturn(503, "Deleting marks failed.");
+}
+
+void RecordingsResponder::cutRecording(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings/cut", request);
+  int rec_number = q.getParamAsInt(0);
+  cThreadLock RecordingsLock(&Recordings);
+  if (rec_number >= 0 && rec_number < Recordings.Count()) {
+     cRecording* recording = Recordings.Get(rec_number);
+#if APIVERSNUM > 20101
+     if (RecordingsHandler.GetUsage(recording->FileName()) != ruNone) {
+#else
+     if (cCutter::Active()) {
+#endif
+        reply.httpReturn(504, "VDR Cutter currently busy.");
+     } else {
+#if APIVERSNUM > 20101
+        RecordingsHandler.Add(ruCut, recording->FileName());
+#else
+        cCutter::Start(recording->FileName());
+#endif
+     }
+     return;
+  }
+  reply.httpReturn(503, "Cutting recordings failed.");
+}
+
+void RecordingsResponder::showCutterStatus(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/recordings/cut", request);
+  StreamExtension s(&out);
+
+#if APIVERSNUM > 20101
+  bool active = RecordingsHandler.Active();
+#else
+  bool active = cCutter::Active();
+#endif
+
+  if (q.isFormat(".html")) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     s.writeHtmlHeader("HtmlCutterStatus");
+     s.write((active ? "True" : "False"));
+     s.write("</body></html>");
+  } else if (q.isFormat(".json")) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     cxxtools::JsonSerializer serializer(out);
+     serializer.serialize(active, "active");
+     serializer.finish();     
+  } else if (q.isFormat(".xml")) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     s.write("<cutter xmlns=\"http://www.domain.org/restfulapi/2011/cutter-xml\">\n");
+     s.write(cString::sprintf(" <param name=\"active\">%s</param>\n", (active ? "true" : "false")));
+     s.write("</cutter>");
+  } else {
+     reply.httpReturn(502, "Only the following formats are supported: .xml, .json and .html");
+  } 
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerRecording& p)
+{
+  si.addMember("number") <<= p.Number;
+  si.addMember("name") <<= p.Name;
+  si.addMember("file_name") <<= p.FileName;
+  si.addMember("relative_file_name") <<= p.RelativeFileName;
+  si.addMember("is_new") <<= p.IsNew;
+  si.addMember("is_edited") <<= p.IsEdited;
+  si.addMember("is_pes_recording") <<= p.IsPesRecording;
+  si.addMember("duration") <<= p.Duration;
+  si.addMember("filesize_mb") <<= p.FileSizeMB;
+  si.addMember("channel_id") <<= p.ChannelID;
+  si.addMember("frames_per_second") <<= p.FramesPerSecond;
+  si.addMember("marks") <<= p.Marks.marks;
+  si.addMember("event_title") <<= p.EventTitle;
+  si.addMember("event_short_text") <<= p.EventShortText;
+  si.addMember("event_description") <<= p.EventDescription;
+  si.addMember("event_start_time") <<= p.EventStartTime;
+  si.addMember("event_duration") <<= p.EventDuration;
+  si.addMember("additional_media") <<= p.AdditionalMedia;
+}
+
+RecordingList::RecordingList(ostream *out, bool _read_marks)
+{
+  s = new StreamExtension(out);
+  read_marks = _read_marks;
+  Scraper2VdrService sc;
+  total = 0;
+}
+
+RecordingList::~RecordingList()
+{
+  delete s;
+}
+
+void HtmlRecordingList::init()
+{
+  s->writeHtmlHeader("HtmlRecordingList");
+  s->write("<ul>");
+}
+
+void HtmlRecordingList::addRecording(cRecording* recording, int nr)
+{
+  if ( filtered() ) return;
+  s->write("<li>");
+  s->write((char*)recording->Name());
+}
+
+void HtmlRecordingList::finish()
+{
+  s->write("</ul>");
+  s->write("</body></html>");
+}
+
+void JsonRecordingList::addRecording(cRecording* recording, int nr)
+{
+  if ( filtered() ) return;
+
+  cxxtools::String empty = StringExtension::UTF8Decode("");
+  cxxtools::String eventTitle = empty;
+  cxxtools::String eventShortText = empty;
+  cxxtools::String eventDescription = empty;
+  int eventStartTime = -1;
+  int eventDuration = -1;
+    
+  cEvent* event = (cEvent*)recording->Info()->GetEvent();
+  
+  if (event != NULL)
+  {
+     if (event->Title())         { eventTitle = StringExtension::UTF8Decode(event->Title()); }
+     if (event->ShortText())     { eventShortText = StringExtension::UTF8Decode(event->ShortText()); }
+     if (event->Description())   { eventDescription = StringExtension::UTF8Decode(event->Description()); }
+     if (event->StartTime() > 0) { eventStartTime = event->StartTime(); }
+     if (event->Duration() > 0)  { eventDuration = event->Duration(); }
+  }
+
+  SerRecording serRecording;
+
+  SerAdditionalMedia am;
+  if (sc.getMedia(recording, am)) {
+      serRecording.AdditionalMedia = am;
+  }
+
+
+  serRecording.Number = nr;
+  serRecording.Name = StringExtension::encodeToJson(recording->Name());
+  serRecording.FileName = StringExtension::UTF8Decode(recording->FileName());
+  serRecording.RelativeFileName = StringExtension::UTF8Decode(VdrExtension::getRelativeVideoPath(recording).c_str());
+  serRecording.IsNew = recording->IsNew();
+  serRecording.IsEdited = recording->IsEdited();
+
+  #if APIVERSNUM >= 10703
+  serRecording.IsPesRecording = recording->IsPesRecording();
+  serRecording.FramesPerSecond = recording->FramesPerSecond();
+  #else
+  serRecording.IsPesRecording = true;
+  serRecording.FramesPerSecond = FRAMESPERSEC;
+  #endif
+
+  serRecording.Duration = VdrExtension::RecordingLengthInSeconds(recording);
+  serRecording.FileSizeMB = recording->FileSizeMB();
+  serRecording.ChannelID = StringExtension::UTF8Decode((string) recording->Info()->ChannelID().ToString());
+
+  serRecording.EventTitle = eventTitle;
+  serRecording.EventShortText = eventShortText;
+  serRecording.EventDescription = eventDescription;
+  serRecording.EventStartTime = eventStartTime;
+  serRecording.EventDuration = eventDuration;
+
+  SerMarks serMarks;
+  if (read_marks) {
+     serMarks.marks = VdrMarks::get()->readMarks(recording);
+  }
+  serRecording.Marks = serMarks;
+
+  serRecordings.push_back(serRecording);
+}
+
+void JsonRecordingList::finish()
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(serRecordings, "recordings");
+  serializer.serialize(serRecordings.size(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
+
+void XmlRecordingList::init()
+{
+  s->writeXmlHeader();
+  s->write("<recordings xmlns=\"http://www.domain.org/restfulapi/2011/recordings-xml\">\n");
+}
+
+void XmlRecordingList::addRecording(cRecording* recording, int nr)
+{
+  if ( filtered() ) return;
+
+  string eventTitle = "";
+  string eventShortText = "";
+  string eventDescription = "";
+  int eventStartTime = -1;
+  int eventDuration = -1;
+
+  cEvent* event = (cEvent*)recording->Info()->GetEvent();
+
+  if (event != NULL)
+  {
+     if (event->Title())         { eventTitle = event->Title(); }
+     if (event->ShortText())     { eventShortText = event->ShortText(); }
+     if (event->Description())   { eventDescription = event->Description(); }
+     if (event->StartTime() > 0) { eventStartTime = event->StartTime(); }
+     if (event->Duration() > 0)  { eventDuration = event->Duration(); }
+  }
+
+  s->write(" <recording>\n");
+  s->write(cString::sprintf("  <param name=\"number\">%i</param>\n", nr));
+  s->write(cString::sprintf("  <param name=\"name\">%s</param>\n", StringExtension::encodeToXml(recording->Name()).c_str() ));
+  s->write(cString::sprintf("  <param name=\"filename\">%s</param>\n", StringExtension::encodeToXml(recording->FileName()).c_str()) );
+  s->write(cString::sprintf("  <param name=\"relative_filename\">%s</param>\n", StringExtension::encodeToXml(VdrExtension::getRelativeVideoPath(recording).c_str()).c_str()));
+  s->write(cString::sprintf("  <param name=\"is_new\">%s</param>\n", recording->IsNew() ? "true" : "false" ));
+  s->write(cString::sprintf("  <param name=\"is_edited\">%s</param>\n", recording->IsEdited() ? "true" : "false" ));
+
+  #if APIVERSNUM >= 10703
+  s->write(cString::sprintf("  <param name=\"is_pes_recording\">%s</param>\n", recording->IsPesRecording() ? "true" : "false" ));
+  s->write(cString::sprintf("  <param name=\"frames_per_second\">%.2f</param>\n", recording->FramesPerSecond()));
+  #else
+  s->write(cString::sprintf("  <param name=\"is_pes_recording\">%s</param>\n", true ? "true" : "false" ));
+  s->write(cString::sprintf("  <param name=\"frames_per_second\">%i</param>\n", FRAMESPERSEC));
+  #endif
+  s->write(cString::sprintf("  <param name=\"duration\">%i</param>\n", VdrExtension::RecordingLengthInSeconds(recording)));
+  s->write(cString::sprintf("  <param name=\"filesize_mb\">%i</param>\n", recording->FileSizeMB()));
+  s->write(cString::sprintf("  <param name=\"channel_id\">%s</param>\n", StringExtension::encodeToXml((string) recording->Info()->ChannelID().ToString()).c_str()));
+
+  if (read_marks) {
+     s->write("  <param name=\"marks\">\n");
+     vector< string > marks = VdrMarks::get()->readMarks(recording);
+     for(int i=0;i<(int)marks.size();i++) {
+        s->write(cString::sprintf("   <mark>%s</mark>\n", marks[i].c_str()));
+     }
+     s->write("  </param>\n");
+  }
+
+  s->write(cString::sprintf("  <param name=\"event_title\">%s</param>\n", StringExtension::encodeToXml(eventTitle).c_str()));
+  s->write(cString::sprintf("  <param name=\"event_short_text\">%s</param>\n", StringExtension::encodeToXml(eventShortText).c_str()));
+  s->write(cString::sprintf("  <param name=\"event_description\">%s</param>\n", StringExtension::encodeToXml(eventDescription).c_str()));
+  s->write(cString::sprintf("  <param name=\"event_start_time\">%i</param>\n", eventStartTime));
+  s->write(cString::sprintf("  <param name=\"event_duration\">%i</param>\n", eventDuration));
+
+
+  sc.getMedia(recording, s);
+
+
+  s->write(" </recording>\n");
+}
+
+void XmlRecordingList::finish()
+{
+  s->write(cString::sprintf(" <count>%i</count><total>%i</total>", Count(), total));
+  s->write("</recordings>");
+}
diff --git a/recordings.h b/recordings.h
new file mode 100644
index 0000000..805d872
--- /dev/null
+++ b/recordings.h
@@ -0,0 +1,116 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/arg.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include <cxxtools/utf8codec.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include "tools.h"
+#include "scraper2vdr.h"
+
+#include <vdr/cutter.h>
+#include <vdr/recording.h>
+#include <vdr/videodir.h>
+
+class RecordingsResponder : public cxxtools::http::Responder
+{
+  public:
+    explicit RecordingsResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service)
+      { }
+
+    virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void deleteRecording(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void deleteRecordingByName(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void showRecordings(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void saveMarks(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void deleteMarks(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void cutRecording(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void showCutterStatus(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void playRecording(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void rewindRecording(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void moveRecording(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    void replyRecordingMoved(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply, cRecording* recording);
+};
+
+typedef cxxtools::http::CachedService<RecordingsResponder> RecordingsService;
+
+class SerMarks
+{
+  public:
+    std::vector< std::string > marks;
+};
+
+struct SerRecording
+{
+  int Number;
+  cxxtools::String Name;
+  cxxtools::String FileName;
+  cxxtools::String RelativeFileName;
+  cxxtools::String ChannelID;
+  bool IsNew;
+  bool IsEdited;
+  bool IsPesRecording;
+  int Duration;
+  int FileSizeMB;
+  double FramesPerSecond;
+  SerMarks Marks;
+  cxxtools::String EventTitle;
+  cxxtools::String EventShortText;
+  cxxtools::String EventDescription;
+  int EventStartTime;
+  int EventDuration;
+  struct SerAdditionalMedia AdditionalMedia;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerRecording& p);
+
+class RecordingList : public BaseList
+{
+  protected:
+    bool read_marks;
+    int total;
+    StreamExtension *s;
+    Scraper2VdrService sc;
+  public:
+    RecordingList(std::ostream* _out, bool _read_marks);
+    virtual ~RecordingList();
+    virtual void init() { };
+    virtual void addRecording(cRecording* recording, int nr) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; }
+};
+
+class HtmlRecordingList : RecordingList
+{
+  public:
+    HtmlRecordingList(std::ostream* _out, bool _read_marks) : RecordingList(_out, _read_marks) { };
+    ~HtmlRecordingList() { };
+    virtual void init();
+    virtual void addRecording(cRecording* recording, int nr);
+    virtual void finish();
+};
+
+class JsonRecordingList : RecordingList
+{
+  private:
+    std::vector < struct SerRecording > serRecordings;
+  public:
+    JsonRecordingList(std::ostream* _out, bool _read_marks) : RecordingList(_out, _read_marks) { };
+    ~JsonRecordingList() { };
+    virtual void addRecording(cRecording* recording, int nr);
+    virtual void finish();
+};
+
+class XmlRecordingList : RecordingList
+{
+  public:
+    XmlRecordingList(std::ostream* _out, bool _read_marks) : RecordingList(_out, _read_marks) { };
+    ~XmlRecordingList() { };
+    virtual void init();
+    virtual void addRecording(cRecording* recording, int nr);
+    virtual void finish();
+};
diff --git a/remote.cpp b/remote.cpp
new file mode 100644
index 0000000..82e2eb8
--- /dev/null
+++ b/remote.cpp
@@ -0,0 +1,180 @@
+#include "remote.h"
+using namespace std;
+
+void RemoteResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler::addHeader(reply);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "POST");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  if (request.method() != "POST") {
+     reply.httpReturn(403, "Only POST method is support by the remote control");
+     return;
+  }
+
+  if ( (int)request.url().find("/remote/switch") != -1 ) {
+     QueryHandler q("/remote/switch", request);
+     cChannel* channel = VdrExtension::getChannel(q.getParamAsString(0));
+     if ( channel == NULL ) {
+        reply.httpReturn(404, "Channel-Id is not valid.");
+     } else {
+        TaskScheduler::get()->SwitchableChannel(channel->GetChannelID());
+     }
+
+     return;
+  } 
+
+  if (!keyPairList->hitKey(request, reply)) {
+     reply.httpReturn(404, "Remote Control does not support the requested key.");
+  }
+}
+
+KeyPairList::KeyPairList()
+{
+  append( "up" ,         kUp           );
+  append( "down" ,       kDown         );
+  append( "menu" ,       kMenu         );
+  append( "ok" ,         kOk           );
+  append( "back" ,       kBack         );
+  append( "left" ,       kLeft         );
+  append( "right" ,      kRight        );
+  append( "red" ,        kRed          );
+  append( "green" ,      kGreen        );
+  append( "yellow" ,     kYellow       );
+  append( "blue" ,       kBlue         );
+  append( "0" ,          k0            );
+  append( "1" ,          k1            );
+  append( "2" ,          k2            );
+  append( "3" ,          k3            );
+  append( "4" ,          k4            );
+  append( "5" ,          k5            );
+  append( "6" ,          k6            );
+  append( "7" ,          k7            );
+  append( "8" ,          k8            );
+  append( "9" ,          k9            );
+  append( "info" ,       kInfo         );
+  append( "play" ,       kPlay         );
+  append( "pause" ,      kPause        );
+  append( "stop" ,       kStop         );
+  append( "record" ,     kRecord       );
+  append( "fastfwd" ,    kFastFwd      );
+  append( "fastrew" ,    kFastRew      );
+  append( "next" ,       kNext         );
+  append( "prev" ,       kPrev         );
+  append( "power" ,      kPower        );
+  append( "chanup" ,     kChanUp       );
+  append( "chandn" ,     kChanDn       );
+  append( "chanprev",    kChanPrev     );
+  append( "volup" ,      kVolUp        );
+  append( "voldn" ,      kVolDn        );
+  append( "mute" ,       kMute         );
+  append( "audio" ,      kAudio        );
+  append( "subtitles" ,  kSubtitles    );
+  append( "schedule" ,   kSchedule     );
+  append( "channels" ,   kChannels     );
+  append( "timers" ,     kTimers       );
+  append( "recordings",  kRecordings   );
+  append( "setup" ,      kSetup        );
+  append( "commands" ,   kCommands     );
+  #if APIVERSNUM >= 10715
+  append( "user0" ,      kUser0        );
+  #endif
+  append( "user1" ,      kUser1        );
+  append( "user2" ,      kUser2        );
+  append( "user3" ,      kUser3        );
+  append( "user4" ,      kUser4        );
+  append( "user5" ,      kUser5        );
+  append( "user6" ,      kUser6        );
+  append( "user7" ,      kUser7        );
+  append( "user8" ,      kUser8        );
+  append( "user9" ,      kUser9        );
+  append( "none" ,       kNone         );
+  append( "kbd" ,        kKbd          );
+}
+
+KeyPairList::~KeyPairList()
+{
+
+}
+
+bool KeyPairList::hitKey(cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{ 
+  
+  if ( (int)request.url().find("/remote/kbd") != -1) {
+    QueryHandler q("/remote/kbd", request);
+    cxxtools::String kbd = StringExtension::UTF8Decode(q.getBodyAsString("kbd"));
+    if ( kbd == StringExtension::UTF8Decode("") ) {
+	reply.httpReturn(400, "Key is empty.");
+    }
+    std::size_t n = 0;
+    while (kbd[n]) {
+      cRemote::Put(KBDKEY(kbd[n]));
+      ++n;
+    }
+    return true;
+
+  } else if ( (int)request.url().find("/remote/seq") != -1) {
+    QueryHandler q("/remote/seq", request);
+    JsonArray* seq = q.getBodyAsArray("seq");
+
+    if ( seq == NULL ) {
+	reply.httpReturn(400, "Sequence is empty.");
+        return false;
+    }
+
+    for (int i = 0; i < seq->CountItem(); i++) {
+      JsonBase* jsonBase = seq->GetItem(i);
+      if (jsonBase->IsBasicValue()) {
+	JsonBasicValue* jsonBasicValue = (JsonBasicValue*)jsonBase;
+	if (jsonBasicValue->IsString()) {
+          string key = jsonBasicValue->ValueAsString();
+          for (int n=0;n<(int)key.length();n++ ) {
+            key[n] = tolower(key[n]);
+          }
+	  for (int x=0;x<(int)keys.size();x++) {
+	    if (string(keys[x].str) == key) {
+		cRemote::Put(keys[x].key);
+	    }
+	  }
+	}
+      }
+    }
+
+    return true;
+
+  } else {
+    QueryHandler q("/remote", request);
+    string key = q.getParamAsString(0);
+
+    if (key.length() == 0) {
+       reply.httpReturn(404, "Please add a key to the parameter list, see API-file for more details.");
+       return false;
+    }
+
+    for (int i=0;i<(int)key.length();i++) {
+      key[i] = tolower(key[i]);
+    }
+
+    for (int i=0;i<(int)keys.size();i++)
+    {
+      if (string(keys[i].str) == key) {
+        cRemote::Put(keys[i].key);
+        return true;
+      }
+    }
+  }
+
+
+
+  return false;
+}
+
+void KeyPairList::append(const char* str, eKeys key)
+{
+  eKeyPair keyPair = { str, key };
+  keys.push_back(keyPair);
+}
diff --git a/remote.h b/remote.h
new file mode 100644
index 0000000..ce62aab
--- /dev/null
+++ b/remote.h
@@ -0,0 +1,44 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include "tools.h"
+#include <vector>
+
+#include <vdr/channels.h>
+#include <vdr/keys.h>
+#include <vdr/remote.h>
+
+struct eKeyPair
+{
+  const char* str;
+  eKeys key;
+};
+
+class KeyPairList
+{
+  private:
+    std::vector< struct eKeyPair > keys;
+    void append(const char* str, eKeys key);
+  public:
+    KeyPairList();
+    ~KeyPairList();
+    bool hitKey(cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+};
+
+class RemoteResponder : public cxxtools::http::Responder
+{
+  private:
+    KeyPairList* keyPairList;
+  public:
+    explicit RemoteResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service)
+      {
+        keyPairList = new KeyPairList(); 
+      }
+    ~RemoteResponder() { delete keyPairList; }
+    
+    virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+};
+
+typedef cxxtools::http::CachedService<RemoteResponder> RemoteService;
+
diff --git a/restfulapi.cpp b/restfulapi.cpp
new file mode 100644
index 0000000..0e4a006
--- /dev/null
+++ b/restfulapi.cpp
@@ -0,0 +1,231 @@
+/*
+ * restfulapi.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <getopt.h>
+#include <vdr/plugin.h>
+#include "serverthread.h"
+#include "statusmonitor.h"
+
+static const char *VERSION        = "0.2.1.2";
+static const char *DESCRIPTION    = "Offers a RESTful-API to retrieve data from VDR";
+static const char *MAINMENUENTRY  = NULL;//"Restfulapi";
+
+class cPluginRestfulapi : public cPlugin {
+private:
+  // Add any member variables or functions you may need here.
+  cServerThread serverThread;
+  StatusMonitor* statusMonitor;
+public:
+  cPluginRestfulapi(void);
+  virtual ~cPluginRestfulapi();
+  virtual const char *Version(void) { return VERSION; }
+  virtual const char *Description(void) { return DESCRIPTION; }
+  virtual const char *CommandLineHelp(void);
+  virtual bool ProcessArgs(int argc, char *argv[]);
+  virtual bool Initialize(void);
+  virtual bool Start(void);
+  virtual void Stop(void);
+  virtual void Housekeeping(void);
+  virtual void MainThreadHook(void);
+  virtual cString Active(void);
+  virtual time_t WakeupTime(void);
+  virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
+  virtual cOsdObject *MainMenuAction(void);
+  virtual cMenuSetupPage *SetupMenu(void);
+  virtual bool SetupParse(const char *Name, const char *Value);
+  virtual bool Service(const char *Id, void *Data = NULL);
+  virtual const char **SVDRPHelpPages(void);
+  virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+  };
+
+cPluginRestfulapi::cPluginRestfulapi(void)
+{
+  // Initialize any member variables here.
+  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+}
+
+cPluginRestfulapi::~cPluginRestfulapi()
+{
+  // Clean up after yourself!
+}
+
+const char *cPluginRestfulapi::CommandLineHelp(void)
+{
+  return "  -i 0.0.0.0,      --ip=0.0.0.0             ip of the interface on which the services should listen\n"
+         "  -p 8002,         --port=8002              tcp port\n"
+         "  -e ABC,          --epgimages=ABC          folder which stores the epg-images\n"
+         "  -c DEF,          --channellogos=DEF       folder which stores the channel-logos\n"
+	 "  -h false,        --headers=true           disable additional http headers for accessing the data by javascript even so it's on another port"
+	 "  -w GHI,          --webapp=GHI             folder which stores a webapp";
+}
+
+bool cPluginRestfulapi::ProcessArgs(int argc, char *argv[])
+{
+  Settings* settings = Settings::get();
+  esyslog("restfulapi: trying to parse command line arguments");
+
+  static struct option long_options[] = {
+       { "port",         required_argument,  NULL,  'p' },
+       { "ip",           required_argument,  NULL,  'i' },
+       { "epgimages",    required_argument,  NULL,  'e' },
+       { "channellogos", required_argument,  NULL,  'c' },
+       { "headers",      required_argument,  NULL,  'h' },
+       { "webapp",       required_argument,  NULL,  'w' }
+     };
+
+  int optchar, optind = 0;
+
+  while ( ( optchar = getopt_long( argc, argv, "p:i:e:c:h:w:", long_options, &optind ) ) != -1 ) {
+     switch ( optchar ) {
+        case 'p': settings->SetPort((std::string)optarg); break;
+        case 'i': settings->SetIp((std::string)optarg); break;
+        case 'e': settings->SetEpgImageDirectory((std::string)optarg);  break;
+        case 'c': settings->SetChannelLogoDirectory((std::string)optarg); break;
+        case 'h': settings->SetHeaders((std::string)optarg); break;
+        case 'w': settings->SetWebappDirectory((std::string)optarg); break;
+     };
+  }
+
+  std::string headers = "activated";
+  if ( settings->Headers() == false ) { headers = "deactivated"; }
+
+  esyslog("RESTful-API Settings: port: %i, ip: %s, eimgs: %s, cimgs: %s, webapp: %s, headers: %s",
+          settings->Port(),
+          settings->Ip().c_str(),
+          settings->EpgImageDirectory().c_str(),
+          settings->ChannelLogoDirectory().c_str(),
+          settings->WebappDirectory().c_str(),
+          headers.c_str());
+
+  return true;
+}
+
+bool cPluginRestfulapi::Initialize(void)
+{
+  // Initialize any background activities the plugin shall perform.
+  statusMonitor = StatusMonitor::get();
+  return true;
+}
+
+bool cPluginRestfulapi::Start(void)
+{
+  // Start any background activities the plugin shall perform.
+  Settings* settings = Settings::get();
+
+  std::string headers = "activated";
+  if ( settings->Headers() == false ) { headers = "deactivated"; }
+
+  esyslog("restfulapi: Used settings: port: %i, ip: %s, eimgs: %s, cimgs: %s, webapp: %s, headers: %s",
+          settings->Port(),
+          settings->Ip().c_str(),
+          settings->EpgImageDirectory().c_str(),
+          settings->ChannelLogoDirectory().c_str(),
+          settings->WebappDirectory().c_str(),
+          headers.c_str());
+
+  FileCaches::get(); //cache files
+  serverThread.Initialize();
+  serverThread.Start();
+  return true;
+}
+
+void cPluginRestfulapi::Stop(void)
+{
+  // Stop any background activities the plugin is performing. 
+  FileCaches::get()->stopNotifier();
+  serverThread.Stop();
+  statusMonitor = NULL;
+}
+
+void cPluginRestfulapi::Housekeeping(void)
+{
+  // Perform any cleanup or other regular tasks.
+}
+
+void cPluginRestfulapi::MainThreadHook(void)
+{
+  // Perform actions in the context of the main program thread.
+  // WARNING: Use with great care - see PLUGINS.html!
+  TaskScheduler* scheduler = TaskScheduler::get();
+
+  scheduler->DoTasks();
+ 
+  tChannelID channelID = scheduler->SwitchableChannel();
+  
+  if (!( channelID == tChannelID::InvalidID )) {
+     cChannel* channel = Channels.GetByChannelID(channelID);
+     if (channel != NULL) {
+        Channels.SwitchTo( channel->Number() );
+        scheduler->SwitchableChannel(tChannelID::InvalidID);
+     }
+  }
+
+  cRecording* recording = scheduler->SwitchableRecording();
+
+  if (recording != NULL) {
+     #if APIVERSNUM > 10727
+     cReplayControl::SetRecording(recording->FileName());
+     #else
+     cReplayControl::SetRecording(recording->FileName(), recording->Title());
+     #endif
+     scheduler->SwitchableRecording(NULL);
+     cControl::Shutdown();
+     cControl::Launch(new cReplayControl);
+  }
+}
+
+cString cPluginRestfulapi::Active(void)
+{
+  // Return a message string if shutdown should be postponed
+  return NULL;
+}
+
+time_t cPluginRestfulapi::WakeupTime(void)
+{
+  // Return custom wakeup time for shutdown script
+  return 0;
+}
+
+cOsdObject *cPluginRestfulapi::MainMenuAction(void)
+{
+  // Perform the action when selected from the main VDR menu.
+  return NULL;
+}
+
+cMenuSetupPage *cPluginRestfulapi::SetupMenu(void)
+{
+  // Return a setup menu in case the plugin supports one.
+  return NULL;
+}
+
+bool cPluginRestfulapi::SetupParse(const char *Name, const char *Value)
+{
+  // Parse your own setup parameters and store their values.
+  return false;
+}
+
+bool cPluginRestfulapi::Service(const char *Id, void *Data)
+{
+  // Handle custom service requests from other plugins
+  return false;
+}
+
+const char **cPluginRestfulapi::SVDRPHelpPages(void)
+{
+  // Return help text for SVDRP commands this plugin implements
+  return NULL;
+}
+
+cString cPluginRestfulapi::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+  // Process SVDRP commands this plugin implements
+  return NULL;
+}
+
+VDRPLUGINCREATOR(cPluginRestfulapi); // Don't touch this!
diff --git a/scraper2vdr.cpp b/scraper2vdr.cpp
new file mode 100644
index 0000000..2850365
--- /dev/null
+++ b/scraper2vdr.cpp
@@ -0,0 +1,609 @@
+#include "scraper2vdr.h"
+using namespace std;
+
+/**
+ * initialize scraper service
+ */
+Scraper2VdrService::Scraper2VdrService() {
+  scraper = getScraperPlugin();
+  epgImagesDir = Settings::get()->EpgImageDirectory();
+};
+
+/**
+ * destroy scraper service
+ */
+Scraper2VdrService::~Scraper2VdrService() {
+  scraper = NULL;
+};
+
+/**
+ * retrieve scraper plugin
+ * @return cPlugin
+ */
+cPlugin *Scraper2VdrService::getScraperPlugin () {
+
+  cPlugin *pScraper = cPluginManager::GetPlugin("scraper2vdr");
+  if ( !pScraper )
+    pScraper = cPluginManager::GetPlugin("tvscraper");
+
+  return pScraper;
+};
+
+/**
+ * determine eventType
+ * @param ScraperGetEventType &eventType
+ * @return bool
+ */
+bool Scraper2VdrService::getEventType(ScraperGetEventType &eventType) {
+
+  if (scraper) {
+    return scraper->Service("GetEventType", &eventType);
+  }
+
+  return false;
+};
+
+/**
+ * retrieve media by eventType
+ * @param ScraperGetEventType &eventType
+ * @param SerAdditionalMedia &am
+ * @return bool
+ */
+bool Scraper2VdrService::getMedia(ScraperGetEventType &eventType, SerAdditionalMedia &am) {
+
+  if (getEventType(eventType)) {
+      if (eventType.seriesId > 0) {
+	  getSeriesMedia(am, eventType);
+	  if (am.SeriesId) {
+	      return true;
+	  }
+      } else if (eventType.movieId > 0) {
+	  getMovieMedia(am, eventType);
+	  if (am.MovieId) {
+	      return true;
+	  }
+      }
+  }
+  return false;
+};
+/**
+ * retrieve media by eventType
+ * @param ScraperGetEventType &eventType
+ * @param StreamExtension* s
+ * @return bool
+ */
+bool Scraper2VdrService::getMedia(ScraperGetEventType &eventType, StreamExtension* s) {
+
+  if (getEventType(eventType)) {
+      if (eventType.seriesId > 0) {
+	  getSeriesMedia(s, eventType);
+	  return true;
+      } else if (eventType.movieId > 0) {
+	  getMovieMedia(s, eventType);
+	  return true;
+      }
+  }
+  return false;
+};
+
+/**
+ * API begin
+ */
+
+/**
+ * retrieve additional media according to event
+ * @param cEvent *event
+ * @param SerAdditionalMedia &am
+ * @return bool
+ */
+bool Scraper2VdrService::getMedia(cEvent *event, SerAdditionalMedia &am) {
+
+  if (!scraper || !event) return false;
+
+  ScraperGetEventType eventType;
+  eventType.event = event;
+
+  return getMedia(eventType, am);
+};
+
+/**
+ * retrieve additional media according to event
+ * @param cEvent *event
+ * @param StreamExtension* s
+ * @return bool
+ */
+bool Scraper2VdrService::getMedia(cEvent *event, StreamExtension* s) {
+
+  if (!scraper || !event || !s) return false;
+
+  ScraperGetEventType eventType;
+  eventType.event = event;
+
+  return getMedia(eventType, s);
+};
+
+/**
+ * retrieve additional media according to recording
+ * @param cRecording* recording
+ * @param SerAdditionalMedia &am
+ * @return bool
+ */
+bool Scraper2VdrService::getMedia(cRecording* recording, SerAdditionalMedia &am) {
+
+  if (!scraper || !recording) return false;
+
+  ScraperGetEventType eventType;
+  eventType.recording = recording;
+
+  return getMedia(eventType, am);
+};
+
+/**
+ * retrieve additional media according to recording
+ * @param cRecording* recording
+ * @param StreamExtension* s
+ * @return bool
+ */
+bool Scraper2VdrService::getMedia(cRecording* recording, StreamExtension* s) {
+
+  if (!scraper || !recording || !s) return false;
+
+  ScraperGetEventType eventType;
+  eventType.recording = recording;
+
+  return getMedia(eventType, s);
+};
+
+/**
+ * API end
+ */
+
+/**
+ * strip filesystem path from image path
+ * @param string path
+ * return string
+ */
+string Scraper2VdrService::cleanImagePath(string path) {
+
+  path = StringExtension::replace(path, epgImagesDir, "");
+  path.erase(0, path.find_first_not_of("/"));
+  return path;
+};
+
+/**
+ * enrich additional media structure with series data
+ * @param SerAdditionalMedia &am
+ * @param ScraperGetEventType &eventType
+ * @return void
+ */
+void Scraper2VdrService::getSeriesMedia(SerAdditionalMedia &am, ScraperGetEventType &eventType) {
+
+  cSeries series;
+  series.seriesId = eventType.seriesId;
+  series.episodeId = eventType.episodeId;
+
+  if (scraper->Service("GetSeries", &series)) {
+    am.MovieId = 0;
+    am.Scraper = "series";
+    am.SeriesId = series.seriesId;
+    am.EpisodeId = series.episodeId;
+    am.SeriesName = StringExtension::UTF8Decode(series.name);
+    am.SeriesOverview = StringExtension::UTF8Decode(series.overview);
+    am.SeriesFirstAired = StringExtension::UTF8Decode(series.firstAired);
+    am.SeriesNetwork = StringExtension::UTF8Decode(series.network);
+    am.SeriesGenre = StringExtension::UTF8Decode(series.genre);
+    am.SeriesRating = series.rating;
+    am.SeriesStatus = StringExtension::UTF8Decode(series.status);
+
+    am.EpisodeNumber = series.episode.number;
+    am.EpisodeSeason = series.episode.season;
+    am.EpisodeName = StringExtension::UTF8Decode(series.episode.name);
+    am.EpisodeFirstAired = StringExtension::UTF8Decode(series.episode.firstAired);
+    am.EpisodeGuestStars = StringExtension::UTF8Decode(series.episode.guestStars);
+    am.EpisodeOverview = StringExtension::UTF8Decode(series.episode.overview);
+    am.EpisodeRating = series.episode.rating;
+    am.EpisodeImage = StringExtension::UTF8Decode(cleanImagePath(series.episode.episodeImage.path));
+
+    if (series.actors.size() > 0) {
+       int _actors = series.actors.size();
+       for (int i = 0; i < _actors; i++) {
+	   struct SerActor actor;
+	   actor.Name = StringExtension::UTF8Decode(series.actors[i].name);
+	   actor.Role = StringExtension::UTF8Decode(series.actors[i].role);
+	   actor.Thumb = StringExtension::UTF8Decode(cleanImagePath(series.actors[i].actorThumb.path));
+	   am.Actors.push_back(actor);
+       }
+    }
+
+    if (series.posters.size() > 0) {
+       int _posters = series.posters.size();
+       for (int i = 0; i < _posters; i++) {
+	   if ((series.posters[i].width > 0) && (series.posters[i].height > 0)) {
+	      struct SerImage poster;
+	      poster.Path = StringExtension::UTF8Decode(cleanImagePath(series.posters[i].path));
+	      poster.Width = series.posters[i].width;
+	      poster.Height = series.posters[i].height;
+	      am.Posters.push_back(poster);
+	   }
+       }
+    }
+
+    if (series.banners.size() > 0) {
+       int _banners = series.banners.size();
+       for (int i = 0; i < _banners; i++) {
+	   if ((series.banners[i].width > 0) && (series.banners[i].height > 0)) {
+	      struct SerImage banner;
+	      banner.Path = StringExtension::UTF8Decode(cleanImagePath(series.banners[i].path));
+	      banner.Width = series.banners[i].width;
+	      banner.Height = series.banners[i].height;
+	      am.Banners.push_back(banner);
+	   }
+       }
+    }
+
+    if (series.fanarts.size() > 0) {
+       int _fanarts = series.fanarts.size();
+       for (int i = 0; i < _fanarts; i++) {
+	   if ((series.fanarts[i].width > 0) && (series.fanarts[i].height > 0)) {
+	      struct SerImage fanart;
+	      fanart.Path = StringExtension::UTF8Decode(cleanImagePath(series.fanarts[i].path));
+	      fanart.Width = series.fanarts[i].width;
+	      fanart.Height = series.fanarts[i].height;
+	      am.Fanarts.push_back(fanart);
+	   }
+       }
+    }
+  }
+};
+
+/**
+ * enrich additional media structure with series data
+ * @param StreamExtension* s
+ * @param ScraperGetEventType &eventType
+ * @return void
+ */
+void Scraper2VdrService::getSeriesMedia(StreamExtension* s, ScraperGetEventType &eventType) {
+
+  cSeries series;
+  series.seriesId = eventType.seriesId;
+  series.episodeId = eventType.episodeId;
+
+  if (scraper->Service("GetSeries", &series)) {
+    s->write("  <param name=\"additional_media\" type=\"series\">\n");
+    s->write(cString::sprintf("    <series_id>%i</series_id>\n", series.seriesId));
+    if (series.episodeId > 0) {
+	s->write(cString::sprintf("    <episode_id>%i</episode_id>\n", series.episodeId));
+    }
+    if (series.name != "") {
+	s->write(cString::sprintf("    <name>%s</name>\n", StringExtension::encodeToXml(series.name).c_str()));
+    }
+    if (series.overview != "") {
+	s->write(cString::sprintf("    <overview>%s</overview>\n", StringExtension::encodeToXml(series.overview).c_str()));
+    }
+    if (series.firstAired != "") {
+	s->write(cString::sprintf("    <first_aired>%s</first_aired>\n", StringExtension::encodeToXml(series.firstAired).c_str()));
+    }
+    if (series.network != "") {
+	s->write(cString::sprintf("    <network>%s</network>\n", StringExtension::encodeToXml(series.network).c_str()));
+    }
+    if (series.genre != "") {
+	s->write(cString::sprintf("    <genre>%s</genre>\n", StringExtension::encodeToXml(series.genre).c_str()));
+    }
+    if (series.rating > 0) {
+	s->write(cString::sprintf("    <rating>%.2f</rating>\n", series.rating));
+    }
+    if (series.status != "") {
+	s->write(cString::sprintf("    <status>%s</status>\n", StringExtension::encodeToXml(series.status).c_str()));
+    }
+
+    if (series.episode.number > 0) {
+       s->write(cString::sprintf("    <episode_number>%i</episode_number>\n", series.episode.number));
+       s->write(cString::sprintf("    <episode_season>%i</episode_season>\n", series.episode.season));
+       s->write(cString::sprintf("    <episode_name>%s</episode_name>\n", StringExtension::encodeToXml(series.episode.name).c_str()));
+       s->write(cString::sprintf("    <episode_first_aired>%s</episode_first_aired>\n", StringExtension::encodeToXml(series.episode.firstAired).c_str()));
+       s->write(cString::sprintf("    <episode_guest_stars>%s</episode_guest_stars>\n", StringExtension::encodeToXml(series.episode.guestStars).c_str()));
+       s->write(cString::sprintf("    <episode_overview>%s</episode_overview>\n", StringExtension::encodeToXml(series.episode.overview).c_str()));
+       s->write(cString::sprintf("    <episode_rating>%.2f</episode_rating>\n", series.episode.rating));
+       s->write(cString::sprintf("    <episode_image>%s</episode_image>\n", StringExtension::encodeToXml(cleanImagePath(series.episode.episodeImage.path)).c_str()));
+    }
+
+    if (series.actors.size() > 0) {
+       int _actors = series.actors.size();
+       for (int i = 0; i < _actors; i++) {
+	   s->write(cString::sprintf("    <actor name=\"%s\" role=\"%s\" thumb=\"%s\"/>\n",
+				     StringExtension::encodeToXml(series.actors[i].name).c_str(),
+				     StringExtension::encodeToXml(series.actors[i].role).c_str(),
+				     StringExtension::encodeToXml(cleanImagePath(series.actors[i].actorThumb.path)).c_str() ));
+       }
+    }
+    if (series.posters.size() > 0) {
+       int _posters = series.posters.size();
+       for (int i = 0; i < _posters; i++) {
+	   if ((series.posters[i].width > 0) && (series.posters[i].height > 0))
+	      s->write(cString::sprintf("    <poster path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+					StringExtension::encodeToXml(cleanImagePath(series.posters[i].path)).c_str(), series.posters[i].width, series.posters[i].height));
+       }
+    }
+    if (series.banners.size() > 0) {
+       int _banners = series.banners.size();
+       for (int i = 0; i < _banners; i++) {
+	   if ((series.banners[i].width > 0) && (series.banners[i].height > 0))
+	      s->write(cString::sprintf("    <banner path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+					StringExtension::encodeToXml(cleanImagePath(series.banners[i].path)).c_str(), series.banners[i].width, series.banners[i].height));
+       }
+    }
+    if (series.fanarts.size() > 0) {
+       int _fanarts = series.fanarts.size();
+       for (int i = 0; i < _fanarts; i++) {
+	   if ((series.fanarts[i].width > 0) && (series.fanarts[i].height > 0))
+	      s->write(cString::sprintf("    <fanart path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+					StringExtension::encodeToXml(cleanImagePath(series.fanarts[i].path)).c_str(), series.fanarts[i].width, series.fanarts[i].height));
+       }
+    }
+    if ((series.seasonPoster.width > 0) && (series.seasonPoster.height > 0) && (series.seasonPoster.path.size() > 0)) {
+       s->write(cString::sprintf("    <season_poster path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+				 StringExtension::encodeToXml(cleanImagePath(series.seasonPoster.path)).c_str(), series.seasonPoster.width, series.seasonPoster.height));
+    }
+    s->write("  </param>\n");
+  }
+};
+
+/**
+ * enrich additional media structure with movie data
+ * @param SerAdditionalMedia &am
+ * @param ScraperGetEventType &eventType
+ * @return void
+ */
+void Scraper2VdrService::getMovieMedia(SerAdditionalMedia &am, ScraperGetEventType &eventType) {
+
+  cMovie movie;
+  movie.movieId = eventType.movieId;
+
+  if (scraper->Service("GetMovie", &movie)) {
+      am.SeriesId = 0;
+      am.Scraper = "movie";
+      am.MovieId = movie.movieId;
+      am.MovieTitle = StringExtension::UTF8Decode(movie.title);
+      am.MovieOriginalTitle = StringExtension::UTF8Decode(movie.originalTitle);
+      am.MovieTagline = StringExtension::UTF8Decode(movie.tagline);
+      am.MovieOverview = StringExtension::UTF8Decode(movie.overview);
+      am.MovieAdult = movie.adult;
+      am.MovieCollectionName = StringExtension::UTF8Decode(movie.collectionName);
+      am.MovieBudget = movie.budget;
+      am.MovieRevenue = movie.revenue;
+      am.MovieGenres = StringExtension::UTF8Decode(movie.genres);
+      am.MovieHomepage = StringExtension::UTF8Decode(movie.homepage);
+      am.MovieReleaseDate = StringExtension::UTF8Decode(movie.releaseDate);
+      am.MovieRuntime = movie.runtime;
+      am.MoviePopularity = movie.popularity;
+      am.MovieVoteAverage = movie.voteAverage;
+      am.MoviePoster = StringExtension::UTF8Decode(cleanImagePath(movie.poster.path));
+      am.MovieFanart = StringExtension::UTF8Decode(cleanImagePath(movie.fanart.path));
+      am.MovieCollectionPoster = StringExtension::UTF8Decode(cleanImagePath(movie.collectionPoster.path));
+      am.MovieCollectionFanart = StringExtension::UTF8Decode(cleanImagePath(movie.collectionFanart.path));
+      if (movie.actors.size() > 0) {
+         int _actors = movie.actors.size();
+         for (int i = 0; i < _actors; i++) {
+             struct SerActor actor;
+             actor.Name = StringExtension::UTF8Decode(movie.actors[i].name);
+             actor.Role = StringExtension::UTF8Decode(movie.actors[i].role);
+             actor.Thumb = StringExtension::encodeToJson(cleanImagePath(movie.actors[i].actorThumb.path)).c_str();
+             am.Actors.push_back(actor);
+         }
+      }
+  }
+};
+
+/**
+ * enrich additional media structure with movie data
+ * @param StreamExtension* s
+ * @param ScraperGetEventType &eventType
+ * @return void
+ */
+void Scraper2VdrService::getMovieMedia(StreamExtension* s, ScraperGetEventType &eventType) {
+
+  cMovie movie;
+  movie.movieId = eventType.movieId;
+
+  if (scraper->Service("GetMovie", &movie)) {
+    s->write("  <param name=\"additional_media\" type=\"movie\">\n");
+    s->write(cString::sprintf("    <movie_id>%i</movie_id>\n", movie.movieId));
+    if (movie.title != "") {
+       s->write(cString::sprintf("    <title>%s</title>\n", StringExtension::encodeToXml(movie.title).c_str()));
+    }
+    if (movie.originalTitle != "") {
+       s->write(cString::sprintf("    <original_title>%s</original_title>\n", StringExtension::encodeToXml(movie.originalTitle).c_str()));
+    }
+    if (movie.tagline != "") {
+       s->write(cString::sprintf("    <tagline>%s</tagline>\n", StringExtension::encodeToXml(movie.tagline).c_str()));
+    }
+    if (movie.overview != "") {
+       s->write(cString::sprintf("    <overview>%s</overview>\n", StringExtension::encodeToXml(movie.overview).c_str()));
+    }
+    s->write(cString::sprintf("    <adult>%s</adult>\n", (movie.adult ? "true" : "false")));
+    if (movie.collectionName != "") {
+       s->write(cString::sprintf("    <collection_name>%s</collection_name>\n", StringExtension::encodeToXml(movie.collectionName).c_str()));
+    }
+    if (movie.budget > 0) {
+       s->write(cString::sprintf("    <budget>%i</budget>\n", movie.budget));
+    }
+    if (movie.revenue > 0) {
+       s->write(cString::sprintf("    <revenue>%i</revenue>\n", movie.revenue));
+    }
+    if (movie.genres != "") {
+       s->write(cString::sprintf("    <genres>%s</genres>\n", StringExtension::encodeToXml(movie.genres).c_str()));
+    }
+    if (movie.homepage != "") {
+       s->write(cString::sprintf("    <homepage>%s</homepage>\n", StringExtension::encodeToXml(movie.homepage).c_str()));
+    }
+    if (movie.releaseDate != "") {
+       s->write(cString::sprintf("    <release_date>%s</release_date>\n", StringExtension::encodeToXml(movie.releaseDate).c_str()));
+    }
+    if (movie.runtime > 0) {
+       s->write(cString::sprintf("    <runtime>%i</runtime>\n", movie.runtime));
+    }
+    if (movie.popularity > 0) {
+       s->write(cString::sprintf("    <popularity>%.2f</popularity>\n", movie.popularity));
+    }
+    if (movie.voteAverage > 0) {
+       s->write(cString::sprintf("    <vote_average>%.2f</vote_average>\n", movie.voteAverage));
+    }
+    if ((movie.poster.width > 0) && (movie.poster.height > 0) && (movie.poster.path.size() > 0)) {
+       s->write(cString::sprintf("    <poster path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+				 StringExtension::encodeToXml(cleanImagePath(movie.poster.path)).c_str(), movie.poster.width, movie.poster.height));
+    }
+    if ((movie.fanart.width > 0) && (movie.fanart.height > 0) && (movie.fanart.path.size() > 0)) {
+       s->write(cString::sprintf("    <fanart path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+				 StringExtension::encodeToXml(cleanImagePath(movie.fanart.path)).c_str(), movie.fanart.width, movie.fanart.height));
+    }
+    if ((movie.collectionPoster.width > 0) && (movie.collectionPoster.height > 0) && (movie.collectionPoster.path.size() > 0)) {
+       s->write(cString::sprintf("    <collection_poster path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+				 StringExtension::encodeToXml(cleanImagePath(movie.collectionPoster.path)).c_str(), movie.collectionPoster.width, movie.collectionPoster.height));
+    }
+    if ((movie.collectionFanart.width > 0) && (movie.collectionFanart.height > 0) && (movie.collectionFanart.path.size() > 0)) {
+       s->write(cString::sprintf("    <collection_fanart path=\"%s\" width=\"%i\" height=\"%i\" />\n",
+				 StringExtension::encodeToXml(cleanImagePath(movie.collectionFanart.path)).c_str(), movie.collectionFanart.width, movie.collectionFanart.height));
+    }
+    if (movie.actors.size() > 0) {
+       int _actors = movie.actors.size();
+       for (int i = 0; i < _actors; i++) {
+	   s->write(cString::sprintf("    <actor name=\"%s\" role=\"%s\" thumb=\"%s\"/>\n",
+				     StringExtension::encodeToXml(movie.actors[i].name).c_str(),
+				     StringExtension::encodeToXml(movie.actors[i].role).c_str(),
+				     StringExtension::encodeToXml(cleanImagePath(movie.actors[i].actorThumb.path)).c_str()));
+       }
+    }
+    s->write("  </param>\n");
+  }
+};
+
+/**
+ * respond to image requests
+ * @param ostream& out
+ * @param cxxtools::http::Request& request
+ * @param cxxtools::http::Reply& reply
+ * return void
+ */
+void ScraperImageResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply) {
+
+  QueryHandler::addHeader(reply);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+  if ( request.method() != "GET") {
+     reply.httpReturn(403, "To retrieve images use the GET method!");
+     return;
+  }
+
+  double timediff = -1;
+  string base = "/scraper/image/";
+  string epgImagesPath = Settings::get()->EpgImageDirectory();
+  string url = request.url();
+
+  if ( (int)url.find(base) == 0 ) {
+
+      isyslog("restfulapi Scraper: image request url %s", request.url().c_str());
+
+      string image = url.replace(0, base.length(), "");
+      string path = epgImagesPath + (string)"/" + image;
+
+      if (!FileExtension::get()->exists(path)) {
+	  isyslog("restfulapi Scraper: image %s does not exist", request.url().c_str());
+	  reply.httpReturn(404, "File not found");
+	  return;
+      }
+
+      if (request.hasHeader("If-Modified-Since")) {
+	  timediff = difftime(FileExtension::get()->getModifiedTime(path), FileExtension::get()->getModifiedSinceTime(request));
+      }
+      if (timediff > 0.0 || timediff < 0.0) {
+	  string type = image.substr(image.find_last_of(".")+1);
+	  string contenttype = (string)"image/" + type;
+	  StreamExtension se(&out);
+	  if ( se.writeBinary(path) ) {
+	      isyslog("restfulapi Scraper: successfully piped image %s", request.url().c_str());
+	      QueryHandler::addHeader(reply);
+	      FileExtension::get()->addModifiedHeader(path, reply);
+	      reply.addHeader("Content-Type", contenttype.c_str());
+	  } else {
+	      isyslog("restfulapi Scraper: error piping image %s", request.url().c_str());
+	      reply.httpReturn(404, "File not found");
+	  }
+      } else {
+	  isyslog("restfulapi Scraper: image %s not modified, returning 304", request.url().c_str());
+	  reply.httpReturn(304, "Not-Modified");
+      }
+  }
+};
+
+/* ********* */
+
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerImage& i)
+{
+  si.addMember("path") <<= i.Path;
+  si.addMember("width") <<= i.Width;
+  si.addMember("height") <<= i.Height;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerActor& a)
+{
+  si.addMember("name") <<= a.Name;
+  si.addMember("role") <<= a.Role;
+  si.addMember("thumb") <<= a.Thumb;
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerAdditionalMedia& am)
+{
+  if (am.Scraper.length() > 0) {
+     si.addMember("type") <<= am.Scraper;
+     if (am.SeriesId > 0) {
+        si.addMember("series_id") <<= am.SeriesId;
+        si.addMember("episode_id") <<= am.EpisodeId;
+        si.addMember("name") <<= am.SeriesName;
+        si.addMember("overview") <<= am.SeriesOverview;
+        si.addMember("first_aired") <<= am.SeriesFirstAired;
+        si.addMember("network") <<= am.SeriesNetwork;
+        si.addMember("genre") <<= am.SeriesGenre;
+        si.addMember("rating") <<= am.SeriesRating;
+        si.addMember("status") <<= am.SeriesStatus;
+
+        si.addMember("episode_number") <<= am.EpisodeNumber;
+        si.addMember("episode_season") <<= am.EpisodeSeason;
+        si.addMember("episode_name") <<= am.EpisodeName;
+        si.addMember("episode_first_aired") <<= am.EpisodeFirstAired;
+        si.addMember("episode_guest_stars") <<= am.EpisodeGuestStars;
+        si.addMember("episode_overview") <<= am.EpisodeOverview;
+        si.addMember("episode_rating") <<= am.EpisodeRating;
+        si.addMember("episode_image") <<= am.EpisodeImage;
+        si.addMember("posters") <<= am.Posters;
+        si.addMember("banners") <<= am.Banners;
+        si.addMember("fanarts") <<= am.Fanarts;
+     }
+     else if (am.MovieId > 0) {
+        si.addMember("movie_id") <<= am.MovieId;
+        si.addMember("title") <<= am.MovieTitle;
+        si.addMember("original_title") <<= am.MovieOriginalTitle;
+        si.addMember("tagline") <<= am.MovieTagline;
+        si.addMember("overview") <<= am.MovieOverview;
+        si.addMember("adult") <<= am.MovieAdult;
+        si.addMember("collection_name") <<= am.MovieCollectionName;
+        si.addMember("budget") <<= am.MovieBudget;
+        si.addMember("revenue") <<= am.MovieRevenue;
+        si.addMember("genres") <<= am.MovieGenres;
+        si.addMember("homepage") <<= am.MovieHomepage;
+        si.addMember("release_date") <<= am.MovieReleaseDate;
+        si.addMember("runtime") <<= am.MovieRuntime;
+        si.addMember("popularity") <<= am.MoviePopularity;
+        si.addMember("vote_average") <<= am.MovieVoteAverage;
+        si.addMember("poster") <<= am.MoviePoster;
+        si.addMember("fanart") <<= am.MovieFanart;
+        si.addMember("collection_poster") <<= am.MovieCollectionPoster;
+        si.addMember("collection_fanart") <<= am.MovieCollectionFanart;
+     }
+     si.addMember("actors") <<= am.Actors;
+  }
+}
diff --git a/scraper2vdr.h b/scraper2vdr.h
new file mode 100644
index 0000000..57260d9
--- /dev/null
+++ b/scraper2vdr.h
@@ -0,0 +1,106 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <locale.h>
+#include <time.h>
+#include "scraper2vdr/services.h"
+#include "tools.h"
+
+#ifndef SCRAPER2VDRESTFULAPI_H
+#define SCRAPER2VDRESTFULAPI_H
+
+struct SerActor
+{
+  cxxtools::String Name;
+  cxxtools::String Role;
+  cxxtools::String Thumb;
+};
+
+struct SerImage
+{
+  cxxtools::String Path;
+  int Width;
+  int Height;
+};
+
+struct SerAdditionalMedia
+{
+  cxxtools::String Scraper;
+  int SeriesId;
+  cxxtools::String SeriesName;
+  cxxtools::String SeriesOverview;
+  cxxtools::String SeriesFirstAired;
+  cxxtools::String SeriesNetwork;
+  cxxtools::String SeriesGenre;
+  float SeriesRating;
+  cxxtools::String SeriesStatus;
+  int EpisodeId;
+  int EpisodeNumber;
+  int EpisodeSeason;
+  cxxtools::String EpisodeName;
+  cxxtools::String EpisodeFirstAired;
+  cxxtools::String EpisodeGuestStars;
+  cxxtools::String EpisodeOverview;
+  float EpisodeRating;
+  cxxtools::String EpisodeImage;
+  std::vector< struct SerImage > Posters;
+  std::vector< struct SerImage > Banners;
+  std::vector< struct SerImage > Fanarts;
+  int MovieId;
+  cxxtools::String MovieTitle;
+  cxxtools::String MovieOriginalTitle;
+  cxxtools::String MovieTagline;
+  cxxtools::String MovieOverview;
+  bool MovieAdult;
+  cxxtools::String MovieCollectionName;
+  int MovieBudget;
+  int MovieRevenue;
+  cxxtools::String MovieGenres;
+  cxxtools::String MovieHomepage;
+  cxxtools::String MovieReleaseDate;
+  int MovieRuntime;
+  float MoviePopularity;
+  float MovieVoteAverage;
+  cxxtools::String MoviePoster;
+  cxxtools::String MovieFanart;
+  cxxtools::String MovieCollectionPoster;
+  cxxtools::String MovieCollectionFanart;
+  std::vector< struct SerActor > Actors;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerActor& a);
+void operator<<= (cxxtools::SerializationInfo& si, const SerImage& i);
+void operator<<= (cxxtools::SerializationInfo& si, const SerAdditionalMedia& am);
+
+
+class Scraper2VdrService {
+private:
+  cPlugin *getScraperPlugin(void);
+  cPlugin *scraper;
+  std::string epgImagesDir;
+  bool getEventType	(ScraperGetEventType &eventType);
+  void getSeriesMedia	(SerAdditionalMedia &am, ScraperGetEventType &eventType);
+  void getMovieMedia	(SerAdditionalMedia &am, ScraperGetEventType &eventType);
+  void getSeriesMedia	(StreamExtension* s, ScraperGetEventType &eventType);
+  void getMovieMedia	(StreamExtension* s, ScraperGetEventType &eventType);
+  bool getMedia		(ScraperGetEventType &eventType, SerAdditionalMedia &am);
+  bool getMedia		(ScraperGetEventType &eventType, StreamExtension* s);
+  std::string cleanImagePath(std::string path);
+public:
+  explicit Scraper2VdrService();
+  virtual ~Scraper2VdrService();
+  bool getMedia(cEvent *event, SerAdditionalMedia &am);
+  bool getMedia(cRecording *recording, SerAdditionalMedia &am);
+  bool getMedia(cEvent *event, StreamExtension *s);
+  bool getMedia(cRecording *recording, StreamExtension* s);
+};
+
+class ScraperImageResponder : public cxxtools::http::Responder {
+public:
+  explicit ScraperImageResponder(cxxtools::http::Service& service) : cxxtools::http::Responder(service) {}
+  virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+};
+
+typedef cxxtools::http::CachedService<ScraperImageResponder> ScraperService;
+
+#endif // SCRAPER2VDRESTFULAPI_H
diff --git a/scraper2vdr/services.h b/scraper2vdr/services.h
new file mode 100644
index 0000000..6c48592
--- /dev/null
+++ b/scraper2vdr/services.h
@@ -0,0 +1,197 @@
+#ifndef __SCRAPER2VDRSERVICES_H
+#define __SCRAPER2VDRSERVICES_H
+
+#include <string>
+#include <vector>
+#include <vdr/epg.h>
+#include <vdr/recording.h>
+#include <vdr/plugin.h>
+
+enum tvType {
+    tSeries,
+    tMovie,
+    tNone,
+};
+
+/*********************************************************************
+* Helper Structures
+*********************************************************************/
+class cTvMedia {
+public:
+	cTvMedia(void) {
+		path = "";
+		width = height = 0;
+	};
+    std::string path;
+    int width;
+    int height;
+};
+
+class cEpisode {
+public:
+    cEpisode(void) {
+        number = 0;
+        season = 0;
+        name = "";
+        firstAired = "";
+        guestStars = "";
+        overview = "";
+        rating = 0.0;
+    };
+    int number;
+    int season;
+    std::string name;
+    std::string firstAired;
+    std::string guestStars;
+    std::string overview;
+    float rating;
+    cTvMedia episodeImage;
+};
+
+class cActor {
+public:
+    cActor(void) {
+        name = "";
+        role = "";
+    };
+    std::string name;
+    std::string role;
+    cTvMedia actorThumb;
+};
+
+/*********************************************************************
+* Data Structures for Service Calls
+*********************************************************************/
+
+// Data structure for service "GetEventType"
+class ScraperGetEventType {
+public:
+  ScraperGetEventType(void) {
+    event = NULL;
+    recording = NULL;
+    type = tNone;
+    movieId = 0;
+    seriesId = 0;
+    episodeId = 0;
+  };
+// in
+  const cEvent *event;             // check type for this event
+  const cRecording *recording;     // or for this recording
+//out
+  tvType type;                	 //typeSeries or typeMovie
+  int movieId;
+  int seriesId;
+  int episodeId;
+};
+
+//Data structure for full series and episode information
+class cMovie {
+public:
+    cMovie(void) {
+        title = "";
+        originalTitle = "";
+        tagline = "";    
+        overview = "";
+        adult = false;
+        collectionName = "";
+        budget = 0;
+        revenue = 0;
+        genres = "";
+        homepage = "";
+        releaseDate = "";
+        runtime = 0;
+        popularity = 0.0;
+        voteAverage = 0.0;
+    };
+//IN
+    int movieId;                    // movieId fetched from ScraperGetEventType
+//OUT    
+    std::string title;
+    std::string originalTitle;
+    std::string tagline;    
+    std::string overview;
+    bool adult;
+    std::string collectionName;
+    int budget;
+    int revenue;
+    std::string genres;
+    std::string homepage;
+    std::string releaseDate;
+    int runtime;
+    float popularity;
+    float voteAverage;
+    cTvMedia poster;
+    cTvMedia fanart;
+    cTvMedia collectionPoster;
+    cTvMedia collectionFanart;
+    std::vector<cActor> actors;
+};
+
+//Data structure for full series and episode information
+class cSeries {
+public:
+    cSeries(void) {
+        seriesId = 0;
+        episodeId = 0;
+        name = "";
+        overview = "";
+        firstAired = "";
+        network = "";
+        genre = "";
+        rating = 0.0;
+        status = "";
+    };
+//IN
+    int seriesId;                   // seriesId fetched from ScraperGetEventType
+    int episodeId;                  // episodeId fetched from ScraperGetEventType
+//OUT
+    std::string name;
+    std::string overview;
+    std::string firstAired;
+    std::string network;
+    std::string genre;
+    float rating;
+    std::string status;
+    cEpisode episode;
+    std::vector<cActor> actors;
+    std::vector<cTvMedia> posters;
+    std::vector<cTvMedia> banners;
+    std::vector<cTvMedia> fanarts;
+    cTvMedia seasonPoster;
+};
+
+// Data structure for service "GetPosterBanner"
+class ScraperGetPosterBanner {
+public:
+	ScraperGetPosterBanner(void) {
+		type = tNone;
+	};
+// in
+    const cEvent *event;             // check type for this event 
+//out
+    tvType type;                	 //typeSeries or typeMovie
+    cTvMedia poster;
+    cTvMedia banner;
+};
+
+// Data structure for service "GetPoster"
+class ScraperGetPoster {
+public:
+// in
+    const cEvent *event;             // check type for this event
+    const cRecording *recording;     // or for this recording
+//out
+    cTvMedia poster;
+};
+
+// Data structure for service "GetPosterThumb"
+class ScraperGetPosterThumb {
+public:
+// in
+    const cEvent *event;             // check type for this event
+    const cRecording *recording;     // or for this recording
+//out
+    cTvMedia poster;
+};
+
+#endif //__SCRAPER2VDRSERVICES_H
diff --git a/searchtimers.cpp b/searchtimers.cpp
new file mode 100644
index 0000000..d6de07b
--- /dev/null
+++ b/searchtimers.cpp
@@ -0,0 +1,214 @@
+#include "searchtimers.h"
+using namespace std;
+
+void SearchTimersResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler::addHeader(reply);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET, POST, DELETE");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  cPlugin* plugin = cPluginManager::GetPlugin("epgsearch");
+  if (plugin == NULL) {
+     reply.httpReturn(403, "Epgsearch isn't installed!");
+     return; 
+  }
+
+  if ((int)request.url().find("/searchtimers/search/") == 0 ) {
+     replySearch(out, request, reply);
+  } else { 
+     if (request.method() == "GET") {
+        replyShow(out, request, reply);
+     } else if (request.method() == "POST") {
+        replyCreate(out, request, reply);
+     } else if (request.method() == "DELETE") {
+        replyDelete(out, request, reply);
+     } else {
+        reply.httpReturn(404, "The searchtimer-service does only support the following methods: GET, POST and DELETE.");
+     }
+  }
+}
+
+void SearchTimersResponder::replyShow(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/searchtimers", request);
+  vdrlive::SearchTimers service;
+  SearchTimerList* stList;
+
+  if (q.isFormat(".json")) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     stList = (SearchTimerList*)new JsonSearchTimerList(&out);
+  } else if ( q.isFormat(".html")) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     stList = (SearchTimerList*)new HtmlSearchTimerList(&out);
+  } else if ( q.isFormat(".xml")) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     stList = (SearchTimerList*)new XmlSearchTimerList(&out);
+  } else {
+     reply.httpReturn(405, "Resources are not available for the selected format. (Use: .json, .html or .xml)");
+     return;
+  }
+
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+  if ( start_filter >= 0 && limit_filter >= 1 ) stList->activateLimit(start_filter, limit_filter);
+
+  stList->init();
+  int counter = 0;
+
+  for(vdrlive::SearchTimers::iterator timer = service.begin(); timer != service.end(); ++timer)
+  { 
+    SerSearchTimerContainer container;
+    container.timer = &(*timer);
+    stList->addSearchTimer(container);
+    counter++;
+  }
+
+  stList->setTotal(counter);
+  stList->finish();
+  
+  delete stList;
+}
+
+void SearchTimersResponder::replyCreate(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/searchtimers", request);
+  vdrlive::SearchTimer* searchTimer = new vdrlive::SearchTimer();
+  vdrlive::SearchTimers searchTimers;
+  string result = searchTimer->LoadFromQuery(q);
+
+  if (result.length() > 0)
+  { 
+     reply.httpReturn(406, result.c_str());
+  } else {
+     bool succeeded = searchTimers.Save(searchTimer);
+     if(succeeded) {
+        reply.httpReturn(200, (const char*)cString::sprintf("OK, Id:%i", searchTimer->Id()));
+     } else {
+        reply.httpReturn(407, "Creating searchtimer failed.");
+     }
+  }
+
+  delete searchTimer;
+}
+
+void SearchTimersResponder::replyDelete(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/searchtimers", request);
+  vdrlive::SearchTimers searchTimers;
+  string id = q.getParamAsString(0);
+  bool result = searchTimers.Delete(id);
+
+  if (!result)
+     reply.httpReturn(408, "Deleting searchtimer failed!");
+  else
+     reply.httpReturn(200, "Searchtimer deleted.");  
+}
+
+void SearchTimersResponder::replySearch(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/searchtimers/search", request);
+  vdrlive::SearchResults searchResults;
+  int id = q.getParamAsInt(0);
+
+  EventList* eventList;
+
+  if ( q.isFormat(".json") ) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     eventList = (EventList*)new JsonEventList(&out);
+  } else if ( q.isFormat(".html") ) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     eventList = (EventList*)new HtmlEventList(&out);
+  } else if ( q.isFormat(".xml") ) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     eventList = (EventList*)new XmlEventList(&out);
+  } else {
+     reply.httpReturn(403, "Resources are not available for the selected format. (Use: .json, .xml or .html)");
+     return;
+  }
+  
+  searchResults.GetByID(id);
+
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+  if ( start_filter >= 0 && limit_filter >= 1 )
+     eventList->activateLimit(start_filter, limit_filter);
+  
+  eventList->init();
+  int total = 0;
+  
+  for (vdrlive::SearchResults::iterator item = searchResults.begin(); item != searchResults.end(); ++item) {
+    eventList->addEvent((cEvent*)item->GetEvent());
+    total++;
+  }
+
+  eventList->setTotal(total);
+  eventList->finish();
+  delete eventList;
+}
+
+SearchTimerList::SearchTimerList(ostream* _out)
+{
+  s = new StreamExtension(_out);
+  total = 0;
+}
+
+SearchTimerList::~SearchTimerList()
+{
+  delete s;
+}
+
+void HtmlSearchTimerList::init()
+{
+  s->writeHtmlHeader("HtmlSearchTimerList");
+  s->write("<ul>");
+}
+
+void HtmlSearchTimerList::addSearchTimer(SerSearchTimerContainer searchTimer)
+{
+  if ( filtered() ) return;
+  s->write(searchTimer.timer->ToHtml().c_str());
+}
+
+void HtmlSearchTimerList::finish()
+{
+  s->write("</ul>");
+  s->write("</body></html>");
+}
+
+void JsonSearchTimerList::addSearchTimer(SerSearchTimerContainer searchTimer)
+{
+  if ( filtered() ) return;
+  _items.push_back(searchTimer);
+}
+
+void JsonSearchTimerList::finish()
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(_items, "searchtimers");
+  serializer.serialize(Count(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
+
+void XmlSearchTimerList::init()
+{
+  s->writeXmlHeader();
+  s->write("<searchtimers xmlns=\"http://www.domain.org/restfulapi/2011/searchtimers-xml\" >\n");
+}
+
+void XmlSearchTimerList::addSearchTimer(SerSearchTimerContainer searchTimer)
+{
+  if ( filtered() ) return;
+  s->write(searchTimer.timer->ToXml().c_str());
+}
+
+void XmlSearchTimerList::finish()
+{
+  s->write(cString::sprintf(" <count>%i</count><total>%i</total>\n", Count(), total));
+  s->write("</searchtimers>\n");
+}
+
diff --git a/searchtimers.h b/searchtimers.h
new file mode 100644
index 0000000..9f6730d
--- /dev/null
+++ b/searchtimers.h
@@ -0,0 +1,80 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/arg.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include <cxxtools/utf8codec.h>
+#include <vdr/epg.h>
+#include <vdr/plugin.h>
+
+#include "tools.h"
+#include "epgsearch/services.h"
+#include "epgsearch.h"
+#include "events.h"
+
+#ifndef __RESTFUL_SEARCHTIMERS_H
+#define __RESETFUL_SEARCHTIMERS_H
+
+class SearchTimersResponder : public cxxtools::http::Responder
+{
+  public:
+    explicit SearchTimersResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service)
+      { }
+    virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void replyShow(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void replyCreate(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void replyDelete(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+    virtual void replySearch(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+};
+
+typedef cxxtools::http::CachedService<SearchTimersResponder> SearchTimersService;
+
+class SearchTimerList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+  public: 
+    SearchTimerList(std::ostream* _out);
+    ~SearchTimerList();
+    virtual void init() { };
+    virtual void addSearchTimer(SerSearchTimerContainer searchTimer) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; };
+};
+
+class HtmlSearchTimerList : public SearchTimerList
+{
+  public:
+    HtmlSearchTimerList(std::ostream* _out) : SearchTimerList(_out) { };
+    ~HtmlSearchTimerList() { };
+    virtual void init();
+    virtual void addSearchTimer(SerSearchTimerContainer searchTimer);
+    virtual void finish();
+};
+
+class JsonSearchTimerList : public SearchTimerList
+{
+  private:
+    std::vector< SerSearchTimerContainer > _items;
+  public:
+    JsonSearchTimerList(std::ostream* _out) : SearchTimerList(_out) { };
+    ~JsonSearchTimerList() { };
+    virtual void init() { };
+    virtual void addSearchTimer(SerSearchTimerContainer searchTimer);
+    virtual void finish();
+};
+
+class XmlSearchTimerList : public SearchTimerList
+{
+  public:
+    XmlSearchTimerList(std::ostream* _out) : SearchTimerList(_out) { };
+    ~XmlSearchTimerList() { };
+    virtual void init();
+    virtual void addSearchTimer(SerSearchTimerContainer searchTimer);
+    virtual void finish();
+};
+
+#endif
diff --git a/serverthread.cpp b/serverthread.cpp
new file mode 100644
index 0000000..b1efacb
--- /dev/null
+++ b/serverthread.cpp
@@ -0,0 +1,116 @@
+/*
+ * serverthread.cpp: JSONAPI plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "serverthread.h"
+
+void cServerThread::Initialize()
+{
+  active = false; 
+
+  listenIp = Settings::get()->Ip();
+  listenPort = Settings::get()->Port();
+
+  isyslog("create server");
+  server = new cxxtools::http::Server(loop, listenIp, listenPort);
+}
+
+void cServerThread::Stop() {
+  active = false;
+  loop.exit();
+  int now = time(NULL);
+  esyslog("restfulapi: will end server thread: /%i/", now);
+  usleep(100000);//100ms//sleep(1);
+  delete server;
+}
+
+cServerThread::~cServerThread()
+{
+  Cancel(0);
+}
+
+void cServerThread::Action(void)
+{
+  active = true;
+
+  InfoService infoService;
+  ChannelsService channelsService;
+  EventsService eventsService;
+  RecordingsService recordingsService;
+  RemoteService remoteService;
+  TimersService timersService;
+  OsdService osdService;
+  SearchTimersService searchTimersService;
+  ScraperService scraperService;
+  WirbelscanService wirbelscanService;
+  WebappService webappService;
+  FemonService femonService;
+  
+  RestfulServices* services = RestfulServices::get();
+  
+  RestfulService* info = new RestfulService("/info", true, 1);
+  RestfulService* channels = new RestfulService("/channels", true, 1);
+  RestfulService* channelGroups = new RestfulService("/channels/groups", true, 1, channels);
+  RestfulService* channelImage = new RestfulService("/channels/image", true, 1, channels);
+  RestfulService* events = new RestfulService("/events", true, 1);
+  RestfulService* eventsImage = new RestfulService("/events/image", true, 1, events);
+  RestfulService* eventsSearch = new RestfulService("/events/search", false, 1, events);
+  RestfulService* recordings = new RestfulService("/recordings", true, 1);
+  RestfulService* recordingsCut = new RestfulService("/recordings/cut", true, 1, recordings);
+  RestfulService* recordingsMarks = new RestfulService("/recordings/marks", true, 1, recordings);
+  RestfulService* remote = new RestfulService("/remote", true, 1);
+  RestfulService* timers = new RestfulService("/timers", true, 1);
+  RestfulService* osd = new RestfulService("/osd", true, 1);
+  RestfulService* searchtimers = new RestfulService("/searchtimers", false, 1);
+  RestfulService* scraper = new RestfulService("/scraper", true, 1);
+  RestfulService* wirbelscan = new RestfulService("/wirbelscan", true, 1);
+  RestfulService* wirbelscanCountries = new RestfulService("/wirbelscan/countries", true, 1, wirbelscan);
+  RestfulService* webapp = new RestfulService("/webapp", true, 1);
+  RestfulService* femon = new RestfulService("/femon", true, 1);
+  
+  services->appendService(info);
+  services->appendService(channels);
+  services->appendService(channelGroups);
+  services->appendService(channelImage);
+  services->appendService(events);
+  services->appendService(eventsImage);
+  services->appendService(eventsSearch);
+  services->appendService(recordings);
+  services->appendService(recordingsCut);
+  services->appendService(recordingsMarks);
+  services->appendService(remote);
+  services->appendService(timers);
+  services->appendService(osd);
+  services->appendService(searchtimers);
+  services->appendService(scraper);
+  services->appendService(wirbelscan);
+  services->appendService(wirbelscanCountries);
+  services->appendService(webapp);
+  services->appendService(femon);
+
+  server->addService(*info->Regex(), infoService);
+  server->addService(*channels->Regex(), channelsService);
+  server->addService(*events->Regex(), eventsService);
+  server->addService(*recordings->Regex(), recordingsService);
+  server->addService(*remote->Regex(), remoteService);
+  server->addService(*timers->Regex(), timersService);
+  server->addService(*osd->Regex(), osdService);
+  server->addService(*searchtimers->Regex(), searchTimersService);
+  server->addService(*scraper->Regex(), scraperService);
+  server->addService(*wirbelscan->Regex(), wirbelscanService);
+  server->addService(*webapp->Regex(), webappService);
+  server->addService(*femon->Regex(), femonService);
+
+  try {
+    loop.run();
+  } catch ( const std::exception& e) {
+    esyslog("restfulapi: starting services failed: /%s/", e.what());
+  }
+  int now = time(NULL);
+  esyslog("restfulapi: server thread end: /%i/", now);
+
+  dsyslog("restfulapi: server thread ended (pid=%d)", getpid());
+}
diff --git a/serverthread.h b/serverthread.h
new file mode 100644
index 0000000..ed55d83
--- /dev/null
+++ b/serverthread.h
@@ -0,0 +1,56 @@
+/*
+ * serverthread.h: JSONAPI plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SERVERTHREAD_H
+#define __SERVERTHREAD_H
+
+#include <cxxtools/http/server.h>
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/eventloop.h>
+#include <cxxtools/arg.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/regex.h>
+
+#include <unistd.h>
+
+#include "info.h"
+#include "channels.h"
+#include "events.h"
+#include "recordings.h"
+#include "remote.h"
+#include "timers.h"
+#include "osd.h"
+#include "searchtimers.h"
+#include "epgsearch.h"
+#include "scraper2vdr.h"
+#include "wirbelscan.h"
+#include "webapp.h"
+#include "femon.h"
+
+using namespace std;
+
+class cServerThread : public cThread {
+private:
+    bool active;
+    std::string listenIp;
+    unsigned short int listenPort;
+    cxxtools::EventLoop loop;
+    cxxtools::http::Server *server;
+    void Action(void);
+
+public:
+    cServerThread() { };
+    ~cServerThread();
+    void Initialize();
+    void StartUpdate();
+    bool isActive() { return active; };
+    void Stop();
+};
+
+#endif //__SERVERTHREAD_H
diff --git a/statusmonitor.cpp b/statusmonitor.cpp
new file mode 100644
index 0000000..30580ed
--- /dev/null
+++ b/statusmonitor.cpp
@@ -0,0 +1,344 @@
+#include "statusmonitor.h"
+
+// --- TextOsd ---------------------------------------------------------------------------------------
+
+TextOsd::TextOsd()
+{
+  _selected = NULL;
+  _title = "";
+  _message = "";
+  _red = "";
+  _green = "";
+  _yellow = "";
+  _blue = "";
+}
+
+TextOsd::~TextOsd()
+{
+  ClearItems();
+}
+
+TextOsdItem* TextOsd::GetItem(int i)
+{
+  std::list<TextOsdItem*>::iterator it;
+  int counter = 0;
+
+  for (it=_items.begin(); it != _items.end(); it++)
+  {
+    if ( counter == i ) return *it;
+    counter++;
+  }
+
+  return NULL;
+}
+
+TextOsdItem* TextOsd::GetItem(std::string item)
+{
+  std::list<TextOsdItem*>::iterator it;
+  for(it = _items.begin(); it != _items.end();++it)
+  {
+     if ((*it)->Text() == item)
+     { 
+        return *it;
+     }
+  }
+  return NULL;
+}
+
+bool TextOsd::ReplaceItem(TextOsdItem* item, int i)
+{
+  if ((size_t)i < _items.size() && i >= 0)
+  {
+     TextOsdItem* old = NULL; 
+     int counter = 0;
+     std::list<TextOsdItem*>::iterator it;
+     for(it = _items.begin();it != _items.end();++it)
+     {
+        if (counter == i)
+        {
+           old = *it;
+           *it = item;
+        }
+        counter++;
+     }
+     if ( old != NULL ) {
+        delete old;
+     }
+     return true;
+  }
+  return false;
+
+}
+
+int TextOsd::CountItems()
+{
+  return _items.size();
+}
+
+void TextOsd::ClearItems()
+{
+  _selected = NULL;
+
+  while(_items.size() != 0 ) {
+     TextOsdItem* swap = _items.back();
+     _items.pop_back();
+     delete swap;
+  }
+}
+
+void TextOsd::AddItem(TextOsdItem* item)
+{
+  _items.push_back(item);
+}
+
+void TextOsd::RemoveItem(TextOsdItem* item)
+{
+  _items.remove(item);
+}
+
+void TextOsd::RemoveItem(std::string item)
+{
+  TextOsdItem* o = NULL;
+  std::list<TextOsdItem*>::iterator it;
+  for(it = _items.begin(); it != _items.end(); it++) {
+     if ((*it)->Text() == item)
+        o = *it;
+  }
+  if ( o != NULL ) {
+     _items.remove(o);
+  }
+}
+
+std::string  TextOsd::Title() { return _title; }
+std::string  TextOsd::Message() { return _message; }
+std::string  TextOsd::Red() { return _red; }
+std::string  TextOsd::Green() { return _green; }
+std::string  TextOsd::Yellow() { return _yellow; }
+std::string  TextOsd::Blue() { return _blue; }
+TextOsdItem* TextOsd::Selected() { return _selected; }
+
+void TextOsd::Title(std::string title) { _title = title; }
+void TextOsd::Message(std::string message) { _message = message; }
+void TextOsd::Red(std::string red) { _red = red; }
+void TextOsd::Green(std::string green) { _green = green; }
+void TextOsd::Yellow(std::string yellow) { _yellow = yellow; }
+void TextOsd::Blue(std::string blue) { _blue = blue; }
+void TextOsd::Selected(TextOsdItem* item) { _selected = item; }
+
+// --- ProgrammeOsd ----------------------------------------------------------------------------------
+
+ProgrammeOsd::ProgrammeOsd(time_t PresentTime, std::string PresentTitle, std::string PresentSubtitle,
+                           time_t FollowingTime, std::string FollowingTitle, std::string FollowingSubtitle)
+{
+  _presentTime = PresentTime;
+  _presentTitle = PresentTitle;
+  _presentSubtitle = PresentSubtitle;
+  _followingTime = FollowingTime;
+  _followingTitle = FollowingTitle;
+  _followingSubtitle = FollowingSubtitle;
+}
+
+// --- StatusMonitor ---------------------------------------------------------------------------------
+
+void StatusMonitor::OsdCreate(void)
+{
+  if ( _osd == NULL )
+     _osd = (BasicOsd*)new TextOsd();
+  if ( _osd != NULL && _osd->Type() != 0x01 ) {
+     OsdDestroy();
+     _osd = (BasicOsd*)new TextOsd();
+  }
+}
+
+void StatusMonitor::OsdDestroy(void)
+{
+  //TaskScheduler currently buggy...
+  //TaskScheduler::get()->AddTask((BaseTask*)new DeleteOsdTask(_osd));
+  //wait until users got the data:
+  if ( _osd != NULL ) {
+     usleep(100); //wait 0.1ms
+     if ( _osd->Type() == 0x01) {
+        TextOsd* tosd = (TextOsd*)_osd;
+        tosd->ClearItems();
+     }
+     delete _osd;
+     _osd = NULL;
+  }
+}
+
+void StatusMonitor::TimerChange(const cTimer *Timer, eTimerChange Change)
+{
+  //not important for restfulapi because of timer_server already being available?
+}
+
+#if APIVERSNUM >= 10726
+void StatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView)
+#else
+void StatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber)
+#endif
+{
+  if (ChannelNumber != 0) {
+     channel_number = ChannelNumber;
+  }
+}
+
+void StatusMonitor::Recording(const cDevice *Device, const char *Name, const char *FileName, bool On)
+{
+  //to be implemented
+}
+
+void StatusMonitor::Replaying(const cControl *Control, const char *Name, const char *FileName, bool On)
+{
+  if (On) {
+     if(Name != NULL) recording_name = std::string(Name); else recording_name = "";
+     if(FileName != NULL) recording_file = std::string(FileName); else recording_file = "";
+  } else {
+     recording_name = "";
+     recording_file = "";
+  }
+}
+
+void StatusMonitor::SetVolume(int Volume, bool Absolute)
+{
+  if(Absolute) {
+    volume = Volume;
+  } else {
+    volume += Volume;
+  }
+}
+
+void StatusMonitor::SetAudioTrack(int Index, const char * const *Tracks)
+{
+  //to be implemented
+}
+
+void StatusMonitor::SetAudioChannel(int AudioChannel)
+{
+  //to be implemented
+}
+
+void StatusMonitor::SetSubtitleTrack(int Index, const char * const *Tracks)
+{
+  //to be implemented
+}
+
+void StatusMonitor::OsdClear(void)
+{
+  OsdDestroy();
+}
+
+void StatusMonitor::OsdTitle(const char *Title)
+{
+  OsdCreate(); 
+  TextOsd* _tOsd = (TextOsd*)_osd;
+  if (Title != NULL)
+     _tOsd->Title((std::string)Title);
+}
+
+void StatusMonitor::OsdStatusMessage(const char *Message)
+{
+  OsdCreate();
+  TextOsd* _tOsd = (TextOsd*)_osd;
+  if (Message != NULL)
+     _tOsd->Message((std::string)Message);
+}
+
+void StatusMonitor::OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue)
+{
+  OsdCreate();
+  TextOsd* _tOsd = (TextOsd*)_osd;
+  if(Red != NULL) _tOsd->Red((std::string)Red);
+  if(Green != NULL) _tOsd->Green((std::string)Green);
+  if(Yellow != NULL) _tOsd->Yellow((std::string)Yellow);
+  if(Blue != NULL) _tOsd->Blue((std::string)Blue);
+}
+
+void StatusMonitor::OsdItem(const char *Text, int Index)
+{
+  OsdCreate();
+  TextOsd* _tOsd = (TextOsd*)_osd;
+  if (Text != NULL)
+  {
+     bool result = _tOsd->ReplaceItem(new TextOsdItem((std::string)Text), Index);
+     if (!result) {
+        _tOsd->AddItem(new TextOsdItem((std::string)Text));
+     }
+  }
+}
+
+void StatusMonitor::OsdCurrentItem(const char *Text)
+{
+  if ( Text == NULL ) return;
+  OsdCreate();
+  TextOsd* _tOsd = (TextOsd*)_osd;
+
+  TextOsdItem* osdItem = _tOsd->GetItem((std::string)Text);
+
+  if ( osdItem == NULL && _tOsd->Selected() == NULL ) {
+      TextOsdItem* newItem = new TextOsdItem((std::string)Text);
+      _tOsd->AddItem(newItem);
+  } else if ( osdItem == NULL) {
+     _tOsd->Selected()->Text(Text);
+  } else {
+     _tOsd->Selected(osdItem);
+  }
+}
+
+void StatusMonitor::OsdTextItem(const char *Text, bool Scroll)
+{
+  OsdCreate();
+  TextOsd* _tOsd = (TextOsd*)_osd;
+  if (Text != NULL)
+  {
+     //std::vector< std::string > lines = StringExtension::split((std::string)Text, "\n");
+     //for(int i=0;i<(int)lines.size();i++)
+        _tOsd->AddItem(new TextOsdItem((std::string)Text)/*new TextOsdItem(lines[i])*/);
+  }
+}
+
+void StatusMonitor::OsdChannel(const char *Text)
+{
+  if ( Text != NULL ) {
+     OsdDestroy();
+     _osd = (BasicOsd*)new ChannelOsd((std::string)Text);
+  }
+}
+
+void StatusMonitor::OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle)
+{
+  OsdDestroy();
+
+  std::string presentTitle = "";
+  std::string presentSubtitle = "";
+  std::string followingTitle = "";
+  std::string followingSubtitle = "";
+  
+  if ( PresentTitle != NULL ) { presentTitle = (std::string)PresentTitle; }
+  if ( PresentSubtitle != NULL ) { presentSubtitle = (std::string)PresentSubtitle; }
+  if ( FollowingTitle != NULL ) { followingTitle = (std::string)FollowingTitle; }
+  if ( FollowingSubtitle != NULL ) { followingSubtitle = (std::string)FollowingSubtitle; }
+
+  _osd = (BasicOsd*)new ProgrammeOsd(PresentTime, presentTitle, presentSubtitle, 
+				     FollowingTime, (std::string)followingTitle, followingSubtitle);
+}
+
+StatusMonitor* StatusMonitor::get()
+{
+  static StatusMonitor sm;
+  return &sm;
+}
+
+// --- DeleteOsdTask ---------------------------------------------------------
+
+DeleteOsdTask::~DeleteOsdTask()
+{
+  if (osd != NULL)
+  {
+     if (osd->Type() == 0x01) {
+        TextOsd* tosd = (TextOsd*)osd;
+        tosd->ClearItems();
+     }
+     delete osd;
+  }
+}
+
diff --git a/statusmonitor.h b/statusmonitor.h
new file mode 100644
index 0000000..a7d4750
--- /dev/null
+++ b/statusmonitor.h
@@ -0,0 +1,165 @@
+#include "tools.h"
+#include <list>
+#include <vdr/status.h>
+
+
+#ifndef __STATUSMONITOR_H
+#define __STATUSMONITOR_H
+
+class TextOsdItem
+{
+  private:
+    std::string _text;
+  public:
+    TextOsdItem(std::string text) { _text = text; };
+    ~TextOsdItem() { };
+    std::string Text() { return _text; };
+    void Text(std::string text) { _text = text; };
+};
+
+class TextOsd : BasicOsd
+{
+  private:
+    std::string _title;
+    std::string _message;
+    
+    std::string _red;
+    std::string _green;
+    std::string _yellow;
+    std::string _blue;
+    
+    std::list< TextOsdItem* > _items;
+    TextOsdItem*   _selected;
+  public:
+    TextOsd();
+    ~TextOsd();
+    virtual int Type() { return 0x01; }
+
+    TextOsdItem*    GetItem(int i);
+    TextOsdItem*    GetItem(std::string item);
+    std::list< TextOsdItem* > GetItems() { return _items; };
+    bool        ReplaceItem(TextOsdItem* item, int i);
+    int         CountItems();
+    void        ClearItems();
+    void	AddItem(TextOsdItem* item);
+    void        RemoveItem(TextOsdItem* item);
+    void        RemoveItem(std::string item);
+
+    std::string Title();
+    std::string Message();
+    std::string Red();
+    std::string Green();
+    std::string Yellow();
+    std::string Blue();
+    TextOsdItem* Selected();
+
+    void        Title(std::string title); 
+    void        Message(std::string message);
+    void        Red(std::string red);
+    void        Green(std::string green);
+    void        Yellow(std::string yellow);
+    void        Blue(std::string blue);
+    void        Selected(TextOsdItem* item);
+};
+
+class ChannelOsd : BasicOsd
+{
+  private:
+    std::string _channel;
+  public:
+    ChannelOsd(std::string channel) { _channel = channel; };
+    ~ChannelOsd() { };
+    virtual int Type() { return 0x02; }
+    void Channel(std::string channel) { _channel = channel; };
+    std::string Channel() { return _channel; };
+};
+
+class ProgrammeOsd : BasicOsd
+{
+  private:
+    time_t _presentTime;
+    std::string _presentTitle;
+    std::string _presentSubtitle;
+    time_t _followingTime;
+    std::string _followingTitle;
+    std::string _followingSubtitle;
+  public:
+    ProgrammeOsd(time_t PresentTime, std::string PresentTitle, std::string PresentSubtitle,
+                 time_t FollowingTime, std::string FollowingTitle, std::string FollowingSubtitle);
+    ~ProgrammeOsd() { };
+    virtual int  Type() { return 0x03; };
+
+    time_t       PresentTime() { return _presentTime; };
+    void         PresentTime(time_t PresentTime) { _presentTime = PresentTime; };
+
+    time_t       FollowingTime() { return _followingTime; };
+    void         FollowingTime(time_t FollowingTime) { _followingTime = FollowingTime; };
+
+    std::string  PresentTitle() { return _presentTitle; };
+    void         PresentTitle(std::string PresentTitle) { _presentTitle = PresentTitle; };
+
+    std::string  FollowingTitle() { return _followingTitle; };
+    void         FollowingTitle(std::string FollowingTitle) { _followingTitle = FollowingTitle; };
+    
+    std::string  PresentSubtitle() { return _presentSubtitle; };
+    void         PresentSubtitle(std::string PresentSubtitle) { _presentSubtitle = PresentSubtitle; };
+
+    std::string  FollowingSubtitle() { return _followingSubtitle; };
+    void         FollowingSubtitle(std::string FollowingSubtitle) { _followingSubtitle = FollowingSubtitle; };    
+};
+
+class StatusMonitor : public cStatus
+{
+  private:
+    BasicOsd* _osd;
+    int osd_type;
+    int channel_number;
+    cDevice *device;
+    std::string recording_name;
+    std::string recording_file;
+    int volume;
+    void OsdCreate(void);
+    void OsdDestroy(void);
+  protected:
+    void TimerChange(const cTimer *Timer, eTimerChange Change);
+#if APIVERSNUM >= 10726
+    void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView);
+#else
+    void ChannelSwitch(const cDevice *Device, int ChannelNumber);
+#endif
+    void Recording(const cDevice *Device, const char *Name, const char *FileName, bool On);
+    void Replaying(const cControl *Control, const char *Name, const char *FileName, bool On);
+    void SetVolume(int Volume, bool Absolute);
+    void SetAudioTrack(int Index, const char * const *Tracks);
+    void SetAudioChannel(int AudioChannel);
+    void SetSubtitleTrack(int Index, const char * const *Tracks);
+    void OsdClear(void);
+    void OsdTitle(const char *Title);
+    void OsdStatusMessage(const char *Message);
+    void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue);
+    void OsdItem(const char *Text, int Index);
+    void OsdCurrentItem(const char *Text);
+    void OsdTextItem(const char *Text, bool Scroll);
+    void OsdChannel(const char *Text);
+    void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle);
+  public:
+    StatusMonitor() { _osd = NULL; };
+    ~StatusMonitor() { };
+    static StatusMonitor* get();
+    BasicOsd* getOsd() { return _osd; }
+    int getChannel() { return channel_number; }
+    std::string getRecordingName() { return recording_name; }
+    std::string getRecordingFile() { return recording_file; }
+};
+
+class DeleteOsdTask : BaseTask
+{
+  protected:
+    BasicOsd* osd;
+  public:
+   DeleteOsdTask(BasicOsd* osd) { };
+   ~DeleteOsdTask();
+};
+
+#endif
+
diff --git a/test/HTTPRequest.java b/test/HTTPRequest.java
new file mode 100644
index 0000000..1b463bb
--- /dev/null
+++ b/test/HTTPRequest.java
@@ -0,0 +1,57 @@
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+public class HTTPRequest {
+
+	/**
+	 * @param args
+	 * @throws IOException 
+	 * @throws UnknownHostException 
+	 */
+	public static void main(String[] args) throws UnknownHostException, IOException {
+				
+		String method = "GET";
+		String page = "/";
+		String body = "";
+		
+		if ( args.length > 0 ) { method = args[0]; }
+		if ( args.length > 1 ) { page = args[1]; }
+		if ( args.length > 2 ) { body = args[2]; }
+		
+		long start = System.currentTimeMillis();
+		
+		Socket socket = new Socket("127.0.0.1", 8002);
+		
+		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+		BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
+		
+		bufferedWriter.write(method + " " + page + " HTTP/1.1");
+		bufferedWriter.newLine();
+		bufferedWriter.write("Content-Length: "+body.length());
+		bufferedWriter.newLine();
+		bufferedWriter.write("Connection: close");
+		bufferedWriter.newLine();
+		bufferedWriter.newLine();
+		bufferedWriter.write(body);
+		bufferedWriter.newLine();
+		bufferedWriter.flush();
+		
+		String line = null;
+		while((line = bufferedReader.readLine()) != null) {
+			System.out.println(line);
+		}
+		
+		bufferedReader.close();
+		bufferedWriter.close();
+		socket.close();
+		
+		long stop = System.currentTimeMillis();
+		System.out.println("Time: "+(stop-start));
+	}
+
+}
diff --git a/test/README b/test/README
new file mode 100644
index 0000000..c578f14
--- /dev/null
+++ b/test/README
@@ -0,0 +1,6 @@
+This is a simple tool to test the web service which is provided by the plugin.
+
+Syntax to run the program:
+   java -jar httprequest.jar <method> <service> <http-body>
+An easy example:
+   java -jar httprequest.jar POST "/timers" file=movietitle&start=2015&stop=2230&day=1234567890 
diff --git a/test/httprequest.jar b/test/httprequest.jar
new file mode 100644
index 0000000..5b31d96
Binary files /dev/null and b/test/httprequest.jar differ
diff --git a/test/restart.sh b/test/restart.sh
new file mode 100755
index 0000000..25dd438
--- /dev/null
+++ b/test/restart.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+COUNTER=0
+SUCCESS=0
+while [ $COUNTER -lt 100 ]
+do
+  /etc/init.d/vdr start
+  sleep 3;
+  /etc/init.d/vdr stop
+  NUMBER=`tail -1 /var/log/syslog | cut -d ',' -f 2 | cut -d ' ' -f 4`
+  if [ "$NUMBER" == "0" ]; then
+     SUCCESS=$[$SUCCESS+1]
+  fi
+  echo "Managed to restart vdr $SUCCESS times!"
+  COUNTER=$[$COUNTER+1]
+done
+echo $SUCCESS
diff --git a/test/switch_channels_test.sh b/test/switch_channels_test.sh
new file mode 100755
index 0000000..bd4d2e8
--- /dev/null
+++ b/test/switch_channels_test.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+CHANNELS=`java -jar httprequest.jar GET /channels.xml | grep -i channel_id`
+for line in $CHANNELS
+do
+  SINGLEID=`echo $line | cut -d '>' -f 2 | cut -d '<' -f 1`
+  if [ "$SINGLEID" != "" ] 
+  then
+  echo "Now switching to $SINGLEID!"
+  RESULT=`java -jar httprequest.jar POST "/remote/switch/$SINGLEID"`
+  HTTPRESULT=`echo $RESULT | grep "HTTP/1.1" | cut -d ' ' -f 2`   #HTTP/1.1 200 OK
+    if [ $HTTPRESULT != 200 ] 
+    then
+      echo "Failed!"
+    fi
+  fi
+done
diff --git a/timers.cpp b/timers.cpp
new file mode 100644
index 0000000..abcb1a4
--- /dev/null
+++ b/timers.cpp
@@ -0,0 +1,615 @@
+#include "timers.h"
+using namespace std;
+
+void TimersResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler::addHeader(reply);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET, POST, DELETE, PUT");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+
+  if ( request.method() == "GET" ) {
+     showTimers(out, request, reply);
+  } else if ( request.method() == "DELETE" ) {
+     deleteTimer(out, request, reply);
+  } else if ( request.method() == "POST" ) {
+     createOrUpdateTimer(out, request, reply, false);
+  } else if ( request.method() == "PUT" ) {
+     createOrUpdateTimer(out, request, reply, true);
+  } else if (request.method() == "OPTIONS") {
+     return;
+  } else {
+     reply.httpReturn(501, "Only GET, DELETE, POST and PUT methods are supported.");
+  }
+}
+
+void TimersResponder::createOrUpdateTimer(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply, bool update)
+{
+  QueryHandler q("/timers", request);
+
+  if ( Timers.BeingEdited() ) {
+     reply.httpReturn(502, "Timers are being edited - try again later");
+     return;
+  }
+
+  int error = false;
+  string error_values = "";
+  static TimerValues v;
+
+  int flags = v.ConvertFlags(q.getBodyAsString("flags"));
+  string aux = v.ConvertAux(q.getBodyAsString("aux"));
+  string file = v.ConvertFile(q.getBodyAsString("file"));
+  int lifetime = v.ConvertLifetime(q.getBodyAsString("lifetime"));
+  int priority = v.ConvertPriority(q.getBodyAsString("priority"));
+  int stop = v.ConvertStop(q.getBodyAsString("stop"));
+  int start = v.ConvertStart(q.getBodyAsString("start"));
+  string weekdays = q.getBodyAsString("weekdays");
+  string day = v.ConvertDay(q.getBodyAsString("day"));
+  cChannel* chan = v.ConvertChannel(q.getBodyAsString("channel"));
+  cTimer* timer_orig = v.ConvertTimer(q.getBodyAsString("timer_id"));
+  
+  if ( update == false ) { //create
+     int eventid = q.getBodyAsInt("eventid");
+     int minpre = q.getBodyAsInt("minpre");
+     int minpost = q.getBodyAsInt("minpost");
+     if (eventid >= 0 && chan != NULL) {
+        const cEvent* event = VdrExtension::GetEventById((tEventID)eventid, chan);
+
+        if (event == NULL) {
+           reply.httpReturn(407, "eventid invalid");
+           return;
+        } else {
+           if (minpre < 0) minpre = 0;
+           if (minpost < 0) minpost = 0;
+           if (!v.IsFlagsValid(flags)) flags = 1;
+           if (!v.IsFileValid(file)) file = (string)event->Title();
+           if (!v.IsWeekdaysValid(weekdays)) weekdays = "-------";
+           if (!v.IsLifetimeValid(lifetime)) lifetime = 50;
+           if (!v.IsPriorityValid(priority)) priority = 99;
+           chan = VdrExtension::getChannel((const char*)event->ChannelID().ToString());
+           if (!v.IsStartValid(start) || !v.IsStopValid(stop) || !v.IsDayValid(day)) {
+              time_t estart = event->StartTime()-minpre*60;
+              time_t estop = event->EndTime()+minpost*60;
+              struct tm *starttime = localtime(&estart);
+
+              ostringstream daystream;
+              daystream << StringExtension::addZeros((starttime->tm_year + 1900), 4) << "-"
+                        << StringExtension::addZeros((starttime->tm_mon + 1), 2) << "-"
+                        << StringExtension::addZeros((starttime->tm_mday), 2);
+              day = daystream.str();
+ 
+              start = starttime->tm_hour * 100 + starttime->tm_min;
+
+              struct tm *stoptime = localtime(&estop);
+              stop = stoptime->tm_hour * 100 + stoptime->tm_min;
+           }
+        }
+     } else {
+        if ( !v.IsFlagsValid(flags) ) { flags = 1; }
+        if ( !v.IsFileValid(file) ) { error = true; error_values += "file, "; }
+        if ( !v.IsLifetimeValid(lifetime) ) { lifetime = 50; }
+        if ( !v.IsPriorityValid(priority) ) { priority = 99; }
+        if ( !v.IsStopValid(stop) ) { error = true; error_values += "stop, "; }
+        if ( !v.IsStartValid(start) ) { error = true; error_values += "start, "; }
+        if ( !v.IsWeekdaysValid(weekdays) ) { error = true; error_values += "weekdays, "; }
+        if ( !v.IsDayValid(day)&& !day.empty() ) { error = true; error_values += "day, "; }
+        if ( chan == NULL ) { error = true; error_values += "channel, "; }
+     }
+  } else { //update
+     if ( timer_orig == NULL ) { error = true; error_values += "timer_id, "; }
+     if ( !error ) {
+        if ( !v.IsFlagsValid(flags) ) { flags = timer_orig->Flags(); }
+        if ( !v.IsFileValid(file) ) { file = (string)timer_orig->File(); }
+        if ( !v.IsLifetimeValid(lifetime) ) { lifetime = timer_orig->Lifetime(); }
+        if ( !v.IsPriorityValid(priority) ) { priority = timer_orig->Priority(); }
+        if ( !v.IsStopValid(stop) ) { stop = timer_orig->Stop(); }
+        if ( !v.IsStartValid(start) ) { start = timer_orig->Start(); }
+        if ( !v.IsWeekdaysValid(weekdays) ) { weekdays = v.ConvertWeekdays(timer_orig->WeekDays()); }
+        if ( !v.IsDayValid(day) ) { day = v.ConvertDay(timer_orig->Day()); }
+        if ( chan == NULL ) { chan = (cChannel*)timer_orig->Channel(); }
+     }
+  }
+
+  if (error) {
+     string error_message = (string)"The following parameters aren't valid: " + error_values.substr(0, error_values.length()-2) + (string)"!";
+     reply.httpReturn(403, error_message);
+     return;
+  }
+ 
+  ostringstream builder;
+  builder << flags << ":"
+          << (const char*)chan->GetChannelID().ToString() << ":"
+          << ( weekdays != "-------" ? weekdays : "" )
+          << ( weekdays == "-------" || day.empty() ? "" : "@" ) << day << ":"
+          << start << ":"
+          << stop << ":"
+          << priority << ":"
+          << lifetime << ":"
+          << file << ":" 
+          << aux;
+
+  dsyslog("restfulapi: /%s/ ", builder.str().c_str());
+  chan = NULL;
+  if ( update == false ) { // create timer
+     cTimer* timer = new cTimer();
+     if ( timer->Parse(builder.str().c_str()) ) { 
+        cTimer* checkTimer = Timers.GetTimer(timer);
+        if ( checkTimer != NULL ) {
+           delete timer;
+           reply.httpReturn(403, "Timer already defined!"); 
+           esyslog("restfulapi: Timer already defined!");
+        } else {
+           replyCreatedId(timer, request, reply, out);
+           timer->SetEventFromSchedule();
+           Timers.Add(timer);
+           Timers.SetModified();
+           esyslog("restfulapi: timer created!");
+        }
+     } else {
+        reply.httpReturn(403, "Creating timer failed!");
+        esyslog("restfulapi: timer creation failed!");
+     }
+  } else {
+     if ( timer_orig->Parse(builder.str().c_str()) ) {
+        timer_orig->SetEventFromSchedule();
+        Timers.SetModified();
+        replyCreatedId(timer_orig, request, reply, out);
+        esyslog("restfulapi: updating timer successful!");
+     } else { 
+        reply.httpReturn(403, "updating timer failed!");
+        esyslog("restfulapi: updating timer failed!");
+     }
+  }
+}
+
+void TimersResponder::replyCreatedId(cTimer* timer, cxxtools::http::Request& request, cxxtools::http::Reply& reply, ostream& out)
+{
+  QueryHandler q("/timers", request);
+  TimerList* timerList;
+
+  if ( q.isFormat(".html") ) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     timerList = (TimerList*)new HtmlTimerList(&out);
+  } else if ( q.isFormat(".xml") ) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     timerList = (TimerList*)new XmlTimerList(&out);
+  } else {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     timerList = (TimerList*)new JsonTimerList(&out);
+  }
+
+  timerList->init();
+  timerList->addTimer(timer);
+  timerList->setTotal(1);
+  timerList->finish();
+  delete timerList;
+}
+
+void TimersResponder::deleteTimer(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/timers", request);
+
+  if ( Timers.BeingEdited() ) {
+     reply.httpReturn(502, "Timers are being edited - try again later");
+     return;
+  }
+
+  TimerValues v;
+
+  cTimer* timer = v.ConvertTimer(q.getParamAsString(0));
+ 
+  if ( timer == NULL) {
+     reply.httpReturn(404, "Timer id invalid!");
+  } else {
+     if ( timer->Recording() ) {
+        timer->Skip();
+        cRecordControls::Process(time(NULL));
+     }
+     Timers.Del(timer);
+     Timers.SetModified();
+     reply.httpReturn(200, "Timer deleted."); 
+  }
+}
+
+void TimersResponder::showTimers(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+  QueryHandler q("/timers", request);
+  TimerList* timerList;
+ 
+  Timers.SetModified();
+
+  if ( q.isFormat(".json") ) {
+     reply.addHeader("Content-Type", "application/json; charset=utf-8");
+     timerList = (TimerList*)new JsonTimerList(&out);
+  } else if ( q.isFormat(".html") ) {
+     reply.addHeader("Content-Type", "text/html; charset=utf-8");
+     timerList = (TimerList*)new HtmlTimerList(&out);
+  } else if ( q.isFormat(".xml") ) {
+     reply.addHeader("Content-Type", "text/xml; charset=utf-8");
+     timerList = (TimerList*)new XmlTimerList(&out);
+  } else {
+     reply.httpReturn(404, "Resources are not available for the selected format. (Use: .json, .html or .xml)");
+     return;
+  }
+
+  int start_filter = q.getOptionAsInt("start");
+  int limit_filter = q.getOptionAsInt("limit");
+
+  string timer_id = q.getParamAsString(0);
+
+  if ( start_filter >= 0 && limit_filter >= 1 ) {
+     timerList->activateLimit(start_filter, limit_filter);
+  }
+
+  timerList->init();
+
+  vector< cTimer* > timers = VdrExtension::SortedTimers();
+  for (int i=0;i<(int)timers.size();i++)
+  {
+     if ( VdrExtension::getTimerID(timers[i]) == timer_id || timer_id.length() == 0 ) {
+        timerList->addTimer(timers[i]);   
+     }
+  }
+  timerList->setTotal((int)timers.size());
+
+  timerList->finish();
+  delete timerList;   
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerTimer& t)
+{
+  si.addMember("id") <<= t.Id;
+  si.addMember("flags") <<= t.Flags;
+  si.addMember("start") <<= t.Start;
+  si.addMember("start_timestamp") <<= t.StartTimeStamp;
+  si.addMember("stop_timestamp") <<= t.StopTimeStamp;
+  si.addMember("stop") <<= t.Stop;
+  si.addMember("priority") <<= t.Priority;
+  si.addMember("lifetime") <<= t.Lifetime;
+  si.addMember("event_id") <<= t.EventID;
+  si.addMember("weekdays") <<= t.WeekDays;
+  si.addMember("day") <<= t.Day;
+  si.addMember("channel") <<= t.Channel;
+  si.addMember("filename") <<= t.FileName;
+  si.addMember("channel_name") <<= t.ChannelName;
+  si.addMember("is_pending") <<= t.IsPending;
+  si.addMember("is_recording") <<= t.IsRecording;
+  si.addMember("is_active") <<= t.IsActive;
+  si.addMember("aux") <<= t.Aux;
+}
+
+TimerList::TimerList(ostream *out)
+{
+  s = new StreamExtension(out);
+}
+
+TimerList::~TimerList()
+{
+  delete s;
+}
+
+void HtmlTimerList::init()
+{
+  s->writeHtmlHeader("HtmlTimerList");
+  s->write("<ul>");
+}
+
+void HtmlTimerList::addTimer(cTimer* timer)
+{
+  if ( filtered() ) return;
+  s->write("<li>");
+  s->write((char*)timer->File()); //TODO: add date, time and duration
+  s->write("\n");
+}
+
+void HtmlTimerList::finish()
+{
+  s->write("</ul>");
+  s->write("</body></html>");
+}
+
+void JsonTimerList::addTimer(cTimer* timer)
+{
+  if ( filtered() ) return;
+  static TimerValues v;
+
+  SerTimer serTimer;
+  serTimer.Id = StringExtension::UTF8Decode(VdrExtension::getTimerID(timer));
+  serTimer.Flags = timer->Flags();
+  serTimer.Start = timer->Start();
+  serTimer.Stop = timer->Stop();
+  serTimer.Priority = timer->Priority();
+  serTimer.Lifetime = timer->Lifetime();
+  serTimer.EventID = timer->Event() != NULL ? timer->Event()->EventID() : -1;
+  serTimer.WeekDays = StringExtension::UTF8Decode(v.ConvertWeekdays(timer->WeekDays()));
+  serTimer.Day = StringExtension::UTF8Decode(v.ConvertDay(timer->Day()));
+  serTimer.Channel = StringExtension::UTF8Decode((const char*)timer->Channel()->GetChannelID().ToString());
+  serTimer.IsRecording = timer->Recording();
+  serTimer.IsPending = timer->Pending();
+  serTimer.FileName = StringExtension::UTF8Decode(timer->File());
+  serTimer.ChannelName = StringExtension::UTF8Decode(timer->Channel()->Name());
+  serTimer.IsActive = (timer->Flags() & tfActive) == tfActive ? true : false;
+  serTimer.Aux = StringExtension::UTF8Decode(timer->Aux() != NULL ? timer->Aux() : "");
+
+  int tstart = timer->Day() - ( timer->Day() % 3600 ) + ((int)(timer->Start()/100)) * 3600 + ((int)(timer->Start()%100)) * 60;
+  int tstop = timer->Day() - ( timer->Day() % 3600 ) + ((int)(timer->Stop()/100)) * 3600 + ((int)(timer->Stop()%100)) * 60;
+
+  //if a timer starts before and ends after midnight, add a day to tstop
+  if ( (int)(timer->Start()) > (int)(timer->Stop()) )
+    tstop += 86400;
+
+  serTimer.StartTimeStamp = StringExtension::UTF8Decode(StringExtension::dateToString((time_t)tstart));
+  serTimer.StopTimeStamp = StringExtension::UTF8Decode(StringExtension::dateToString((time_t)tstop));
+
+  serTimers.push_back(serTimer);
+}
+
+void JsonTimerList::finish()
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(serTimers, "timers");
+  serializer.serialize(serTimers.size(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
+
+void XmlTimerList::init()
+{
+  counter = 0;
+  s->writeXmlHeader();
+  s->write("<timers xmlns=\"http://www.domain.org/restfulapi/2011/timers-xml\">\n");
+}
+
+void XmlTimerList::addTimer(cTimer* timer)
+{
+  if ( filtered() ) return;
+  static TimerValues v;
+
+  s->write(" <timer>\n");
+  s->write(cString::sprintf("  <param name=\"id\">%s</param>\n", StringExtension::encodeToXml(VdrExtension::getTimerID(timer)).c_str()));
+  s->write(cString::sprintf("  <param name=\"flags\">%i</param>\n", timer->Flags()));
+  s->write(cString::sprintf("  <param name=\"start\">%i</param>\n", timer->Start()) );
+  s->write(cString::sprintf("  <param name=\"stop\">%i</param>\n", timer->Stop()) );
+
+  int tstart = timer->Day() - ( timer->Day() % 3600 ) + ((int)(timer->Start()/100)) * 3600 + ((int)(timer->Start()%100)) * 60;
+  int tstop = timer->Day() - ( timer->Day() % 3600 ) + ((int)(timer->Stop()/100)) * 3600 + ((int)(timer->Stop()%100)) * 60;
+
+  s->write(cString::sprintf("  <param name=\"start_timestamp\">%s</param>\n", StringExtension::encodeToXml(StringExtension::dateToString(tstart)).c_str()));
+  s->write(cString::sprintf("  <param name=\"stop_timestamp\">%s</param>\n", StringExtension::encodeToXml(StringExtension::dateToString(tstop)).c_str()));
+
+  s->write(cString::sprintf("  <param name=\"priority\">%i</param>\n", timer->Priority()) );
+  s->write(cString::sprintf("  <param name=\"lifetime\">%i</param>\n", timer->Lifetime()) );
+  s->write(cString::sprintf("  <param name=\"event_id\">%i</param>\n", timer->Event() != NULL ? timer->Event()->EventID() : -1) );
+  s->write(cString::sprintf("  <param name=\"weekdays\">%s</param>\n", StringExtension::encodeToXml(v.ConvertWeekdays(timer->WeekDays())).c_str()));
+  s->write(cString::sprintf("  <param name=\"day\">%s</param>\n", StringExtension::encodeToXml(v.ConvertDay(timer->Day())).c_str()));
+  s->write(cString::sprintf("  <param name=\"channel\">%s</param>\n", StringExtension::encodeToXml((const char*)timer->Channel()->GetChannelID().ToString()).c_str()) );
+  s->write(cString::sprintf("  <param name=\"is_recording\">%s</param>\n", timer->Recording() ? "true" : "false" ) );
+  s->write(cString::sprintf("  <param name=\"is_pending\">%s</param>\n", timer->Pending() ? "true" : "false" ));
+  s->write(cString::sprintf("  <param name=\"file_name\">%s</param>\n", StringExtension::encodeToXml(timer->File()).c_str()) );
+  s->write(cString::sprintf("  <param name=\"channel_name\">%s</param>\n", StringExtension::encodeToXml(timer->Channel()->Name()).c_str()));
+  s->write(cString::sprintf("  <param name=\"is_active\">%s</param>\n", (timer->Flags() & tfActive) == tfActive ? "true" : "false" ));
+  s->write(cString::sprintf("  <param name=\"aux\">%s</param>\n", StringExtension::encodeToXml(timer->Aux() != NULL ? timer->Aux() : "").c_str()));
+  s->write(" </timer>\n");
+}
+
+void XmlTimerList::finish()
+{
+  s->write((const char*)cString::sprintf(" <count>%i</count><total>%i</total>", Count(), total));
+  s->write("</timers>");
+}
+
+// --- TimerValues class ------------------------------------------------------------
+
+queue<int> TimerValues::ConvertToBinary(int v)
+{
+   int b;
+   queue <int> res;
+
+   while ( v != 0) {
+     b = v % 2;
+     res.push(b);
+     v = (v-b) / 2;
+   }
+   return res;
+}
+
+bool TimerValues::IsDayValid(string v)
+{
+  static cxxtools::Regex regex("[0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2}");
+  return regex.match(v);
+}
+
+bool TimerValues::IsFlagsValid(int v)
+{
+  if (v == tfAll) return true;
+  int valid_tf = (tfNone | tfActive | tfInstant | tfVps | tfRecording);
+  return (valid_tf & v) == v;
+}
+
+bool TimerValues::IsFileValid(string v) 
+{
+  if ( v.length() > 0 && v.length() <= 99 ) 
+     return true;
+  return false;
+}
+
+bool TimerValues::IsLifetimeValid(int v) 
+{
+  if ( v >= 0 && v <= 99 )
+     return true;
+  return false;
+}
+
+bool TimerValues::IsPriorityValid(int v)
+{
+  return IsLifetimeValid(v); //uses the same values as the lifetime
+}
+
+bool TimerValues::IsStopValid(int v)
+{
+  int minutes = v % 100;
+  int hours = (v - minutes) / 100;
+  if ( minutes >= 0 && minutes < 60 && hours >= 0 && hours < 24 )
+     return true;
+  return false;
+
+}
+
+bool TimerValues::IsStartValid(int v)
+{
+  return IsStopValid(v); //uses the syntax as start time, f.e. 2230 means half past ten in the evening
+}
+
+bool TimerValues::IsWeekdaysValid(string v)
+{
+  /*static cxxtools::Regex regex("[\\-M][\\-T][\\-W][\\-T][\\-F][\\-S][\\-S]");
+  return regex.match(v);*/
+  if ( v.length() != 7 ) return false;
+  const char* va = v.c_str();
+  if ( va[0] != '-' && va[0] != 'M' ) return false;
+  if ( va[1] != '-' && va[1] != 'T' ) return false;
+  if ( va[2] != '-' && va[2] != 'W' ) return false;
+  if ( va[3] != '-' && va[3] != 'T' ) return false;
+  if ( va[4] != '-' && va[4] != 'F' ) return false;
+  if ( va[5] != '-' && va[5] != 'S' ) return false;
+  if ( va[6] != '-' && va[6] != 'S' ) return false;
+  return true;
+}
+
+int TimerValues::ConvertFlags(string v)
+{
+  return StringExtension::strtoi(v);
+}
+
+cEvent* TimerValues::ConvertEvent(string event_id, cChannel* channel)
+{
+  if ( channel == NULL ) return NULL;
+
+  int eventid = StringExtension::strtoi(event_id);
+  if ( eventid <= -1 ) return NULL;
+
+  cSchedulesLock MutexLock;
+  const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
+  if ( !Schedules ) return NULL;
+
+  const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
+
+  if ( !Schedule ) return NULL;
+
+  return (cEvent*)Schedule->GetEvent(eventid);
+}
+
+string TimerValues::ConvertFile(string v)
+{
+  return StringExtension::replace(v, ":", "|");
+}
+
+string TimerValues::ConvertAux(string v)
+{
+  return ConvertFile(v);
+}
+
+int TimerValues::ConvertLifetime(string v)
+{
+  return StringExtension::strtoi(v);
+}
+
+int TimerValues::ConvertPriority(string v)
+{
+  return StringExtension::strtoi(v);
+}
+
+int TimerValues::ConvertStop(string v)
+{
+  return StringExtension::strtoi(v);
+}
+
+int TimerValues::ConvertStart(string v)
+{
+  return StringExtension::strtoi(v);
+}
+
+string TimerValues::ConvertDay(time_t v)
+{
+  if (v==0) return "";
+  struct tm *timeinfo = localtime(&v); //must not be deleted because it's statically allocated by localtime
+  ostringstream str;
+  int year = timeinfo->tm_year + 1900;
+  int month = timeinfo->tm_mon + 1; //append 0, vdr wants two digits!
+  int day = timeinfo->tm_mday; //append 0, vdr wants two digits!
+  str << year << "-" 
+      << (month < 10 ? "0" : "") << month << "-" 
+      << (day < 10 ? "0" : "") << day;
+  return str.str();
+}
+
+string TimerValues::ConvertDay(string v)
+{
+  if ( !IsDayValid(v) ) return "wrong format";
+  //now append 0 (required by vdr) if month/day don't already have two digits
+  int a = v.find_first_of('-');
+  int b = v.find_last_of('-');
+
+  string year = v.substr(0, a);
+  string month = v.substr(a+1, b-a-1);
+  string day = v.substr(b+1);
+
+  ostringstream res;
+  res << year << "-"
+      << (month.length() == 1 ? "0" : "") << month << "-"
+      << (day.length() == 1 ? "0" : "") << day;
+  return res.str();
+}
+
+cChannel* TimerValues::ConvertChannel(string v)
+{
+  return VdrExtension::getChannel(v);
+}
+
+cTimer* TimerValues::ConvertTimer(string v)
+{
+  return VdrExtension::getTimer(v);
+}
+
+string TimerValues::ConvertWeekdays(int v)
+{
+  queue<int> b = ConvertToBinary(v);
+  int counter = 0;
+  ostringstream res;
+  while ( !b.empty() && counter < 7 ) {
+     int val = b.front();
+     switch(counter) {
+       case 0: res << (val == 1 ? 'M' : '-'); break;
+       case 1: res << (val == 1 ? 'T' : '-'); break;
+       case 2: res << (val == 1 ? 'W' : '-'); break;
+       case 3: res << (val == 1 ? 'T' : '-'); break;
+       case 4: res << (val == 1 ? 'F' : '-'); break;
+       case 5: res << (val == 1 ? 'S' : '-'); break;
+       case 6: res << (val == 1 ? 'S' : '-'); break;
+     }
+     b.pop();
+     counter++;
+  }
+  while ( counter < 7 ) {
+     res << '-';
+     counter++;
+  }
+  return res.str();
+}
+
+int TimerValues::ConvertWeekdays(string v)
+{
+  const char* str = v.c_str();
+  int res = 0;
+  if ( str[0] == 'M' ) res += 64;
+  if ( str[1] == 'T' ) res += 32;
+  if ( str[2] == 'W' ) res += 16;
+  if ( str[3] == 'T' ) res += 8;
+  if ( str[4] == 'F' ) res += 4;
+  if ( str[5] == 'S' ) res += 2;
+  if ( str[6] == 'S' ) res += 1;
+  return res;
+}
diff --git a/timers.h b/timers.h
new file mode 100644
index 0000000..7200b2a
--- /dev/null
+++ b/timers.h
@@ -0,0 +1,136 @@
+#include <vdr/channels.h>
+#include <vdr/timers.h>
+#include <vdr/epg.h>
+#include <vdr/menu.h>
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/query_params.h>
+#include <cxxtools/arg.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/regex.h>
+#include <cxxtools/serializationinfo.h>
+#include <cxxtools/utf8codec.h>
+#include <iostream>
+#include <sstream>
+#include <stack>
+#include <queue>
+#include <time.h>
+#include "tools.h"
+
+
+class TimersResponder : public cxxtools::http::Responder
+{
+  public:
+    explicit TimersResponder(cxxtools::http::Service& service)
+      : cxxtools::http::Responder(service)
+      { }
+     virtual void reply(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+     void createOrUpdateTimer(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply, bool update);
+     void deleteTimer(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+     void showTimers(std::ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+     void replyCreatedId(cTimer* timer, cxxtools::http::Request& request, cxxtools::http::Reply& reply, std::ostream& out);
+};
+
+typedef cxxtools::http::CachedService<TimersResponder> TimersService;
+
+struct SerTimer
+{
+  cxxtools::String Id;
+  int Flags;
+  int Start;
+  cxxtools::String StartTimeStamp;
+  int Stop;
+  cxxtools::String StopTimeStamp;
+  int Priority;
+  int Lifetime;
+  int EventID;
+  cxxtools::String WeekDays;
+  cxxtools::String Day;
+  cxxtools::String Channel;
+  bool IsRecording;
+  bool IsPending;
+  bool IsActive;
+  cxxtools::String FileName;
+  cxxtools::String ChannelName;
+  cxxtools::String Aux;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerTimer& t);
+
+class TimerList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+  public:
+    explicit TimerList(std::ostream* _out);
+    virtual ~TimerList();
+    virtual void init() { };
+    virtual void addTimer(cTimer* timer) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; }
+};
+
+class HtmlTimerList : TimerList
+{
+  public:
+    explicit HtmlTimerList(std::ostream* _out) : TimerList(_out) { };
+    ~HtmlTimerList() { };
+    virtual void init();
+    virtual void addTimer(cTimer* timer);
+    virtual void finish();
+};
+
+class JsonTimerList : TimerList
+{
+  private:
+    std::vector < struct SerTimer > serTimers;
+  public:
+    explicit JsonTimerList(std::ostream* _out) : TimerList(_out) { };
+    ~JsonTimerList() { };
+    virtual void addTimer(cTimer* timer);
+    virtual void finish();
+};
+
+class XmlTimerList : TimerList
+{
+  public:
+    explicit XmlTimerList(std::ostream* _out) : TimerList(_out) { };
+    ~XmlTimerList() { };
+    virtual void init();
+    virtual void addTimer(cTimer* timer);
+    virtual void finish();
+};
+
+class TimerValues
+{
+  private:
+    std::queue<int>  ConvertToBinary(int v);
+  public:
+    TimerValues() { };
+    ~TimerValues() { };
+    bool	IsDayValid(std::string v);
+    bool 	IsFlagsValid(int v);
+    bool	IsFileValid(std::string v);
+    bool	IsLifetimeValid(int v);
+    bool	IsPriorityValid(int v);
+    bool	IsStopValid(int v);
+    bool	IsStartValid(int v);
+    bool	IsWeekdaysValid(std::string v);
+
+    int		ConvertFlags(std::string v);
+    cEvent*	ConvertEvent(std::string event_id, cChannel* channel);
+    std::string ConvertFile(std::string v); // replaces : with | - required by parsing method of VDR
+    std::string ConvertAux(std::string v);  // replaces : with | - required by parsing method of VDR
+    int		ConvertLifetime(std::string v);
+    int		ConvertPriority(std::string v);
+    int		ConvertStop(std::string v);
+    int		ConvertStart(std::string v);
+    std::string ConvertDay(std::string v); // appends 0 to day/month because vdr requires two digits
+    std::string ConvertDay(time_t v);
+    std::string ConvertWeekdays(int v);
+    int		ConvertWeekdays(std::string v);
+    cChannel*	ConvertChannel(std::string v);
+    cTimer*	ConvertTimer(std::string v);
+};
diff --git a/tools.cpp b/tools.cpp
new file mode 100644
index 0000000..17a61ba
--- /dev/null
+++ b/tools.cpp
@@ -0,0 +1,1627 @@
+#include "tools.h"
+#include <vdr/videodir.h>
+using namespace std;
+
+// --- Settings ----------------------------------------------------------------
+
+bool Settings::SetPort(std::string v)
+{
+  int p = StringExtension::strtoi(v);
+  if ( p > 1024 && p <= 65535 ) {
+     port = p;
+     esyslog("restfulapi: Port has been set to %i!", port);
+     return true;
+  }
+  return false;
+}
+
+bool Settings::SetIp(std::string v)
+{
+  vector<string> parts = StringExtension::split(v, ".");
+  if ( parts.size() != 4 ) {
+     return false;
+  }
+
+  for (int i=0;i<(int)parts.size();i++) {
+      int val = StringExtension::strtoi(parts[i]);
+      if ( val < 0 || val > 255 ) {
+         return false;
+      }
+  }
+  ip = v;
+  esyslog("restfulapi: Ip has been set to %s!", ip.c_str());
+  return true;
+}
+
+bool Settings::SetEpgImageDirectory(std::string v)
+{
+  struct stat stat_info;
+  if ( stat(v.c_str(), &stat_info) == 0) {
+     if (v[v.length()-1] == '/')
+        epgimage_dir = v.substr(0, v.length()-1);
+     else
+        epgimage_dir = v;
+     esyslog("restfulapi: The EPG-Images will be loaded from %s!", epgimage_dir.c_str());
+     return true;
+  }
+  return false;
+}
+
+bool Settings::SetChannelLogoDirectory(std::string v)
+{
+  struct stat stat_info;
+  if ( stat(v.c_str(), &stat_info) == 0) {
+     if (v[v.length()-1] == '/')
+        channellogo_dir = v.substr(0, v.length()-1);
+     else
+        channellogo_dir = v;
+     esyslog("restfulapi: The Channel-Logos will be loaded from %s!", channellogo_dir.c_str());
+     return true;
+  }
+  return false;
+}
+
+bool Settings::SetWebappDirectory(std::string v)
+{
+  struct stat stat_info;
+  if ( stat(v.c_str(), &stat_info) == 0) {
+     if (v[v.length()-1] == '/')
+        webapp_dir = v.substr(0, v.length()-1);
+     else
+        webapp_dir = v;
+     esyslog("restfulapi: The Webapp will be loaded from %s!", webapp_dir.c_str());
+     return true;
+  }
+  return false;
+}
+
+bool Settings::SetHeaders(std::string v)
+{
+  if ( v == "false" ) {
+     activateHeaders = false;
+  } else {
+     activateHeaders = true;
+  }
+  return true;
+}
+
+Settings* Settings::get() 
+{
+  static Settings settings;
+  return &settings;
+}
+
+void Settings::initDefault()
+{
+  SetPort((string)"8002");
+  SetIp((string)"0.0.0.0");
+  SetEpgImageDirectory((string)"/var/cache/vdr/epgimages");
+  SetChannelLogoDirectory((string)"/usr/share/vdr/channel-logos");
+  SetWebappDirectory((string)"/var/lib/vdr/restfulapi/webapp");
+  SetHeaders((string)"true");
+}
+
+// --- HtmlHeader --------------------------------------------------------------
+
+void HtmlHeader::ToStream(StreamExtension* se)
+{
+  se->write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+  se->write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
+  se->write("<html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n");
+  se->write("<head>\n");
+
+  se->write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n");
+  // WebApp support
+  se->write("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n");
+  se->write("<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\" />\n");
+  se->write("<meta name=\"viewport\" content=\"width=device-width\" id=\"viewport\" />\n");
+  
+  for (int i=0;i<(int)_metatags.size();i++)
+  {
+    se->write(_metatags[i].c_str());
+    se->write("\n");
+  }
+
+  se->write("<title>");
+  se->write(_title.c_str());
+  se->write("</title>\n");
+
+  se->write("<style type=\"text/css\">\n");
+  for (int i=0;i<(int)_stylesheets.size();i++) 
+  {
+    se->writeBinary(_stylesheets[i]);
+    se->write("\n");
+  }
+  se->write("</style>\n");
+  
+  se->write("<script type=\"text/javascript\">\n//<![CDATA[\n\n");
+
+  for (int i=0;i<(int)_scripts.size();i++) 
+  {
+    se->writeBinary(_scripts[i]);
+  }
+
+  se->write("\n//]]>\n</script>\n");  
+
+  if ( _onload.size() == 0 ) {
+     se->write("</head><body>\n");
+  } else {
+     se->write("<body onload=\"");
+     //f.e. javascript:bootstrap();
+     se->write(_onload.c_str());
+     se->write("\">\n");
+  }
+}
+
+// --- StreamExtension ---------------------------------------------------------
+
+StreamExtension::StreamExtension(std::ostream *out)
+{
+  _out = out;
+}
+
+std::ostream* StreamExtension::getBasicStream()
+{
+  return _out;
+}
+
+void StreamExtension::write(const char* str)
+{
+  std::string data = (std::string)str;
+  _out->write(str, data.length());
+}
+
+void StreamExtension::writeHtmlHeader(std::string title)
+{
+  write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+  write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
+  write("<html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n");
+  write("<head>\n");
+  write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n");
+  // WebApp support
+  write("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n");
+  write("<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\" />\n");
+  write("<meta name=\"viewport\" content=\"width=device-width\" id=\"viewport\" />\n");
+    
+  write("<title>VDR-Plugin-Restfulapi: ");
+  if ( title.length() > 0 ) write(title.c_str());
+  write("</title>");
+  write("</head>\n<body>\n");
+}
+
+void StreamExtension::writeXmlHeader()
+{
+  write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
+}
+
+bool StreamExtension::writeBinary(std::string path)
+{
+  ifstream* in = new ifstream(path.c_str(), ios::in | ios::binary | ios::ate);
+  bool result = false;
+  if ( in->is_open() ) {
+
+     int size = in->tellg();
+     char* memory = new char[size];
+     in->seekg(0, ios::beg);
+     in->read(memory, size);
+     _out->write(memory, size);
+     delete[] memory;
+     result = true;
+  } 
+  in->close();
+  delete in;
+  return result;
+}
+
+// --- FileNotifier -----------------------------------------------------------
+
+FileNotifier::~FileNotifier()
+{
+  if ( active == true ) {
+     Stop();
+  }
+}
+
+void FileNotifier::Initialize(int mode)
+{
+  _mode = mode;
+  string dir;
+
+  if ( _mode == FileNotifier::EVENTS) {
+     dir = Settings::get()->EpgImageDirectory().c_str();
+  } else {
+     dir = Settings::get()->ChannelLogoDirectory().c_str();
+  }
+  
+  _filedescriptor = inotify_init();
+  _wd = -1;
+
+  if ( dir.length() == 0 ) {
+     esyslog("restfulapi: Initializing inotify for epgimages or channellogos failed! (Check restfulapi-settings!)");
+     _wd = -1;
+  } else {
+     _wd = inotify_add_watch( _filedescriptor, dir.c_str(), IN_CREATE | IN_DELETE );
+     if ( _wd < 0 )
+        esyslog("restfulapi: Initializing inotify for epgimages failed!");
+     else
+        esyslog("restfulapi: Initializing inotify for %s finished.", dir.c_str());
+  }
+ 
+
+
+  if (_wd >= 0) {
+    active = true;
+    Start();
+  }
+}
+
+void FileNotifier::Action(void)
+{
+  int length, i;
+  char buffer[BUF_LEN];
+
+  while(active) {
+    i = 0;
+    struct pollfd pfd[1];
+    pfd[0].fd = _filedescriptor;
+    pfd[0].events = POLLIN;
+    
+    if ( poll(pfd, 1, 500) > 0 ) {
+       length = read( _filedescriptor, buffer, BUF_LEN );
+    
+       if ( length > 0 ) {
+          while ( i < length ) {
+             struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
+             if ( event->len > 0 && !(event->mask & IN_ISDIR) ) {
+ 
+                if (event->mask & IN_CREATE) {
+                   //esyslog("restfulapi: inotify: found new image: %s", event->name);
+                   if ( _mode == FileNotifier::EVENTS )
+                      FileCaches::get()->addEventImage((std::string)event->name);
+                   else
+                      FileCaches::get()->addChannelLogo((std::string)event->name);
+                }
+             
+                if (event->mask & IN_DELETE)  {
+                   //esyslog("restfulapi: inotify: image %s has been removed", event->name);
+                   if ( _mode == FileNotifier::EVENTS )
+                      FileCaches::get()->removeEventImage((std::string)event->name);
+                   else
+                      FileCaches::get()->removeChannelLogo((std::string)event->name);
+                }
+             }
+             i += EVENT_SIZE + event->len;
+          }
+       }
+    }
+  }
+  esyslog("restfulapi: Stopped watching directory!");
+}
+
+void FileNotifier::Stop()
+{ 
+  if ( active != false ) {
+     active = false;
+     Cancel(0.1);
+     ( void ) inotify_rm_watch( _filedescriptor, _wd );
+     ( void ) close( _filedescriptor );
+  }
+}
+
+// --- FileCaches -------------------------------------------------------------
+
+FileCaches* FileCaches::get()
+{
+  static FileCaches instance;
+  return &instance;
+}
+
+void FileCaches::cacheEventImages()
+{
+  string imageFolder = Settings::get()->EpgImageDirectory();
+  string folderWildcard = imageFolder + (string)"/*";
+  VdrExtension::scanForFiles(folderWildcard, eventImages);
+}
+
+void FileCaches::cacheChannelLogos()
+{
+  string imageFolder = Settings::get()->ChannelLogoDirectory();
+  string folderWildcard = imageFolder + (string)"/*";
+  VdrExtension::scanForFiles(folderWildcard, channelLogos);
+}
+
+void FileCaches::searchEventImages(int eventid, std::vector< std::string >& files)
+{
+  cxxtools::Regex regex( (string)"^" + StringExtension::itostr(eventid) + (string)"(_[0-9]+)?.[a-z]{3,4}$" );
+  for ( int i=0; i < (int)eventImages.size(); i++ ) {
+      if ( regex.match(eventImages[i]) ) {
+         files.push_back(eventImages[i]);
+      }
+  }
+}
+
+std::string FileCaches::searchChannelLogo(cChannel *channel)
+{
+  std::string cid = (std::string)(*channel->GetChannelID().ToString());
+  std::string cname = (std::string)channel->Name();
+  std::string clname;
+  std::transform(cname.begin(), cname.end(), cname.begin(), ::tolower);
+
+  for ( int i=0; i < (int)channelLogos.size(); i++ ) {
+      string name = channelLogos[i];
+      int delim = name.find_last_of(".");
+      if ( delim != -1 ) { name = name.substr(0, delim); }
+
+      if (( name == cid ) || ( name == clname ) || ( name == cname )) {
+         return channelLogos[i];
+      }
+  }
+  return "";
+}
+
+void FileCaches::addEventImage(string file)
+{
+  eventImages.push_back(file);
+}
+
+void FileCaches::addChannelLogo(string file)
+{
+  channelLogos.push_back(file);
+}
+
+void FileCaches::removeEventImage(string file)
+{
+  for (size_t i = 0; i < eventImages.size(); i++) {
+      if ( eventImages[i] == file ) {
+         eventImages[i] = "";
+         break;
+      }
+  }
+}
+
+void FileCaches::removeChannelLogo(string file)
+{
+  for (size_t i = 0; i < channelLogos.size(); i++) {
+      if ( channelLogos[i] == file ) {
+         eventImages[i] = "";
+         break;
+      }
+  }
+}
+
+// --- FileExtension ------------------------------------------------------------
+FileExtension* FileExtension::get()
+{
+  static FileExtension instance;
+  return &instance;
+}
+
+/**
+ * retrieve locale
+ * @return const char*
+ */
+const char* FileExtension::getLocale() {
+
+  const char* locale;
+  setlocale(LC_ALL, "");
+  locale = setlocale(LC_TIME,NULL);
+  return locale;
+};
+
+/**
+ * retrieve modified tm struct
+ * @param string path
+ * @param struct tm*
+ */
+struct tm* FileExtension::getModifiedTm(string path) {
+
+  struct stat attr;
+  stat(path.c_str(), &attr);
+  struct tm* attrtm = gmtime(&(attr.st_mtime));
+  return attrtm;
+};
+
+/**
+ * retrieve modified time for given path
+ * @param string path
+ * @retrun time_t
+ */
+time_t FileExtension::getModifiedTime(string path) {
+
+  return mktime(getModifiedTm(path));
+};
+
+/**
+ * add last-modified header
+ * @param string path
+ * @return void
+ */
+void FileExtension::addModifiedHeader(string path, cxxtools::http::Reply& reply) {
+
+  char buffer[30];
+  struct tm* tm = getModifiedTm(path);
+  setlocale(LC_TIME,"POSIX");
+  strftime(buffer,30,"%a, %d %b %Y %H:%M:%S %Z",tm);
+  setlocale(LC_TIME,getLocale());
+  esyslog("restfulapi: FileExtension: adding last-modified-header %s to file %s", buffer, path.c_str());
+  reply.addHeader("Last-Modified", buffer);
+};
+
+/**
+ * convert if-modified-since request header
+ * @param cxxtools::http::Request& request
+ * @return time_t
+ */
+time_t FileExtension::getModifiedSinceTime(cxxtools::http::Request& request) {
+
+  time_t now;
+  time(&now);
+  struct tm* tm = localtime(&now);
+  setlocale(LC_TIME,"POSIX");
+  strptime(request.getHeader("If-Modified-Since"), "%a, %d %b %Y %H:%M:%S %Z", tm);
+  setlocale(LC_TIME,getLocale());
+  return mktime(tm);
+};
+
+/**
+ * determine if requested file exists
+ * @param string path the path to check
+ * @return bool
+ */
+bool FileExtension::exists(string path) {
+
+  char* nptr = NULL;
+  const char* cPath = path.c_str();
+  char* rPath = realpath(cPath, nptr);
+
+  esyslog("restfulapi: FileExtension: requested path %s", cPath);
+  esyslog("restfulapi: FileExtension: realpath %s", rPath);
+
+  if (!rPath || (rPath && strcmp(cPath, rPath) != 0)) {
+      esyslog("restfulapi: realpath does not match requested path");
+      return false;
+  }
+  FILE *fp = fopen(path.c_str(),"r");
+  if( fp ) {
+    fclose(fp);
+    return true;
+  }
+  esyslog("restfulapi: FileExtension: requested file %s does not exists", cPath);
+
+  return false;
+};
+
+// --- VdrExtension -----------------------------------------------------------
+
+cChannel* VdrExtension::getChannel(int number)
+{
+  if( number == -1 || number >= Channels.Count() ) { return NULL; }
+
+  cChannel* result = NULL;
+  int counter = 1;
+  for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
+  {
+      if (!channel->GroupSep()) {
+         if (counter == number)
+         {
+            result = channel;
+            break;
+         }
+         counter++;
+      }
+  }
+  return result;
+}
+
+cChannel* VdrExtension::getChannel(string id)
+{
+  if ( id.length() == 0 ) return NULL;
+ 
+  for (cChannel* channel = Channels.First(); channel; channel= Channels.Next(channel))
+  {
+      if ( id == (string)channel->GetChannelID().ToString() ) {
+         return channel;
+      }
+  }
+  return NULL;
+}
+
+cTimer* VdrExtension::getTimer(string id)
+{
+  cTimer* timer;
+  int tc = Timers.Count();
+  for (int i=0;i<tc;i++) {
+      timer = Timers.Get(i);
+      if ( VdrExtension::getTimerID(timer) == id ) {
+         return timer;
+      }  
+  }
+  return NULL;
+}
+
+string VdrExtension::getTimerID(cTimer* timer)
+{
+  ostringstream str;
+  str << (const char*)timer->Channel()->GetChannelID().ToString() << ":" << timer->WeekDays() << ":"
+      << timer->Day() << ":"
+      << timer->Start() << ":" << timer->Stop();
+  return str.str();
+}
+
+int VdrExtension::scanForFiles(const std::string wildcardpath, std::vector< std::string >& files)
+{
+  int found = 0;
+  glob_t globbuf;
+  globbuf.gl_offs = 0;
+  if ( wildcardpath.empty() == false && glob(wildcardpath.c_str(), GLOB_DOOFFS, NULL, &globbuf) == 0) {
+     for (size_t i = 0; i < globbuf.gl_pathc; i++) {
+         string file(globbuf.gl_pathv[i]);
+         size_t delimPos = file.find_last_of('/');
+         files.push_back(file.substr(delimPos+1));
+         found++;
+     }
+     globfree(&globbuf);
+  }
+  esyslog("restfulapi: found %i files in %s.", found, wildcardpath.c_str());
+  return found;
+}
+
+bool VdrExtension::doesFileExistInFolder(string wildcardpath, string filename)
+{
+  glob_t globbuf;
+  globbuf.gl_offs = 0;
+  if ( wildcardpath.empty() == false && glob(wildcardpath.c_str(), GLOB_DOOFFS, NULL, &globbuf) == 0) {
+     for (size_t i = 0; i < globbuf.gl_pathc; i++) {
+         string file(globbuf.gl_pathv[i]);
+         size_t delimPos = file.find_last_of('/');
+         if (file.substr(delimPos+1) == filename) {
+            globfree(&globbuf);
+            return true;
+         }
+     }
+     globfree(&globbuf);
+  }
+  return false;
+}
+
+bool VdrExtension::IsRadio(cChannel* channel)
+{
+  if ((channel->Vpid() == 0 && channel->Apid(0) != 0) || channel->Vpid() == 1 ) {
+     return true;
+  }
+  return false;
+}
+
+bool VdrExtension::IsRecording(cRecording* recording)
+{
+  cTimer* timer = NULL;
+  for (int i=0;i<Timers.Count();i++)
+  {
+     timer = Timers.Get(i);
+     if (string(timer->File()).compare(recording->Name())) {
+        return true;
+     }
+  }
+  return false;
+}
+
+cTimer* VdrExtension::TimerExists(cEvent* event)
+{
+  for(int i=0;i<Timers.Count();i++) {
+     cTimer* timer = Timers.Get(i);
+
+     if ( timer->Event() != NULL &&  timer->Event()->EventID() == event->EventID() && strcmp(timer->Event()->ChannelID().ToString(), event->ChannelID().ToString()) == 0 ) {
+        return timer;
+     }
+    
+     if ( strcmp(timer->Channel()->GetChannelID().ToString(), event->ChannelID().ToString()) == 0 ) {
+        int timer_start = (int)timer->Day() - ((int)timer->Day()) % 3600;
+        int timer_stop = timer_start;
+        timer_start += ((int)(timer->Start() / 100)) * 60 * 60;
+        timer_start += (timer->Start() % 100) * 60;
+
+        if ( timer->Stop() < timer->Start() ) timer_stop += 24 * 60 * 60;
+        timer_stop += ((int)(timer->Stop() / 100)) * 60 * 60;
+        timer_stop += (timer->Stop() % 100) * 60;
+
+        if ( timer_stop >= (int)event->EndTime() && timer_start <= (int)event->StartTime() ) {
+           return timer;
+        }
+     }
+  }
+  return NULL;
+}
+
+vector< cTimer* > VdrExtension::SortedTimers()
+{
+  vector< cTimer* > timers;
+  for(int i=0;i<Timers.Count();i++)
+  {
+     timers.push_back(Timers.Get(i));
+  }
+
+  for(int i=0;i<(int)timers.size() - 1;i++)
+  {
+     bool changed = false;
+     for(int k=0;k<(int)timers.size() - i - 1;k++)
+     {
+         if (VdrExtension::CompareTimers(timers[k], timers[k+1])) 
+         {
+            cTimer* swap = timers[k];
+            timers[k] = timers[k+1];
+            timers[k+1] = swap;
+            changed = true;
+         }
+     }
+     if(!changed) break;
+  }
+
+  return timers;
+}
+
+bool VdrExtension::CompareTimers(cTimer* timer1, cTimer* timer2)
+{
+  int day1 = (int)timer1->Day() - ((int)timer1->Day() % 3600);
+  int day2 = (int)timer2->Day() - ((int)timer2->Day() % 3600);
+
+  if (day1 > day2) {
+     return true;
+  } else if ( day1 < day2) {
+     return false;
+  } else {
+     if (timer1->StartTime() > timer2->StartTime()) {
+        return true;
+     } else if (timer1->StartTime() < timer2->StartTime()) {
+        return false;
+     }
+  }
+
+  if ( ( (string)timer1->File() ).compare( (string)timer2->File() ) > 0 ) {
+     return true;
+  }
+
+  return false;
+}
+
+int VdrExtension::RecordingLengthInSeconds(cRecording* recording)
+{
+  int nf = recording->NumFrames();
+  if (nf >= 0)
+#if APIVERSNUM >= 10703
+     return int(((double)nf / recording->FramesPerSecond()));
+#else
+     return int((double)nf / FRAMESPERSEC);
+#endif
+  return -1;
+}
+
+const cEvent* VdrExtension::GetEventById(tEventID eventID, cChannel* channel)
+{
+  cSchedulesLock MutexLock;
+  const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
+
+  if (!Schedules)
+     return NULL;
+
+  const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
+  if (Schedule)
+     return Schedule->GetEvent(eventID);
+
+  return NULL;
+}
+
+string VdrExtension::getRelativeVideoPath(cRecording* recording)
+{
+  string path = (string)recording->FileName();
+#if APIVERSNUM > 20101
+  string VIDEODIR(cVideoDirectory::Name());
+#else
+  string VIDEODIR(VideoDirectory);
+#endif
+  return path.substr(VIDEODIR.length());
+}
+
+cEvent* VdrExtension::getCurrentEventOnChannel(cChannel* channel)
+{
+  if ( channel == NULL ) return NULL; 
+
+  cSchedulesLock MutexLock;
+  const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
+
+  if ( ! Schedules ) { return NULL; }
+  const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
+  if ( !Schedule ) { return NULL; }
+
+  time_t now = time(NULL);
+  return (cEvent*)Schedule->GetEventAround(now);
+}
+
+string VdrExtension::getVideoDiskSpace()
+{
+  int FreeMB, UsedMB;
+#if APIVERSNUM > 20101
+  int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
+#else
+  int Percent = VideoDiskSpace(&FreeMB, &UsedMB);
+#endif
+  ostringstream str;
+  str << FreeMB + UsedMB << "MB " << FreeMB << "MB " << Percent << "%";
+  return str.str();  
+}
+
+// Move or copy directory from vdr-plugin-live
+string VdrExtension::FileSystemExchangeChars(std::string const & s, bool ToFileSystem)
+{
+  char *str = strdup(s.c_str());
+  str = ExchangeChars(str, ToFileSystem);
+  std::string data = str;
+  if (str) {
+     free(str);
+  }
+  return data;
+}
+
+bool VdrExtension::MoveDirectory(std::string const & sourceDir, std::string const & targetDir, bool copy)
+{
+  const char* delim = "/";
+  std::string source = sourceDir;
+  std::string target = targetDir;
+
+  // add missing directory delimiters
+  if (source.compare(source.size() - 1, 1, delim) != 0) {
+     source += "/";
+  }
+  if (target.compare(target.size() - 1, 1, delim) != 0) {
+     target += "/";
+  }
+
+  if (source != target) {
+     // validate target directory
+     if (target.find(source) != std::string::npos) {
+        esyslog("[Restfulapi]: cannot move under sub-directory\n");
+        return false;
+     }
+     RemoveFileOrDir(target.c_str());
+     if (!MakeDirs(target.c_str(), true)) {
+        esyslog("[Restfulapi]: cannot create directory %s", target.c_str());
+        return false;
+     }
+
+     struct stat st1, st2;
+     stat(source.c_str(), &st1);
+     stat(target.c_str(),&st2);
+     if (!copy && (st1.st_dev == st2.st_dev)) {
+#if APIVERSNUM > 20101
+        if (!cVideoDirectory::RenameVideoFile(source.c_str(), target.c_str())) { 
+#else
+        if (!RenameVideoFile(source.c_str(), target.c_str())) { 
+#endif
+           esyslog("[Restfulapi]: rename failed from %s to %s", source.c_str(), target.c_str());
+           return false;
+        }
+     }
+     else {
+        int required = DirSizeMB(source.c_str());
+        int available = FreeDiskSpaceMB(target.c_str());
+
+        // validate free space
+        if (required < available) {
+           cReadDir d(source.c_str());
+           struct dirent *e;
+           bool success = true;
+
+           // allocate copying buffer
+           const int len = 1024 * 1024;
+           char *buffer = MALLOC(char, len);
+           if (!buffer) {
+              esyslog("[Restfulapi]: cannot allocate renaming buffer");
+              return false;
+           }
+
+           // loop through all files, but skip all subdirectories
+           while ((e = d.Next()) != NULL) {
+              // skip generic entries
+              if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..") && strcmp(e->d_name, "lost+found")) {
+                 string sourceFile = source + e->d_name;
+                 string targetFile = target + e->d_name;
+
+                 // copy only regular files
+                 if (!stat(sourceFile.c_str(), &st1) && S_ISREG(st1.st_mode)) {
+                    int r = -1, w = -1;
+                    cUnbufferedFile *inputFile = cUnbufferedFile::Create(sourceFile.c_str(), O_RDONLY | O_LARGEFILE);
+                    cUnbufferedFile *outputFile = cUnbufferedFile::Create(targetFile.c_str(), O_RDWR | O_CREAT | O_LARGEFILE);
+
+                    // validate files
+                    if (!inputFile || !outputFile) {
+                       esyslog("[Restfulapi]: cannot open file %s or %s", sourceFile.c_str(), targetFile.c_str());
+                       success = false;
+                       break;
+                    }
+
+                    // do actual copy
+                   dsyslog("[Restfulapi]: copying %s to %s", sourceFile.c_str(), targetFile.c_str());
+                    do {
+                       r = inputFile->Read(buffer, len);
+                       if (r > 0)
+                          w = outputFile->Write(buffer, r);
+                       else
+                          w = 0;
+                    } while (r > 0 && w > 0);
+                    DELETENULL(inputFile);
+                    DELETENULL(outputFile);
+
+                    // validate result
+                    if (r < 0 || w < 0) {
+                       success = false;
+                       break;
+                    }
+                 }
+              }
+           }
+
+           // release allocated buffer
+           free(buffer);
+
+           // delete all created target files and directories
+           if (!success) {
+              size_t found = target.find_last_of(delim);
+              if (found != std::string::npos) {
+                 target = target.substr(0, found);
+              }
+              if (!RemoveFileOrDir(target.c_str(), true)) {
+                 esyslog("[Restfulapi]: cannot remove target %s", target.c_str());
+              }
+              found = target.find_last_of(delim);
+              if (found != std::string::npos) {
+                 target = target.substr(0, found);
+              }
+              if (!RemoveEmptyDirectories(target.c_str(), true)) {
+                 esyslog("[Restfulapi]: cannot remove target directory %s", target.c_str());
+              }
+              esyslog("[Restfulapi]: copying failed");
+              return false;
+           }
+           else if (!copy && !RemoveFileOrDir(source.c_str(), true)) { // delete source files
+              esyslog("[Restfulapi]: cannot remove source directory %s", source.c_str());
+              return false;
+           }
+
+           // delete all empty source directories
+           if (!copy) {
+              size_t found = source.find_last_of(delim);
+              if (found != std::string::npos) {
+                 source = source.substr(0, found);
+#if APIVERSNUM > 20101
+                 while (source != cVideoDirectory::Name()) {
+#else
+		while (source != VideoDirectory) {
+#endif
+                    found = source.find_last_of(delim);
+                    if (found == std::string::npos)
+                       break;
+                    source = source.substr(0, found);
+                    if (!RemoveEmptyDirectories(source.c_str(), true))
+                       break;
+                 }
+              }
+           }
+        }
+        else {
+           esyslog("[Restfulapi]: %s requires %dMB - only %dMB available", copy ? "moving" : "copying", required, available);
+           // delete all created empty target directories
+           size_t found = target.find_last_of(delim);
+           if (found != std::string::npos) {
+              target = target.substr(0, found);
+#if APIVERSNUM > 20101
+              while (target != cVideoDirectory::Name()) {
+#else
+              while (target != VideoDirectory) {
+#endif
+                 found = target.find_last_of(delim);
+                 if (found == std::string::npos)
+                    break;
+                 target = target.substr(0, found);
+                 if (!RemoveEmptyDirectories(target.c_str(), true))
+                    break;
+              }
+           }
+           return false;
+        }
+     }
+  }
+  return true;
+}
+
+
+string VdrExtension::MoveRecording(cRecording const * recording, string const & name, bool copy)
+{
+  if (!recording)
+     return "";
+
+  string oldname = recording->FileName();
+  size_t found = oldname.find_last_of("/");
+
+  if (found == string::npos)
+     return "";
+
+#if APIVERSNUM > 20101
+  string newname = string(cVideoDirectory::Name()) + "/" + name + oldname.substr(found);
+#else
+  string newname = string(VideoDirectory) + "/" + name + oldname.substr(found);
+#endif
+
+  if (!MoveDirectory(oldname.c_str(), newname.c_str(), copy)) {
+     esyslog("[Restfulapi]: renaming failed from '%s' to '%s'", oldname.c_str(), newname.c_str());
+     return "";
+  }
+
+  if (!copy)
+     Recordings.DelByName(oldname.c_str());
+  Recordings.AddByName(newname.c_str());
+  cRecordingUserCommand::InvokeCommand(*cString::sprintf("rename \"%s\"", *strescape(oldname.c_str(), "\\\"$'")), newname.c_str());
+  return newname;
+}
+
+// --- VdrMarks ---------------------------------------------------------------
+
+VdrMarks* VdrMarks::get()
+{
+  static VdrMarks vdrMarks;
+  return &vdrMarks;
+}
+
+string VdrMarks::cutComment(string str)
+{
+  bool esc = false;
+  char c;
+  int counter = 0;
+  while ( counter < (int)str.length() ) {
+    c = str[counter];
+    switch(c) {
+      case '\\': if (!esc) esc = true; else esc = false;
+                  break;
+      case ' ': if (!esc) return str.substr(0, counter);
+                  break;
+      default: esc = false;
+                  break;
+    }
+    counter++;
+  }
+  return str;
+}
+
+bool VdrMarks::validateMark(string mark)
+{
+  static cxxtools::Regex regex("[0-9]{1,2}:[0-9]{2,2}:[0-9]{2,2}[.]{0,1}[0-9]{0,2}");
+  return regex.match(mark);
+}
+
+string VdrMarks::getPath(cRecording* recording)
+{
+  string filename = recording->FileName();
+  return filename + "/marks";
+}
+
+bool VdrMarks::parseLine(std::vector<string >& marks, string line)
+{
+  line = cutComment(line);
+  if ( validateMark(line) ) {
+     marks.push_back(line);
+     return true;
+  }
+  return false;
+}
+
+vector<string > VdrMarks::readMarks(cRecording* recording)
+{
+  vector<string > marks;
+  string path = getPath(recording);
+
+  if ( path.length() == 0 ) {
+     return marks;
+  }
+
+  FILE* f = fopen(path.c_str(), "r");
+  if ( f != NULL ) {
+     ostringstream data;
+     char c;
+     while ( !feof(f) ) {
+       c = fgetc(f);
+       if ( c == '\n' ) {
+          parseLine(marks, data.str());
+          data.str(""); //.clear() is inherited from std::ios and does only clear the error state
+       } else {
+          if (!feof(f)) //don't add EOF-char to string
+             data << c;
+       }
+     }
+     parseLine(marks, data.str());
+     fclose(f);
+  }
+
+  return marks;
+}
+
+bool VdrMarks::saveMarks(cRecording* recording, std::vector< std::string > marks)
+{
+  if (recording == NULL) {
+     return false;
+  }
+
+  for(int i=0;i<(int)marks.size();i++)
+  {
+     if ( !validateMark(marks[i]) ) {
+        return false;
+     }
+  }
+
+  string path = getPath(recording);
+  if ( path.length() == 0 ) {
+     return false;
+  }
+
+  deleteMarks(recording);
+
+  FILE* file = fopen(path.c_str(), "w");
+  if ( file != NULL ) {
+     for(int i=0;i<(int)marks.size();i++) {
+        for(int k=0;k<(int)marks[i].length();k++)
+        {
+           fputc( (int)marks[i][k], file );
+        }
+        fputc( (int)'\n', file );
+     }
+
+     fclose(file);
+     return true;
+  }
+
+  return false;
+}
+
+bool VdrMarks::deleteMarks(cRecording* recording)
+{
+  string marksfile = getPath(recording);
+
+  if ( remove( marksfile.c_str() ) != 0 ) {
+     return false;
+  }
+  return true;
+}
+
+// --- StringExtension --------------------------------------------------------
+
+string StringExtension::itostr(int i)
+{
+  stringstream str;
+  str << i;
+  return str.str();
+}
+
+int StringExtension::strtoi(string str)
+{
+  static cxxtools::Regex regex("-?[0-9]{1,}");
+  if(!regex.match(str)) return -LOWINT; // lowest possible integer
+  return atoi(str.c_str());
+}
+
+string StringExtension::replace(string const& text, string const& substring, string const& replacement)
+{
+  std::string result = text;
+  std::string::size_type pos = 0;
+  while ( ( pos = result.find( substring, pos ) ) != std::string::npos ) {
+    result.replace( pos, substring.length(), replacement );
+    pos += replacement.length();
+  }
+  return result;
+}
+
+string StringExtension::encodeToXml(const string &str)
+{
+    //source: http://www.mdawson.net/misc/xmlescape.php
+    ostringstream result;
+
+    for( string::const_iterator iter = str.begin(); iter!=str.end(); iter++ )
+    {
+         unsigned char c = (unsigned char)*iter;
+         // filter invalid bytes in xml
+         if (c < 0x20 && c != 0x09 && c != 0x0a && c != 0x0d)
+            continue;
+
+         switch( c )
+         {
+             case '&': result << "&"; break;
+             case '<': result << "<"; break;
+             case '>': result << ">"; break;
+             case '"': result << """; break;
+             case '\'': result << "'"; break;
+
+             default:
+                   result << c;
+         }
+    }
+
+    string res = result.str();
+    try {
+       string converted;
+       utf8::replace_invalid(res.begin(), res.end(), back_inserter(converted));
+       return converted;
+    } catch(utf8::not_enough_room& e) {
+       return (string)"Invalid piece of text. (Fixing Unicode failed.)";
+    }
+}
+
+
+cxxtools::String StringExtension::encodeToJson(const string &str)
+{
+  // http://en.wikipedia.org/wiki/UTF-8#Codepage_layout
+  ostringstream result;
+
+  for( string::const_iterator iter = str.begin(); iter!=str.end(); iter++ )
+  {
+       unsigned char c = (unsigned char)*iter;
+       if (c < 0x20)
+          continue;
+       else if (c == 0xc0 || c == 0xc1 || (c >= 0xf5 && c <= 0xff))
+          result << (0xc0 | (c >> 6)) << (0x80 | (c & 0x3f));
+       else
+          result << c;
+  }
+
+  string res = result.str();
+  static cxxtools::Utf8Codec utf8;
+  try {
+     string converted;
+     utf8::replace_invalid(res.begin(), res.end(), back_inserter(converted));
+     return utf8.decode(converted);
+  } catch(utf8::not_enough_room& e) {
+     return utf8.decode((string)"Invalid piece of text. (Fixing Unicode failed.)");
+  }
+}
+
+
+cxxtools::String StringExtension::UTF8Decode(string str)
+{
+  static cxxtools::Utf8Codec utf8;
+  try {
+     string converted;
+     utf8::replace_invalid(str.begin(), str.end(), back_inserter(converted));
+     return utf8.decode(converted);
+  } catch (utf8::not_enough_room& e) {
+     return utf8.decode((string)"Invalid piece of text. (Fixing Unicode failed.)");
+  }
+}
+
+string StringExtension::toLowerCase(string str)
+{
+  ostringstream res;
+  for (int i=0;i<(int)str.length();i++)
+  {
+      res << (char)tolower(str[i]);
+  }
+  return res.str();
+}
+
+string StringExtension::trim(string str)
+{
+  int a = str.find_first_not_of(" \t");
+  int b = str.find_last_not_of(" \t");
+  if ( a == -1 ) a = 0;
+  if ( b == -1 ) b = str.length() - 1;
+  return str.substr(a, (b-a)+1);
+}
+
+vector<string > StringExtension::split(string str, string s)
+{
+  vector< string > result;
+  if ( str.length() <= 1 ) return result;
+
+  int found = 0;
+  int previous = -1;
+  while((found = str.find_first_of(s.c_str(), found+1)) != -1 ) {
+    result.push_back(str.substr(previous+1, (found+(-previous-1))));
+    previous = found;
+  }
+  found++;
+  result.push_back(str.substr(previous+1));
+
+  return result;
+}
+
+string StringExtension::timeToString(time_t time)
+{
+  struct tm *ltime = localtime(&time);
+  char buffer[26];
+  strftime(buffer, 26, "%H:%M", ltime);
+  return (std::string)buffer;
+}
+
+string StringExtension::dateToString(time_t time)
+{
+  struct tm *ltime = localtime(&time);
+
+  ostringstream data;
+  data << StringExtension::addZeros((ltime->tm_year+1900), 4) << "-"
+       << StringExtension::addZeros((ltime->tm_mon+1), 2) << "-"
+       << StringExtension::addZeros((ltime->tm_mday), 2) << " "
+       << StringExtension::addZeros((ltime->tm_hour), 2) << ":"
+       << StringExtension::addZeros((ltime->tm_min), 2) << ":"
+       << StringExtension::addZeros((ltime->tm_sec), 2);
+  return data.str();
+}
+
+string StringExtension::addZeros(int value, int digits)
+{
+  string strValue = StringExtension::itostr(value);
+  if ( value < 0 ) return strValue;
+
+  while ( (int)strValue.length() < digits ) {
+    strValue = "0" + strValue;
+  }
+
+  return strValue;
+}
+
+// --- QueryHandler -----------------------------------------------------------
+
+QueryHandler::QueryHandler(string service, cxxtools::http::Request& request)
+{
+  _url = request.url();
+  _service = service;
+  _options.parse_url(request.qparams());
+  string body = request.bodyStr();
+  bool found_json = false;
+ 
+  int i = 0;
+  while(!found_json && body.length() > (size_t)i) {
+    if (body[i] == '{') {
+       found_json = true;
+    } else if (body[i] != '\t' && body[i] != '\n' && body[i] != ' ') {
+       break;
+    }
+    i++;
+  }  
+
+  if ( found_json ) {
+     jsonObject = jsonParser.Parse(body);
+     esyslog("restfulapi: JSON parsed successfully: %s", jsonObject == NULL ? "no" : "yes");
+  } else {
+     _body.parse_url(body);
+     jsonObject = NULL;
+  }
+
+  string params = _url.substr(_service.length());
+  parseRestParams(params);
+
+  _format = "";
+  if ( (int)_url.find(".xml") != -1 ) { _format = ".xml"; }
+  if ( (int)_url.find(".json") != -1 ) { _format = ".json"; }
+  if ( (int)_url.find(".html") != -1 ) { _format = ".html"; }
+}
+
+QueryHandler::~QueryHandler()
+{
+  if (jsonObject != NULL) {
+     delete jsonObject;
+  }
+}
+
+void QueryHandler::parseRestParams(std::string params)
+{
+  int start = -1;
+
+  for(int i=0;i<(int)params.length();i++)
+  {
+    if(params[i] == '/')
+    {
+      if(start == -1)
+      {
+        start = i;
+      } else {
+        string p = params.substr(start+1, (i-1)-(start));
+        _params.push_back(p);
+        start = i;
+      }
+    }
+  }
+
+  if(start != (int)params.length() - 1) {
+    _params.push_back(params.substr(start + 1));
+  }
+}
+
+bool QueryHandler::has(string name) {
+
+  return hasJson(name) || hasOption(name) || hasBody(name);
+};
+
+bool QueryHandler::hasJson(string name) {
+  return jsonObject != NULL && jsonObject->GetItem(name) != NULL;
+};
+bool QueryHandler::hasOption(string name) {
+  return _options.has(name);
+};
+bool QueryHandler::hasBody(string name) {
+  return _body.has(name);
+};
+
+string QueryHandler::getJsonString(string name)
+{
+  if ( jsonObject == NULL ) return "";
+  JsonValue* jsonValue = jsonObject->GetItem(name);
+  if ( jsonValue == NULL ) return "";
+  JsonBase* jsonBase = jsonValue->Value();
+  if ( jsonBase == NULL || !jsonBase->IsBasicValue()) return "";
+  JsonBasicValue* jsonBasicValue = (JsonBasicValue*)jsonBase;
+  if ( jsonBasicValue->IsString() ) return jsonBasicValue->ValueAsString();
+  if ( jsonBasicValue->IsBool() ) return jsonBasicValue->ValueAsBool() ? "true" : "false";
+  std::ostringstream str;
+  if ( jsonBasicValue->IsDouble() ) { str << jsonBasicValue->ValueAsDouble(); return str.str(); }
+  return "";
+}
+
+int QueryHandler::getJsonInt(std::string name)
+{
+  if ( jsonObject == NULL ) return -LOWINT;
+  JsonValue* jsonValue = jsonObject->GetItem(name);
+  if ( jsonValue == NULL ) return -LOWINT;
+  JsonBase* jsonBase = jsonValue->Value();
+  if ( jsonBase == NULL || !jsonBase->IsBasicValue()) return -LOWINT;
+  JsonBasicValue* jsonBasicValue = (JsonBasicValue*)jsonBase;
+  if ( jsonBasicValue->IsBool() ) return jsonBasicValue->ValueAsBool() ? 1 : 0;
+  if ( jsonBasicValue->IsDouble() ) return (int)jsonBasicValue->ValueAsDouble();
+  return -LOWINT;
+}
+
+bool QueryHandler::getJsonBool(std::string name)
+{
+  if (jsonObject == NULL) return false;
+  JsonValue* jsonValue = jsonObject->GetItem(name);
+  if (jsonValue == NULL) return false;
+  JsonBase* jsonBase = jsonValue->Value();
+  if (jsonBase == NULL || !jsonBase->IsBasicValue()) return false;
+  JsonBasicValue* jsonBasicValue = (JsonBasicValue*)jsonBase;
+  if (jsonBasicValue->IsBool()) return jsonBasicValue->ValueAsBool();
+  if (jsonBasicValue->IsDouble()) return jsonBasicValue->ValueAsDouble() != 0 ? true : false;
+  return false;
+}
+
+string QueryHandler::getParamAsString(int level)
+{
+  if ( level >= (int)_params.size() )
+     return "";
+
+  string param = _params[level];
+  if ( param == _format ) return "";
+  if ( level == ((int)_params.size() -1) && _format != "" && param.length() > _format.length() ) {
+     int f = param.find(_format);
+     if ( f > 0 ) {
+        return param.substr(0, f);
+     } 
+  }
+  return param;
+}
+
+string QueryHandler::getOptionAsString(string name)
+{
+  return _options.param(name);
+}
+
+bool QueryHandler::getOptionAsBool(string name)
+{
+  if (jsonObject != NULL) {
+     return getJsonBool(name);
+  }
+  string result = _options.param(name);
+  if (result == "true") return true;
+  if (result == "1") return true;
+  return false;
+}
+
+string QueryHandler::getBodyAsString(string name)
+{
+  if (jsonObject != NULL) {
+     return getJsonString(name);
+  }
+  return _body.param(name);
+}
+
+int QueryHandler::getParamAsInt(int level)
+{
+  return StringExtension::strtoi(getParamAsString(level));  
+}
+
+int QueryHandler::getOptionAsInt(std::string name)
+{
+  return StringExtension::strtoi(getOptionAsString(name));
+}
+
+int QueryHandler::getBodyAsInt(string name)
+{
+  if (jsonObject != NULL) {
+     return getJsonInt(name);
+  }
+  return StringExtension::strtoi(getBodyAsString(name));
+}
+
+bool QueryHandler::getBodyAsBool(string name)
+{
+  if (jsonObject != NULL) {
+     return getJsonBool(name);
+  }
+  string result = getBodyAsString(name);
+  if (result == "true") return true;
+  if (result == "false") return false;
+  if (result == "1") return true;
+  return false;
+}
+
+JsonArray* QueryHandler::getBodyAsArray(string name)
+{
+  if ( jsonObject == NULL ) {
+     return NULL;
+  }
+  JsonValue* jsonValue = jsonObject->GetItem(name);
+  if ( jsonValue == NULL || jsonValue->Value() == NULL || !jsonValue->Value()->IsArray() ) {
+     return NULL;
+  }
+  return (JsonArray*)jsonValue->Value();
+}
+
+bool QueryHandler::isFormat(string format)
+{
+  if ((int)_url.find(format) != -1)
+     return true;
+  return false;
+}
+
+void QueryHandler::addHeader(cxxtools::http::Reply& reply)
+{
+  reply.addHeader("Access-Control-Allow-Origin", "*");
+  reply.addHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT");
+}
+
+// --- BaseList ---------------------------------------------------------------
+
+BaseList::BaseList()
+{
+  iterator = 0;
+  counter = 0;
+  start = -1;
+  limit = -1;
+}
+
+void BaseList::activateLimit(int _start, int _limit)
+{
+  if ( _start >= 0 && _limit >= 1 ) {
+     start = _start;
+     limit = _limit;
+  }
+}
+
+bool BaseList::filtered()
+{
+  if ( start != -1 && limit != -1 ) {
+     if (iterator >= start && iterator < (start+limit)) {
+        counter++;
+        iterator++;
+        return false;
+     }
+     iterator++;
+     return true;
+  } else { 
+     counter++;
+     return false;
+  }
+}
+
+// --- RestfulService ---------------------------------------------------------
+
+RestfulService::RestfulService(string path, bool internal, int version, RestfulService* parent)
+{
+  _path = path;
+  _internal = internal;
+  _regex = new cxxtools::Regex((std::string)"^" + path + (std::string)"(.xml*|.html*|.json*|/*|$)");
+  _version = version;
+  _parent = parent;
+}
+
+RestfulService::~RestfulService()
+{
+  delete _regex;
+}
+
+// --- RestfulServices --------------------------------------------------------
+
+RestfulServices::~RestfulServices()
+{
+  while( (int)services.size() != 0 ) {
+    RestfulService* s = services.back();
+    services.pop_back();
+    delete s;
+  }
+}
+
+RestfulServices* RestfulServices::get()
+{
+  static RestfulServices rs;
+  return &rs;
+}
+
+void RestfulServices::appendService(string path, bool internal, int version, RestfulService* parent)
+{
+  appendService(new RestfulService(path, internal, version, parent));
+}
+
+void RestfulServices::appendService(RestfulService* service)
+{
+  services.push_back(service);
+}
+
+vector< RestfulService* > RestfulServices::Services(bool internal, bool children)
+{
+  vector< RestfulService* > result;
+  for (size_t i = 0; i < services.size(); i++) {
+    if (((services[i]->Internal() && internal) || !internal) || (children || (!children && services[i]->Parent() == NULL))) {
+       result.push_back(services[i]);
+    }
+  }
+  return result;
+}
+
+// --- TaskScheduler ---------------------------------------------------------
+
+TaskScheduler* TaskScheduler::get()
+{
+  static TaskScheduler ts;
+  return &ts;
+}
+
+void TaskScheduler::DoTasks()
+{
+  /*int now = time(NULL);
+  BaseTask* bt = NULL;
+  if ( tasks.size() > 0 ) {
+     do
+     {
+       if ( bt != NULL) {
+          if ( bt->Created()+1 > now) {
+             tasks.pop_front();
+             delete bt;
+             break;
+          }
+       }
+       bt = tasks.front();
+     }while(bt != NULL);
+  }*/
+}
+
+TaskScheduler::~TaskScheduler()
+{
+  BaseTask* bt = NULL;
+  do
+  {
+    if (bt != NULL) 
+    {
+       tasks.pop_front();
+       delete bt;
+    }
+    bt = tasks.front();
+  }while(bt != NULL);
+}
+
+void TaskScheduler::SwitchableChannel(tChannelID channel)
+{
+  _channelMutex.Lock();
+  _channel = channel;
+  _channelMutex.Unlock();
+}
+
+tChannelID TaskScheduler::SwitchableChannel()
+{
+  _channelMutex.Lock();
+  tChannelID tmp = _channel;
+  _channel = tChannelID::InvalidID;
+  _channelMutex.Unlock();
+  return tmp;
+}
diff --git a/tools.h b/tools.h
new file mode 100644
index 0000000..7841203
--- /dev/null
+++ b/tools.h
@@ -0,0 +1,362 @@
+#include <glob.h>
+#include <list>
+#include <unistd.h>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <exception>
+#include <iostream>
+#include <fstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/inotify.h>
+#include <cxxtools/regex.h>
+#include <cxxtools/string.h>
+#include <cxxtools/utf8codec.h>
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/query_params.h>
+#include <cxxtools/serializationinfo.h> // only AdditionalMedia
+#include <vdr/channels.h>
+#include <vdr/timers.h>
+#include <vdr/recording.h>
+#include <vdr/plugin.h>
+#include "utf8_checked.h"
+#include "jsonparser.h"
+
+#define LOWINT 2147483647;
+
+//some defines for inotify memory allocation
+#define EVENT_SIZE ( sizeof (struct inotify_event) )
+#define BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )
+
+#ifndef RESTFULAPI_EXTENSIONS
+#define RESTFULAPI_EXTENSIONS
+
+#ifndef DOCUMENT_ROOT
+#define DOCUMENT_ROOT "/var/lib/vdr/plugins/restfulapi/"
+#endif
+
+class Settings
+{
+  private:
+    int port;
+    std::string ip;
+    std::string epgimage_dir;
+    std::string channellogo_dir;
+    std::string webapp_dir;
+    bool activateHeaders;
+  public:
+    Settings() { initDefault(); }
+    ~Settings() { };
+    static Settings* get();
+    void initDefault();
+    int Port() { return port; }
+    std::string Ip() { return ip; }
+    std::string EpgImageDirectory() { return epgimage_dir; }
+    std::string ChannelLogoDirectory() { return channellogo_dir; }
+    std::string WebappDirectory() { return webapp_dir; }
+    bool Headers() { return activateHeaders; }
+    bool SetPort(std::string v);
+    bool SetIp(std::string v);
+    bool SetEpgImageDirectory(std::string v);
+    bool SetChannelLogoDirectory(std::string v);
+    bool SetWebappDirectory(std::string v);
+    bool SetHeaders(std::string v);
+};
+
+class StreamExtension
+{
+  private:
+    std::ostream *_out;
+  public:
+    StreamExtension(std::ostream *out);
+    ~StreamExtension() { };
+    std::ostream* getBasicStream();
+    void write(const char* str);
+    void write(cString& str) { write((const char*)str); }
+    void writeHtmlHeader(std::string title);
+    void writeXmlHeader();
+    bool writeBinary(std::string path);
+};
+
+class HtmlHeader
+{
+  private:
+    std::string _title;
+    std::string _onload;
+    std::vector< std::string > _stylesheets;
+    std::vector< std::string > _scripts;
+    std::vector< std::string > _metatags;
+  public:
+    HtmlHeader() { }
+    ~HtmlHeader() { }
+    void Title(std::string title) { _title = title; }
+    std::string Title() { return _title; }
+
+    void OnLoad(std::string onload) { _onload = onload; }
+    std::string OnLoad() { return _onload; }
+
+    void Stylesheet(std::string stylesheet) { _stylesheets.push_back(stylesheet); }
+    std::vector< std::string >& Stylesheet() { return _stylesheets; }
+
+    void Script(std::string script) { _scripts.push_back(script); }
+    std::vector< std::string >& Scripts() { return _scripts; }
+
+    void MetaTag(std::string metatag) { _metatags.push_back(metatag); }
+    std::vector< std::string >& MataTags() { return _metatags; }
+
+    void ToStream(StreamExtension* se);
+};
+
+class FileNotifier : public cThread
+{
+  private:
+    int _filedescriptor;
+    int _wd;
+    int _mode;
+    bool active;
+    void Action(void);
+  public:
+    static const int EVENTS = 0x01;
+    static const int CHANNELS = 0x02;
+    FileNotifier() { active = false; };
+    ~FileNotifier();
+    void Initialize(int mode);
+    void Stop();
+    bool isActive() { return active; }
+};
+
+class FileCaches
+{
+  private:
+    std::vector< std::string > eventImages;
+    std::vector< std::string > channelLogos;
+    FileNotifier notifierEvents;
+    FileNotifier notifierChannels;
+  public:
+    FileCaches() {
+         cacheEventImages();
+         cacheChannelLogos();
+         notifierEvents.Initialize(FileNotifier::EVENTS);
+         notifierChannels.Initialize(FileNotifier::CHANNELS);
+      };
+    ~FileCaches() { };
+    static FileCaches* get();
+    void cacheEventImages();
+    void cacheChannelLogos();
+    void searchEventImages(int eventid, std::vector< std::string >& files);
+    std::string searchChannelLogo(cChannel *channel);
+    void addEventImage(std::string file);
+    void addChannelLogo(std::string file);
+    void removeEventImage(std::string file);
+    void removeChannelLogo(std::string file);
+    void stopNotifier() {
+      notifierEvents.Stop();
+      notifierChannels.Stop();
+    };
+};
+
+class FileExtension {
+  public:
+    static FileExtension* get();
+    struct tm* getModifiedTm(std::string path);
+    time_t getModifiedTime(std::string path);
+    void addModifiedHeader(std::string path, cxxtools::http::Reply& reply);
+    time_t getModifiedSinceTime(cxxtools::http::Request& request);
+    const char* getLocale();
+    bool exists(std::string path);
+};
+
+class VdrExtension
+{
+  private:
+    static bool MoveDirectory(std::string const & sourceDir, std::string const & targetDir, bool copy = false);
+  public:
+    static cChannel* getChannel(int number);
+    static cChannel* getChannel(std::string id);
+    static cTimer* getTimer(std::string id);
+    static std::string getTimerID(cTimer* timer);
+    static int scanForFiles(const std::string wildcardpath, std::vector< std::string >& files);
+    static bool doesFileExistInFolder(std::string wildcardpath, std::string filename);
+    static bool IsRadio(cChannel* channel);
+    static bool IsRecording(cRecording* recording);
+    static cTimer* TimerExists(cEvent* event);
+    static std::vector< cTimer* > SortedTimers();
+    static bool CompareTimers(cTimer* timer1, cTimer* timer2);
+    static int RecordingLengthInSeconds(cRecording* recording);
+    static const cEvent* GetEventById(tEventID eventID, cChannel* channel);
+    static std::string getRelativeVideoPath(cRecording* recording);
+    static cEvent* getCurrentEventOnChannel(cChannel* channel);
+    static std::string getVideoDiskSpace();
+    static std::string FileSystemExchangeChars(std::string const & s, bool ToFileSystem);
+    static std::string MoveRecording(cRecording const * recording, std::string const & name, bool copy = false);
+};
+
+class VdrMarks
+{
+  private:
+    std::string cutComment(std::string str);
+    bool validateMark(std::string mark);
+    std::string getPath(cRecording* recording);
+    bool parseLine(std::vector< std::string >& marks, std::string line);
+  public:
+    static VdrMarks* get();
+    std::vector< std::string > readMarks(cRecording* recording);
+    bool saveMarks(cRecording* recording, std::vector< std::string > marks);
+    bool deleteMarks(cRecording* recording);
+};
+
+class StringExtension
+{
+  public:
+    static std::string itostr(int i);
+    static int strtoi(std::string str);
+    static std::string replace(std::string const& text, std::string const& substring, std::string const& replacement);
+    static std::string encodeToXml(const std::string &str);
+    static cxxtools::String encodeToJson(const std::string &str);
+    static cxxtools::String UTF8Decode(std::string str);
+    static std::string toLowerCase(std::string str);
+    static std::string trim(std::string str);
+    static std::vector< std::string > split(std::string str, std::string s);
+    static std::string timeToString(time_t time);
+    static std::string dateToString(time_t time);
+    static std::string addZeros(int value, int digits);
+};
+
+class QueryHandler
+{
+  private:
+    std::string _url;
+    std::string _service;
+    std::vector< std::string > _params;
+    cxxtools::QueryParams _options;
+    cxxtools::QueryParams _body;
+    JsonParser jsonParser;
+    JsonObject* jsonObject;
+    void parseRestParams(std::string params);
+    std::string getJsonString(std::string name);
+    int         getJsonInt(std::string name);
+    bool        getJsonBool(std::string name);
+    std::string _format;
+  public:
+    QueryHandler(std::string service, cxxtools::http::Request& request);
+    ~QueryHandler();
+    bool has(std::string name);
+    bool hasJson(std::string name);
+    bool hasOption(std::string name);
+    bool hasBody(std::string name);
+    std::string getParamAsString(int level);              //Parameters are part of the url (the rest after you cut away the service path)
+    std::string getOptionAsString(std::string name);      //Options are the normal url query parameters after the question mark
+    std::string getBodyAsString(std::string name);        //Are variables in the body of the http-request -> for now only html/json are supported, xml is not implemented (!)
+    int getParamAsInt(int level);
+    int getOptionAsInt(std::string name);
+    bool getOptionAsBool(std::string name);
+    int getBodyAsInt(std::string name);
+    bool getBodyAsBool(std::string name);
+    JsonArray* getBodyAsArray(std::string name);
+    bool isFormat(std::string format);
+    std::string getFormat() { return _format; }
+    static void addHeader(cxxtools::http::Reply& reply);
+};
+
+class BaseList
+{
+  protected:
+    int iterator;
+    int counter;
+    int start;
+    int limit;
+  public:
+    BaseList();
+    virtual ~BaseList() { };
+    virtual void activateLimit(int _start, int _limit);
+    virtual bool filtered();
+    virtual int Count() { return counter; }
+};
+
+class RestfulService
+{
+  protected:
+    cxxtools::Regex* _regex;
+    RestfulService* _parent;
+    std::string _path;
+    bool _internal;
+    int _version;
+  public:
+    RestfulService(std::string path, bool internal = false, int version = 1, RestfulService* parent = NULL);
+    ~RestfulService();
+    RestfulService* Parent() { return _parent; }
+    cxxtools::Regex* Regex() { return _regex; }
+    std::string Path() { return _path; }
+    bool Internal() { return _internal; }
+    int Version() { return _version; }
+};
+
+class RestfulServices
+{
+  protected:
+    std::vector< RestfulService* > services;
+  public:
+    RestfulServices() { };
+    ~RestfulServices();
+    static RestfulServices* get();
+    void appendService(std::string path, bool internal = false, int version = 1, RestfulService* parent = NULL);
+    void appendService(RestfulService* service);
+    std::vector< RestfulService* > Services(bool internal = false, bool children = false);
+};
+
+#endif
+
+#ifndef __RESTFUL_BASICOSD_H
+#define __RESTFUL_BASICOSD_H
+
+class BasicOsd
+{
+  public:
+    BasicOsd() { };
+    virtual ~BasicOsd() { };
+    virtual int Type() { return 0x00; };
+};
+
+#endif
+
+#ifndef __RESTFUL_TASKS_H
+#define __RESTFUL_TASKS_H
+
+class BaseTask
+{
+  protected:
+    int created; //time in s
+  public:
+    BaseTask() { created = (int)time(NULL); };
+    virtual ~BaseTask() { };
+    int Created() { return created; };
+};
+
+class TaskScheduler
+{
+  protected:
+    std::list<BaseTask*> tasks;
+    tChannelID _channel;
+    cRecording* _recording;
+    cMutex     _channelMutex;
+  public:
+    TaskScheduler() { _channel = tChannelID::InvalidID; _recording = NULL; };
+    ~TaskScheduler();
+    static TaskScheduler* get();
+    void AddTask(BaseTask* task) { tasks.push_back(task); };
+    void DoTasks();
+    void SwitchableChannel(tChannelID channel);
+    tChannelID SwitchableChannel();
+    void SwitchableRecording(cRecording* recording) { _recording = recording; }
+    cRecording* SwitchableRecording() { return _recording; }
+};
+
+#endif
+
diff --git a/utf8_checked.h b/utf8_checked.h
new file mode 100644
index 0000000..ad7fde4
--- /dev/null
+++ b/utf8_checked.h
@@ -0,0 +1,332 @@
+// Copyright 2006 Nemanja Trifunovic
+// http://utfcpp.sourceforge.net
+
+/*
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+
+#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+
+#include "utf8_core.h"
+#include <stdexcept>
+
+namespace utf8
+{
+    // Base for the exceptions that may be thrown from the library
+    class exception : public std::exception {
+    };
+
+    // Exceptions that may be thrown from the library functions.
+    class invalid_code_point : public exception {
+        uint32_t cp;
+    public:
+        invalid_code_point(uint32_t cp) : cp(cp) {}
+        virtual const char* what() const throw() { return "Invalid code point"; }
+        uint32_t code_point() const {return cp;}
+    };
+
+    class invalid_utf8 : public exception {
+        uint8_t u8;
+    public:
+        invalid_utf8 (uint8_t u) : u8(u) {}
+        virtual const char* what() const throw() { return "Invalid UTF-8"; }
+        uint8_t utf8_octet() const {return u8;}
+    };
+
+    class invalid_utf16 : public exception {
+        uint16_t u16;
+    public:
+        invalid_utf16 (uint16_t u) : u16(u) {}
+        virtual const char* what() const throw() { return "Invalid UTF-16"; }
+        uint16_t utf16_word() const {return u16;}
+    };
+
+    class not_enough_room : public exception {
+    public:
+        virtual const char* what() const throw() { return "Not enough space"; }
+    };
+
+    /// The library API - functions intended to be called by the users
+
+    //the following declaration might fix a warning on archlinux
+    template <typename octet_iterator>
+    octet_iterator append(uint32_t cp, octet_iterator result);
+
+    template <typename octet_iterator, typename output_iterator>
+    output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement)
+    {
+        while (start != end) {
+            octet_iterator sequence_start = start;
+            internal::utf_error err_code = internal::validate_next(start, end);
+            switch (err_code) {
+                case internal::UTF8_OK :
+                    for (octet_iterator it = sequence_start; it != start; ++it)
+                        *out++ = *it;
+                    break;
+                case internal::NOT_ENOUGH_ROOM:
+                    throw not_enough_room();
+                case internal::INVALID_LEAD:
+                    append (replacement, out);
+                    ++start;
+                    break;
+                case internal::INCOMPLETE_SEQUENCE:
+                case internal::OVERLONG_SEQUENCE:
+                case internal::INVALID_CODE_POINT:
+                    append (replacement, out);
+                    ++start;
+                    // just one replacement mark for the sequence
+                    while (internal::is_trail(*start) && start != end)
+                        ++start;
+                    break;
+            }
+        }
+        return out;
+    }
+
+    template <typename octet_iterator, typename output_iterator>
+    inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out)
+    {
+        static const uint32_t replacement_marker = internal::mask16(0xfffd);
+        return replace_invalid(start, end, out, replacement_marker);
+    }
+
+    template <typename octet_iterator>
+    octet_iterator append(uint32_t cp, octet_iterator result)
+    {
+        if (!internal::is_code_point_valid(cp))
+            throw invalid_code_point(cp);
+
+        if (cp < 0x80)                        // one octet
+            *(result++) = static_cast<uint8_t>(cp);
+        else if (cp < 0x800) {                // two octets
+            *(result++) = static_cast<uint8_t>((cp >> 6)            | 0xc0);
+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);
+        }
+        else if (cp < 0x10000) {              // three octets
+            *(result++) = static_cast<uint8_t>((cp >> 12)           | 0xe0);
+            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);
+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);
+        }
+        else {      // four octets
+            *(result++) = static_cast<uint8_t>((cp >> 18)           | 0xf0);
+            *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)  | 0x80);
+            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);
+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);
+        }
+        return result;
+    }
+
+    template <typename octet_iterator>
+    uint32_t next(octet_iterator& it, octet_iterator end)
+    {
+        uint32_t cp = 0;
+        internal::utf_error err_code = internal::validate_next(it, end, &cp);
+        switch (err_code) {
+            case internal::UTF8_OK :
+                break;
+            case internal::NOT_ENOUGH_ROOM :
+                throw not_enough_room();
+            case internal::INVALID_LEAD :
+            case internal::INCOMPLETE_SEQUENCE :
+            case internal::OVERLONG_SEQUENCE :
+                throw invalid_utf8(*it);
+            case internal::INVALID_CODE_POINT :
+                throw invalid_code_point(cp);
+        }
+        return cp;
+    }
+
+    template <typename octet_iterator>
+    uint32_t peek_next(octet_iterator it, octet_iterator end)
+    {
+        return next(it, end);
+    }
+
+    template <typename octet_iterator>
+    uint32_t prior(octet_iterator& it, octet_iterator start)
+    {
+        // can't do much if it == start
+        if (it == start)
+            throw not_enough_room();
+
+        octet_iterator end = it;
+        // Go back until we hit either a lead octet or start
+        while (internal::is_trail(*(--it)))
+            if (it == start)
+                throw invalid_utf8(*it); // error - no lead byte in the sequence
+        return peek_next(it, end);
+    }
+
+    /// Deprecated in versions that include "prior"
+    template <typename octet_iterator>
+    uint32_t previous(octet_iterator& it, octet_iterator pass_start)
+    {
+        octet_iterator end = it;
+        while (internal::is_trail(*(--it)))
+            if (it == pass_start)
+                throw invalid_utf8(*it); // error - no lead byte in the sequence
+        octet_iterator temp = it;
+        return next(temp, end);
+    }
+
+    template <typename octet_iterator, typename distance_type>
+    void advance (octet_iterator& it, distance_type n, octet_iterator end)
+    {
+        for (distance_type i = 0; i < n; ++i)
+            next(it, end);
+    }
+
+    template <typename octet_iterator>
+    typename std::iterator_traits<octet_iterator>::difference_type
+    distance (octet_iterator first, octet_iterator last)
+    {
+        typename std::iterator_traits<octet_iterator>::difference_type dist;
+        for (dist = 0; first < last; ++dist)
+            next(first, last);
+        return dist;
+    }
+
+    template <typename u16bit_iterator, typename octet_iterator>
+    octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)
+    {
+        while (start != end) {
+            uint32_t cp = internal::mask16(*start++);
+            // Take care of surrogate pairs first
+            if (internal::is_lead_surrogate(cp)) {
+                if (start != end) {
+                    uint32_t trail_surrogate = internal::mask16(*start++);
+                    if (internal::is_trail_surrogate(trail_surrogate))
+                        cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
+                    else
+                        throw invalid_utf16(static_cast<uint16_t>(trail_surrogate));
+                }
+                else
+                    throw invalid_utf16(static_cast<uint16_t>(cp));
+
+            }
+            // Lone trail surrogate
+            else if (internal::is_trail_surrogate(cp))
+                throw invalid_utf16(static_cast<uint16_t>(cp));
+
+            result = append(cp, result);
+        }
+        return result;
+    }
+
+    template <typename u16bit_iterator, typename octet_iterator>
+    u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
+    {
+        while (start != end) {
+            uint32_t cp = next(start, end);
+            if (cp > 0xffff) { //make a surrogate pair
+                *result++ = static_cast<uint16_t>((cp >> 10)   + internal::LEAD_OFFSET);
+                *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
+            }
+            else
+                *result++ = static_cast<uint16_t>(cp);
+        }
+        return result;
+    }
+
+    template <typename octet_iterator, typename u32bit_iterator>
+    octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)
+    {
+        while (start != end)
+            result = append(*(start++), result);
+
+        return result;
+    }
+
+    template <typename octet_iterator, typename u32bit_iterator>
+    u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)
+    {
+        while (start != end)
+            (*result++) = next(start, end);
+
+        return result;
+    }
+
+    // The iterator class
+    template <typename octet_iterator>
+    class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {
+      octet_iterator it;
+      octet_iterator range_start;
+      octet_iterator range_end;
+      public:
+      iterator () {};
+      explicit iterator (const octet_iterator& octet_it,
+                         const octet_iterator& range_start,
+                         const octet_iterator& range_end) :
+               it(octet_it), range_start(range_start), range_end(range_end)
+      {
+          if (it < range_start || it > range_end)
+              throw std::out_of_range("Invalid utf-8 iterator position");
+      }
+      // the default "big three" are OK
+      octet_iterator base () const { return it; }
+      uint32_t operator * () const
+      {
+          octet_iterator temp = it;
+          return next(temp, range_end);
+      }
+      bool operator == (const iterator& rhs) const
+      {
+          if (range_start != rhs.range_start || range_end != rhs.range_end)
+              throw std::logic_error("Comparing utf-8 iterators defined with different ranges");
+          return (it == rhs.it);
+      }
+      bool operator != (const iterator& rhs) const
+      {
+          return !(operator == (rhs));
+      }
+      iterator& operator ++ ()
+      {
+          next(it, range_end);
+          return *this;
+      }
+      iterator operator ++ (int)
+      {
+          iterator temp = *this;
+          next(it, range_end);
+          return temp;
+      }
+      iterator& operator -- ()
+      {
+          prior(it, range_start);
+          return *this;
+      }
+      iterator operator -- (int)
+      {
+          iterator temp = *this;
+          prior(it, range_start);
+          return temp;
+      }
+    }; // class iterator
+
+} // namespace utf8
+
+#endif //header guard
+
+
diff --git a/utf8_core.h b/utf8_core.h
new file mode 100644
index 0000000..b250522
--- /dev/null
+++ b/utf8_core.h
@@ -0,0 +1,359 @@
+// Copyright 2006 Nemanja Trifunovic
+// http://utfcpp.sourceforge.net
+
+/*
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+
+#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+
+#include <iterator>
+
+namespace utf8
+{
+    // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers
+    // You may need to change them to match your system.
+    // These typedefs have the same names as ones from cstdint, or boost/cstdint
+    typedef unsigned char   uint8_t;
+    typedef unsigned short  uint16_t;
+    typedef unsigned int    uint32_t;
+
+// Helper code - not intended to be directly called by the library users. May be changed at any time
+namespace internal
+{
+    // Unicode constants
+    // Leading (high) surrogates: 0xd800 - 0xdbff
+    // Trailing (low) surrogates: 0xdc00 - 0xdfff
+    const uint16_t LEAD_SURROGATE_MIN  = 0xd800u;
+    const uint16_t LEAD_SURROGATE_MAX  = 0xdbffu;
+    const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u;
+    const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu;
+    const uint16_t LEAD_OFFSET         = LEAD_SURROGATE_MIN - (0x10000 >> 10);
+    const uint32_t SURROGATE_OFFSET    = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN;
+
+    // Maximum valid value for a Unicode code point
+    const uint32_t CODE_POINT_MAX      = 0x0010ffffu;
+
+    template<typename octet_type>
+    inline uint8_t mask8(octet_type oc)
+    {
+        return static_cast<uint8_t>(0xff & oc);
+    }
+    template<typename u16_type>
+    inline uint16_t mask16(u16_type oc)
+    {
+        return static_cast<uint16_t>(0xffff & oc);
+    }
+    template<typename octet_type>
+    inline bool is_trail(octet_type oc)
+    {
+        return ((mask8(oc) >> 6) == 0x2);
+    }
+
+    template <typename u16>
+    inline bool is_lead_surrogate(u16 cp)
+    {
+        return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX);
+    }
+
+    template <typename u16>
+    inline bool is_trail_surrogate(u16 cp)
+    {
+        return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);
+    }
+
+    template <typename u16>
+    inline bool is_surrogate(u16 cp)
+    {
+        return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);
+    }
+
+    template <typename u32>
+    inline bool is_code_point_valid(u32 cp)
+    {
+        return (cp <= CODE_POINT_MAX && !is_surrogate(cp));
+    }
+
+    template <typename octet_iterator>
+    inline typename std::iterator_traits<octet_iterator>::difference_type
+    sequence_length(octet_iterator lead_it)
+    {
+        uint8_t lead = mask8(*lead_it);
+        if (lead < 0x80)
+            return 1;
+        else if ((lead >> 5) == 0x6)
+            return 2;
+        else if ((lead >> 4) == 0xe)
+            return 3;
+        else if ((lead >> 3) == 0x1e)
+            return 4;
+        else
+            return 0;
+    }
+
+    template <typename octet_difference_type>
+    inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length)
+    {
+        if (cp < 0x80) {
+            if (length != 1) 
+                return true;
+        }
+        else if (cp < 0x800) {
+            if (length != 2) 
+                return true;
+        }
+        else if (cp < 0x10000) {
+            if (length != 3) 
+                return true;
+        }
+
+        return false;
+    }
+
+    enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT};
+
+    /// get_sequence_x functions decode utf-8 sequences of the length x
+
+    template <typename octet_iterator>
+    utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t* code_point)
+    {
+        if (it != end) {
+            if (code_point)
+                *code_point = mask8(*it);
+            return UTF8_OK;
+        }
+        return NOT_ENOUGH_ROOM;
+    }
+
+    template <typename octet_iterator>
+    utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t* code_point)
+    {
+        utf_error ret_code = NOT_ENOUGH_ROOM;
+
+        if (it != end) {
+            uint32_t cp = mask8(*it);
+            if (++it != end) {
+                if (is_trail(*it)) {
+                    cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
+
+                    if (code_point)
+                        *code_point = cp;
+                    ret_code = UTF8_OK;
+                }
+                else
+                    ret_code = INCOMPLETE_SEQUENCE;
+            }
+            else
+                ret_code = NOT_ENOUGH_ROOM;
+        }
+
+        return ret_code;
+    }
+
+    template <typename octet_iterator>
+    utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t* code_point)
+    {
+        utf_error ret_code = NOT_ENOUGH_ROOM;
+
+        if (it != end) {
+            uint32_t cp = mask8(*it);
+            if (++it != end) {
+                if (is_trail(*it)) {
+                    cp = ((cp << 12) & 0xffff) + ((mask8(*it) << 6) & 0xfff);
+                    if (++it != end) {
+                        if (is_trail(*it)) {
+                            cp += (*it) & 0x3f;
+
+                            if (code_point)
+                                *code_point = cp;
+                            ret_code = UTF8_OK;
+                        }
+                        else 
+                            ret_code = INCOMPLETE_SEQUENCE;
+                    }
+                    else
+                        ret_code = NOT_ENOUGH_ROOM;
+                }
+                else
+                    ret_code = INCOMPLETE_SEQUENCE;
+            }
+            else
+                ret_code = NOT_ENOUGH_ROOM;
+        }
+
+        return ret_code;
+    }
+
+    template <typename octet_iterator>
+    utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t* code_point)
+    {
+        utf_error ret_code = NOT_ENOUGH_ROOM;
+
+        if (it != end) {
+            uint32_t cp = mask8(*it);
+            if (++it != end) {
+                if (is_trail(*it)) {
+                    cp = ((cp << 18) & 0x1fffff) + ((mask8(*it) << 12) & 0x3ffff);
+                    if (++it != end) {
+                        if (is_trail(*it)) {
+                            cp += (mask8(*it) << 6) & 0xfff;
+                            if (++it != end) {
+                                if (is_trail(*it)) {
+                                    cp += (*it) & 0x3f;
+
+                                    if (code_point)
+                                        *code_point = cp;
+                                    ret_code = UTF8_OK;
+                                }
+                                else
+                                    ret_code = INCOMPLETE_SEQUENCE;
+                            }
+                            else
+                                ret_code = NOT_ENOUGH_ROOM;
+                        }
+                        else
+                            ret_code = INCOMPLETE_SEQUENCE;
+                    }
+                    else
+                        ret_code = NOT_ENOUGH_ROOM;
+                }
+                else 
+                    ret_code = INCOMPLETE_SEQUENCE;
+            }
+            else
+                ret_code = NOT_ENOUGH_ROOM;
+        }
+
+        return ret_code;
+    }
+
+    template <typename octet_iterator>
+    utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t* code_point)
+    {
+        // Save the original value of it so we can go back in case of failure
+        // Of course, it does not make much sense with i.e. stream iterators
+        octet_iterator original_it = it;
+
+        uint32_t cp = 0;
+        // Determine the sequence length based on the lead octet
+        typedef typename std::iterator_traits<octet_iterator>::difference_type octet_difference_type;
+        octet_difference_type length = sequence_length(it);
+        if (length == 0)
+            return INVALID_LEAD;
+
+        // Now that we have a valid sequence length, get trail octets and calculate the code point
+        utf_error err = UTF8_OK;
+        switch (length) {
+            case 1:
+                err = get_sequence_1(it, end, &cp);
+                break;
+            case 2:
+                err = get_sequence_2(it, end, &cp);
+            break;
+            case 3:
+                err = get_sequence_3(it, end, &cp);
+            break;
+            case 4:
+                err = get_sequence_4(it, end, &cp);
+            break;
+        }
+
+        if (err == UTF8_OK) {
+            // Decoding succeeded. Now, security checks...
+            if (is_code_point_valid(cp)) {
+                if (!is_overlong_sequence(cp, length)){
+                    // Passed! Return here.
+                    if (code_point)
+                        *code_point = cp;
+                    ++it;
+                    return UTF8_OK;
+                }
+                else
+                    err = OVERLONG_SEQUENCE;
+            }
+            else 
+                err = INVALID_CODE_POINT;
+        }
+
+        // Failure branch - restore the original value of the iterator
+        it = original_it;
+        return err;
+    }
+
+    template <typename octet_iterator>
+    inline utf_error validate_next(octet_iterator& it, octet_iterator end) {
+        return validate_next(it, end, 0);
+    }
+
+} // namespace internal
+
+    /// The library API - functions intended to be called by the users
+
+    // Byte order mark
+    const uint8_t bom[] = {0xef, 0xbb, 0xbf};
+
+    template <typename octet_iterator>
+    octet_iterator find_invalid(octet_iterator start, octet_iterator end)
+    {
+        octet_iterator result = start;
+        while (result != end) {
+            internal::utf_error err_code = internal::validate_next(result, end);
+            if (err_code != internal::UTF8_OK)
+                return result;
+        }
+        return result;
+    }
+
+    template <typename octet_iterator>
+    inline bool is_valid(octet_iterator start, octet_iterator end)
+    {
+        return (find_invalid(start, end) == end);
+    }
+
+    template <typename octet_iterator>
+    inline bool starts_with_bom (octet_iterator it, octet_iterator end)
+    {
+        return (
+            ((it != end) && (internal::mask8(*it++)) == bom[0]) &&
+            ((it != end) && (internal::mask8(*it++)) == bom[1]) &&
+            ((it != end) && (internal::mask8(*it))   == bom[2])
+           );
+    }
+	
+	//Deprecated in release 2.3 
+    template <typename octet_iterator>
+    inline bool is_bom (octet_iterator it)
+    {
+        return (
+            (internal::mask8(*it++)) == bom[0] &&
+            (internal::mask8(*it++)) == bom[1] &&
+            (internal::mask8(*it))   == bom[2]
+           );
+    }
+} // namespace utf8
+
+#endif // header guard
+
+
diff --git a/web/osd.css b/web/osd.css
new file mode 100644
index 0000000..53e33cb
--- /dev/null
+++ b/web/osd.css
@@ -0,0 +1,267 @@
+body
+{
+  font-family: Arial, sans-serif;
+  margin: 0px;
+  padding: 0px;
+  overflow: hidden;
+  height: 100%;
+  width: 100%;
+  font-size: 120%;
+}
+
+/* osd_container - main wrapper element */
+
+div#osd_bg
+{
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  margin: 0%;
+  padding: 0%;
+  overflow: hidden;
+  background: #d8e0de; /* Old browsers */
+  background: -moz-linear-gradient(top, #d8e0de 0%, #aebfbc 22%, #99afab 33%, #8ea6a2 50%, #829d98 67%, #4e5c5a 82%, #0e0e0e 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d8e0de), color-stop(22%,#aebfbc), color-stop(33%,#99afab), color-stop(50%,#8ea6a2), color-stop(67%,#829d98), color-stop(82%,#4e5c5a), color-stop(100%,#0e0e0e)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#d8e0de', endColorstr='#0e0e0e',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #d8e0de 0%,#aebfbc 22%,#99afab 33%,#8ea6a2 50%,#829d98 67%,#4e5c5a 82%,#0e0e0e 100%); /* W3C */
+}
+
+div#osd_container
+{
+  position: absolute;
+  width: 90%;
+  height: 90%;
+  margin: 0%;
+  padding: 5%;
+  overflow: hidden;
+}
+
+/* header */
+
+div#header
+{
+  background-color:#222266;
+  font-size: 2em;
+  color:#CDCDCD;
+  white-space: pre;
+  overflow: hidden;
+  padding: 2%;
+  -moz-border-radius-topleft:25px;
+  -moz-border-radius-topright:25px;
+  border-top-left-radius:25px;
+  border-top-right-radius:25px;
+}
+
+/* content - between header and colored buttons */
+
+div#content
+{
+  height: 70%;
+  padding: 1%;
+  margin: 0px;
+  overflow: auto;
+
+  background: rgb(222,239,255); /* Old browsers */
+  background: -moz-linear-gradient(top, rgba(222,239,255,1) 0%, rgba(152,190,222,1) 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(222,239,255,1)), color-stop(100%,rgba(152,190,222,1))); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#deefff', endColorstr='#98bede',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, rgba(222,239,255,1) 0%,rgba(152,190,222,1) 100%); /* W3C */
+}
+
+div#content ul
+{
+  padding: 0px;
+  margin: 0px;
+  list-style: none;
+}
+
+div#content ul li.item
+{
+  padding: 0% 1% 0% 1%;
+  margin: 0% 4% 0.5% 4%;
+  font-weight:bold;
+  font-size: 1.5em;
+  white-space: nowrap;
+}
+
+div#content ul li#selectedItem,
+div#content ul li.item:hover
+{
+  -moz-border-radius:25px;
+  -moz-border-radius:25px;
+}
+
+div#content ul li#selectedItem
+{
+  color: white;
+  background: #b5bdc8; /* Old browsers */
+  background: -moz-linear-gradient(top, #b5bdc8 0%, #828c95 36%, #28343b 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b5bdc8), color-stop(36%,#828c95), color-stop(100%,#28343b)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b5bdc8', endColorstr='#28343b',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #b5bdc8 0%,#828c95 36%,#28343b 100%); /* W3C */}
+
+div#content ul li.item:hover
+{
+  border-radius:25px;
+  border-radius:25px;
+  background: rgb(246,248,249); /* Old browsers */
+  background: -moz-linear-gradient(top, rgba(246,248,249,1) 0%, rgba(229,235,238,1) 50%, rgba(215,222,227,1) 51%, rgba(245,247,249,1) 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(246,248,249,1)), color-stop(50%,rgba(229,235,238,1)), color-stop(51%,rgba(215,222,227,1)), color-stop(100%,rgba(245,247,249,1))); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f8f9', endColorstr='#f5f7f9',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* W3C */
+}
+
+#content2
+{
+  position:absolute;
+  top: 75%;
+  bottom:5%;
+  left:4%;
+  right:4%;
+  -moz-border-radius:25px;
+  border-radius:25px;
+  color:#CDCDCD;
+  background-color:#222266;
+}
+
+#innercontent
+{
+  padding: 25px;
+}
+
+#eventtitle
+{
+  font-size: 25pt;
+}
+
+#eventsubtitle
+{
+  font-size: 15pt;
+}
+
+#eventtime
+{
+  font-size:15pt;
+  right: 7%;
+  top: 78%;
+  width: 20%;
+}
+
+div#color_buttons{
+  width: 96%;
+  padding: 0% 2% 0% 2%;
+  -moz-border-radius-bottomleft:25px;
+  -moz-border-radius-bottomright:25px;
+  border-bottom-left-radius:25px;
+  border-bottom-right-radius:25px;
+}
+
+div#header,
+div#color_buttons
+{
+  background: rgb(76,76,76); /* Old browsers */
+  background: -moz-linear-gradient(top, rgba(76,76,76,1) 0%, rgba(89,89,89,1) 12%, rgba(102,102,102,1) 25%, rgba(71,71,71,1) 39%, rgba(44,44,44,1) 50%, rgba(0,0,0,1) 51%, rgba(17,17,17,1) 60%, rgba(43,43,43,1) 76%, rgba(28,28,28,1) 91%, rgba(19,19,19,1) 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(76,76,76,1)), color-stop(12%,rgba(89,89,89,1)), color-stop(25%,rgba(102,102,102,1)), color-stop(39%,rgba(71,71,71,1)), color-stop(50%,rgba(44,44,44,1)), color-stop(51%,rgba(0,0,0,1)), color-stop(60%,rgba(17,17,17,1)), color-stop(76%,rgba(43,43,43,1)), color-stop(91%,rgba(28,28,28,1)), color-stop(100%,rgba(19,19,19,1))); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, rgba(76,76,76,1) 0%,rgba(89,89,89,1) 12%,rgba(102,102,102,1) 25%,rgba(71,71,71,1) 39%,rgba(44,44,44,1) 50%,rgba(0,0,0,1) 51%,rgba(17,17,17,1) 60%,rgba(43,43,43,1) 76%,rgba(28,28,28,1) 91%,rgba(19,19,19,1) 100%); /* W3C */
+}
+
+div#color_buttons div.active,
+div#color_buttons div.inactive
+{
+  float: left;
+  padding: 0%;
+  margin: 1%;
+  font-size: 1.2em;
+  width: 23%;
+  /*height: 60%;*/
+  overflow: hidden;
+  text-align: center;
+  -moz-border-radius:25px;
+  -moz-border-radius:25px;
+  border-radius:25px;
+  border-radius:25px;
+}
+
+div#color_buttons div.inactive
+{
+  background: rgb(149,149,149); /* Old browsers */
+  background: -moz-linear-gradient(top, rgba(149,149,149,1) 0%, rgba(13,13,13,1) 46%, rgba(1,1,1,1) 50%, rgba(10,10,10,1) 53%, rgba(78,78,78,1) 76%, rgba(56,56,56,1) 87%, rgba(27,27,27,1) 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(149,149,149,1)), color-stop(46%,rgba(13,13,13,1)), color-stop(50%,rgba(1,1,1,1)), color-stop(53%,rgba(10,10,10,1)), color-stop(76%,rgba(78,78,78,1)), color-stop(87%,rgba(56,56,56,1)), color-stop(100%,rgba(27,27,27,1))); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#959595', endColorstr='#1b1b1b',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, rgba(149,149,149,1) 0%,rgba(13,13,13,1) 46%,rgba(1,1,1,1) 50%,rgba(10,10,10,1) 53%,rgba(78,78,78,1) 76%,rgba(56,56,56,1) 87%,rgba(27,27,27,1) 100%); /* W3C */
+}
+
+div#color_buttons div#red.active
+{
+  background: #feccb1; /* Old browsers */
+  background: -moz-linear-gradient(top, #feccb1 0%, #f17432 50%, #ea5507 51%, #fb955e 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#feccb1), color-stop(50%,#f17432), color-stop(51%,#ea5507), color-stop(100%,#fb955e)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#feccb1', endColorstr='#fb955e',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #feccb1 0%,#f17432 50%,#ea5507 51%,#fb955e 100%); /* W3C */
+}
+
+div#color_buttons div#green.active
+{
+  background: #bfd255; /* Old browsers */
+  background: -moz-linear-gradient(top, #bfd255 0%, #8eb92a 50%, #72aa00 51%, #9ecb2d 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bfd255), color-stop(50%,#8eb92a), color-stop(51%,#72aa00), color-stop(100%,#9ecb2d)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#bfd255', endColorstr='#9ecb2d',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #bfd255 0%,#8eb92a 50%,#72aa00 51%,#9ecb2d 100%); /* W3C */
+}
+
+div#color_buttons div#yellow.active
+{
+  background: #fefcea; /* Old browsers */
+  background: -moz-linear-gradient(top, #fefcea 0%, #f1da36 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fefcea), color-stop(100%,#f1da36)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #fefcea 0%,#f1da36 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #fefcea 0%,#f1da36 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #fefcea 0%,#f1da36 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fefcea', endColorstr='#f1da36',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #fefcea 0%,#f1da36 100%); /* W3C */
+}
+
+div#color_buttons div#blue.active
+{
+  background: #ebf1f6; /* Old browsers */
+  background: -moz-linear-gradient(top, #ebf1f6 0%, #abd3ee 50%, #89c3eb 51%, #d5ebfb 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ebf1f6), color-stop(50%,#abd3ee), color-stop(51%,#89c3eb), color-stop(100%,#d5ebfb)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* IE10+ */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ebf1f6', endColorstr='#d5ebfb',GradientType=0 ); /* IE6-9 */
+  background: linear-gradient(top, #ebf1f6 0%,#abd3ee 50%,#89c3eb 51%,#d5ebfb 100%); /* W3C */
+}
+
+div#color_buttons br.clear{
+  clear: both;
+}
+
+div.invisible{
+  display: none;
+}
diff --git a/web/osd.js b/web/osd.js
new file mode 100644
index 0000000..281b197
--- /dev/null
+++ b/web/osd.js
@@ -0,0 +1,5 @@
+//bootstrap will be called on onload event
+function bootstrap()
+{
+  //alert("javascript bootstrap called!");
+}
diff --git a/webapp.cpp b/webapp.cpp
new file mode 100644
index 0000000..790690f
--- /dev/null
+++ b/webapp.cpp
@@ -0,0 +1,142 @@
+#include "webapp.h"
+
+void WebappResponder::reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply) {
+
+  QueryHandler::addHeader(reply);
+
+  if ( request.method() == "OPTIONS" ) {
+      reply.addHeader("Allow", "GET");
+      reply.httpReturn(200, "OK");
+      return;
+  }
+  if ( request.method() != "GET") {
+     reply.httpReturn(403, "To retrieve files use the GET method!");
+     return;
+  }
+
+  double timediff = -1;
+  string url = request.url();
+  string base = "/webapp";
+
+  if ( base == request.url() ) {
+      reply.addHeader("Location", "/webapp/");
+      reply.httpReturn(301, "Moved Permanently");
+      return;
+  }
+
+  if ( (int)url.find(base) == 0) {
+
+      esyslog("restfulapi Webapp: file request url %s", request.url().c_str());
+
+      string fileName = getFileName(base, url);
+      string file = getFile(fileName);
+
+      if (!FileExtension::get()->exists(file)) {
+	  esyslog("restfulapi Webapp: file does not exist");
+	  reply.httpReturn(404, "File not found");
+	  return;
+      }
+
+      if (request.hasHeader("If-Modified-Since")) {
+	  timediff = difftime(FileExtension::get()->getModifiedTime(file), FileExtension::get()->getModifiedSinceTime(request));
+      }
+      if (timediff > 0.0 || timediff < 0.0) {
+	  streamResponse(fileName, out, file, reply);
+      } else {
+	  esyslog("restfulapi Webapp: file not modified, returning 304");
+	  reply.httpReturn(304, "Not-Modified");
+      }
+  }
+}
+
+/**
+ * retrieve filename width path
+ * @param string fileName
+ */
+string WebappResponder::getFile(std::string fileName) {
+
+  string webappPath = Settings::get()->WebappDirectory();
+
+  if ( webappPath.find_last_of("/") == (webappPath.length() - 1) ) {
+      webappPath = webappPath.substr(0, webappPath.length() - 1);
+  }
+
+  if ( fileName.find_first_of("/") == 0 ) {
+      fileName = fileName.substr(1, fileName.length() - 1);
+  }
+
+  return webappPath + (string)"/" + fileName;
+};
+
+/**
+ * retrieve filename
+ * @param string base url
+ * @param string url
+ */
+string WebappResponder::getFileName(string base, string url) {
+
+  const char *empty = "";
+
+  if ( url.find_last_of("/") == (url.length() - 1) ) {
+      url = url.substr(0, url.length() - 1);
+  }
+  string file = url.replace(0, base.length(), "");
+
+  if (strcmp(file.c_str(), empty) == 0) {
+      file = "index.html";
+  }
+  return file;
+};
+
+/**
+ * determine contenttype
+ * @param string fileName
+ */
+const char *WebappResponder::getContentType(string fileName) {
+
+  const char *type = fileName.substr(fileName.find_last_of(".")+1).c_str();
+  const char *contentType = "";
+  const char *extHtml = "html";
+  const char *extJs = "js";
+  const char *extCss = "css";
+  const char *extJpg = "jpg";
+  const char *extJpeg = "jpeg";
+  const char *extGif = "gif";
+  const char *extPng = "png";
+  const char *extIco = "ico";
+  const char *extAppCacheManifest = "appcache";
+  esyslog("restfulapi Webapp: file extension of %s is %s", fileName.c_str(), type);
+
+  if ( strcmp(type, extHtml) == 0 ) {
+      contentType = "text/html";
+  } else if ( strcmp(type, extJs) == 0 ) {
+      contentType = "application/javascript";
+  } else if ( strcmp(type, extCss) == 0 ) {
+      contentType = "text/css";
+  } else if ( strcmp(type, extJpg) == 0 || strcmp(type, extJpeg) == 0 || strcmp(type, extGif) == 0 || strcmp(type, extPng) == 0 ) {
+      contentType = ("image/" + (string)type).c_str();
+  } else if ( strcmp(type, extIco) == 0 ) {
+      contentType = "image/x-icon";
+  } else if ( strcmp(type, extAppCacheManifest) == 0 ) {
+      contentType = "text/cache-manifest";
+  }
+  esyslog("restfulapi Webapp: file type of %s is %s", fileName.c_str(), contentType);
+
+  return contentType;
+};
+
+void WebappResponder::streamResponse(string fileName, ostream& out, string file, cxxtools::http::Reply& reply) {
+
+  const char *empty = "";
+  const char * contentType = getContentType(fileName);
+
+  StreamExtension se(&out);
+  if ( strcmp(contentType, empty) != 0 && se.writeBinary(file) ) {
+      esyslog("restfulapi Webapp: successfully piped file %s", fileName.c_str());
+      FileExtension::get()->addModifiedHeader(file, reply);
+      reply.addHeader("Content-Type", contentType);
+  } else {
+      esyslog("restfulapi Webapp: error piping file %s", fileName.c_str());
+      reply.httpReturn(404, "File not found");
+  }
+};
diff --git a/webapp.h b/webapp.h
new file mode 100644
index 0000000..507751a
--- /dev/null
+++ b/webapp.h
@@ -0,0 +1,26 @@
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <locale.h>
+#include <time.h>
+#include "tools.h"
+
+#ifndef WEBAPPESTFULAPI_H
+#define WEBAPPRESTFULAPI_H
+
+using namespace std;
+
+class WebappResponder : public cxxtools::http::Responder {
+private:
+  string getFile(string fileName);
+  string getFileName(string base, string url);
+  const char *getContentType(string fileName);
+  void streamResponse(string fileName, ostream& out, string file, cxxtools::http::Reply& reply);
+public:
+  explicit WebappResponder(cxxtools::http::Service& service) : cxxtools::http::Responder(service) {};
+  virtual void reply(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply);
+};
+
+typedef cxxtools::http::CachedService<WebappResponder> WebappService;
+
+#endif // WEBAPPRESTFULAPI_H
diff --git a/wirbelscan.cpp b/wirbelscan.cpp
new file mode 100644
index 0000000..f702ade
--- /dev/null
+++ b/wirbelscan.cpp
@@ -0,0 +1,408 @@
+#include "wirbelscan.h"
+
+#include <sstream>
+
+using namespace std;
+using namespace WIRBELSCAN_SERVICE;
+
+void WirbelscanResponder::reply(ostream& out, cxxtools::http::Request& request,
+		cxxtools::http::Reply& reply) {
+
+	QueryHandler::addHeader(reply);
+
+	if ( request.method() == "OPTIONS" ) {
+	    reply.addHeader("Allow", "GET, POST, PUT");
+	    reply.httpReturn(200, "OK");
+	    return;
+	}
+
+	bool isGet = request.method() == "GET";
+	bool isPut = request.method() == "PUT";
+	bool isPost = request.method() == "POST";
+
+	if ((wirbelscan = cPluginManager::GetPlugin("wirbelscan")) == NULL) {
+		reply.httpReturn(403U, "wirbelscan was not found - pls install.");
+		return;
+	}
+
+	if ( !isGet && !isPut && !isPost) {
+		reply.httpReturn(403U, "To retrieve information use the GET method! To update Settings use the PUT method! ");
+		return;
+	}
+	static cxxtools::Regex countriesRegex("/wirbelscan/countries.json");
+	static cxxtools::Regex satellitesRegex("/wirbelscan/satellites.json");
+	static cxxtools::Regex getStatusRegex("/wirbelscan/getStatus.json");
+	static cxxtools::Regex getSetupRegex("/wirbelscan/getSetup.json");
+
+	static cxxtools::Regex doCmdRegex("/wirbelscan/doCommand");
+	static cxxtools::Regex setSetupRegex("/wirbelscan/setSetup");
+
+	if (isGet && countriesRegex.match(request.url())) {
+		replyCountries(out, request, reply);
+	} else if (isGet && satellitesRegex.match(request.url())) {
+		replySatellites(out, request, reply);
+	} else if (isGet && getStatusRegex.match(request.url())) {
+		replyGetStatus(out, request, reply);
+	} else if (isPost && doCmdRegex.match(request.url())) {
+		replyDoCmd(out, request, reply);
+	} else if (isGet && getSetupRegex.match(request.url())) {
+		replyGetSetup(out, request, reply);
+	} else if (isPut && setSetupRegex.match(request.url())) {
+		replySetSetup(out, request, reply);
+	} else {
+		replyGetStatus(out, request, reply);
+	}
+}
+
+void WirbelscanResponder::replyCountries(ostream& out,
+		cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+
+	QueryHandler q("/countries", request);
+
+	CountryList* countryList;
+
+	reply.addHeader("Content-Type", "application/json; charset=utf-8");
+	countryList = (CountryList*) new JsonCountryList(&out);
+
+	countryList->init();
+
+	std::stringstream cmd;
+	cmd << SPlugin << "Get" << SCountry;
+
+	cPreAllocBuffer countryBuffer;
+	countryBuffer.size = 0;
+	countryBuffer.count = 0;
+	countryBuffer.buffer = NULL;
+
+	wirbelscan->Service(cmd.str().c_str(), &countryBuffer); // query buffer size.
+
+	SListItem *cbuf = (SListItem *) malloc(
+			countryBuffer.size * sizeof(SListItem)); // now, allocate memory.
+	countryBuffer.buffer = cbuf; // assign buffer
+	wirbelscan->Service(cmd.str().c_str(), &countryBuffer); // fill buffer with values.
+
+	for (unsigned int i = 0; i < countryBuffer.count; ++i)
+	{
+		countryList->addCountry(cbuf++);
+	}
+
+	countryList->setTotal(countryBuffer.count);
+	countryList->finish();
+	delete countryList;
+}
+
+void WirbelscanResponder::replyGetStatus(ostream& out,
+		cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+    QueryHandler::addHeader(reply);
+	QueryHandler q("/getStatus", request);
+
+	std::stringstream cmd;
+	cmd << SPlugin << "Get" << SStatus;
+
+	cWirbelscanStatus statusBuffer;
+
+	wirbelscan->Service(cmd.str().c_str(), &statusBuffer); // query buffer size.
+
+	StreamExtension se(&out);
+
+	if (q.isFormat(".json"))
+	{
+		reply.addHeader("Content-Type", "application/json; charset=utf-8");
+
+		cxxtools::JsonSerializer serializer(*se.getBasicStream());
+
+		serializer.serialize((int)statusBuffer.status, "status");
+		if (statusBuffer.status == StatusScanning)
+		{
+			serializer.serialize(statusBuffer.curr_device, "currentDevice");
+			serializer.serialize((int) statusBuffer.progress, "progress");
+			serializer.serialize((int) statusBuffer.strength, "strength");
+			serializer.serialize(statusBuffer.transponder, "transponder");
+			serializer.serialize((int) statusBuffer.numChannels, "numChannels");
+			serializer.serialize((int) statusBuffer.newChannels, "newChannels");
+			serializer.serialize((int) statusBuffer.nextTransponders,
+					"nextTransponder");
+		}
+		serializer.finish();
+	}
+	else
+	{
+		reply.httpReturn(403,
+				"Resources are not available for the selected format. (Use: .json)");
+		return;
+	}
+}
+
+void WirbelscanResponder::replyGetSetup(ostream& out,
+		cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+    QueryHandler::addHeader(reply);
+	QueryHandler q("/getSetup", request);
+
+	std::stringstream cmd;
+	cmd << SPlugin << "Get" << SSetup;
+
+	cWirbelscanScanSetup setupBuffer;
+
+	wirbelscan->Service(cmd.str().c_str(), &setupBuffer); // query buffer size.
+
+	StreamExtension se(&out);
+
+	if (q.isFormat(".json"))
+	{
+		reply.addHeader("Content-Type", "application/json; charset=utf-8");
+
+		cxxtools::JsonSerializer serializer(*se.getBasicStream());
+
+		serializer.serialize((int) setupBuffer.verbosity, "verbosity");
+		serializer.serialize((int) setupBuffer.logFile, "logFile");
+		serializer.serialize((int) setupBuffer.DVB_Type, "DVB_Type");
+		serializer.serialize((int) setupBuffer.DVBT_Inversion,
+				"DVBT_Inversion");
+		serializer.serialize((int) setupBuffer.DVBC_Inversion,
+				"DVBC_Inversion");
+		serializer.serialize((int) setupBuffer.DVBC_Symbolrate,
+				"DVBC_Symbolrate");
+		serializer.serialize((int) setupBuffer.DVBC_QAM, "DVBC_QAM");
+		serializer.serialize((int) setupBuffer.CountryId, "CountryId");
+		serializer.serialize((int) setupBuffer.SatId, "SatId");
+		serializer.serialize((int) setupBuffer.scanflags, "scanflags");
+		serializer.serialize((int) setupBuffer.ATSC_type, "ATSC_type");
+
+		serializer.finish();
+	}
+	else
+	{
+		reply.httpReturn(403,
+				"Resources are not available for the selected format. (Use: .json)");
+		return;
+	}
+}
+
+void WirbelscanResponder::replySetSetup(ostream& out,
+		cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+	QueryHandler q("/setSetup", request);
+
+	std::stringstream getcmd;
+	getcmd << SPlugin << "Get" << SSetup;
+
+	cWirbelscanScanSetup setupBuffer;
+
+	wirbelscan->Service(getcmd.str().c_str(), &setupBuffer); // query buffer size.
+
+	if (q.hasBody("verbosity"))
+	{
+		setupBuffer.verbosity = q.getBodyAsInt("verbosity");
+	}
+
+	if (q.hasBody("logFile"))
+	{
+		setupBuffer.logFile = q.getBodyAsInt("logFile");
+	}
+
+	if (q.hasBody("DVB_Type"))
+	{
+		setupBuffer.DVB_Type = q.getBodyAsInt("DVB_Type");
+	}
+
+	if (q.hasBody("DVBT_Inversion"))
+	{
+		setupBuffer.DVBT_Inversion = q.getBodyAsInt("DVBT_Inversion");
+	}
+
+	if (q.hasBody("DVBC_Inversion"))
+	{
+		setupBuffer.DVBC_Inversion = q.getBodyAsInt("DVBC_Inversion");
+	}
+
+	if (q.hasBody("DVBC_Symbolrate"))
+	{
+		setupBuffer.DVBC_Symbolrate = q.getBodyAsInt("DVBC_Symbolrate");
+	}
+
+	if (q.hasBody("DVBC_QAM"))
+	{
+		setupBuffer.DVBC_QAM = q.getBodyAsInt("DVBC_QAM");
+	}
+
+	if (q.hasBody("CountryId"))
+	{
+		setupBuffer.CountryId = q.getBodyAsInt("CountryId");
+	}
+
+	if (q.hasBody("SatId"))
+	{
+		setupBuffer.SatId = q.getBodyAsInt("SatId");
+	}
+
+	if (q.hasBody("scanflags"))
+	{
+		setupBuffer.scanflags = q.getBodyAsInt("scanflags");
+	}
+
+	if (q.hasBody("ATSC_type"))
+	{
+		setupBuffer.ATSC_type = q.getBodyAsInt("ATSC_type");
+	}
+
+	std::stringstream setcmd;
+	setcmd << SPlugin << "Set" << SSetup;
+	wirbelscan->Service(setcmd.str().c_str(), &setupBuffer); // query buffer size.
+
+	if (q.isFormat(".json"))
+	{
+		replyGetSetup(out, request, reply);
+	}
+	else
+	{
+		reply.httpReturn(403,
+				"Resources are not available for the selected format. (Use: .json)");
+		return;
+	}
+}
+
+void WirbelscanResponder::replyDoCmd(ostream& out, cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+    QueryHandler::addHeader(reply);
+	QueryHandler q("/doCommand", request);
+
+	if ( !q.hasBody("command") || q.getBodyAsInt("command") < 0 || q.getBodyAsInt("command") > 2) {
+	    reply.httpReturn(400, "command body parameter is missing!");
+	    return;
+	}
+
+	cWirbelscanCmd commandBuffer;
+
+	commandBuffer.cmd = (s_cmd)q.getBodyAsInt("command");
+
+	std::stringstream cmd;
+	cmd << SPlugin << SCommand;
+
+	wirbelscan->Service(cmd.str().c_str(), &commandBuffer); // query buffer size.
+
+	StreamExtension se(&out);
+
+	if (q.isFormat(".json"))
+	{
+		reply.addHeader("Content-Type", "application/json; charset=utf-8");
+
+		cxxtools::JsonSerializer serializer(*se.getBasicStream());
+
+		serializer.serialize(commandBuffer.replycode, "replycode");
+		serializer.finish();
+	}
+	else
+	{
+		reply.httpReturn(403,
+				"Resources are not available for the selected format. (Use: .json)");
+		return;
+	}
+}
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerListItem& l)
+{
+  si.addMember("id") <<= l.id;
+  si.addMember("shortName") <<= l.shortName;
+  si.addMember("fullName") <<= l.fullName;
+}
+
+CountryList::CountryList(ostream* _out)
+{
+  s = new StreamExtension(_out);
+}
+
+CountryList::~CountryList()
+{
+  delete s;
+}
+
+void JsonCountryList::addCountry(SListItem* country)
+{
+  SerListItem serCountry;
+
+  serCountry.id = country->id;
+  serCountry.shortName = StringExtension::UTF8Decode(country->short_name);
+  serCountry.fullName = StringExtension::UTF8Decode(country->full_name);
+
+  SerCountries.push_back(serCountry);
+}
+
+void JsonCountryList::finish()
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(SerCountries, "countries");
+  serializer.serialize(Count(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
+
+void WirbelscanResponder::replySatellites(ostream& out,
+		cxxtools::http::Request& request, cxxtools::http::Reply& reply)
+{
+
+	QueryHandler q("/satellites", request);
+
+	SatelliteList* satelliteList;
+
+	reply.addHeader("Content-Type", "application/json; charset=utf-8");
+	satelliteList = (SatelliteList*) new JsonSatelliteList(&out);
+
+	satelliteList->init();
+
+	std::stringstream cmd;
+	cmd << SPlugin << "Get" << SSat;
+
+	cPreAllocBuffer satelliteBuffer;
+	satelliteBuffer.size = 0;
+	satelliteBuffer.count = 0;
+	satelliteBuffer.buffer = NULL;
+
+	wirbelscan->Service(cmd.str().c_str(), &satelliteBuffer); // query buffer size.
+
+	SListItem *cbuf = (SListItem *) malloc(
+			satelliteBuffer.size * sizeof(SListItem)); // now, allocate memory.
+	satelliteBuffer.buffer = cbuf; // assign buffer
+	wirbelscan->Service(cmd.str().c_str(), &satelliteBuffer); // fill buffer with values.
+
+	for (unsigned int i = 0; i < satelliteBuffer.count; ++i)
+	{
+		satelliteList->addSatellite(cbuf++);
+	}
+
+	satelliteList->setTotal(satelliteBuffer.count);
+	satelliteList->finish();
+	delete satelliteList;
+}
+
+SatelliteList::SatelliteList(ostream* _out)
+{
+  s = new StreamExtension(_out);
+}
+
+SatelliteList::~SatelliteList()
+{
+  delete s;
+}
+
+void JsonSatelliteList::addSatellite(SListItem* satellite)
+{
+  SerListItem serSatellite;
+
+  serSatellite.id = satellite->id;
+  serSatellite.shortName = StringExtension::UTF8Decode(satellite->short_name);
+  serSatellite.fullName = StringExtension::UTF8Decode(satellite->full_name);
+
+  SerSatellites.push_back(serSatellite);
+}
+
+void JsonSatelliteList::finish()
+{
+  cxxtools::JsonSerializer serializer(*s->getBasicStream());
+  serializer.serialize(SerSatellites, "satellites");
+  serializer.serialize(Count(), "count");
+  serializer.serialize(total, "total");
+  serializer.finish();
+}
diff --git a/wirbelscan.h b/wirbelscan.h
new file mode 100644
index 0000000..44eb682
--- /dev/null
+++ b/wirbelscan.h
@@ -0,0 +1,108 @@
+#include <vector>
+#include <ostream>
+
+#include <cxxtools/http/request.h>
+#include <cxxtools/http/reply.h>
+#include <cxxtools/http/responder.h>
+#include <cxxtools/jsonserializer.h>
+#include <cxxtools/serializationinfo.h>
+#include <vdr/osdbase.h>
+#include <vdr/plugin.h>
+#include "tools.h"
+
+#include "wirbelscan/wirbelscan_services.h"
+
+using namespace WIRBELSCAN_SERVICE;
+
+class WirbelscanResponder: public cxxtools::http::Responder {
+public:
+	explicit WirbelscanResponder(cxxtools::http::Service& service) :
+			cxxtools::http::Responder(service) {};
+
+	virtual ~WirbelscanResponder() {};
+
+	virtual void reply(std::ostream& out, cxxtools::http::Request& request,
+			cxxtools::http::Reply& reply);
+
+	virtual void replyCountries(std::ostream& out, cxxtools::http::Request& request,
+			cxxtools::http::Reply& reply);
+
+	virtual void replySatellites(std::ostream& out, cxxtools::http::Request& request,
+			cxxtools::http::Reply& reply);
+
+	virtual void replyGetStatus(std::ostream& out, cxxtools::http::Request& request,
+			cxxtools::http::Reply& reply);
+
+	virtual void replyGetSetup(std::ostream& out, cxxtools::http::Request& request,
+			cxxtools::http::Reply& reply);
+
+	virtual void replySetSetup(std::ostream& out, cxxtools::http::Request& request,
+			cxxtools::http::Reply& reply);
+
+	virtual void replyDoCmd(std::ostream& out, cxxtools::http::Request& request,
+			cxxtools::http::Reply& reply);
+
+private:
+	cPlugin *wirbelscan;
+};
+
+typedef cxxtools::http::CachedService<WirbelscanResponder> WirbelscanService;
+
+struct SerListItem
+{
+  int id;
+  cxxtools::String shortName;
+  cxxtools::String fullName;
+};
+
+void operator<<= (cxxtools::SerializationInfo& si, const SerListItem& l);
+
+class CountryList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+  public:
+    explicit CountryList(std::ostream* _out);
+    virtual ~CountryList();
+    virtual void init() { };
+    virtual void addCountry(SListItem* country) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; }
+};
+
+class JsonCountryList : CountryList
+{
+  private:
+    std::vector < struct SerListItem > SerCountries;
+  public:
+    explicit JsonCountryList(std::ostream* _out) : CountryList(_out) { };
+    ~JsonCountryList() { };
+    virtual void addCountry(SListItem* country);
+    virtual void finish();
+};
+
+class SatelliteList : public BaseList
+{
+  protected:
+    StreamExtension *s;
+    int total;
+  public:
+    explicit SatelliteList(std::ostream* _out);
+    virtual ~SatelliteList();
+    virtual void init() { };
+    virtual void addSatellite(SListItem* satellite) { };
+    virtual void finish() { };
+    virtual void setTotal(int _total) { total = _total; }
+};
+
+class JsonSatelliteList : SatelliteList
+{
+  private:
+    std::vector < struct SerListItem > SerSatellites;
+  public:
+    explicit JsonSatelliteList(std::ostream* _out) : SatelliteList(_out) { };
+    ~JsonSatelliteList() { };
+    virtual void addSatellite(SListItem* satellite);
+    virtual void finish();
+};
diff --git a/wirbelscan/wirbelscan_services.h b/wirbelscan/wirbelscan_services.h
new file mode 100644
index 0000000..9078d73
--- /dev/null
+++ b/wirbelscan/wirbelscan_services.h
@@ -0,0 +1,266 @@
+/*
+ * wirbelscan_services.h
+ *
+ * Copyright (C) 2010 Winfried Koehler 
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * The author can be reached at: handygewinnspiel AT gmx DOT de
+ *
+ * The project's page is http://wirbel.htpc-forum.de/wirbelscan/index2.html
+ */
+
+#ifndef __WIRBELSCAN_SERVICES_H__
+#define __WIRBELSCAN_SERVICES_H__
+
+
+/********************************************************************
+ *
+ * wirbelscans plugin service interface
+ *
+ * see http://wirbel.htpc-forum.de/wirbelscan/vdr-servdemo-0.0.1.tgz
+ * for example on usage.
+ *
+ *******************************************************************/
+
+namespace WIRBELSCAN_SERVICE { 
+/* begin of namespace. to use this header file:
+ * #include "../wirbelscan/wirbelscan_services.h"
+ * using namespace WIRBELSCAN_SERVICE;
+ */
+
+/* --- service(s) version ----------------------------------------------------
+ */
+
+#define SPlugin  "wirbelscan_"     // prefix
+#define SInfo    "GetVersion"      // plugin version and service api
+#define SCommand "DoCmd#0001"      // command api 0001
+#define SStatus  "Status#0002"     // query status
+#define SSetup   "Setup#0001"      // get/set setup, GetSetup#XXXX/SetSetup#XXXX
+#define SCountry "Country#0001"    // get list of country IDs and Names
+#define SSat     "Sat#0001"        // get list of satellite IDs and Names
+#define SUser    "User#0002"       // get/set single user transponder, GetUser#XXXX/SetUser#XXXX
+
+/* --- wirbelscan_GetVersion -------------------------------------------------
+ * Query wirbelscans versions, will fail only if plugin version doesnt support service at all.
+ */
+
+typedef struct {
+  const char * PluginVersion;                    // plugin version
+  const char * CommandVersion;                   // commands service version
+  const char * StatusVersion;                    // status service version
+  const char * SetupVersion;                     // get/put setup service version
+  const char * CountryVersion;                   // country ID list version
+  const char * SatVersion;                       // satellite ID list version
+  const char * UserVersion;                      // user transponder api version, 0.0.5-pre12b or higher.
+  const char * reserved2;                        // reserved, dont use.
+  const char * reserved3;                        // reserved, dont use.
+} cWirbelscanInfo;
+
+/* --- wirbelscan_DoCmd ------------------------------------------------------
+ * Execute commands.
+ */
+
+typedef enum {
+  CmdStartScan = 0,                              // start scanning
+  CmdStopScan  = 1,                              // stop scanning
+  CmdStore     = 2,                              // store current setup
+} s_cmd;
+
+typedef struct {
+  s_cmd cmd;                                     // see above.
+  bool replycode;                                // false, if unsuccessful.
+} cWirbelscanCmd;
+
+/* --- wirbelscan_Status -----------------------------------------------------
+ * Query Status. Use this to build up your osd information displayed to user.
+ */
+
+typedef enum {
+  StatusUnknown  = 0,                            // no status information available, try again later.
+  StatusScanning = 1,                            // scan in progress.
+  StatusStopped  = 2,                            // no scan in progress (not started, finished or stopped).
+  StatusBusy     = 3,                            // plugin is busy, try again later.
+} cStatus;
+
+typedef struct {
+  cStatus status;                                // see above.
+  char curr_device[256];                         // name of current device. meaningless, if (status != StatusScanning)
+  uint16_t progress;                             // progress by means of "percent of predefined transponders". NOTE: will differ in terms of time. meaningless, if (status != StatusScanning)
+  uint16_t strength;                             // current signal strength as reported from device. NOTE: updated only after switching to new transponder. meaningless, if (status != StatusScanning)
+  char transponder[256];                         // current transponder. meaningless, if (status != StatusScanning)
+  uint16_t numChannels;                          // current number of (all) channels, including those which are new. meaningless, if (status != StatusScanning)
+  uint16_t newChannels;                          // number of channels found during this scan. meaningless, if (status != StatusScanning)
+  uint16_t nextTransponders;                     // number of transponders still to be scanned from NIT on this transponder. meaningless, if (status != StatusScanning)
+  uint16_t reserved2;                            // reserved, dont use.
+  uint16_t reserved3;                            // reserved, dont use.
+} cWirbelscanStatus;
+
+/* --- wirbelscan_GetSetup, wirbelscan_SetSetup ------------------------------
+ * Get/Set Setup. Use this to build up your setup osd displayed to user.
+ */
+
+typedef struct {
+  uint16_t  verbosity;                           // 0 (errors only) .. 5 (extended debug); default = 3 (messages)
+  uint16_t  logFile;                             // 0 = off, 1 = stdout, 2 = syslog
+  uint16_t DVB_Type;                             // DVB-T = 0, DVB-C = 1, DVB-S/S2 = 2, PVRINPUT = 3, PVRINPUT(FM Radio) = 4, ATSC = 5, TRANSPONDER = 999
+  uint16_t DVBT_Inversion;                       // AUTO/OFF = 0, AUTO/ON = 1
+  uint16_t DVBC_Inversion;                       // AUTO/OFF = 0, AUTO/ON = 1
+  uint16_t DVBC_Symbolrate;                      // careful here - may change. AUTO = 0, 6900 = 1, 6875 = 2  (...)  14 = 5483, 15 = ALL
+  uint16_t DVBC_QAM;                             // AUTO = 0,QAM64 = 1, QAM128 = 2, QAM256 = 3, ALL = 4
+  uint16_t CountryId;                            // the id according to country, found in country list,   see wirbelscan_GetCountry
+  uint16_t SatId;                                // the id according to satellite, found in list,         see wirbelscan_GetSat
+  uint32_t scanflags;                            // bitwise flag of wanted channels: TV = (1 << 0), RADIO = (1 << 1), FTA = (1 << 2), SCRAMBLED = (1 << 4), HDTV = (1 << 5)
+  uint16_t ATSC_type;                            // VSB = 0, QAM = 1, both VSB+QAM = 2
+  uint16_t stuffing[6];                          // dont use.
+} cWirbelscanScanSetup;
+
+/* --- wirbelscan_GetCountry, wirbelscan_GetSat ------------------------------
+ * Use this to build up your setup OSD - user needs to choose satellite and
+ * country by name and assign correct IDs to setup, see
+ *    * cWirbelscanScanSetup::CountryId
+ *    * cWirbelscanScanSetup::SatId.
+ *
+ * 1) Query needed buffer size with cPreAllocBuffer::size == 0
+ * 2) allocate memory in your plugin, to be at least: cPreAllocBuffer::size * sizeof(SListItem)
+ * 3) second call fills buffer with count * sizeof(SListItem) bytes.
+ * 4) access to items as SListItem*
+ */
+
+typedef struct {
+  int id;
+  char short_name[8];
+  char full_name[64];
+} SListItem;
+
+typedef struct {
+  uint32_t size;
+  uint32_t count;
+  SListItem * buffer;
+} cPreAllocBuffer;
+
+/* --- wirbelscan_GetUser, wirbelscan_SetUser --------------------------------
+ * Scan a user defined Transponder. Service() expects a pointer to uint32_t Data[3];
+ * Data should be initialized and read using class cUserTransponder.
+ *
+ * ---------------------------------------------
+ * id            : 9  country-id/sat-id
+ * frequency     : 21 DVB-T: 177500..858000; ATSC/DVB-C: 73000..858000, DVB-S/S2: 2000..15000
+ * polarisation  : 2  0=h, 1=v, 2=l, 3=r
+ * type          : 3  DVB_Type
+ * symbolrate    : 17 i.e. 27500, 6900
+ * fec_hi        : 4  DVB-S/S2: 1=1/2, 2=2/3, 3=3/4, 4=4/5, 5=5/6, 6=6/7, 7=7/8, 8=8/9, 9=forbidden, 10=3/5, 11=9/10
+ *                    DVB-T     1=1/2, 2=2/3, 3=3/4, 4=forbidden, 5=5/6, 6=forbidden, 7=7/8, 8..15=forbidden
+ * fec_lo        : 4  DVB-T     1=1/2, 2=2/3, 3=3/4, 4=forbidden, 5=5/6, 6=forbidden, 7=7/8, 8..15=forbidden
+ * modulation    : 4  DVB-C: 0=forbidden, 1=QAM16, 2=QAM32, 3=QAM64, 4=QAM128, 5=QAM256, 6..15: forbidden
+ *                    DVB-T: 0=QPSK, 1=QAM16, 2=forbidden, 3=QAM64, 4..15: forbidden
+ *                    DVB-S: 0=QPSK, 1=QAM16, 2..8: forbidden, 9=PSK8, 10..15:forbidden
+ *                    ATSC:  0..2: forbidden, 3=QAM64, 4=forbidden, 5=QAM256, 6=QAM_AUTO, 7=VSB8, 8=VSB16, 9..15=forbidden                         
+ * orbit         : 12 i.e. 192 for S19E2
+ * we_flag       : 1  0=west, 1=east
+ * rolloff       : 2  0=0,35, 1=0,25, 2=0,20
+ * 2nd_gen_sys   : 1  0=DVB-S/T, 1=DVB-S2/T2
+ * bw            : 2  0=8MHz, 1=7MHz, 2=6MHz, 3=5MHz
+ * priority      : 1  0=LP, 1=HP
+ * hierarchy     : 4  0=OFF, 1=alpha1, 2=alpha2, 3=alpha4
+ * guardinterval : 2  0=1/32, 1=1/16, 2=1/8, 3=1/4
+ * transmission  : 2  0=2k, 1=8k, 2=4k
+ * inversion     : 1  0=OFF, 1=ON
+ * use_nit       : 1  0=OFF, 1=ON
+ * reserved      : 3  always 0
+ * ---------------------------------------------
+ */
+
+#if ! defined(uint32_t) || ! defined(uint8_t)
+#include <stdint.h>
+#endif
+
+#define P(v,b,p) ((v & ((1 << b) -1)) << p)
+#define G(v,b,p) ((v >> p) & ((1 << b) -1))
+
+class cUserTransponder {
+  private:
+  uint32_t data[3];
+  public:
+  ~cUserTransponder() { };
+  cUserTransponder(uint32_t * Data) {
+     data[0] = *(Data + 0);
+     data[1] = *(Data + 1);
+     data[2] = *(Data + 2);
+     };
+
+  // DVB-T 
+  cUserTransponder(uint8_t id, uint32_t frequency, uint8_t modulation, uint8_t fec_hp, uint8_t fec_lp, uint8_t bw,
+                   uint8_t priority, uint8_t hierarchy, uint8_t guard, uint8_t tm, uint8_t inversion, uint8_t use_nit) {
+     data[0] = P(id,9,23) | P(frequency,21,2);
+     data[1] = P(fec_hp,4,8) | P(fec_lp,4,4) | P(modulation,4,0);
+     data[2] = P(bw,2,14) | P(priority,1,13) | P(hierarchy,4,9) | P(guard,2,7) | P(tm,2,5) | P(inversion,1,4) | P(use_nit,1,3);
+     };
+
+  // DVB-C
+  cUserTransponder(uint8_t id, uint32_t frequency, uint32_t symbolrate, uint8_t modulation, uint8_t inversion, uint8_t use_nit) {
+     data[0] = P(id,9,23) | P(frequency,21,2);
+     data[1] = P(1,3,29) | P(symbolrate,17,12) | P(modulation,4,0);
+     data[2] = P(inversion,1,4) | P(use_nit,1,3);
+     };
+
+  // DVB-S 
+  cUserTransponder(uint8_t id, uint8_t system, uint32_t frequency, uint8_t polarisation, uint32_t symbolrate,
+                   uint8_t modulation, uint8_t fec, uint16_t orbit, uint8_t we_flag, uint8_t rolloff, uint8_t use_nit) { 
+     data[0] = P(id,9,23) | P(frequency,21,2) | P(polarisation,2,0);
+     data[1] = P(2,3,29) | P(symbolrate,17,12) | P(fec,4,8) | P(modulation,4,0);
+     data[2] = P(orbit,12,20) | P(we_flag,1,19) | P(rolloff,2,17) | P(system,1,16) | P(use_nit,1,3);
+     };
+
+  // ATSC
+  cUserTransponder(uint8_t id, uint32_t frequency, uint8_t modulation, uint8_t use_nit) {
+     data[0] = P(id,9,23) | P(frequency,21,2);
+     data[1] = P(5,3,29) | P(modulation,4,0);
+     data[2] = P(use_nit,1,3);
+     };
+
+  const uint32_t * Data(void) { return data; };
+  int Id(void)          { return G(data[0],9,23); };
+  int Frequency(void)   { return G(data[0],21,2); };
+  int Polarisation(void){ return G(data[0],2,0); };
+  int Type(void)        { return G(data[1],3,29); };
+  int Symbolrate(void)  { return G(data[1],17,12); };
+  int FecHP(void)       { return G(data[1],4,8); };
+  int FecLP(void)       { return G(data[1],4,4); };
+  int Modulation(void)  { return G(data[1],4,0); };
+  int Orbit(void)       { return G(data[2],12,20); };
+  int EastFlag(void)    { return G(data[2],1,19); };
+  int Rolloff(void)     { return G(data[2],2,17); };
+  int Satsystem(void)   { return G(data[2],1,16) + 5; };                      /*deprecated. use System() instead.*/
+  int System(void)      { return G(data[2],1,16) + 5; };
+  int Bandwidth(void)   { return (8 - G(data[2],2,14)) * (int) 1E6; };
+  int Priority(void)    { return G(data[2],1,13); };
+  int Hierarchy(void)   { return G(data[2],4,9); };
+  int Guard(void)       { return G(data[2],2,7); };
+  int Transmission(void){ return G(data[2],2,5); };
+  int Inversion(void)   { return G(data[2],1,4); };
+  int UseNit(void)      { return G(data[2],1,3); };
+  bool IsTerr(void)     { return IsType(0); };
+  bool IsCable(void)    { return IsType(1); };
+  bool IsSat(void)      { return IsType(2); };
+  bool IsAtsc(void)     { return IsType(5); };
+  bool IsType(int type) { return type == Type(); };
+ 
+};
+
+
+} /* end of namespace, dont touch */
+#endif

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-vdr-dvb/vdr-plugin-restfulapi.git



More information about the pkg-vdr-dvb-changes mailing list