diff --git a/.gitignore b/.gitignore
index 881c850920071df24204b18dd7f56ec5aba4aaa8..42b5ed4678d9b417b6a0dd56dd08022cac58f695 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,12 @@ autoProcessTV/autoProcessTV.cfg
 server.crt
 server.key
 
+#  SB Test Related   #
+######################
+tests/Logs/*
+tests/sickbeard.*
+tests/cache.db
+
 #  Compiled source   #
 ######################
 *.py[co]
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..11261769f507057b9a4d6c47ab7ed98a4f99c63c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: python
+python:
+  - 2.5
+  - 2.6
+  - 2.7
+
+# whitelist
+branches:
+  only:
+    - development
+    - unittest
+
+before_script: cd ./tests
+script: ./all_tests.py
\ No newline at end of file
diff --git a/autoProcessTV/autoProcessTV.py b/autoProcessTV/autoProcessTV.py
index 413c93ff1b4bf4b29686d4275299eb727be2990d..09532de6d988a4dd638cb0b48f8ec834e34899d9 100644
--- a/autoProcessTV/autoProcessTV.py
+++ b/autoProcessTV/autoProcessTV.py
@@ -90,7 +90,7 @@ def processEpisode(dirName, nzbName=None):
         urlObj = myOpener.openit(url)
     except IOError, e:
         print "Unable to open URL: ", str(e)
-        sys.exit()
+        sys.exit(1)
     
     result = urlObj.readlines()
     for line in result:
diff --git a/data/css/default.css b/data/css/default.css
index 1aa00054414543fd98ea314e819bbd2075059a76..e87bf2429d7cf453a401c63c6f83ad1560b47ab8 100644
--- a/data/css/default.css
+++ b/data/css/default.css
@@ -125,23 +125,34 @@ font-size: 1em;
 }
 
 .sickbeardTable {
-width:100%;
-margin-left:auto;
-margin-right:auto;
+    width: 100%;
+    margin-left:auto;
+    margin-right:auto;
+    text-align:left;
+    color: #000;
+    background-color: #fff;
+    border-spacing: 0;
+}
+.sickbeardTable th,
+.sickbeardTable td {
+    padding: 4px;
+    border-left: #fff 1px solid;
+    border-top: #fff 1px solid;
+}
+.sickbeardTable th:first-child,
+.sickbeardTable td:first-child {
+    border-left: none;
 }
 .sickbeardTable th{
-padding:3px;
-font-weight:700;
-background-color:#333;
-color:#FFF;
-text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
-}
-.sickbeardTable td{
-padding:4px;
+    border-collapse: collapse;
+    background-color: #333;
+    color: #fff;
+    text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
+    text-align: center;
 }
 .sickbeardTable tfoot a {
-color:#FFF;
-text-decoration: none;
+    color:#fff;
+    text-decoration: none;
 }
 
 .row {
diff --git a/data/css/tablesorter.css b/data/css/tablesorter.css
index 50ff913c13942e6a02761f6d4dd6bec2267151dc..d8b64ed69e8049a5b7f3392726f589310b4dbca1 100644
--- a/data/css/tablesorter.css
+++ b/data/css/tablesorter.css
@@ -1,71 +1,83 @@
-/* tables */
+/* SB Theme */
 table.tablesorter {
     width: 100%;
     margin-left:auto;
     margin-right:auto;
     text-align:left;
+    color: #000;
+    background-color: #fff;
+    border-spacing: 0;
+}
+table.tablesorter th,
+table.tablesorter td {
+    padding: 4px;
+    border-left: #fff 1px solid;
+    border-top: #fff 1px solid;
+}
+/* remove extra border from left edge */
+table.tablesorter th:first-child,
+table.tablesorter td:first-child {
+    border-left: none;
 }
 table.tablesorter th {
-    border: 1px solid #241109;
-    padding: 3px;
     border-collapse: collapse;
     background-color: #333;
     color: #fff;
     text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
-    text-align:center;
+    text-align: center;
 }
-table.tablesorter .header {
-/*  background-image: url(../images/tablesorter/bg.gif); */
+table.tablesorter .tablesorter-header {
+/*    background-image: url(../images/tablesorter/bg.gif); */
     background-repeat: no-repeat;
     background-position: center right;
+    padding: 4px 18px 4px 4px;
     cursor: pointer;
-    padding-right: 18px;
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-table.tablesorter td {
-    color: #000;
-    padding: 4px;
-}
-table.sickbeardTable tfoot a {
-    color:#fff;
-    text-decoration: none;
-}
-/*
-table.tablesorter thead tr .headerSortUp {
-    background-image: url(../images/tablesorter/asc.gif);
 }
-table.tablesorter thead tr .headerSortDown {
-    background-image: url(../images/tablesorter/desc.gif);
-}
-*/
-table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
+table.tablesorter th.tablesorter-headerSortUp {
     background-color: #57442B;
+/*    background-image: url(../images/tablesorter/asc.gif); */
 }
-/* stickyheader widget */
-tr.tablesorter-stickyheader {
-    background-color: #fff;
-    padding: 2px 0;
+table.tablesorter th.tablesorter-headerSortDown {
+    background-color: #57442B;
+/*    background-image: url(../images/tablesorter/desc.gif); */
 }
+
 /* Zebra Widget - row alternating colors */
-tr.even {
-    background-color:#dfdacf;
+table.tablesorter tr.odd td {
+    background-color: #f5f1e4;
 }
-tr.odd {
-    background-color:#f5f1e4;
+table.tablesorter tr.even td {
+    background-color: #dfdacf;
 }
 /* filter widget */
-tr.tablesorter-filter {
-    background-color: #eee;
-    text-align: center;
-}
-input.tablesorter-filter {
-    width: 100%; 
+table.tablesorter input.tablesorter-filter {
+    width: 98%;
     -webkit-box-sizing: border-box;
     -moz-box-sizing: border-box;
     box-sizing: border-box;
-} 
-.tablesorter-filter .disabled {
+}
+table.tablesorter tr.tablesorter-filter,
+table.tablesorter tr.tablesorter-filter td {
+    text-align: center;
+    background: #eee;
+}
+/* optional disabled input styling */ 
+table.tablesorter input.tablesorter-filter.disabled {
     display: none;
 }
+
+
+/* xtra css for sb */
+.tablesorter-header-inner {
+    text-align: center;
+    padding: 0 2px;
+}
+tr.tablesorter-stickyHeader {
+    background-color: #fff;
+    padding: 2px 0;
+}
+
+table.tablesorter tfoot a {
+    color:#fff;
+    text-decoration: none;
+}
diff --git a/data/images/notifiers/pushover.gif b/data/images/notifiers/pushover.gif
new file mode 100644
index 0000000000000000000000000000000000000000..ff4de49e067d56707cb7ee810053c94d5679af31
Binary files /dev/null and b/data/images/notifiers/pushover.gif differ
diff --git a/data/interfaces/default/apiBuilder.tmpl b/data/interfaces/default/apiBuilder.tmpl
index 829b77d710892918f0e009dcb253d87e39353c9a..b948b0097d5435014264c0d1ef0ddc8f703864e6 100644
--- a/data/interfaces/default/apiBuilder.tmpl
+++ b/data/interfaces/default/apiBuilder.tmpl
@@ -70,265 +70,265 @@ addOption("Command", "Shows.Stats", "?cmd=shows.stats", "", "", "action");
 
 // addOption("tvdbid", "Optional Param", "", 1);
 #for $curShow in $sortedShowList:
-addOption("tvdbid", "$curShow.name", "&tvdbid=$curShow.tvdbid");
+addOption("tvdbid", "$curShow.name", "&tvdbid=$curShow.tvdbid");
 #end for
 
 addOption("logs", "Optional Param", "", 1);
-addOption("logs", "Debug", "&min_level=debug");
-addOption("logs", "Info", "&min_level=info");
-addOption("logs", "Warning", "&min_level=warning");
-addOption("logs", "Error", "&min_level=error");
+addOption("logs", "Debug", "&min_level=debug");
+addOption("logs", "Info", "&min_level=info");
+addOption("logs", "Warning", "&min_level=warning");
+addOption("logs", "Error", "&min_level=error");
 
 addOption("sb.setdefaults", "Optional Param", "", 1);
-addList("sb.setdefaults", "Exclude Paused Shows on ComingEps", "&future_show_paused=0", "sb.setdefaults-status");
-addList("sb.setdefaults", "Include Paused Shows on ComingEps", "&future_show_paused=1", "sb.setdefaults-status");
+addList("sb.setdefaults", "Exclude Paused Shows on ComingEps", "&future_show_paused=0", "sb.setdefaults-status");
+addList("sb.setdefaults", "Include Paused Shows on ComingEps", "&future_show_paused=1", "sb.setdefaults-status");
 
 addOption("sb.setdefaults-status", "Optional Param", "", 1);
-addList("sb.setdefaults-status", "Wanted", "&status=wanted", "sb.setdefaults-opt");
-addList("sb.setdefaults-status", "Skipped", "&status=skipped", "sb.setdefaults-opt");
-addList("sb.setdefaults-status", "Archived", "&status=archived", "sb.setdefaults-opt");
-addList("sb.setdefaults-status", "Ignored", "&status=ignored", "sb.setdefaults-opt");
+addList("sb.setdefaults-status", "Wanted", "&status=wanted", "sb.setdefaults-opt");
+addList("sb.setdefaults-status", "Skipped", "&status=skipped", "sb.setdefaults-opt");
+addList("sb.setdefaults-status", "Archived", "&status=archived", "sb.setdefaults-opt");
+addList("sb.setdefaults-status", "Ignored", "&status=ignored", "sb.setdefaults-opt");
 
 addOption("sb.setdefaults-opt", "Optional Param", "", 1);
-addList("sb.setdefaults-opt", "No Season Folder", "&season_folder=0", "quality");
-addList("sb.setdefaults-opt", "Use Season Folder", "&season_folder=1", "quality");
+addList("sb.setdefaults-opt", "No Season Folder", "&season_folder=0", "quality");
+addList("sb.setdefaults-opt", "Use Season Folder", "&season_folder=1", "quality");
 
 addOption("shows", "Optional Param", "", 1);
-addOption("shows", "Show Only Paused", "&paused=1");
-addOption("shows", "Show Only Not Paused", "&paused=0");
-addOption("shows", "Sort by Show Name", "&sort=name");
-addOption("shows", "Sort by TVDB ID", "&sort=id");
+addOption("shows", "Show Only Paused", "&paused=1");
+addOption("shows", "Show Only Not Paused", "&paused=0");
+addOption("shows", "Sort by Show Name", "&sort=name");
+addOption("shows", "Sort by TVDB ID", "&sort=id");
 
-addList("show.addexisting", "C:\\temp\\show1", "&location=C:\\temp\\show1", "show.addexisting-tvdbid");
-addList("show.addexisting", "D:\\Temp\\show2", "&location=D:\\Temp\\show2", "show.addexisting-tvdbid");
-addList("show.addexisting", "S:\\TV\\Ancient Aliens", "&location=S:\\TV\\Ancient Aliens", "show.addexisting-tvdbid");
-addList("show.addexisting", "S:\\TV\\Chuck", "&location=S:\\TV\\Chuck", "show.addexisting-tvdbid");
+addList("show.addexisting", "C:\\temp\\show1", "&location=C:\\temp\\show1", "show.addexisting-tvdbid");
+addList("show.addexisting", "D:\\Temp\\show2", "&location=D:\\Temp\\show2", "show.addexisting-tvdbid");
+addList("show.addexisting", "S:\\TV\\Ancient Aliens", "&location=S:\\TV\\Ancient Aliens", "show.addexisting-tvdbid");
+addList("show.addexisting", "S:\\TV\\Chuck", "&location=S:\\TV\\Chuck", "show.addexisting-tvdbid");
 
-addList("show.addexisting-tvdbid", "101501 (Ancient Aliens)", "&tvdbid=101501", "show.addexisting-opt");
-addList("show.addexisting-tvdbid", "80348 (Chuck)", "&tvdbid=80348", "show.addexisting-opt");
+addList("show.addexisting-tvdbid", "101501 (Ancient Aliens)", "&tvdbid=101501", "show.addexisting-opt");
+addList("show.addexisting-tvdbid", "80348 (Chuck)", "&tvdbid=80348", "show.addexisting-opt");
 
 addOption("show.addexisting-opt", "Optional Param", "", 1);
-addList("show.addexisting-opt", "No Season Folder", "&season_folder=0", "quality");
-addList("show.addexisting-opt", "Use Season Folder", "&season_folder=1", "quality");
+addList("show.addexisting-opt", "No Season Folder", "&season_folder=0", "quality");
+addList("show.addexisting-opt", "Use Season Folder", "&season_folder=1", "quality");
 
-addList("show.addnew", "101501 (Ancient Aliens)", "&tvdbid=101501", "show.addnew-loc");
-addList("show.addnew", "80348 (Chuck)", "&tvdbid=80348", "show.addnew-loc");
+addList("show.addnew", "101501 (Ancient Aliens)", "&tvdbid=101501", "show.addnew-loc");
+addList("show.addnew", "80348 (Chuck)", "&tvdbid=80348", "show.addnew-loc");
 
 addOption("show.addnew-loc", "Optional Param", "", 1);
-addList("show.addnew-loc", "C:\\Temp", "&location=C:\\temp", "show.addnew-status");
-addList("show.addnew-loc", "D:\\Temp", "&location=D:\\Temp", "show.addnew-status");
-addList("show.addnew-loc", "S:\\TV", "&location=S:\\TV", "show.addnew-status");
-addList("show.addnew-loc", "/usr/bin", "&location=/usr/bin", "show.addnew-status");
+addList("show.addnew-loc", "C:\\Temp", "&location=C:\\temp", "show.addnew-status");
+addList("show.addnew-loc", "D:\\Temp", "&location=D:\\Temp", "show.addnew-status");
+addList("show.addnew-loc", "S:\\TV", "&location=S:\\TV", "show.addnew-status");
+addList("show.addnew-loc", "/usr/bin", "&location=/usr/bin", "show.addnew-status");
 
 addOption("show.addnew-status", "Optional Param", "", 1);
-addList("show.addnew-status", "Wanted", "&status=wanted", "show.addnew-opt");
-addList("show.addnew-status", "Skipped", "&status=skipped", "show.addnew-opt");
-addList("show.addnew-status", "Archived", "&status=archived", "show.addnew-opt");
-addList("show.addnew-status", "Ignored", "&status=ignored", "show.addnew-opt");
+addList("show.addnew-status", "Wanted", "&status=wanted", "show.addnew-opt");
+addList("show.addnew-status", "Skipped", "&status=skipped", "show.addnew-opt");
+addList("show.addnew-status", "Archived", "&status=archived", "show.addnew-opt");
+addList("show.addnew-status", "Ignored", "&status=ignored", "show.addnew-opt");
 
 addOption("show.addnew-opt", "Optional Param", "", 1);
-addList("show.addnew-opt", "No Season Folder", "&season_folder=0", "quality");
-addList("show.addnew-opt", "Use Season Folder", "&season_folder=1", "quality");
+addList("show.addnew-opt", "No Season Folder", "&season_folder=0", "quality");
+addList("show.addnew-opt", "Use Season Folder", "&season_folder=1", "quality");
 
 addOptGroup("sb.searchtvdb", "Search by Name");
-addList("sb.searchtvdb", "Lost", "&name=Lost", "sb.searchtvdb-lang");
-addList("sb.searchtvdb", "office", "&name=office", "sb.searchtvdb-lang");
-addList("sb.searchtvdb", "OffiCE", "&name=OffiCE", "sb.searchtvdb-lang");
-addList("sb.searchtvdb", "Leno", "&name=leno", "sb.searchtvdb-lang");
-addList("sb.searchtvdb", "Top Gear", "&name=Top Gear", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "Lost", "&name=Lost", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "office", "&name=office", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "OffiCE", "&name=OffiCE", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "Leno", "&name=leno", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "Top Gear", "&name=Top Gear", "sb.searchtvdb-lang");
 endOptGroup("sb.searchtvdb");
 addOptGroup("sb.searchtvdb", "Search by TVDBID");
-addList("sb.searchtvdb", "73739", "&tvdbid=73739", "sb.searchtvdb-lang");
-addList("sb.searchtvdb", "74608", "&tvdbid=74608", "sb.searchtvdb-lang");
-addList("sb.searchtvdb", "199051", "&tvdbid=199051", "sb.searchtvdb-lang");
-addList("sb.searchtvdb", "123456 (invalid show)", "&tvdbid=123456", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "73739", "&tvdbid=73739", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "74608", "&tvdbid=74608", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "199051", "&tvdbid=199051", "sb.searchtvdb-lang");
+addList("sb.searchtvdb", "123456 (invalid show)", "&tvdbid=123456", "sb.searchtvdb-lang");
 endOptGroup("sb.searchtvdb");
 
 addOption("sb.searchtvdb-lang", "Optional Param", "", 1);
-addOption("sb.searchtvdb-lang", "Chinese", "&lang=zh");   // 27
-addOption("sb.searchtvdb-lang", "Croatian", "&lang=hr");  // 31
-addOption("sb.searchtvdb-lang", "Czech", "&lang=cs");     // 28
-addOption("sb.searchtvdb-lang", "Danish", "&lang=da");    // 10
-addOption("sb.searchtvdb-lang", "Dutch", "&lang=nl");     // 13
-addOption("sb.searchtvdb-lang", "English", "&lang=en");   // 7
-addOption("sb.searchtvdb-lang", "Finnish", "&lang=fi");   // 11 -- Suomeksi
-addOption("sb.searchtvdb-lang", "French", "&lang=fr");    // 17
-addOption("sb.searchtvdb-lang", "German", "&lang=de");    // 14
-addOption("sb.searchtvdb-lang", "Greek", "&lang=el");     // 20
-addOption("sb.searchtvdb-lang", "Hebrew", "&lang=he");    // 24
-addOption("sb.searchtvdb-lang", "Hungarian", "&lang=hu"); // 19 -- Magyar
-addOption("sb.searchtvdb-lang", "Italian", "&lang=it");   // 15
-addOption("sb.searchtvdb-lang", "Japanese", "&lang=ja");  // 25
-addOption("sb.searchtvdb-lang", "Korean", "&lang=ko");    // 32
-addOption("sb.searchtvdb-lang", "Norwegian", "&lang=no"); // 9
-addOption("sb.searchtvdb-lang", "Polish", "&lang=pl");    // 18
-addOption("sb.searchtvdb-lang", "Portuguese", "&lang=pt");// 26
-addOption("sb.searchtvdb-lang", "Russian", "&lang=ru");   // 22
-addOption("sb.searchtvdb-lang", "Slovenian", "&lang=sl"); // 30
-addOption("sb.searchtvdb-lang", "Spanish", "&lang=es");   // 16
-addOption("sb.searchtvdb-lang", "Swedish", "&lang=sv");   // 8
-addOption("sb.searchtvdb-lang", "Turkish", "&lang=tr");   // 21
+addOption("sb.searchtvdb-lang", "Chinese", "&lang=zh");   // 27
+addOption("sb.searchtvdb-lang", "Croatian", "&lang=hr");  // 31
+addOption("sb.searchtvdb-lang", "Czech", "&lang=cs");     // 28
+addOption("sb.searchtvdb-lang", "Danish", "&lang=da");    // 10
+addOption("sb.searchtvdb-lang", "Dutch", "&lang=nl");     // 13
+addOption("sb.searchtvdb-lang", "English", "&lang=en");   // 7
+addOption("sb.searchtvdb-lang", "Finnish", "&lang=fi");   // 11 -- Suomeksi
+addOption("sb.searchtvdb-lang", "French", "&lang=fr");    // 17
+addOption("sb.searchtvdb-lang", "German", "&lang=de");    // 14
+addOption("sb.searchtvdb-lang", "Greek", "&lang=el");     // 20
+addOption("sb.searchtvdb-lang", "Hebrew", "&lang=he");    // 24
+addOption("sb.searchtvdb-lang", "Hungarian", "&lang=hu"); // 19 -- Magyar
+addOption("sb.searchtvdb-lang", "Italian", "&lang=it");   // 15
+addOption("sb.searchtvdb-lang", "Japanese", "&lang=ja");  // 25
+addOption("sb.searchtvdb-lang", "Korean", "&lang=ko");    // 32
+addOption("sb.searchtvdb-lang", "Norwegian", "&lang=no"); // 9
+addOption("sb.searchtvdb-lang", "Polish", "&lang=pl");    // 18
+addOption("sb.searchtvdb-lang", "Portuguese", "&lang=pt");// 26
+addOption("sb.searchtvdb-lang", "Russian", "&lang=ru");   // 22
+addOption("sb.searchtvdb-lang", "Slovenian", "&lang=sl"); // 30
+addOption("sb.searchtvdb-lang", "Spanish", "&lang=es");   // 16
+addOption("sb.searchtvdb-lang", "Swedish", "&lang=sv");   // 8
+addOption("sb.searchtvdb-lang", "Turkish", "&lang=tr");   // 21
 
 #for $curShow in $sortedShowList:
-addList("seasons", "$curShow.name", "&tvdbid=$curShow.tvdbid", "seasons-$curShow.tvdbid");
+addList("seasons", "$curShow.name", "&tvdbid=$curShow.tvdbid", "seasons-$curShow.tvdbid");
 #end for
 
 #for $curShow in $sortedShowList:
-addList("show.seasonlist", "$curShow.name", "&tvdbid=$curShow.tvdbid", "show.seasonlist-sort");
+addList("show.seasonlist", "$curShow.name", "&tvdbid=$curShow.tvdbid", "show.seasonlist-sort");
 #end for
 
 addOption("show.seasonlist-sort", "Optional Param", "", 1);
-addOption("show.seasonlist-sort", "Sort by Ascending", "&sort=asc");
+addOption("show.seasonlist-sort", "Sort by Ascending", "&sort=asc");
 
 #for $curShow in $sortedShowList:
-addList("show.setquality", "$curShow.name", "&tvdbid=$curShow.tvdbid", "quality");
+addList("show.setquality", "$curShow.name", "&tvdbid=$curShow.tvdbid", "quality");
 #end for
 
 //build out generic quality options
 addOptGroup("quality", "Quality Templates");
-addOption("quality", "SD", "&initial=sdtv|sddvd");
-addOption("quality", "HD", "&initial=hdtv|hdwebdl|hdbluray");
-addOption("quality", "ANY", "&initial=sdtv|sddvd|hdtv|hdwebdl|hdbluray|unknown");
+addOption("quality", "SD", "&initial=sdtv|sddvd");
+addOption("quality", "HD", "&initial=hdtv|hdwebdl|hdbluray");
+addOption("quality", "ANY", "&initial=sdtv|sddvd|hdtv|hdwebdl|hdbluray|unknown");
 endOptGroup("quality");
 addOptGroup("quality", "Inital (Custom)");
-addList("quality", "SD TV", "&initial=sdtv", "quality-archive");
-addList("quality", "SD DVD", "&initial=sddvd", "quality-archive");
-addList("quality", "HD TV", "&initial=hdtv", "quality-archive");
-addList("quality", "720p Web-DL", "&initial=hdwebdl", "quality-archive");
-addList("quality", "720p BluRay", "&initial=hdbluray", "quality-archive");
-addList("quality", "1080p BluRay", "&initial=fullhdbluray", "quality-archive");
-addList("quality", "Unknown", "&initial=unknown", "quality-archive");
+addList("quality", "SD TV", "&initial=sdtv", "quality-archive");
+addList("quality", "SD DVD", "&initial=sddvd", "quality-archive");
+addList("quality", "HD TV", "&initial=hdtv", "quality-archive");
+addList("quality", "720p Web-DL", "&initial=hdwebdl", "quality-archive");
+addList("quality", "720p BluRay", "&initial=hdbluray", "quality-archive");
+addList("quality", "1080p BluRay", "&initial=fullhdbluray", "quality-archive");
+addList("quality", "Unknown", "&initial=unknown", "quality-archive");
 endOptGroup("quality");
 addOptGroup("quality", "Random (Custom)");
-addList("quality", "SD DVD/720p Web-DL", "&initial=sddvd|hdwebdl", "quality-archive");
-addList("quality", "SD TV/HD TV", "&initial=sdtv|hdtv", "quality-archive");
+addList("quality", "SD DVD/720p Web-DL", "&initial=sddvd|hdwebdl", "quality-archive");
+addList("quality", "SD TV/HD TV", "&initial=sdtv|hdtv", "quality-archive");
 endOptGroup("quality");
 
 addOption("quality-archive", "Optional Param", "", 1);
 addOptGroup("quality-archive", "Archive (Custom)");
-addList("quality-archive", "SD DVD", "&archive=sddvd");
-addList("quality-archive", "HD TV", "&archive=hdtv");
-addList("quality-archive", "720p Web-DL", "&archive=hdwebdl");
-addList("quality-archive", "720p BluRay", "&archive=hdbluray");
-addList("quality-archive", "1080p BluRay", "&archive=fullhdbluray");
-addList("quality-archive", "Unknown", "&archive=unknown");
+addList("quality-archive", "SD DVD", "&archive=sddvd");
+addList("quality-archive", "HD TV", "&archive=hdtv");
+addList("quality-archive", "720p Web-DL", "&archive=hdwebdl");
+addList("quality-archive", "720p BluRay", "&archive=hdbluray");
+addList("quality-archive", "1080p BluRay", "&archive=fullhdbluray");
+addList("quality-archive", "Unknown", "&archive=unknown");
 endOptGroup("quality-archive");
 addOptGroup("quality-archive", "Random (Custom)");
-addList("quality-archive", "HD TV/1080p BluRay", "&archive=hdtv|fullhdbluray");
-addList("quality-archive", "720p Web-DL/720p BluRay", "&archive=hdwebdl|hdbluray");
+addList("quality-archive", "HD TV/1080p BluRay", "&archive=hdtv|fullhdbluray");
+addList("quality-archive", "720p Web-DL/720p BluRay", "&archive=hdwebdl|hdbluray");
 endOptGroup("quality-archive");
 
 // build out each show's season list for season cmd
 #for $curShow in $seasonSQLResults:
 addOption("seasons-$curShow", "Optional Param", "", 1);
     #for $curShowSeason in $seasonSQLResults[$curShow]:
-addOption("seasons-$curShow", "$curShowSeason.season", "&season=$curShowSeason.season");
+addOption("seasons-$curShow", "$curShowSeason.season", "&season=$curShowSeason.season");
     #end for
 #end for
 
 #for $curShow in $sortedShowList:
-addList("episode", "$curShow.name", "&tvdbid=$curShow.tvdbid", "episode-$curShow.tvdbid");
+addList("episode", "$curShow.name", "&tvdbid=$curShow.tvdbid", "episode-$curShow.tvdbid");
 #end for
 
 // build out each show's season+episode list for episode cmd
 #for $curShow in $episodeSQLResults:
     #for $curShowSeason in $episodeSQLResults[$curShow]:
-addList("episode-$curShow", "$curShowSeason.season x $curShowSeason.episode", "&season=$curShowSeason.season&episode=$curShowSeason.episode", "episode-$curShow-full");
+addList("episode-$curShow", "$curShowSeason.season x $curShowSeason.episode", "&season=$curShowSeason.season&episode=$curShowSeason.episode", "episode-$curShow-full");
     #end for
 addOption("episode-$curShow-full", "Optional Param", "", 1);
-addOption("episode-$curShow-full", "Show Full Path", "&full_path=1");
+addOption("episode-$curShow-full", "Show Full Path", "&full_path=1");
 #end for
 
 // build out tvshow list for episode.search
 #for $curShow in $sortedShowList:
-addList("episode.search", "$curShow.name", "&tvdbid=$curShow.tvdbid", "episode.search-$curShow.tvdbid");
+addList("episode.search", "$curShow.name", "&tvdbid=$curShow.tvdbid", "episode.search-$curShow.tvdbid");
 #end for
 
 // build out each show's season+episode list for episode.search cmd
 #for $curShow in $episodeSQLResults:
     #for $curShowSeason in $episodeSQLResults[$curShow]:
-addOption("episode.search-$curShow", "$curShowSeason.season x $curShowSeason.episode", "&season=$curShowSeason.season&episode=$curShowSeason.episode");
+addOption("episode.search-$curShow", "$curShowSeason.season x $curShowSeason.episode", "&season=$curShowSeason.season&episode=$curShowSeason.episode");
     #end for
 #end for
 
 // build out tvshow list for episode.setstatus
 #for $curShow in $sortedShowList:
-addList("episode.setstatus", "$curShow.name", "&tvdbid=$curShow.tvdbid", "episode.setstatus-$curShow.tvdbid");
+addList("episode.setstatus", "$curShow.name", "&tvdbid=$curShow.tvdbid", "episode.setstatus-$curShow.tvdbid");
 #end for
 
 // build out each show's season+episode list for episode.setstatus cmd
 #for $curShow in $episodeSQLResults:
     #for $curShowSeason in $episodeSQLResults[$curShow]:
-addList("episode.setstatus-$curShow", "$curShowSeason.season x $curShowSeason.episode", "&season=$curShowSeason.season&episode=$curShowSeason.episode", "episode-status-$curShow");
+addList("episode.setstatus-$curShow", "$curShowSeason.season x $curShowSeason.episode", "&season=$curShowSeason.season&episode=$curShowSeason.episode", "episode-status-$curShow");
     #end for
-addOption("episode-status-$curShow", "Wanted", "&status=wanted");
-addOption("episode-status-$curShow", "Skipped", "&status=skipped");
-addOption("episode-status-$curShow", "Archived", "&status=archived");
-addOption("episode-status-$curShow", "Ignored", "&status=ignored");
+addOption("episode-status-$curShow", "Wanted", "&status=wanted");
+addOption("episode-status-$curShow", "Skipped", "&status=skipped");
+addOption("episode-status-$curShow", "Archived", "&status=archived");
+addOption("episode-status-$curShow", "Ignored", "&status=ignored");
 #end for
 
 addOption("future", "Optional Param", "", 1);
-addList("future", "Sort by Date", "&sort=date", "future-type");
-addList("future", "Sort by Network", "&sort=network", "future-type");
-addList("future", "Sort by Show Name", "&sort=show", "future-type");
+addList("future", "Sort by Date", "&sort=date", "future-type");
+addList("future", "Sort by Network", "&sort=network", "future-type");
+addList("future", "Sort by Show Name", "&sort=show", "future-type");
 
 addOption("future-type", "Optional Param", "", 1);
-addList("future-type", "Show All Types", "&type=today|missed|soon|later", "future-paused");
-addList("future-type", "Show Today", "&type=today", "future-paused");
-addList("future-type", "Show Missed", "&type=missed", "future-paused");
-addList("future-type", "Show Soon", "&type=soon", "future-paused");
-addList("future-type", "Show Later", "&type=later", "future-paused");
-addList("future-type", "Show Today & Missed", "&type=today|missed", "future-paused");
+addList("future-type", "Show All Types", "&type=today|missed|soon|later", "future-paused");
+addList("future-type", "Show Today", "&type=today", "future-paused");
+addList("future-type", "Show Missed", "&type=missed", "future-paused");
+addList("future-type", "Show Soon", "&type=soon", "future-paused");
+addList("future-type", "Show Later", "&type=later", "future-paused");
+addList("future-type", "Show Today & Missed", "&type=today|missed", "future-paused");
 
 addOption("future-paused", "Optional Param", "", 1);
-addOption("future-paused", "Include Paused Shows", "&paused=1");
-addOption("future-paused", "Exclude Paused Shows", "&paused=0");
+addOption("future-paused", "Include Paused Shows", "&paused=1");
+addOption("future-paused", "Exclude Paused Shows", "&paused=0");
 
 addOption("history", "Optional Param", "", 1);
-addList("history", "Show Only Downloaded", "&type=downloaded", "history-type");
-addList("history", "Show Only Snatched", "&type=snatched", "history-type");
+addList("history", "Show Only Downloaded", "&type=downloaded", "history-type");
+addList("history", "Show Only Snatched", "&type=snatched", "history-type");
 //addOptGroup("history", "Limit Results");
-addList("history", "Limit Results (2)", "&limit=2", "history-limit");
-addList("history", "Limit Results (25)", "&limit=25", "history-limit");
-addList("history", "Limit Results (50)", "&limit=50", "history-limit");
+addList("history", "Limit Results (2)", "&limit=2", "history-limit");
+addList("history", "Limit Results (25)", "&limit=25", "history-limit");
+addList("history", "Limit Results (50)", "&limit=50", "history-limit");
 //endOptGroup("history");
 
 addOption("history-type", "Optional Param", "", 1);
-addOption("history-type", "Limit Results (2)", "&limit=2");
-addOption("history-type", "Limit Results (25)", "&limit=25");
-addOption("history-type", "Limit Results (50)", "&limit=50");
+addOption("history-type", "Limit Results (2)", "&limit=2");
+addOption("history-type", "Limit Results (25)", "&limit=25");
+addOption("history-type", "Limit Results (50)", "&limit=50");
 
 addOption("history-limit", "Optional Param", "", 1);
-addOption("history-limit", "Show Only Downloaded", "&type=downloaded");
-addOption("history-limit", "Show Only Snatched", "&type=snatched");
+addOption("history-limit", "Show Only Downloaded", "&type=downloaded");
+addOption("history-limit", "Show Only Snatched", "&type=snatched");
 
 addOption("exceptions", "Optional Param", "", 1);
 #for $curShow in $sortedShowList:
-addOption("exceptions", "$curShow.name", "&tvdbid=$curShow.tvdbid");
+addOption("exceptions", "$curShow.name", "&tvdbid=$curShow.tvdbid");
 #end for
 
 addOption("sb.pausebacklog", "Optional Param", "", 1);
-addOption("sb.pausebacklog", "Pause", "&pause=1");
-addOption("sb.pausebacklog", "Unpause", "&pause=0");
+addOption("sb.pausebacklog", "Pause", "&pause=1");
+addOption("sb.pausebacklog", "Unpause", "&pause=0");
 
-addList("sb.addrootdir", "C:\\Temp", "&location=C:\\Temp", "sb.addrootdir-opt");
-addList("sb.addrootdir", "/usr/bin", "&location=/usr/bin/", "sb.addrootdir-opt");
-addList("sb.addrootdir", "S:\\Invalid_Location", "&location=S:\\Invalid_Location", "sb.addrootdir-opt");
+addList("sb.addrootdir", "C:\\Temp", "&location=C:\\Temp", "sb.addrootdir-opt");
+addList("sb.addrootdir", "/usr/bin", "&location=/usr/bin/", "sb.addrootdir-opt");
+addList("sb.addrootdir", "S:\\Invalid_Location", "&location=S:\\Invalid_Location", "sb.addrootdir-opt");
 
 addOption("sb.addrootdir-opt", "Optional Param", "", 1);
-addOption("sb.addrootdir-opt", "Default", "&default=1");
-addOption("sb.addrootdir-opt", "Not Default", "&default=0");
+addOption("sb.addrootdir-opt", "Default", "&default=1");
+addOption("sb.addrootdir-opt", "Not Default", "&default=0");
 
-addOption("sb.deleterootdir", "C:\\Temp", "&location=C:\\Temp", "", 1);
-addOption("sb.deleterootdir", "/usr/bin", "&location=/usr/bin/");
-addOption("sb.deleterootdir", "S:\\Invalid_Location", "&location=S:\\Invalid_Location");
+addOption("sb.deleterootdir", "C:\\Temp", "&location=C:\\Temp", "", 1);
+addOption("sb.deleterootdir", "/usr/bin", "&location=/usr/bin/");
+addOption("sb.deleterootdir", "S:\\Invalid_Location", "&location=S:\\Invalid_Location");
 
 #for $curShow in $sortedShowList:
-addList("show.pause", "$curShow.name", "&tvdbid=$curShow.tvdbid", "show.pause-opt");
+addList("show.pause", "$curShow.name", "&tvdbid=$curShow.tvdbid", "show.pause-opt");
 #end for
 addOption("show.pause-opt", "Optional Param", "", 1);
-addOption("show.pause-opt", "Unpause", "&pause=0");
-addOption("show.pause-opt", "Pause", "&pause=1");
+addOption("show.pause-opt", "Unpause", "&pause=0");
+addOption("show.pause-opt", "Pause", "&pause=1");
 
 </script>
 </head>
diff --git a/data/interfaces/default/comingEpisodes.tmpl b/data/interfaces/default/comingEpisodes.tmpl
index 0f3c68169532f43d06aef077856d3ee3c5f7f7f6..71e387f797764a3908a88e3995c060f85df5bff8 100644
--- a/data/interfaces/default/comingEpisodes.tmpl
+++ b/data/interfaces/default/comingEpisodes.tmpl
@@ -25,25 +25,18 @@
 <script type="text/javascript" src="$sbRoot/js/plotTooltip.js"></script>
 <script type="text/javascript" charset="utf-8">
 <!--
-
 \$.tablesorter.addParser({
     id: 'loadingNames',
     is: function(s) {
         return false;
-    }, 
+    },
     format: function(s) {
         if (s.indexOf('Loading...') == 0)
-            return s.replace('Loading...','!!!');
-        else if (s.indexOf('The ') == 0)
-            return s.replace('The ', '')
-        else if (s.indexOf('A ') == 0)
-            return s.replace('A ', '')
-        else
-            return s;
+            return s.replace('Loading...','000');
+        return (s || '').replace(/^(The|A)\s/i,'');
     },
     type: 'text'
 });
-
 \$.tablesorter.addParser({
     id: 'quality',
     is: function(s) {
diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl
index aa71ebd1f302a5d39ef04f7e1ca988a9148a69b9..5cd55d651af62581b779a2e630e84c9cc3870516 100755
--- a/data/interfaces/default/config_notifications.tmpl
+++ b/data/interfaces/default/config_notifications.tmpl
@@ -567,6 +567,53 @@
                 </div><!-- /libnotify component-group //-->
 
 
+                <div class="component-group clearfix">
+                    <div class="component-group-desc">
+                        <h3><a href="http://pushover.net/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/pushover.gif" alt="" title="Pushover" width="16" height="16" /> Pushover </a></h3>
+                        <p>Pushover makes it easy to send real-time notifications to your Android and iOS devices.</p>
+                    </div>
+                    <fieldset class="component-group-list">
+                        <div class="field-pair">
+                            <input type="checkbox" class="enabler" name="use_pushover" id="use_pushover" #if $sickbeard.USE_PUSHOVER then "checked=\"checked\"" else ""# />
+                            <label class="clearfix" for="use_pushover">
+                                <span class="component-title">Enable</span>
+                                <span class="component-desc">Should Sick Beard send notifications through Pushover?</span>
+                            </label>
+                        </div>
+
+                        <div id="content_use_pushover">
+                            <div class="field-pair">
+                                <input type="checkbox" name="pushover_notify_onsnatch" id="pushover_notify_onsnatch" #if $sickbeard.PUSHOVER_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
+                                <label class="clearfix" for="pushover_notify_onsnatch">
+                                    <span class="component-title">Notify on Snatch</span>
+                                    <span class="component-desc">Send notification when we start a download?</span>
+                                </label>
+                            </div>
+                            <div class="field-pair">
+                                <input type="checkbox" name="pushover_notify_ondownload" id="pushover_notify_ondownload" #if $sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# />
+                                <label class="clearfix" for="pushover_notify_ondownload">
+                                    <span class="component-title">Notify on Download</span>
+                                    <span class="component-desc">Send notification when we finish a download?</span>
+                                </label>
+                            </div>
+                            <div class="field-pair">
+                                <label class="nocheck clearfix">
+                                    <span class="component-title"></span>
+                                    <input type="text" name="pushover_userkey" id="pushover_userkey" value="$sickbeard.PUSHOVER_USERKEY" size="35" />
+                                </label>
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">&nbsp;</span>
+                                    <span class="component-desc">User key of your Pushover account</span>
+                                </label>
+                            </div>
+                            <div class="testNotification" id="testPushover-result">Click below to test.</div>
+                            <input type="button" value="Test Pushover" id="testPushover" />
+                            <input type="submit" class="config_submitter" value="Save Changes" />
+                        </div><!-- /content_use_pushover //-->
+
+                    </fieldset>
+                </div><!-- /pushover component-group //-->
+
                 <div class="component-group clearfix">
                     <div class="component-group-desc">
                         <h3><a href="http://boxcar.io/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/boxcar.gif" alt="" title="Boxcar" width="16" height="16" /> Boxcar </a></h3>
diff --git a/data/interfaces/default/history.tmpl b/data/interfaces/default/history.tmpl
index ccf7d6ea0e89339054ed430aa5191df05fb6406f..de073c784b25a00c8668e91e82dfe6a4c1013042 100644
--- a/data/interfaces/default/history.tmpl
+++ b/data/interfaces/default/history.tmpl
@@ -22,7 +22,7 @@
         sortList: [[0,1]],
         textExtraction: {
             4: function(node) { return \$(node).find("span").text().toLowerCase(); }
-        },
+        }
     });
     \$('#limit').change(function(){
         url = '$sbRoot/history/?limit='+\$(this).val()
diff --git a/data/interfaces/default/home.tmpl b/data/interfaces/default/home.tmpl
index fef32c92dd6bc6d0ca0db6d44d5bafb8cfbe82bd..98e14045397c04991917d2dbda7882304b7b8a33 100644
--- a/data/interfaces/default/home.tmpl
+++ b/data/interfaces/default/home.tmpl
@@ -24,16 +24,11 @@
     id: 'loadingNames',
     is: function(s) {
         return false;
-    }, 
-    format: function(s) { 
+    },
+    format: function(s) {
         if (s.indexOf('Loading...') == 0)
-            return s.replace('Loading...','!!!');
-        else if (s.indexOf('The ') == 0)
-            return s.replace('The ', '')
-        else if (s.indexOf('A ') == 0)
-            return s.replace('A ', '')
-        else
-            return s;
+            return s.replace('Loading...','000');
+        return (s || '').replace(/^(The|A)\s/i,'');
     },
     type: 'text'
 });
@@ -73,7 +68,7 @@
 
 \$(document).ready(function(){ 
 
-    \$("#showListTable").tablesorter({
+    \$("#showListTable:has(tbody tr)").tablesorter({
         sortList: [[5,1],[1,0]],
         textExtraction: {
             3: function(node) { return \$(node).find("span").text().toLowerCase(); },
diff --git a/data/interfaces/default/inc_top.tmpl b/data/interfaces/default/inc_top.tmpl
index a5232706e4595bb52fd6a5ee8af405eeefdee184..51ae9fb95d5014dbd71799de367a1469bbef7248 100644
--- a/data/interfaces/default/inc_top.tmpl
+++ b/data/interfaces/default/inc_top.tmpl
@@ -27,9 +27,9 @@
 
 .sf-sub-indicator { background: url("$sbRoot/images/arrows.png") no-repeat -10px -100px; }
 .sf-shadow ul { background: url("$sbRoot/images/shadow.png") no-repeat bottom right; }
-table.tablesorter thead tr .header { background-image: url("$sbRoot/images/tablesorter/bg.gif"); }
-table.tablesorter thead tr .headerSortUp { background-image: url("$sbRoot/images/tablesorter/asc.gif"); }
-table.tablesorter thead tr .headerSortDown { background-image: url("$sbRoot/images/tablesorter/desc.gif"); }
+th.tablesorter-header { background-image: url("$sbRoot/images/tablesorter/bg.gif"); }
+th.tablesorter-headerSortUp { background-image: url("$sbRoot/images/tablesorter/asc.gif"); }
+th.tablesorter-headerSortDown { background-image: url("$sbRoot/images/tablesorter/desc.gif"); }
 
 .ui-autocomplete-loading { background: white url("$sbRoot/images/loading16.gif") right center no-repeat; }
 .browserDialog.busy .ui-dialog-buttonpane { background: url("$sbRoot/images/loading.gif") 10px 50% no-repeat !important; }
@@ -72,7 +72,7 @@ table.tablesorter thead tr .headerSortDown { background-image: url("$sbRoot/imag
     <script type="text/javascript" src="$sbRoot/js/jquery.cookiejar.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.json-2.2.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.selectboxes.min.js"></script>
-    <script type="text/javascript" src="$sbRoot/js/jquery.tablesorter-2.1.6.min.js"></script>
+    <script type="text/javascript" src="$sbRoot/js/jquery.tablesorter-2.1.10.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.tablesorter.widgets.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.qtip-2011-11-14.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.pnotify-1.0.2.min.js"></script>
diff --git a/data/interfaces/default/manage.tmpl b/data/interfaces/default/manage.tmpl
index 2685fec16d64b8be373bfee41e4fd572c6d47411..13277862fcac5a36d91aacb278911b30733a0aa3 100644
--- a/data/interfaces/default/manage.tmpl
+++ b/data/interfaces/default/manage.tmpl
@@ -14,18 +14,12 @@
     id: 'showNames',
     is: function(s) {
         return false;
-    }, 
-    format: function(s) { 
-        if (s.indexOf('The ') == 0)
-            return s.replace('The ', '')
-        else if (s.indexOf('A ') == 0)
-            return s.replace('A ', '')
-        else
-            return s;
+    },
+    format: function(s) {
+        return (s || '').replace(/^(The|A)\s/i,'');
     },
     type: 'text'
 });
-
 \$.tablesorter.addParser({
     id: 'quality',
     is: function(s) {
diff --git a/data/js/configNotifications.js b/data/js/configNotifications.js
index 4f02185a1a787dfb07db7b3fbe7cf40796b134ed..a9ed18fe89ab90b22dee734f49f2e014562cd9e4 100644
--- a/data/js/configNotifications.js
+++ b/data/js/configNotifications.js
@@ -52,6 +52,13 @@ $(document).ready(function(){
         function (data){ $('#testBoxcar-result').html(data); });
     });
 
+    $('#testPushover').click(function(){
+        $('#testPushover-result').html(loading);
+        var pushover_userkey = $("#pushover_userkey").val();
+        $.get(sbRoot+"/home/testPushover", {'userKey': pushover_userkey},
+        function (data){ $('#testPushover-result').html(data); });
+    });
+
     $('#testLibnotify').click(function(){
         $('#testLibnotify-result').html(loading);
         $.get(sbRoot+"/home/testLibnotify",
diff --git a/data/js/jquery.tablesorter-2.1.10.min.js b/data/js/jquery.tablesorter-2.1.10.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2170ba965c639e7c9971e55f5e8088ca3100cf1
--- /dev/null
+++ b/data/js/jquery.tablesorter-2.1.10.min.js
@@ -0,0 +1,6 @@
+/*!
+* TableSorter 2.1.10 - Client-side table sorting with ease!
+* Minified using http://dean.edwards.name/packer/
+* Copyright (c) 2007 Christian Bach
+*/
+!(function($){$.extend({tablesorter:new function(){this.version="2.1.10";var g=[],widgets=[],tbl;this.defaults={cssHeader:"tablesorter-header",cssAsc:"tablesorter-headerSortUp",cssDesc:"tablesorter-headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:false,sortReset:false,sortRestart:false,textExtraction:"simple",parsers:{},widgets:[],headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"mmddyyyy",usNumberFormat:true,onRenderHeader:null,selectorHeaders:'thead th',selectorRemove:"tr.remove-me",tableClass:'tablesorter',debug:false,widgetOptions:{zebra:["even","odd"]}};function log(s){if(typeof console!=="undefined"&&typeof console.log!=="undefined"){console.log(s)}else{alert(s)}}function benchmark(s,d){log(s+" ("+(new Date().getTime()-d.getTime())+"ms)")}this.benchmark=benchmark;this.hasInitialized=false;function getElementText(a,b,c){var d="",te=a.textExtraction;if(!b){return""}if(!a.supportsTextContent){a.supportsTextContent=b.textContent||false}if(te==="simple"){if(a.supportsTextContent){d=b.textContent}else{if(b.childNodes[0]&&b.childNodes[0].hasChildNodes()){d=b.childNodes[0].innerHTML}else{d=b.innerHTML}}}else{if(typeof(te)==="function"){d=te(b,tbl,c)}else if(typeof(te)==="object"&&te.hasOwnProperty(c)){d=te[c](b,tbl,c)}else{d=$(b).text()}}return d}function getParserById(a){var i,l=g.length;for(i=0;i<l;i++){if(g[i].id.toLowerCase()===(a.toString()).toLowerCase()){return g[i]}}return false}function trimAndGetNodeText(a,b,c){return $.trim(getElementText(a,b,c))}function detectParserForColumn(a,b,c,d){var i,l=g.length,node=false,nodeValue='',keepLooking=true;while(nodeValue===''&&keepLooking){c++;if(b[c]){node=b[c].cells[d];nodeValue=trimAndGetNodeText(a.config,node,d);if(a.config.debug){log('Checking if value was empty on row '+c+', column:'+d+": "+nodeValue)}}else{keepLooking=false}}for(i=1;i<l;i++){if(g[i].is(nodeValue,a,node)){return g[i]}}return g[0]}function buildParserCache(a,b){if(a.tBodies.length===0){return}var c=a.tBodies[0].rows,list,cells,l,h,i,p,parsersDebug="";if(c[0]){list=[];cells=c[0].cells;l=cells.length;for(i=0;i<l;i++){p=false;h=$(b[i]);if($.metadata&&(h.metadata()&&h.metadata().sorter)){p=getParserById(h.metadata().sorter)}else if((a.config.headers[i]&&a.config.headers[i].sorter)){p=getParserById(a.config.headers[i].sorter)}else if(h.attr('class')&&h.attr('class').match('sorter-')){p=getParserById(h.attr('class').match(/sorter-(\w+)/)[1]||'')}if(!p){p=detectParserForColumn(a,c,-1,i)}if(a.config.debug){parsersDebug+="column:"+i+"; parser:"+p.id+"\n"}list.push(p)}}if(a.config.debug){log(parsersDebug)}return list}function buildCache(a){var b=a.tBodies[0],totalRows=(b&&b.rows.length)||0,totalCells=(b.rows[0]&&b.rows[0].cells.length)||0,g=a.config.parsers,cache={row:[],normalized:[]},t,i,j,c,cols,cacheTime;if(a.config.debug){cacheTime=new Date()}for(i=0;i<totalRows;++i){c=$(b.rows[i]);cols=[];if(c.hasClass(a.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue}cache.row.push(c);for(j=0;j<totalCells;++j){t=trimAndGetNodeText(a.config,c[0].cells[j],j);cols.push(g[j].format(t,a,c[0].cells[j],j))}cols.push(cache.normalized.length);cache.normalized.push(cols)}if(a.config.debug){benchmark("Building cache for "+totalRows+" rows",cacheTime)}a.config.cache=cache;return cache}function getWidgetById(a){var i,w,l=widgets.length;for(i=0;i<l;i++){w=widgets[i];if(w&&w.hasOwnProperty('id')&&w.id.toLowerCase()===a.toLowerCase()){return w}}}function applyWidget(a,b){var c=a.config.widgets,i,w,l=c.length;for(i=0;i<l;i++){w=getWidgetById(c[i]);if(w){if(b&&w.hasOwnProperty('init')){w.init(a,widgets,w)}else if(!b&&w.hasOwnProperty('format')){w.format(a)}}}}function appendToTable(a,b){var c=a.config,r=b.row,n=b.normalized,totalRows=n.length,checkCell=totalRows?(n[0].length-1):0,rows=[],i,j,l,pos,appendTime;if(c.debug){appendTime=new Date()}for(i=0;i<totalRows;i++){pos=n[i][checkCell];rows.push(r[pos]);if(!c.appender||!c.removeRows){l=r[pos].length;for(j=0;j<l;j++){a.tBodies[0].appendChild(r[pos][j])}}}if(c.appender){c.appender(a,rows)}if(c.debug){benchmark("Rebuilt table",appendTime)}applyWidget(a);$(a).trigger("sortEnd",a)}function computeTableHeaderCellIndexes(t){var a=[],lookup={},thead=t.getElementsByTagName('THEAD')[0],trs=thead.getElementsByTagName('TR'),i,j,k,l,c,cells,rowIndex,cellId,rowSpan,colSpan,firstAvailCol,matrixrow;for(i=0;i<trs.length;i++){cells=trs[i].cells;for(j=0;j<cells.length;j++){c=cells[j];rowIndex=c.parentNode.rowIndex;cellId=rowIndex+"-"+c.cellIndex;rowSpan=c.rowSpan||1;colSpan=c.colSpan||1;if(typeof(a[rowIndex])==="undefined"){a[rowIndex]=[]}for(k=0;k<a[rowIndex].length+1;k++){if(typeof(a[rowIndex][k])==="undefined"){firstAvailCol=k;break}}lookup[cellId]=firstAvailCol;for(k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(a[k])==="undefined"){a[k]=[]}matrixrow=a[k];for(l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x"}}}}return lookup}function formatSortingOrder(v){return(/^d/i.test(v)||v===1)}function checkHeaderMetadata(a){return(($.metadata)&&($(a).metadata().sorter===false))}function checkHeaderOptions(a,i){return((a.config.headers[i])&&(a.config.headers[i].sorter===false))}function checkHeaderLocked(a,i){if((a.config.headers[i])&&(a.config.headers[i].lockedOrder!==null)){return a.config.headers[i].lockedOrder}return false}function checkHeaderOrder(a,i){if((a.config.headers[i])&&(a.config.headers[i].sortInitialOrder)){return a.config.headers[i].sortInitialOrder}return a.config.sortInitialOrder}function buildHeaders(b){var d=($.metadata)?true:false,header_index=computeTableHeaderCellIndexes(b),$th,lock,time,$tableHeaders,c=b.config;c.headerList=[];if(c.debug){time=new Date()}$tableHeaders=$(c.selectorHeaders,b).wrapInner("<div class='tablesorter-header-inner' />").each(function(a){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(checkHeaderOrder(b,a))?[1,0,2]:[0,1,2];this.count=-1;if(checkHeaderMetadata(this)||checkHeaderOptions(b,a)||$(this).hasClass('sorter-false')){this.sortDisabled=true}this.lockedOrder=false;lock=checkHeaderLocked(b,a);if(typeof(lock)!=='undefined'&&lock!==false){this.order=this.lockedOrder=formatSortingOrder(lock)?[1,1,1]:[0,0,0]}if(!this.sortDisabled){$th=$(this).addClass(c.cssHeader);if(c.onRenderHeader){c.onRenderHeader.apply($th,[a])}}c.headerList[a]=this;$(this).parent().addClass(c.cssHeader)});if(c.debug){benchmark("Built headers",time);log($tableHeaders)}return $tableHeaders}function checkCellColSpan(a,b,d){var i,cell,arr=[],r=a.tHead.rows,c=r[d].cells;for(i=0;i<c.length;i++){cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(a,b,d++))}else{if(a.tHead.length===1||(cell.rowSpan>1||!r[d+1])){arr.push(cell)}}}return arr}function isValueInArray(v,a){var i,l=a.length;for(i=0;i<l;i++){if(a[i][0]===v){return true}}return false}function setHeadersCss(b,c,d){var h=[],i,l,css=[b.config.cssDesc,b.config.cssAsc];c.removeClass(css[0]).removeClass(css[1]);c.each(function(a){if(!this.sortDisabled){h[this.column]=$(this)}});l=d.length;for(i=0;i<l;i++){if(d[i][1]===2){continue}h[d[i][0]].addClass(css[d[i][1]])}}function fixColumnWidth(a,b){if(a.config.widthFixed){var c=$('<colgroup>');$("tr:first td",a.tBodies[0]).each(function(){c.append($('<col>').css('width',$(this).width()))});$(a).prepend(c)}}function updateHeaderSortCount(a,b){var i,s,o,c=a.config,l=b.length;for(i=0;i<l;i++){s=b[i];o=c.headerList[s[0]];o.count=s[1]%(c.sortReset?3:2)}}function getCachedSortType(a,i){return(a)?a[i].type:''}function multisort(a,b,d){var f="var sortWrapper = function(a,b) {",col,mx=0,dir=0,tc=a.config,lc=d.normalized.length,l=b.length,sortTime,i,j,c,s,e,order,orgOrderCol;if(tc.debug){sortTime=new Date()}for(i=0;i<l;i++){c=b[i][0];order=b[i][1];s=(getCachedSortType(tc.parsers,c)==="text")?((order===0)?"sortText":"sortTextDesc"):((order===0)?"sortNumeric":"sortNumericDesc");e="e"+i;if(/Numeric/.test(s)&&tc.headers[c]&&tc.headers[c].string){for(j=0;j<lc;j++){col=Math.abs(parseFloat(d.normalized[j][c]));mx=Math.max(mx,isNaN(col)?0:col)}dir=(tc.headers[c])?tc.string[tc.headers[c].string]||0:0}f+="var "+e+" = "+s+"(a["+c+"],b["+c+"],"+mx+","+dir+"); ";f+="if ("+e+") { return "+e+"; } ";f+="else { "}orgOrderCol=(d.normalized&&d.normalized[0])?d.normalized[0].length-1:0;f+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(i=0;i<l;i++){f+="}; "}f+="return 0; ";f+="}; ";eval(f);d.normalized.sort(sortWrapper);if(tc.debug){benchmark("Sorting on "+b.toString()+" and dir "+order+" time",sortTime)}return d}function sortText(a,b){if(a===b){return 0}if(a===''){return 1}if(b===''){return-1}if($.data(tbl[0],"tablesorter").sortLocaleCompare){return a.localeCompare(b)}try{var c=0,ax,t,x=/^(\.)?\d/,L=Math.min(a.length,b.length)+1;while(c<L&&a.charAt(c)===b.charAt(c)&&x.test(b.substring(c))===false&&x.test(a.substring(c))===false){c++}a=a.substring(c);b=b.substring(c);if(x.test(a)||x.test(b)){if(x.test(a)===false){return(a)?1:-1}else if(x.test(b)===false){return(b)?-1:1}else{t=parseFloat(a)-parseFloat(b);if(t!==0){return t}else{t=a.search(/[^\.\d]/)}if(t===-1){t=b.search(/[^\.\d]/)}a=a.substring(t);b=b.substring(t)}}return(a>b)?1:-1}catch(er){return 0}}function sortTextDesc(a,b){if(a===b){return 0}if(a===''){return 1}if(b===''){return-1}if($.data(tbl[0],"tablesorter").sortLocaleCompare){return b.localeCompare(a)}return-sortText(a,b)}function getTextValue(a,b,d){if(b){var i,l=a.length,n=b+d;for(i=0;i<l;i++){n+=a.charCodeAt(i)}return d*n}return 0}function sortNumeric(a,b,c,d){if(a===b){return 0}if(a===''){return 1}if(b===''){return-1}if(isNaN(a)){a=getTextValue(a,c,d)}if(isNaN(b)){b=getTextValue(b,c,d)}return a-b}function sortNumericDesc(a,b,c,d){if(a===b){return 0}if(a===''){return 1}if(b===''){return-1}if(isNaN(a)){a=getTextValue(a,c,d)}if(isNaN(b)){b=getTextValue(b,c,d)}return b-a}this.construct=function(f){return this.each(function(){if(!this.tHead||this.tBodies.length===0){return}var d,$document,$headers,cache,config,shiftDown=0,sortOrder,totalRows,$cell,c,i,j,k,a,s,o;this.config={};c=config=$.extend(true,this.config,$.tablesorter.defaults,f);tbl=d=$(this).addClass(this.config.tableClass);$.data(this,"tablesorter",c);$headers=buildHeaders(this);c.parsers=buildParserCache(this,$headers);c.string={max:1,'max+':1,'max-':-1,none:0};cache=buildCache(this);fixColumnWidth(this);$headers.click(function(e){totalRows=(d[0].tBodies[0]&&d[0].tBodies[0].rows.length)||0;if(!this.sortDisabled){d.trigger("sortStart",tbl[0]);$cell=$(this);k=!e[c.sortMultiSortKey];this.count=(this.count+1)%(c.sortReset?3:2);if(c.sortRestart){i=this;$headers.each(function(){if(this!==i&&(k||!$(this).is('.'+c.cssDesc+',.'+c.cssAsc))){this.count=-1}})}i=this.column;if(k){c.sortList=[];if(c.sortForce!==null){a=c.sortForce;for(j=0;j<a.length;j++){if(a[j][0]!==i){c.sortList.push(a[j])}}}if(this.order[this.count]<2){c.sortList.push([i,this.order[this.count]])}}else{if(isValueInArray(i,c.sortList)){for(j=0;j<c.sortList.length;j++){s=c.sortList[j];o=c.headerList[s[0]];if(s[0]===i){s[1]=o.order[o.count];if(s[1]===2){c.sortList.splice(j,1);o.count=-1}}}}else{if(this.order[this.count]<2){c.sortList.push([i,this.order[this.count]])}}}if(c.sortAppend!==null){a=c.sortAppend;for(j=0;j<a.length;j++){if(a[j][0]!==i){c.sortList.push(a[j])}}}d.trigger("sortBegin",tbl[0]);setHeadersCss(d[0],$headers,c.sortList);appendToTable(d[0],multisort(d[0],c.sortList,cache));return false}}).mousedown(function(){if(c.cancelSelection){this.onselectstart=function(){return false};return false}});d.bind("update",function(){var t=this,c=t.config;$(c.selectorRemove,t.tBodies[0]).remove();t.config.parsers=buildParserCache(t,$headers);cache=buildCache(t);d.trigger("sorton",[t.config.sortList])}).bind("updateCell",function(e,a){var b=[(a.parentNode.rowIndex-1),a.cellIndex];cache.normalized[b[0]][b[1]]=c.parsers[b[1]].format(getElementText(c,a,b[1]),d,a,b[1]);c.cache=cache;d.trigger("sorton",[c.sortList])}).bind("addRows",function(e,a){var i,rows=a.filter('tr').length,dat=[],l=a[0].cells.length;for(i=0;i<rows;i++){for(j=0;j<l;j++){dat[j]=c.parsers[j].format(getElementText(c,a[i].cells[j],j),d,a[i].cells[j],j)}dat.push(cache.row.length);cache.row.push([a[i]]);cache.normalized.push(dat);dat=[]}c.cache=cache;d.trigger("sorton",[c.sortList])}).bind("sorton",function(e,a){$(this).trigger("sortStart",tbl[0]);c.sortList=a;var b=c.sortList;updateHeaderSortCount(this,b);setHeadersCss(this,$headers,b);appendToTable(this,multisort(this,b,cache))}).bind("appendCache",function(){appendToTable(this,cache)}).bind("applyWidgetId",function(e,a){getWidgetById(a).format(this)}).bind("applyWidgets",function(){applyWidget(this)});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){c.sortList=$(this).metadata().sortlist}applyWidget(this,true);if(c.sortList.length>0){d.trigger("sorton",[c.sortList])}else{applyWidget(this)}this.hasInitialized=true})};this.addParser=function(b){var i,l=g.length,a=true;for(i=0;i<l;i++){if(g[i].id.toLowerCase()===b.id.toLowerCase()){a=false}}if(a){g.push(b)}};this.addWidget=function(a){widgets.push(a)};this.formatFloat=function(s){if(typeof(s)!=='string'){return s}if(tbl[0].config.usNumberFormat){s=s.replace(/,/g,'')}else{s=s.replace(/[\s|\.]/g,'').replace(/,/g,'.')}var i=parseFloat(s);return isNaN(i)?$.trim(s):i};this.isDigit=function(s){return(/^[\-+]?\d*$/).test($.trim(s.replace(/[,.'\s]/g,'')))};this.clearTableBody=function(a){$(a.tBodies[0]).empty()}}})();$.fn.extend({tablesorter:$.tablesorter.construct});var m=$.tablesorter;m.addParser({id:"text",is:function(s){return true},format:function(s){return $.trim(s.toLocaleLowerCase())},type:"text"});m.addParser({id:"digit",is:function(s){return $.tablesorter.isDigit(s)},format:function(s){return $.tablesorter.formatFloat(s)},type:"numeric"});m.addParser({id:"currency",is:function(s){return(/^[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]/).test(s)},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9,. \-]/g),""))},type:"numeric"});m.addParser({id:"ipAddress",is:function(s){return(/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/).test(s)},format:function(s){var i,item,a=s.split("."),r="",l=a.length;for(i=0;i<l;i++){item=a[i];if(item.length===2){r+="0"+item}else{r+=item}}return $.tablesorter.formatFloat(r)},type:"numeric"});m.addParser({id:"url",is:function(s){return(/^(https?|ftp|file):\/\/$/).test(s)},format:function(s){return $.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''))},type:"text"});m.addParser({id:"isoDate",is:function(s){return(/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/).test(s)},format:function(s){return $.tablesorter.formatFloat((s!=="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"")},type:"numeric"});m.addParser({id:"percent",is:function(s){return(/\%$/).test($.trim(s))},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""))},type:"numeric"});m.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/))},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime())},type:"numeric"});m.addParser({id:"shortDate",is:function(s){return(/\d{1,4}[\/\-\,\.\s+]\d{1,4}[\/\-\.\,\s+]\d{1,4}/).test(s)},format:function(s,a,b,d){var c=a.config,format=(c.headers&&c.headers[d])?c.headers[d].dateFormat||c.dateFormat:c.dateFormat;s=s.replace(/\s+/g," ").replace(/[\-|\.|\,|\s]/g,"/");if(format==="mmddyyyy"){s=s.replace(/(\d{1,2})\/(\d{1,2})\/(\d{4})/,"$3/$1/$2")}else if(format==="ddmmyyyy"){s=s.replace(/(\d{1,2})\/(\d{1,2})\/(\d{4})/,"$3/$2/$1")}else if(format==="yyyymmdd"){s=s.replace(/(\d{4})\/(\d{1,2})\/(\d{1,2})/,"$1/$2/$3")}return $.tablesorter.formatFloat(new Date(s).getTime())},type:"numeric"});m.addParser({id:"time",is:function(s){return(/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/).test(s)},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime())},type:"numeric"});m.addParser({id:"metadata",is:function(s){return false},format:function(s,a,b){var c=a.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(b).metadata()[p]},type:"numeric"});m.addWidget({id:"zebra",format:function(a){var b,row=0,even,time,c=a.config,child=c.cssChildRow,css=["even","odd"];css=c.widgetZebra&&c.hasOwnProperty('css')?c.widgetZebra.css:(c.widgetOptions&&c.widgetOptions.hasOwnProperty('zebra'))?c.widgetOptions.zebra:css;if(a.config.debug){time=new Date()}$("tr:visible",a.tBodies[0]).each(function(i){b=$(this);if(!b.hasClass(child)){row++}even=(row%2===0);b.removeClass(css[even?1:0]).addClass(css[even?0:1])});if(a.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time)}}})})(jQuery);
diff --git a/data/js/jquery.tablesorter-2.1.6.min.js b/data/js/jquery.tablesorter-2.1.6.min.js
deleted file mode 100644
index 6d031709c5e4230bb05fd6da45abe237380581cb..0000000000000000000000000000000000000000
--- a/data/js/jquery.tablesorter-2.1.6.min.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
-* TableSorter 2.1.6 - Client-side table sorting with ease!
-* Minified using http://dean.edwards.name/packer/
-* Copyright (c) 2007 Christian Bach
-*/
-!(function($){$.extend({tablesorter:new function(){this.version="2.1.6";var g=[],widgets=[],tbl;this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:false,sortReset:false,sortRestart:false,textExtraction:"simple",parsers:{},widgets:[],headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"mmddyyyy",usNumberFormat:true,onRenderHeader:null,selectorHeaders:'thead th',selectorRemove:"tr.remove-me",tableClass:'tablesorter',debug:false,widgetOptions:{zebra:["even","odd"]}};function log(s){if(typeof console!=="undefined"&&typeof console.log!=="undefined"){console.log(s)}else{alert(s)}}function benchmark(s,d){log(s+" ("+(new Date().getTime()-d.getTime())+"ms)")}this.benchmark=benchmark;this.hasInitialized=false;function getElementText(a,b,c){var d="",te=a.textExtraction;if(!b){return""}if(!a.supportsTextContent){a.supportsTextContent=b.textContent||false}if(te==="simple"){if(a.supportsTextContent){d=b.textContent}else{if(b.childNodes[0]&&b.childNodes[0].hasChildNodes()){d=b.childNodes[0].innerHTML}else{d=b.innerHTML}}}else{if(typeof(te)==="function"){d=te(b,tbl,c)}else if(typeof(te)==="object"&&te.hasOwnProperty(c)){d=te[c](b,tbl,c)}else{d=$(b).text()}}return d}function getParserById(a){var i,l=g.length;for(i=0;i<l;i++){if(g[i].id.toLowerCase()===(a.toString()).toLowerCase()){return g[i]}}return false}function trimAndGetNodeText(a,b,c){return $.trim(getElementText(a,b,c))}function detectParserForColumn(a,b,c,d){var i,l=g.length,node=false,nodeValue='',keepLooking=true;while(nodeValue===''&&keepLooking){c++;if(b[c]){node=b[c].cells[d];nodeValue=trimAndGetNodeText(a.config,node,d);if(a.config.debug){log('Checking if value was empty on row '+c+', column:'+d+": "+nodeValue)}}else{keepLooking=false}}for(i=1;i<l;i++){if(g[i].is(nodeValue,a,node)){return g[i]}}return g[0]}function buildParserCache(a,b){if(a.tBodies.length===0){return}var c=a.tBodies[0].rows,list,cells,l,h,i,p,parsersDebug="";if(c[0]){list=[];cells=c[0].cells;l=cells.length;for(i=0;i<l;i++){p=false;h=$(b[i]);if($.metadata&&(h.metadata()&&h.metadata().sorter)){p=getParserById(h.metadata().sorter)}else if((a.config.headers[i]&&a.config.headers[i].sorter)){p=getParserById(a.config.headers[i].sorter)}else if(h.attr('class')&&h.attr('class').match('sorter-')){p=getParserById(h.attr('class').match(/sorter-(\w+)/)[1]||'')}if(!p){p=detectParserForColumn(a,c,-1,i)}if(a.config.debug){parsersDebug+="column:"+i+"; parser:"+p.id+"\n"}list.push(p)}}if(a.config.debug){log(parsersDebug)}return list}function buildCache(a){var b=a.tBodies[0],totalRows=(b&&b.rows.length)||0,totalCells=(b.rows[0]&&b.rows[0].cells.length)||0,g=a.config.parsers,cache={row:[],normalized:[]},t,i,j,c,cols,cacheTime;if(a.config.debug){cacheTime=new Date()}for(i=0;i<totalRows;++i){c=$(b.rows[i]);cols=[];if(c.hasClass(a.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue}cache.row.push(c);for(j=0;j<totalCells;++j){t=trimAndGetNodeText(a.config,c[0].cells[j],j);cols.push(g[j].format(t,a,c[0].cells[j],j))}cols.push(cache.normalized.length);cache.normalized.push(cols)}if(a.config.debug){benchmark("Building cache for "+totalRows+" rows",cacheTime)}a.config.cache=cache;return cache}function getWidgetById(a){var i,w,l=widgets.length;for(i=0;i<l;i++){w=widgets[i];if(w&&w.hasOwnProperty('id')&&w.id.toLowerCase()===a.toLowerCase()){return w}}}function applyWidget(a,b){var c=a.config.widgets,i,w,l=c.length;for(i=0;i<l;i++){w=getWidgetById(c[i]);if(w){if(b&&w.hasOwnProperty('init')){w.init(a,widgets,w)}else if(!b&&w.hasOwnProperty('format')){w.format(a)}}}}function appendToTable(a,b){var c=a.config,r=b.row,n=b.normalized,totalRows=n.length,checkCell=totalRows?(n[0].length-1):0,rows=[],i,j,l,pos,appendTime;if(c.debug){appendTime=new Date()}for(i=0;i<totalRows;i++){pos=n[i][checkCell];rows.push(r[pos]);if(!c.appender||!c.removeRows){l=r[pos].length;for(j=0;j<l;j++){a.tBodies[0].appendChild(r[pos][j])}}}if(c.appender){c.appender(a,rows)}if(c.debug){benchmark("Rebuilt table",appendTime)}applyWidget(a);$(a).trigger("sortEnd",a)}function computeTableHeaderCellIndexes(t){var a=[],lookup={},thead=t.getElementsByTagName('THEAD')[0],trs=thead.getElementsByTagName('TR'),i,j,k,l,c,cells,rowIndex,cellId,rowSpan,colSpan,firstAvailCol,matrixrow;for(i=0;i<trs.length;i++){cells=trs[i].cells;for(j=0;j<cells.length;j++){c=cells[j];rowIndex=c.parentNode.rowIndex;cellId=rowIndex+"-"+c.cellIndex;rowSpan=c.rowSpan||1;colSpan=c.colSpan||1;if(typeof(a[rowIndex])==="undefined"){a[rowIndex]=[]}for(k=0;k<a[rowIndex].length+1;k++){if(typeof(a[rowIndex][k])==="undefined"){firstAvailCol=k;break}}lookup[cellId]=firstAvailCol;for(k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(a[k])==="undefined"){a[k]=[]}matrixrow=a[k];for(l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x"}}}}return lookup}function formatSortingOrder(v){return(/^d/i.test(v)||v===1)}function checkHeaderMetadata(a){return(($.metadata)&&($(a).metadata().sorter===false))}function checkHeaderOptions(a,i){return((a.config.headers[i])&&(a.config.headers[i].sorter===false))}function checkHeaderLocked(a,i){if((a.config.headers[i])&&(a.config.headers[i].lockedOrder!==null)){return a.config.headers[i].lockedOrder}return false}function checkHeaderOrder(a,i){if((a.config.headers[i])&&(a.config.headers[i].sortInitialOrder)){return a.config.headers[i].sortInitialOrder}return a.config.sortInitialOrder}function buildHeaders(b){var d=($.metadata)?true:false,header_index=computeTableHeaderCellIndexes(b),$th,lock,time,$tableHeaders,c=b.config;c.headerList=[];if(c.debug){time=new Date()}$tableHeaders=$(c.selectorHeaders,b).wrapInner("<span/>").each(function(a){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(checkHeaderOrder(b,a))?[1,0,2]:[0,1,2];this.count=-1;if(checkHeaderMetadata(this)||checkHeaderOptions(b,a)||$(this).is('.sorter-false')){this.sortDisabled=true}this.lockedOrder=false;lock=checkHeaderLocked(b,a);if(typeof(lock)!=='undefined'&&lock!==false){this.order=this.lockedOrder=formatSortingOrder(lock)?[1,1,1]:[0,0,0]}if(!this.sortDisabled){$th=$(this).addClass(c.cssHeader);if(c.onRenderHeader){c.onRenderHeader.apply($th,[a])}}c.headerList[a]=this;$(this).parent().addClass('tablesorter-header')});if(c.debug){benchmark("Built headers",time);log($tableHeaders)}return $tableHeaders}function checkCellColSpan(a,b,d){var i,cell,arr=[],r=a.tHead.rows,c=r[d].cells;for(i=0;i<c.length;i++){cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(a,b,d++))}else{if(a.tHead.length===1||(cell.rowSpan>1||!r[d+1])){arr.push(cell)}}}return arr}function isValueInArray(v,a){var i,l=a.length;for(i=0;i<l;i++){if(a[i][0]===v){return true}}return false}function setHeadersCss(b,c,d){var h=[],i,l,css=[b.config.cssDesc,b.config.cssAsc];c.removeClass(css[0]).removeClass(css[1]);c.each(function(a){if(!this.sortDisabled){h[this.column]=$(this)}});l=d.length;for(i=0;i<l;i++){if(d[i][1]===2){continue}h[d[i][0]].addClass(css[d[i][1]])}}function fixColumnWidth(a,b){if(a.config.widthFixed){var c=$('<colgroup>');$("tr:first td",a.tBodies[0]).each(function(){c.append($('<col>').css('width',$(this).width()))});$(a).prepend(c)}}function updateHeaderSortCount(a,b){var i,s,o,c=a.config,l=b.length;for(i=0;i<l;i++){s=b[i];o=c.headerList[s[0]];o.count=(s[1]+1)%(c.sortReset?3:2)}}function getCachedSortType(a,i){return(a)?a[i].type:''}function multisort(a,b,d){var f="var sortWrapper = function(a,b) {",col,mx=0,dir=0,tc=a.config,lc=d.normalized.length,l=b.length,sortTime,i,j,c,s,e,order,orgOrderCol;if(tc.debug){sortTime=new Date()}for(i=0;i<l;i++){c=b[i][0];order=b[i][1];s=(getCachedSortType(tc.parsers,c)==="text")?((order===0)?"sortText":"sortTextDesc"):((order===0)?"sortNumeric":"sortNumericDesc");e="e"+i;if(/Numeric/.test(s)&&tc.headers[c]&&tc.headers[c].string){for(j=0;j<lc;j++){col=Math.abs(parseFloat(d.normalized[j][c]));mx=Math.max(mx,isNaN(col)?0:col)}dir=(tc.headers[c])?tc.string[tc.headers[c].string]||0:0}f+="var "+e+" = "+s+"(a["+c+"],b["+c+"],"+mx+","+dir+"); ";f+="if ("+e+") { return "+e+"; } ";f+="else { "}orgOrderCol=(d.normalized&&d.normalized[0])?d.normalized[0].length-1:0;f+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(i=0;i<l;i++){f+="}; "}f+="return 0; ";f+="}; ";eval(f);d.normalized.sort(sortWrapper);if(tc.debug){benchmark("Sorting on "+b.toString()+" and dir "+order+" time",sortTime)}return d}function sortText(a,b){if(a===''){return 1}if(b===''){return-1}if(a===b){return 0}if($.data(tbl[0],"tablesorter").sortLocaleCompare){return a.localeCompare(b)}try{var c=0,ax,t,x=/^(\.)?\d/,L=Math.min(a.length,b.length)+1;while(c<L&&a.charAt(c)===b.charAt(c)&&x.test(b.substring(c))===false&&x.test(a.substring(c))===false){c++}a=a.substring(c);b=b.substring(c);if(x.test(a)||x.test(b)){if(x.test(a)===false){return(a)?1:-1}else if(x.test(b)===false){return(b)?-1:1}else{t=parseFloat(a)-parseFloat(b);if(t!==0){return t}else{t=a.search(/[^\.\d]/)}if(t===-1){t=b.search(/[^\.\d]/)}a=a.substring(t);b=b.substring(t)}}return(a>b)?1:-1}catch(er){return 0}}function sortTextDesc(a,b){if(a===''){return 1}if(b===''){return-1}if(a===b){return 0}if($.data(tbl[0],"tablesorter").sortLocaleCompare){return b.localeCompare(a)}return-sortText(a,b)}function getTextValue(a,b,d){if(b){var i,l=a.length,n=b+d;for(i=0;i<l;i++){n+=a.charCodeAt(i)}return d*n}return 0}function sortNumeric(a,b,c,d){if(a===''){return 1}if(b===''){return-1}if(isNaN(a)){a=getTextValue(a,c,d)}if(isNaN(b)){b=getTextValue(b,c,d)}return a-b}function sortNumericDesc(a,b,c,d){if(a===''){return 1}if(b===''){return-1}if(isNaN(a)){a=getTextValue(a,c,d)}if(isNaN(b)){b=getTextValue(b,c,d)}return b-a}this.construct=function(f){return this.each(function(){if(!this.tHead||this.tBodies.length===0){return}var d,$document,$headers,cache,config,shiftDown=0,sortOrder,totalRows,$cell,c,i,j,k,a,s,o;this.config={};c=config=$.extend(true,this.config,$.tablesorter.defaults,f);tbl=d=$(this).addClass(this.config.tableClass);$.data(this,"tablesorter",c);$headers=buildHeaders(this);c.parsers=buildParserCache(this,$headers);c.string={max:1,'max+':1,'max-':-1,none:0};cache=buildCache(this);fixColumnWidth(this);$headers.click(function(e){totalRows=(d[0].tBodies[0]&&d[0].tBodies[0].rows.length)||0;if(!this.sortDisabled){d.trigger("sortStart",tbl[0]);$cell=$(this);k=!e[c.sortMultiSortKey];this.count=(this.count+1)%(c.sortReset?3:2);if(c.sortRestart){i=this;$headers.each(function(){if(this!==i&&(k||!$(this).is('.'+c.cssDesc+',.'+c.cssAsc))){this.count=-1}})}i=this.column;if(k){c.sortList=[];if(c.sortForce!==null){a=c.sortForce;for(j=0;j<a.length;j++){if(a[j][0]!==i){c.sortList.push(a[j])}}}if(this.order[this.count]<2){c.sortList.push([i,this.order[this.count]])}}else{if(isValueInArray(i,c.sortList)){for(j=0;j<c.sortList.length;j++){s=c.sortList[j];o=c.headerList[s[0]];if(s[0]===i){s[1]=o.order[o.count];if(s[1]===2){c.sortList.splice(j,1);o.count=-1}}}}else{if(this.order[this.count]<2){c.sortList.push([i,this.order[this.count]])}}}if(c.sortAppend!==null){a=c.sortAppend;for(j=0;j<a.length;j++){if(a[j][0]!==i){c.sortList.push(a[j])}}}d.trigger("sortBegin",tbl[0]);setHeadersCss(d[0],$headers,c.sortList);appendToTable(d[0],multisort(d[0],c.sortList,cache));return false}}).mousedown(function(){if(c.cancelSelection){this.onselectstart=function(){return false};return false}});d.bind("update",function(){var t=this,c=t.config;$(c.selectorRemove,t.tBodies[0]).remove();t.config.parsers=buildParserCache(t,$headers);cache=buildCache(t);d.trigger("sorton",[t.config.sortList])}).bind("updateCell",function(e,a){var b=[(a.parentNode.rowIndex-1),a.cellIndex];cache.normalized[b[0]][b[1]]=c.parsers[b[1]].format(getElementText(c,a,b[1]),d,a,b[1]);c.cache=cache;d.trigger("sorton",[c.sortList])}).bind("addRows",function(e,a){var i,rows=a.filter('tr').length,dat=[],l=a[0].cells.length;for(i=0;i<rows;i++){for(j=0;j<l;j++){dat[j]=c.parsers[j].format(getElementText(c,a[i].cells[j],j),d,a[i].cells[j],j)}dat.push(cache.row.length);cache.row.push([a[i]]);cache.normalized.push(dat);dat=[]}c.cache=cache;d.trigger("sorton",[c.sortList])}).bind("sorton",function(e,a){$(this).trigger("sortStart",tbl[0]);c.sortList=a;var b=c.sortList;updateHeaderSortCount(this,b);setHeadersCss(this,$headers,b);appendToTable(this,multisort(this,b,cache))}).bind("appendCache",function(){appendToTable(this,cache)}).bind("applyWidgetId",function(e,a){getWidgetById(a).format(this)}).bind("applyWidgets",function(){applyWidget(this)});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){c.sortList=$(this).metadata().sortlist}applyWidget(this,true);if(c.sortList.length>0){d.trigger("sorton",[c.sortList])}else{applyWidget(this)}this.hasInitialized=true})};this.addParser=function(b){var i,l=g.length,a=true;for(i=0;i<l;i++){if(g[i].id.toLowerCase()===b.id.toLowerCase()){a=false}}if(a){g.push(b)}};this.addWidget=function(a){widgets.push(a)};this.formatFloat=function(s){if(typeof(s)!=='string'){return s}if(tbl[0].config.usNumberFormat){s=s.replace(/,/g,'')}else{s=s.replace(/[\s|\.]/g,'').replace(/,/g,'.')}var i=parseFloat(s);return isNaN(i)?$.trim(s):i};this.isDigit=function(s){return(/^[\-+]?\d*$/).test($.trim(s.replace(/[,.'\s]/g,'')))};this.clearTableBody=function(a){$(a.tBodies[0]).empty()}}})();$.fn.extend({tablesorter:$.tablesorter.construct});var m=$.tablesorter;m.addParser({id:"text",is:function(s){return true},format:function(s){return $.trim(s.toLocaleLowerCase())},type:"text"});m.addParser({id:"digit",is:function(s){return $.tablesorter.isDigit(s)},format:function(s){return $.tablesorter.formatFloat(s)},type:"numeric"});m.addParser({id:"currency",is:function(s){return(/^[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]/).test(s)},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9,. \-]/g),""))},type:"numeric"});m.addParser({id:"ipAddress",is:function(s){return(/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/).test(s)},format:function(s){var i,item,a=s.split("."),r="",l=a.length;for(i=0;i<l;i++){item=a[i];if(item.length===2){r+="0"+item}else{r+=item}}return $.tablesorter.formatFloat(r)},type:"numeric"});m.addParser({id:"url",is:function(s){return(/^(https?|ftp|file):\/\/$/).test(s)},format:function(s){return $.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''))},type:"text"});m.addParser({id:"isoDate",is:function(s){return(/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/).test(s)},format:function(s){return $.tablesorter.formatFloat((s!=="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"")},type:"numeric"});m.addParser({id:"percent",is:function(s){return(/\%$/).test($.trim(s))},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""))},type:"numeric"});m.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/))},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime())},type:"numeric"});m.addParser({id:"shortDate",is:function(s){return(/\d{1,4}[\/\-\,\.\s+]\d{1,4}[\/\-\.\,\s+]\d{1,4}/).test(s)},format:function(s,a,b,d){var c=a.config,format=(c.headers&&c.headers[d])?c.headers[d].dateFormat||c.dateFormat:c.dateFormat;s=s.replace(/\s+/g," ").replace(/[\-|\.|\,|\s]/g,"/");if(format==="mmddyyyy"){s=s.replace(/(\d{1,2})\/(\d{1,2})\/(\d{4})/,"$3/$1/$2")}else if(format==="ddmmyyyy"){s=s.replace(/(\d{1,2})\/(\d{1,2})\/(\d{4})/,"$3/$2/$1")}else if(format==="yyyymmdd"){s=s.replace(/(\d{4})\/(\d{1,2})\/(\d{1,2})/,"$1/$2/$3")}return $.tablesorter.formatFloat(new Date(s).getTime())},type:"numeric"});m.addParser({id:"time",is:function(s){return(/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/).test(s)},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime())},type:"numeric"});m.addParser({id:"metadata",is:function(s){return false},format:function(s,a,b){var c=a.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(b).metadata()[p]},type:"numeric"});m.addWidget({id:"zebra",format:function(a){var b,row=0,even,time,c=a.config,child=c.cssChildRow,css=["even","odd"];css=c.widgetZebra&&c.hasOwnProperty('css')?c.widgetZebra.css:(c.widgetOptions&&c.widgetOptions.hasOwnProperty('zebra'))?c.widgetOptions.zebra:css;if(a.config.debug){time=new Date()}$("tr:visible",a.tBodies[0]).each(function(i){b=$(this);if(!b.hasClass(child)){row++}even=(row%2===0);b.removeClass(css[even?1:0]).addClass(css[even?0:1])});if(a.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time)}}})})(jQuery);
diff --git a/data/js/jquery.tablesorter.widgets.min.js b/data/js/jquery.tablesorter.widgets.min.js
index 01868eb0bfa54a9704f5227fc8f3ca7eaef5a907..672aa37e0e908ee4edaa4a93aba5be0d007460c1 100644
--- a/data/js/jquery.tablesorter.widgets.min.js
+++ b/data/js/jquery.tablesorter.widgets.min.js
@@ -1,10 +1,10 @@
-/*! TableSorter 2.1 Widgets - updated 3/18/2012 */
+/*! tableSorter 2.1 widgets - updated 4/2/2012 */
 (function(b){
-b.tablesorter.storage=function(a,e,c){var d,g=!1;d={};var a=a.id||b(".tablesorter").index(b(a)),f=window.location.pathname;try{g=!!localStorage.getItem}catch(j){}c&&JSON&&JSON.hasOwnProperty("stringify")?(d[f]={},d[f][a]={},d[f][a]=c,g?localStorage[e]=JSON.stringify(d):(c=new Date,c.setTime(c.getTime()+31536E6),document.cookie=e+"="+JSON.stringify(d).replace(/\"/g,'"')+"; expires="+c.toGMTString()+"; path=/")):b.parseJSON&&(g?d=b.parseJSON(localStorage[e])||{}:(d=document.cookie.split(/[;\s|=]/), c=b.inArray(e,d)+1,d=0!==c?b.parseJSON(d[c])||{}:{}));return d&&d.hasOwnProperty(f)&&d[f].hasOwnProperty(a)?d[f][a]:{}};
-b.tablesorter.addWidget({id:"uitheme",format:function(a){var e,c,d,g,f,j=b(a),i=a.config,h=i.widgetOptions,k=["ui-icon-arrowthick-2-n-s","ui-icon-arrowthick-1-s","ui-icon-arrowthick-1-n"],k=i.widgetUitheme&&i.widgetUitheme.hasOwnProperty("css")?i.widgetUitheme.css||k:h&&h.hasOwnProperty("uitheme")?h.uitheme:k;d=k.join(" ");i.debug&&(e=new Date);j.hasClass("ui-theme")||(j.addClass("ui-widget ui-widget-content ui-corner-all ui-theme"), b.each(i.headerList,function(){b(this).addClass("ui-widget-header ui-corner-all ui-state-default").append('<span class="ui-icon"/>').wrapInner('<div class="tablesorter-inner"/>').hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")})}));b.each(i.headerList,function(a){g=b(this);if(this.sortDisabled)g.find("span.ui-icon").removeClass(d+" ui-icon");else{c=g.hasClass(i.cssAsc)?k[1]:g.hasClass(i.cssDesc)?k[2]:g.hasClass(i.cssHeader)?k[0]:"";f=j.hasClass("hasStickyHeaders")? j.find("tr."+h.stickyHeaders||"tablesorter-stickyheader").find("th").eq(a).add(g):g;f[c===k[0]?"removeClass":"addClass"]("ui-state-active").find("span.ui-icon").removeClass(d).addClass(c)}});i.debug&&b.tablesorter.benchmark("Applying uitheme widget",e)}});
-b.tablesorter.addWidget({id:"columns",format:function(a){var e,c,d,g,f=a.config,j=f.sortList,i=j.length,h=["primary","secondary","tertiary"],h=f.widgetColumns&&f.widgetColumns.hasOwnProperty("css")?f.widgetColumns.css||h:f.widgetOptions&&f.widgetOptions.hasOwnProperty("columns")? f.widgetOptions.columns||h:h;d=h.length-1;g=h.join(" ");f.debug&&(c=new Date);j&&j[0]?b("tr:visible",a.tBodies[0]).each(function(a){e=b(this).children().removeClass(g);e.eq(j[0][0]).addClass(h[0]);if(1<i)for(a=1;a<i;a++)e.eq(j[a][0]).addClass(h[a]||h[d])}):b("td",a.tBodies[0]).removeClass(g);f.debug&&b.tablesorter.benchmark("Applying Columns widget",c)}});
-b.tablesorter.addWidget({id:"filter",format:function(a){if(!b(a).hasClass("hasFilters")){var e,c,d,g,f,j,i,h=a.config,k=h.widgetOptions,l=k.filter_cssFilter|| "tablesorter-filter",m=h.headerList.length,n=b(a).addClass("hasFilters"),a='<tr class="'+l+'">',o;h.debug&&(o=new Date);for(e=0;e<m;e++)a+='<td><input type="search" data-col="'+e+'" class="'+l,a+=h.headers[e]&&h.headers[e].hasOwnProperty("filter")&&!1===h.headers[e].filter||b(h.headerList[e]).is(".filter-false")?' disabled" disabled':'"',a+="></td>";n.find("thead").append(a+="</tr>").find("input."+l).bind("keyup search",function(){c=n.find("thead").find("input."+l).map(function(){return(b(this).val()|| "").toLowerCase()}).get();""===c.join("")?n.find("tr").show():n.find("tbody").find("tr:not(."+h.cssChildRow+")").each(function(){d=!0;j=b(this).nextUntil("tr:not(."+h.cssChildRow+")");g=j.length&&(k&&k.hasOwnProperty("filter_childRows")&&"undefined"!==typeof k.filter_childRows?k.filter_childRows:1)?j.text():"";i=b(this).find("td");for(e=0;e<m;e++)f=b.inArray(c[e],(i.eq(e).text()+g).toLowerCase()),""!==c[e]&&(!k.filter_startsWith&&0<=f||k.filter_startsWith&&0===f)?d=d?!0:!1:""!==c[e]&&(d=!1);b(this)[d? "show":"hide"]();if(j.length)j[d?"show":"hide"]()});n.trigger("applyWidgets")});h.debug&&b.tablesorter.benchmark("Applying Filter widget",o)}}});
-b.tablesorter.addWidget({id:"stickyHeaders",format:function(a){if(!b(a).hasClass("hasStickyHeaders")){var e=b(a).addClass("hasStickyHeaders"),c=a.config.widgetOptions,d=b(window),g=b(a).find("thead"),f=g.find("tr").children(),j=c.stickyHeaders||"tablesorter-stickyheader",i=f.eq(0),h=2*parseInt(f.eq(0).css("border-left-width"),10),k=g.find("tr.tablesorter-header").clone().removeClass("tablesorter-header").addClass(j).css({width:g.outerWidth()+ h,position:"fixed",left:i.offset().left,marginLeft:-h,top:0,visibility:"hidden",zIndex:10}),l=k.children(),m;e.bind("sortEnd",function(a,c){var d=b(c).find("thead tr"),e=d.filter("."+j).children();d.filter(":not(."+j+")").children().each(function(a){e.eq(a).attr("class",b(this).attr("class"))})}).bind("pagerComplete",function(){d.resize()});f.each(function(a){var c=b(this);l.eq(a).width(c.width()).bind("click",function(b){c.trigger(b)}).bind("mousedown",function(){this.onselectstart=function(){return!1}; return!1})});g.prepend(k);d.scroll(function(){var b=i.offset(),a=d.scrollTop(),a=a>b.top&&a<b.top+e.find("tbody").height()?"visible":"hidden";k.css({left:b.left-d.scrollLeft(),visibility:a});a!==m&&(d.resize(),m=a)}).resize(function(){k.css({left:i.offset().left-d.scrollLeft(),width:g.outerWidth()+2*h});l.each(function(a){b(this).width(f.eq(a).width())})})}}});
-b.tablesorter.addWidget({id:"resizable",format:function(a){if(!b(a).hasClass("hasResizable")){b(a).addClass("hasResizable");var e,c,d=a.config, g=b(d.headerList).filter(":gt(0)"),f=0,j=null,i=null,h=function(){f=0;j=i=null;b(window).trigger("resize")};if(c=b.tablesorter.storage?b.tablesorter.storage(a,"tablesorter-resizable"):"")for(e in c)!isNaN(e)&&e<d.headerList.length&&b(d.headerList[e]).width(c[e]);g.each(function(){b(this).append('<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;height:100%;width:20px;left:-20px;top:0;z-index:1;"></div>').wrapInner('<div style="position:relative;height:100%;width:100%"></div>')}).bind("mousemove", function(a){if(0!==f&&j){var b=a.pageX-f;j.width()<-b||i&&i.width()<=b||(i.width(i.width()+b),f=a.pageX)}}).bind("mouseup",function(){c&&b.tablesorter.storage&&j&&(c[i.index()]=i.width(),b.tablesorter.storage(a,"tablesorter-resizable",c));h();return!1}).find(".tablesorter-resizer").bind("mousedown",function(a){j=b(a.target).closest("th");i=j.prev();f=a.pageX});b(a).find("thead").bind("mouseup mouseleave",function(){h()})}}});
-b.tablesorter.addWidget({id:"saveSort",init:function(a,b,c){c.format(a,!0)}, format:function(a,e){var c,d,g=a.config;c={sortList:g.sortList};g.debug&&(d=new Date);b(a).hasClass("hasSaveSort")?a.hasInitialized&&b.tablesorter.storage&&(b.tablesorter.storage(a,"tablesorter-savesort",c),g.debug&&b.tablesorter.benchmark("saveSort widget: Saving last sort: "+g.sortList,d)):(b(a).addClass("hasSaveSort"),c="",b.tablesorter.storage&&(c=(c=b.tablesorter.storage(a,"tablesorter-savesort"))&&c.hasOwnProperty("sortList")&&b.isArray(c.sortList)?c.sortList:"",g.debug&&b.tablesorter.benchmark("saveSort: Last sort loaded: "+ c,d)),e&&c&&0<c.length?g.sortList=c:a.hasInitialized&&c&&0<c.length&&b(a).trigger("sorton",[c]))}})
+b.tablesorter.storage=function(a,e,d){var c,g=!1;c={};var i=a.id||b(".tablesorter").index(b(a)),f=window.location.pathname;try{g=!!localStorage.getItem}catch(j){}b.parseJSON&&(g?c=b.parseJSON(localStorage[e])||{}:(c=document.cookie.split(/[;\s|=]/),a=b.inArray(e,c)+1,c=0!==a?b.parseJSON(c[a])||{}:{}));if(d&&JSON&&JSON.hasOwnProperty("stringify")){if(!c[f]||!c[f][i])c[f]||(c[f]={});c[f][i]=d;g?localStorage[e]=JSON.stringify(c):(a=new Date,a.setTime(a.getTime()+31536E6),document.cookie= e+"="+JSON.stringify(c).replace(/\"/g,'"')+"; expires="+a.toGMTString()+"; path=/")}else return c&&c.hasOwnProperty(f)&&c[f].hasOwnProperty(i)?c[f][i]:{}};
+b.tablesorter.addWidget({id:"uitheme",format:function(a){var e,d,c,g,i,f=b(a),j=a.config,h=j.widgetOptions,k=["ui-icon-arrowthick-2-n-s","ui-icon-arrowthick-1-s","ui-icon-arrowthick-1-n"],k=j.widgetUitheme&&j.widgetUitheme.hasOwnProperty("css")?j.widgetUitheme.css||k:h&&h.hasOwnProperty("uitheme")?h.uitheme:k;c=k.join(" ");j.debug&&(e=new Date); f.hasClass("ui-theme")||(f.addClass("ui-widget ui-widget-content ui-corner-all ui-theme"),b.each(j.headerList,function(){b(this).addClass("ui-widget-header ui-corner-all ui-state-default").append('<span class="ui-icon"/>').wrapInner('<div class="tablesorter-inner"/>').hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")})}));b.each(j.headerList,function(a){g=b(this);if(this.sortDisabled)g.find("span.ui-icon").removeClass(c+" ui-icon");else{d=g.hasClass(j.cssAsc)? k[1]:g.hasClass(j.cssDesc)?k[2]:g.hasClass(j.cssHeader)?k[0]:"";i=f.hasClass("hasStickyHeaders")?f.find("tr."+(h.stickyHeaders||"tablesorter-stickyHeader")).find("th").eq(a).add(g):g;i[d===k[0]?"removeClass":"addClass"]("ui-state-active").find("span.ui-icon").removeClass(c).addClass(d)}});j.debug&&b.tablesorter.benchmark("Applying uitheme widget",e)}});
+b.tablesorter.addWidget({id:"columns",format:function(a){var e,d,c,g,i=a.config,f=i.sortList,j=f.length,h=["primary","secondary","tertiary"],h=i.widgetColumns&& i.widgetColumns.hasOwnProperty("css")?i.widgetColumns.css||h:i.widgetOptions&&i.widgetOptions.hasOwnProperty("columns")?i.widgetOptions.columns||h:h;c=h.length-1;g=h.join(" ");i.debug&&(d=new Date);f&&f[0]?b("tr:visible",a.tBodies[0]).each(function(a){e=b(this).children().removeClass(g);e.eq(f[0][0]).addClass(h[0]);if(1<j)for(a=1;a<j;a++)e.eq(f[a][0]).addClass(h[a]||h[c])}):b("td",a.tBodies[0]).removeClass(g);i.debug&&b.tablesorter.benchmark("Applying Columns widget",d)}});
+b.tablesorter.addWidget({id:"filter", format:function(a){if(!b(a).hasClass("hasFilters")){var e,d,c,g,i,f,j,h=a.config,k=h.widgetOptions,l=k.filter_cssFilter||"tablesorter-filter",n=h.headerList.length,m=b(a).addClass("hasFilters"),a='<tr class="'+l+'">',o;h.debug&&(o=new Date);for(e=0;e<n;e++)a+='<td><input type="search" data-col="'+e+'" class="'+l,a+=h.headers[e]&&h.headers[e].hasOwnProperty("filter")&&!1===h.headers[e].filter||b(h.headerList[e]).is(".filter-false")?' disabled" disabled':'"',a+="></td>";m.find("thead").append(a+="</tr>").find("input."+ l).bind("keyup search",function(){d=m.find("thead").find("input."+l).map(function(){return(b(this).val()||"").toLowerCase()}).get();""===d.join("")?m.find("tr").show():m.find("tbody").find("tr:not(."+h.cssChildRow+")").each(function(){c=!0;f=b(this).nextUntil("tr:not(."+h.cssChildRow+")");g=f.length&&(k&&k.hasOwnProperty("filter_childRows")&&"undefined"!==typeof k.filter_childRows?k.filter_childRows:1)?f.text():"";j=b(this).find("td");for(e=0;e<n;e++)i=(j.eq(e).text()+g).toLowerCase().indexOf(d[e]), ""!==d[e]&&(!k.filter_startsWith&&0<=i||k.filter_startsWith&&0===i)?c=c?!0:!1:""!==d[e]&&(c=!1);b(this)[c?"show":"hide"]();if(f.length)f[c?"show":"hide"]()});m.trigger("applyWidgets")});h.debug&&b.tablesorter.benchmark("Applying Filter widget",o)}}});
+b.tablesorter.addWidget({id:"stickyHeaders",format:function(a){if(!b(a).hasClass("hasStickyHeaders")){var e=b(a).addClass("hasStickyHeaders"),d=a.config.widgetOptions,c=b(window),g=b(a).find("thead"),i=g.find("tr").children(),f=d.stickyHeaders||"tablesorter-stickyHeader", j=i.eq(0),h=g.find("tr.tablesorter-header").clone().removeClass("tablesorter-header").addClass(f).css({width:g.outerWidth(!0),position:"fixed",left:j.offset().left,margin:0,top:0,visibility:"hidden",zIndex:10}),k=h.children(),l="";e.bind("sortEnd",function(a,c){var d=b(c).find("thead tr"),e=d.filter("."+f).children();d.filter(":not(."+f+")").children().each(function(a){e.eq(a).attr("class",b(this).attr("class"))})}).bind("pagerComplete",function(){c.resize()});i.each(function(a){var c=b(this);k.eq(a).bind("click", function(a){c.trigger(a)}).bind("mousedown",function(){this.onselectstart=function(){return!1};return!1}).find(".tablesorter-header-inner").width(c.find(".tablesorter-header-inner").width())});g.prepend(h);c.scroll(function(){var a=j.offset(),b=c.scrollTop(),b=b>a.top&&b<a.top+e.find("tbody").height()?"visible":"hidden";h.css({left:a.left-c.scrollLeft(),visibility:b});b!==l&&(c.resize(),l=b)}).resize(function(){h.css({left:j.offset().left-c.scrollLeft(),width:g.outerWidth()});k.find(".tablesorter-header-inner").each(function(a){b(this).width(i.eq(a).find(".tablesorter-header-inner").width())})})}}}); 
+b.tablesorter.addWidget({id:"resizable",format:function(a){if(!b(a).hasClass("hasResizable")){b(a).addClass("hasResizable");var e,d,c=a.config,g=b(c.headerList).filter(":gt(0)"),i=0,f=null,j=null,h=function(){i=0;f=j=null;b(window).trigger("resize")};if(d=b.tablesorter.storage?b.tablesorter.storage(a,"tablesorter-resizable"):"")for(e in d)!isNaN(e)&&e<c.headerList.length&&b(c.headerList[e]).width(d[e]);g.each(function(){b(this).append('<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;height:100%;width:20px;left:-20px;top:0;z-index:1;"></div>').wrapInner('<div style="position:relative;height:100%;width:100%"></div>')}).bind("mousemove", function(a){if(0!==i&&f){var b=a.pageX-i;f.width()<-b||j&&j.width()<=b||(j.width(j.width()+b),i=a.pageX)}}).bind("mouseup",function(){d&&b.tablesorter.storage&&f&&(d[j.index()]=j.width(),b.tablesorter.storage(a,"tablesorter-resizable",d));h();return!1}).find(".tablesorter-resizer").bind("mousedown",function(a){f=b(a.target).closest("th");j=f.prev();i=a.pageX});b(a).find("thead").bind("mouseup mouseleave",function(){h()})}}});
+b.tablesorter.addWidget({id:"saveSort",init:function(a,b,d){d.format(a,!0)}, format:function(a,e){var d,c,g=a.config;d={sortList:g.sortList};g.debug&&(c=new Date);b(a).hasClass("hasSaveSort")?a.hasInitialized&&b.tablesorter.storage&&(b.tablesorter.storage(a,"tablesorter-savesort",d),g.debug&&b.tablesorter.benchmark("saveSort widget: Saving last sort: "+g.sortList,c)):(b(a).addClass("hasSaveSort"),d="",b.tablesorter.storage&&(d=(d=b.tablesorter.storage(a,"tablesorter-savesort"))&&d.hasOwnProperty("sortList")&&b.isArray(d.sortList)?d.sortList:"",g.debug&&b.tablesorter.benchmark("saveSort: Last sort loaded: "+ d,c)),e&&d&&0<d.length?g.sortList=d:a.hasInitialized&&d&&0<d.length&&b(a).trigger("sorton",[d]))}})
 })(jQuery);
\ No newline at end of file
diff --git a/data/js/newShow.js b/data/js/newShow.js
index 7ba6cf7f59d5b79eb4fe8f81f55261f56eaae116..1f5dd8db9f8f4a0fdec3930da886ffee1c3fad3d 100644
--- a/data/js/newShow.js
+++ b/data/js/newShow.js
@@ -54,8 +54,15 @@ $(document).ready(function(){
                     else
                         resultStr += '<a href="http://thetvdb.com/?tab=series&id=' + obj[0] + '" onclick=\"window.open(this.href, \'_blank\'); return false;\" ><b>' + obj[1] + '</b></a>';
 
-                    if (obj[2] != null)
-                        resultStr += ' (started on ' + obj[2] + ')';
+                    if (obj[2] != null) {
+                        var startDate = new Date(obj[2]);
+                        var today = new Date();
+                        if (startDate>today)
+                                resultStr += ' (will debut on ' + obj[2] + ')';
+                        else
+                                resultStr += ' (started on ' + obj[2] + ')';
+                    }
+                    
                     resultStr += '<br />';
                 });
                 resultStr += '</ul>';
diff --git a/init.fedora b/init.fedora
index 9807645e2032cc94d171cf12acfb424d7b6dcdba..190b2f9428d86206e9a8bac212196ec663923930 100755
--- a/init.fedora
+++ b/init.fedora
@@ -1,3 +1,5 @@
+#!/bin/sh
+#
 ### BEGIN INIT INFO
 # Provides:          sickbeard
 # Required-Start:    $all
@@ -23,7 +25,7 @@ lockfile=/var/lock/subsys/$prog
 ## the defaults
 username=${SB_USER-sickbeard}
 homedir=${SB_HOME-/opt/sickbeard}
-datadir=${SB_DATA-~/.sickbeard}
+datadir=${SB_DATA-/opt/sickbeard}
 pidfile=${SB_PIDFILE-/var/run/sickbeard/sickbeard.pid}
 nice=${SB_NICE-}
 ##
@@ -33,13 +35,13 @@ options=" --daemon --nolaunch --pidfile=${pidfile} --datadir=${datadir}"
 
 # create PID directory if not exist and ensure the SickBeard user can write to it
 if [ ! -d $pidpath ]; then
-	mkdir -p $pidpath
-	chown $username $pidpath
+        mkdir -p $pidpath
+        chown $username $pidpath
 fi
 
 if [ ! -d $datadir ]; then
-	mkdir -p $datadir
-	chown $username $datadir
+        mkdir -p $datadir
+        chown $username $datadir
 fi
 
 start() {
@@ -89,4 +91,3 @@ case "$1" in
         echo $"Usage: $0 {start|stop|status|restart|try-restart|force-reload}"
         exit 2
 esac
-
diff --git a/lib/pygithub/__init__.py b/lib/pygithub/__init__.py
deleted file mode 100644
index ad56e08736281760d4c5d20c45411ed16e552588..0000000000000000000000000000000000000000
--- a/lib/pygithub/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-# <http://www.opensource.org/licenses/mit-license.php>
-"""
-github module.
-"""
-__all__ = ['github','ghsearch','githubsync']
diff --git a/lib/pygithub/ghsearch.py b/lib/pygithub/ghsearch.py
deleted file mode 100644
index 586e502b18297fd16026e579ec70fe611e297284..0000000000000000000000000000000000000000
--- a/lib/pygithub/ghsearch.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-# <http://www.opensource.org/licenses/mit-license.php>
-"""
-Search script.
-"""
-
-import sys
-
-import github
-
-def usage():
-    """display the usage and exit"""
-    print "Usage:  %s keyword [keyword...]" % (sys.argv[0])
-    sys.exit(1)
-
-def mk_url(repo):
-    return "http://github.com/%s/%s" % (repo.username, repo.name)
-
-if __name__ == '__main__':
-    g = github.GitHub()
-    if len(sys.argv) < 2:
-        usage()
-    res = g.repos.search(' '.join(sys.argv[1:]))
-
-    for repo in res:
-        try:
-            print "Found %s at %s" % (repo.name, mk_url(repo))
-        except AttributeError:
-            print "Bug: Couldn't format %s" % repo.__dict__
diff --git a/lib/pygithub/github.py b/lib/pygithub/github.py
deleted file mode 100644
index c0bfad7780f8bf8a0a5b6cb75397c2dcc9efb59b..0000000000000000000000000000000000000000
--- a/lib/pygithub/github.py
+++ /dev/null
@@ -1,520 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-# <http://www.opensource.org/licenses/mit-license.php>
-"""
-Interface to github's API (v2).
-
-Basic usage:
-
-g = GitHub()
-
-for r in g.user.search('dustin'):
-    print r.name
-
-See the GitHub docs or README.markdown for more usage.
-
-Copyright (c) 2007  Dustin Sallings <dustin@spy.net>
-"""
-
-# GAE friendly URL detection (theoretically)
-try:
-    import urllib2
-    default_fetcher = urllib2.urlopen
-except LoadError:
-    pass
-
-import urllib
-import xml
-import xml.dom.minidom
-
-def _string_parser(x):
-    """Extract the data from the first child of the input."""
-    return x.firstChild.data
-
-_types = {
-    'string': _string_parser,
-    'integer': lambda x: int(_string_parser(x)),
-    'float': lambda x: float(_string_parser(x)),
-    'datetime': _string_parser,
-    'boolean': lambda x: _string_parser(x) == 'true'
-}
-
-def _parse(el):
-    """Generic response parser."""
-
-    type = 'string'
-    if el.attributes and 'type' in el.attributes.keys():
-        type = el.attributes['type'].value
-    elif el.localName in _types:
-        type = el.localName
-    elif len(el.childNodes) > 1:
-        # This is a container, find the child type
-        type = None
-        ch = el.firstChild
-        while ch and not type:
-            if ch.localName == 'type':
-                type = ch.firstChild.data
-            ch = ch.nextSibling
-
-    if not type:
-        raise Exception("Can't parse %s, known: %s"
-                        % (el.toxml(), repr(_types.keys())))
-
-    return _types[type](el)
-
-def parses(t):
-    """Parser for a specific type in the github response."""
-    def f(orig):
-        orig.parses = t
-        return orig
-    return f
-
-def with_temporary_mappings(m):
-    """Allow temporary localized altering of type mappings."""
-    def f(orig):
-        def every(self, *args):
-            global _types
-            o = _types.copy()
-            for k,v in m.items():
-                if v:
-                    _types[k] = v
-                else:
-                    del _types[k]
-            try:
-                return orig(self, *args)
-            finally:
-                _types = o
-        return every
-    return f
-
-@parses('array')
-def _parseArray(el):
-    rv = []
-    ch = el.firstChild
-    while ch:
-        if ch.nodeType != xml.dom.Node.TEXT_NODE and ch.firstChild:
-            rv.append(_parse(ch))
-        ch=ch.nextSibling
-    return rv
-
-class BaseResponse(object):
-    """Base class for XML Response Handling."""
-
-    def __init__(self, el):
-        ch = el.firstChild
-        while ch:
-            if ch.nodeType != xml.dom.Node.TEXT_NODE and ch.firstChild:
-                ln = ch.localName.replace('-', '_')
-                self.__dict__[ln] = _parse(ch)
-            ch=ch.nextSibling
-
-    def __repr__(self):
-        return "<<%s>>" % str(self.__class__)
-
-class User(BaseResponse):
-    """A github user."""
-
-    parses = 'user'
-
-    def __repr__(self):
-        return "<<User %s>>" % self.name
-
-class Plan(BaseResponse):
-    """A github plan."""
-
-    parses = 'plan'
-
-    def __repr__(self):
-        return "<<Plan %s>>" % self.name
-
-class Repository(BaseResponse):
-    """A repository."""
-
-    parses = 'repository'
-
-    @property
-    def owner_name(self):
-        if hasattr(self, 'owner'):
-            return self.owner
-        else:
-            return self.username
-
-    def __repr__(self):
-        return "<<Repository %s/%s>>" % (self.owner_name, self.name)
-
-class PublicKey(BaseResponse):
-    """A public key."""
-
-    parses = 'public-key'
-    title = 'untitled'
-
-    def __repr__(self):
-        return "<<Public key %s>>" % self.title
-
-class Commit(BaseResponse):
-    """A commit."""
-
-    parses = 'commit'
-
-    def __repr__(self):
-        return "<<Commit: %s>>" % self.id
-
-class Parent(Commit):
-    """A commit parent."""
-
-    parses = 'parent'
-
-class Author(User):
-    """A commit author."""
-
-    parses = 'author'
-
-class Committer(User):
-    """A commit committer."""
-
-    parses = 'committer'
-
-class Issue(BaseResponse):
-    """An issue within the issue tracker."""
-
-    parses = 'issue'
-
-    def __repr__(self):
-        return "<<Issue #%d>>" % self.number
-
-class Label(BaseResponse):
-    """A Label within the issue tracker."""
-    parses = 'label'
-
-    def __repr__(self):
-        return "<<Label $%d>>" % self.number
-
-class Tree(BaseResponse):
-    """A Tree object."""
-
-    # Parsing is scoped to objects...
-    def __repr__(self):
-        return "<<Tree: %s>>" % self.name
-
-class Blob(BaseResponse):
-    """A Blob object."""
-
-    # Parsing is scoped to objects...
-    def __repr__(self):
-        return "<<Blob: %s>>" % self.name
-
-class Modification(BaseResponse):
-    """A modification object."""
-
-    # Parsing is scoped to usage
-    def __repr__(self):
-        return "<<Modification of %s>>" % self.filename
-
-class Network(BaseResponse):
-    """A network entry."""
-
-    parses = 'network'
-
-    def __repr__(self):
-        return "<<Network of %s/%s>>" % (self.owner, self.name)
-
-# Load the known types.
-for __t in (t for t in globals().values() if hasattr(t, 'parses')):
-    _types[__t.parses] = __t
-
-class BaseEndpoint(object):
-
-    BASE_URL = 'https://github.com/api/v2/xml/'
-
-    def __init__(self, user, token, fetcher):
-        self.user = user
-        self.token = token
-        self.fetcher = fetcher
-
-    def _raw_fetch(self, path):
-        p = self.BASE_URL + path
-        args = ''
-        if self.user and self.token:
-            params = '&'.join(['login=' + urllib.quote(self.user),
-                               'token=' + urllib.quote(self.token)])
-            if '?' in path:
-                p += params
-            else:
-                p += '?' + params
-        return self.fetcher(p).read()
-
-    def _fetch(self, path):
-        return xml.dom.minidom.parseString(self._raw_fetch(path))
-
-    def _post(self, path, **kwargs):
-        p = {'login': self.user, 'token': self.token}
-        p.update(kwargs)
-        return self.fetcher(self.BASE_URL + path, urllib.urlencode(p)).read()
-
-    def _parsed(self, path):
-        doc = self._fetch(path)
-        return _parse(doc.documentElement)
-
-    def _posted(self,path,**kwargs):
-        stuff = self._post(path,**kwargs)
-        doc = xml.dom.minidom.parseString(stuff)
-        return _parse(doc.documentElement)
-
-class UserEndpoint(BaseEndpoint):
-
-    def search(self, query):
-        """Search for a user."""
-        return self._parsed('user/search/' + query)
-
-    def show(self, username):
-        """Get the info for a user."""
-        return self._parsed('user/show/' + username)
-
-    def keys(self):
-        """Get the public keys for a user."""
-        return self._parsed('user/keys')
-
-    def removeKey(self, keyId):
-        """Remove the key with the given ID (as retrieved from keys)"""
-        self._post('user/key/remove', id=keyId)
-
-    def addKey(self, name, key):
-        """Add an ssh key."""
-        self._post('user/key/add', name=name, key=key)
-
-class RepositoryEndpoint(BaseEndpoint):
-
-    def forUser(self, username):
-        """Get the repositories for the given user."""
-        return self._parsed('repos/show/' + username)
-
-    def branches(self, user, repo):
-        """List the branches for a repo."""
-        doc = self._fetch("repos/show/" + user + "/" + repo + "/branches")
-        rv = {}
-        for c in doc.documentElement.childNodes:
-            if c.nodeType != xml.dom.Node.TEXT_NODE:
-                rv[c.localName] = str(c.firstChild.data)
-        return rv
-
-    def search(self, term):
-        """Search for repositories."""
-        return self._parsed('repos/search/' + urllib.quote_plus(term))
-
-    def show(self, user, repo):
-        """Lookup an individual repository."""
-        return self._parsed('/'.join(['repos', 'show', user, repo]))
-
-    def watch(self, user, repo):
-        """Watch a repository."""
-        self._post('repos/watch/' + user + '/' + repo)
-
-    def unwatch(self, user, repo):
-        """Stop watching a repository."""
-        self._post('repos/unwatch/' + user + '/' + repo)
-
-    def watched(self, user):
-        """Get watched repositories of a user."""
-        return self._parsed('repos/watched/' + user)
-
-    def network(self, user, repo):
-        """Get the network for a given repo."""
-        return self._parsed('repos/show/' + user + '/' + repo + '/network')
-
-    def setVisible(self, repo, public=True):
-        """Set the visibility of the given repository (owned by the current user)."""
-        if public:
-            path = 'repos/set/public/' + repo
-        else:
-            path = 'repos/set/private/' + repo
-        self._post(path)
-
-    def create(self, name, description='', homepage='', public=1):
-        """Create a new repository."""
-        self._post('repos/create', name=name, description=description,
-                   homepage=homepage, public=str(public))
-
-    def delete(self, repo):
-        """Delete a repository."""
-        self._post('repos/delete/' + repo)
-
-    def fork(self, user, repo):
-        """Fork a user's repo."""
-        self._post('repos/fork/' + user + '/' + repo)
-
-    def collaborators(self, user, repo):
-        """Find all of the collaborators of one of your repositories."""
-        return self._parsed('repos/show/%s/%s/collaborators' % (user, repo))
-
-    def addCollaborator(self, repo, username):
-        """Add a collaborator to one of your repositories."""
-        self._post('repos/collaborators/' + repo + '/add/' + username)
-
-    def removeCollaborator(self, repo, username):
-        """Remove a collaborator from one of your repositories."""
-        self._post('repos/collaborators/' + repo + '/remove/' + username)
-
-    def collaborators_all(self):
-        """Find all of the collaborators of every of your repositories.
-
-        Returns a dictionary with reponame as key and a list of collaborators as value."""
-        ret = {}
-        for reponame in (rp.name for rp in self.forUser(self.user)):
-            ret[reponame] = self.collaborators(self.user, reponame)
-        return ret
-
-    def addCollaborator_all(self, username):
-        """Add a collaborator to all of your repositories."""
-        for reponame in (rp.name for rp in self.forUser(self.user)):
-            self.addCollaborator(reponame, username)
-
-    def removeCollaborator_all(self, username):
-        """Remove a collaborator from all of your repositories."""
-        for reponame in (rp.name for rp in self.forUser(self.user)):
-            self.removeCollaborator(reponame, username)
-
-    def deployKeys(self, repo):
-        """List the deploy keys for the given repository.
-
-        The repository must be owned by the current user."""
-        return self._parsed('repos/keys/' + repo)
-
-    def addDeployKey(self, repo, title, key):
-        """Add a deploy key to a repository."""
-        self._post('repos/key/' + repo + '/add', title=title, key=key)
-
-    def removeDeployKey(self, repo, keyId):
-        """Remove a deploy key."""
-        self._post('repos/key/' + repo + '/remove', id=keyId)
-
-class CommitEndpoint(BaseEndpoint):
-
-    def forBranch(self, user, repo, branch='master'):
-        """Get the commits for the given branch."""
-        return self._parsed('/'.join(['commits', 'list', user, repo, branch]))
-
-    def forFile(self, user, repo, path, branch='master'):
-        """Get the commits for the given file within the given branch."""
-        return self._parsed('/'.join(['commits', 'list', user, repo, branch, path]))
-
-    @with_temporary_mappings({'removed': _parseArray,
-                              'added': _parseArray,
-                              'modified': Modification,
-                              'diff': _string_parser,
-                              'filename': _string_parser})
-    def show(self, user, repo, sha):
-        """Get an individual commit."""
-        c = self._parsed('/'.join(['commits', 'show', user, repo, sha]))
-        # Some fixup due to weird XML structure
-        if hasattr(c, 'removed'):
-            c.removed = [i[0] for i in c.removed]
-        if hasattr(c, 'added'):
-            c.added = [i[0] for i in c.added]
-        return c
-
-class IssuesEndpoint(BaseEndpoint):
-
-    @with_temporary_mappings({'user': None})
-    def list(self, user, repo, state='open'):
-        """Get the list of issues for the given repo in the given state."""
-        return self._parsed('/'.join(['issues', 'list', user, repo, state]))
-
-    @with_temporary_mappings({'user': None})
-    def show(self, user, repo, issue_id):
-        """Show an individual issue."""
-        return self._parsed('/'.join(['issues', 'show', user, repo, str(issue_id)]))
-
-    def add_label(self, user, repo, issue_id, label):
-        """Add a label to an issue."""
-        self._post('issues/label/add/' + user + '/'
-                       + repo + '/' + label + '/' + str(issue_id))
-
-    def remove_label(self, user, repo, issue_id, label):
-        """Remove a label from an issue."""
-        self._post('issues/label/remove/' + user + '/'
-                   + repo + '/' + label + '/' + str(issue_id))
-
-    def close(self, user, repo, issue_id):
-        """Close an issue."""
-        self._post('/'.join(['issues', 'close', user, repo, str(issue_id)]))
-
-    def reopen(self, user, repo, issue_id):
-        """Reopen an issue."""
-        self._post('/'.join(['issues', 'reopen', user, repo, str(issue_id)]))
-
-    def new(self, user, repo, title, body=''):
-        """Create a new issue."""
-        return self._posted('/'.join(['issues', 'open', user, repo]),
-                            title=title, body=body)
-
-    def edit(self, user, repo, issue_id, title, body):
-        """Create a new issue."""
-        self._post('/'.join(['issues', 'edit', user, repo, str(issue_id)]),
-                   title=title, body=body)
-
-class ObjectsEndpoint(BaseEndpoint):
-
-    @with_temporary_mappings({'tree': Tree, 'type': _string_parser})
-    def tree(self, user, repo, t):
-        """Get the given tree from the given repo."""
-        tl = self._parsed('/'.join(['tree', 'show', user, repo, t]))
-        return dict([(t.name, t) for t in tl])
-
-    @with_temporary_mappings({'blob': Blob})
-    def blob(self, user, repo, t, fn):
-        return self._parsed('/'.join(['blob', 'show', user, repo, t, fn]))
-
-    def raw_blob(self, user, repo, sha):
-        """Get a raw blob from a repo."""
-        path = 'blob/show/%s/%s/%s' % (user, repo, sha)
-        return self._raw_fetch(path)
-
-class GitHub(object):
-    """Interface to github."""
-
-    def __init__(self, user=None, token=None, fetcher=default_fetcher):
-        self.user = user
-        self.token = token
-        self.fetcher = fetcher
-
-    @property
-    def users(self):
-        """Get access to the user API."""
-        return UserEndpoint(self.user, self.token, self.fetcher)
-
-    @property
-    def repos(self):
-        """Get access to the user API."""
-        return RepositoryEndpoint(self.user, self.token, self.fetcher)
-
-    @property
-    def commits(self):
-        return CommitEndpoint(self.user, self.token, self.fetcher)
-
-    @property
-    def issues(self):
-        return IssuesEndpoint(self.user, self.token, self.fetcher)
-
-    @property
-    def objects(self):
-        return ObjectsEndpoint(self.user, self.token, self.fetcher)
diff --git a/lib/pygithub/githubsync.py b/lib/pygithub/githubsync.py
deleted file mode 100644
index 50d70a076ad566970637f9f812dd733ee07f9b26..0000000000000000000000000000000000000000
--- a/lib/pygithub/githubsync.py
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-# <http://www.opensource.org/licenses/mit-license.php>
-"""
-Grab all of a user's projects from github.
-"""
-
-import os
-import sys
-import subprocess
-
-import github
-
-def check_for_old_format(path, url):
-    p = subprocess.Popen(['git', '--git-dir=' + path, 'config',
-        'remote.origin.fetch'], stdout = subprocess.PIPE)
-    stdout, stderr = p.communicate()
-    if stdout.strip() != '+refs/*:refs/*':
-        print "Not properly configured for mirroring, repairing."
-        subprocess.call(['git', '--git-dir=' + path, 'remote', 'rm', 'origin'])
-        add_mirror(path, url)
-
-def add_mirror(path, url):
-    subprocess.call(['git', '--git-dir=' + path, 'remote', 'add', '--mirror',
-            'origin', url])
-
-def sync(path, url, repo_name):
-    p = os.path.join(path, repo_name) + ".git"
-    print "Syncing %s -> %s" % (repo_name, p)
-    if not os.path.exists(p):
-        subprocess.call(['git', 'clone', '--bare', url, p])
-        add_mirror(p, url)
-    check_for_old_format(p, url)
-    subprocess.call(['git', '--git-dir=' + p, 'fetch', '-f'])
-
-def sync_user_repo(path, repo):
-    sync(path, "git://github.com/%s/%s" % (repo.owner, repo.name), repo.name)
-
-def usage():
-    sys.stderr.write("Usage:  %s username destination_url\n" % sys.argv[0])
-    sys.stderr.write(
-        """Ensures you've got the latest stuff for the given user.
-
-Also, if the file $HOME/.github-private exists, it will be read for
-additional projects.
-
-Each line must be a simple project name (e.g. py-github), a tab character,
-and a git URL.
-""")
-
-if __name__ == '__main__':
-    try:
-        user, path = sys.argv[1:]
-    except ValueError:
-        usage()
-        exit(1)
-
-    privfile = os.path.join(os.getenv("HOME"), ".github-private")
-    if os.path.exists(privfile):
-        f = open(privfile)
-        for line in f:
-            name, url = line.strip().split("\t")
-            sync(path, url, name)
-
-    gh = github.GitHub()
-
-    for repo in gh.repos.forUser(user):
-        sync_user_repo(path, repo)
diff --git a/lib/pygithub/githubtest.py b/lib/pygithub/githubtest.py
deleted file mode 100644
index bfb73cd892db1262689bad17bc74c6617fbd83f9..0000000000000000000000000000000000000000
--- a/lib/pygithub/githubtest.py
+++ /dev/null
@@ -1,493 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-# <http://www.opensource.org/licenses/mit-license.php>
-"""
-Defines and runs unittests.
-"""
-
-import urllib
-import hashlib
-import unittest
-
-import github
-
-class BaseCase(unittest.TestCase):
-
-    def _gh(self, expUrl, filename):
-
-        def opener(url):
-            self.assertEquals(expUrl, url)
-            return open(filename)
-        return github.GitHub(fetcher=opener)
-
-    def _agh(self, expUrl, u, t, filename):
-
-        def opener(url):
-            self.assertEquals(expUrl, url + '?login=' + u + '&token=' + t)
-            return open(filename)
-        return github.GitHub(fetcher=opener)
-
-    def _ghp(self, expUrl, u, t, **kv):
-
-        def opener(url, data):
-            h = {'login': u, 'token': t}
-            h.update(kv)
-            self.assertEquals(github.BaseEndpoint.BASE_URL + expUrl, url)
-            self.assertEquals(sorted(data.split('&')),
-                              sorted(urllib.urlencode(h).split('&')))
-        return github.GitHub(u, t, fetcher=opener)
-
-class UserTest(BaseCase):
-
-    def __loadUserSearch(self):
-        return self._gh('http://github.com/api/v2/xml/user/search/dustin',
-            'data/user.search.xml').users.search('dustin')
-
-    def __loadUser(self, which, u=None, p=None):
-        if u:
-            return self._agh('http://github.com/api/v2/xml/user/show/dustin'
-                              + '?login=' + u + '&token=' + p,
-                              u, p, 'data/' + which).users.show('dustin')
-
-        else:
-            return self._gh('http://github.com/api/v2/xml/user/show/dustin',
-                             'data/' + which).users.show('dustin')
-
-    def testUserSearch(self):
-        """Test the base properties of the user object."""
-        u = self.__loadUserSearch()[0]
-        self.assertEquals("Dustin Sallings", u.fullname)
-        self.assertEquals("dustin", u.name)
-        self.assertEquals("dustin@spy.net", u.email)
-        self.assertEquals("Santa Clara, CA", u.location)
-        self.assertEquals("Ruby", u.language)
-        self.assertEquals(35, u.actions)
-        self.assertEquals(77, u.repos)
-        self.assertEquals(78, u.followers)
-        self.assertEquals('user-1779', u.id)
-        self.assertAlmostEquals(12.231684, u.score)
-        self.assertEquals('user', u.type)
-        self.assertEquals('2008-02-29T17:59:09Z', u.created)
-        self.assertEquals('2009-03-19T09:15:24.663Z', u.pushed)
-        self.assertEquals("<<User dustin>>", repr(u))
-
-    def testUserPublic(self):
-        """Test the user show API with no authentication."""
-        u = self.__loadUser('user.public.xml')
-        self.assertEquals("Dustin Sallings", u.name)
-        # self.assertEquals(None, u.company)
-        self.assertEquals(10, u.following_count)
-        self.assertEquals(21, u.public_gist_count)
-        self.assertEquals(81, u.public_repo_count)
-        self.assertEquals('http://bleu.west.spy.net/~dustin/', u.blog)
-        self.assertEquals(1779, u.id)
-        self.assertEquals(82, u.followers_count)
-        self.assertEquals('dustin', u.login)
-        self.assertEquals('Santa Clara, CA', u.location)
-        self.assertEquals('dustin@spy.net', u.email)
-        self.assertEquals('2008-02-29T09:59:09-08:00', u.created_at)
-
-    def testUserPrivate(self):
-        """Test the user show API with extra info from auth."""
-        u = self.__loadUser('user.private.xml', 'dustin', 'blahblah')
-        self.assertEquals("Dustin Sallings", u.name)
-        # self.assertEquals(None, u.company)
-        self.assertEquals(10, u.following_count)
-        self.assertEquals(21, u.public_gist_count)
-        self.assertEquals(81, u.public_repo_count)
-        self.assertEquals('http://bleu.west.spy.net/~dustin/', u.blog)
-        self.assertEquals(1779, u.id)
-        self.assertEquals(82, u.followers_count)
-        self.assertEquals('dustin', u.login)
-        self.assertEquals('Santa Clara, CA', u.location)
-        self.assertEquals('dustin@spy.net', u.email)
-        self.assertEquals('2008-02-29T09:59:09-08:00', u.created_at)
-
-        # Begin private data
-
-        self.assertEquals("micro", u.plan.name)
-        self.assertEquals(1, u.plan.collaborators)
-        self.assertEquals(614400, u.plan.space)
-        self.assertEquals(5, u.plan.private_repos)
-        self.assertEquals(155191, u.disk_usage)
-        self.assertEquals(6, u.collaborators)
-        self.assertEquals(4, u.owned_private_repo_count)
-        self.assertEquals(5, u.total_private_repo_count)
-        self.assertEquals(0, u.private_gist_count)
-
-    def testKeysList(self):
-        """Test key listing."""
-        kl = self._agh('http://github.com/api/v2/xml/user/keys?login=dustin&token=blahblah',
-                       'dustin', 'blahblah', 'data/keys.xml').users.keys()
-        self.assertEquals(7, len(kl))
-        k = kl[0]
-
-        self.assertEquals('some key', k.title)
-        self.assertEquals(2181, k.id)
-        self.assertEquals(549, k.key.find('cdEXwCSjAIFp8iRqh3GOkxGyFSc25qv/MuOBg=='))
-
-    def testRemoveKey(self):
-        """Remove a key."""
-        self._ghp('user/key/remove',
-                  'dustin', 'p', id=828).users.removeKey(828)
-
-    def testAddKey(self):
-        """Add a key."""
-        self._ghp('user/key/add',
-                  'dustin', 'p', name='my key', key='some key').users.addKey(
-            'my key', 'some key')
-
-class RepoTest(BaseCase):
-
-    def __loadUserRepos(self):
-        return self._gh('http://github.com/api/v2/xml/repos/show/verbal',
-            'data/repos.xml').repos.forUser('verbal')
-
-    def testUserRepoList(self):
-        """Get a list of repos for a user."""
-        rs = self.__loadUserRepos()
-        self.assertEquals(10, len(rs))
-        r = rs[0]
-        self.assertEquals('A beanstalk client for the twisted network framework.',
-                          r.description)
-        self.assertEquals(2, r.watchers)
-        self.assertEquals(0, r.forks)
-        self.assertEquals('beanstalk-client-twisted', r.name)
-        self.assertEquals(False, r.private)
-        self.assertEquals('http://github.com/verbal/beanstalk-client-twisted',
-                          r.url)
-        self.assertEquals(True, r.fork)
-        self.assertEquals('verbal', r.owner)
-        # XXX:  Can't parse empty elements.  :(
-        # self.assertEquals('', r.homepage)
-
-    def testRepoSearch(self):
-        """Test searching a repository."""
-        rl = self._gh('http://github.com/api/v2/xml/repos/search/ruby+testing',
-                      'data/repos.search.xml').repos.search('ruby testing')
-        self.assertEquals(12, len(rl))
-
-        r = rl[0]
-        self.assertEquals('synthesis', r.name)
-        self.assertAlmostEquals(0.3234576, r.score, 4)
-        self.assertEquals(4656, r.actions)
-        self.assertEquals(2048, r.size)
-        self.assertEquals('Ruby', r.language)
-        self.assertEquals(26, r.followers)
-        self.assertEquals('gmalamid', r.username)
-        self.assertEquals('repo', r.type)
-        self.assertEquals('repo-3555', r.id)
-        self.assertEquals(1, r.forks)
-        self.assertFalse(r.fork)
-        self.assertEquals('Ruby test code analysis tool employing a '
-                          '"Synthesized Testing" strategy, aimed to reduce '
-                          'the volume of slower, coupled, complex wired tests.',
-                          r.description)
-        self.assertEquals('2009-01-08T13:45:06Z', r.pushed)
-        self.assertEquals('2008-03-11T23:38:04Z', r.created)
-
-    def testBranchList(self):
-        """Test branch listing for a repo."""
-        bl = self._gh('http://github.com/api/v2/xml/repos/show/schacon/ruby-git/branches',
-                      'data/repos.branches.xml').repos.branches('schacon', 'ruby-git')
-        self.assertEquals(4, len(bl))
-        self.assertEquals('ee90922f3da3f67ef19853a0759c1d09860fe3b3', bl['master'])
-
-    def testGetOneRepo(self):
-        """Fetch an individual repository."""
-        r = self._gh('http://github.com/api/v2/xml/repos/show/schacon/grit',
-                     'data/repo.xml').repos.show('schacon', 'grit')
-
-        self.assertEquals('Grit is a Ruby library for extracting information from a '
-                          'git repository in an object oriented manner - this fork '
-                          'tries to intergrate as much pure-ruby functionality as possible',
-                          r.description)
-        self.assertEquals(68, r.watchers)
-        self.assertEquals(4, r.forks)
-        self.assertEquals('grit', r.name)
-        self.assertFalse(r.private)
-        self.assertEquals('http://github.com/schacon/grit', r.url)
-        self.assertTrue(r.fork)
-        self.assertEquals('schacon', r.owner)
-        self.assertEquals('http://grit.rubyforge.org/', r.homepage)
-
-    def testGetRepoNetwork(self):
-        """Test network fetching."""
-        nl = self._gh('http://github.com/api/v2/xml/repos/show/dustin/py-github/network',
-                      'data/network.xml').repos.network('dustin', 'py-github')
-        self.assertEquals(5, len(nl))
-
-        n = nl[0]
-        self.assertEquals('Python interface for talking to the github API',
-                          n.description)
-        self.assertEquals('py-github', n.name)
-        self.assertFalse(n.private)
-        self.assertEquals('http://github.com/dustin/py-github', n.url)
-        self.assertEquals(30, n.watchers)
-        self.assertEquals(4, n.forks)
-        self.assertFalse(n.fork)
-        self.assertEquals('dustin', n.owner)
-        self.assertEquals('http://dustin.github.com/2008/12/29/github-sync.html',
-                          n.homepage)
-
-    def testSetPublic(self):
-        """Test setting a repo visible."""
-        self._ghp('repos/set/public/py-github', 'dustin', 'p').repos.setVisible(
-            'py-github')
-
-    def testSetPrivate(self):
-        """Test setting a repo to private."""
-        self._ghp('repos/set/private/py-github', 'dustin', 'p').repos.setVisible(
-            'py-github', False)
-
-    def testCreateRepository(self):
-        """Test creating a repository."""
-        self._ghp('repos/create', 'dustin', 'p',
-                  name='testrepo',
-                  description='woo',
-                  homepage='',
-                  public='1').repos.create(
-            'testrepo', description='woo')
-
-    def testDeleteRepo(self):
-        """Test setting a repo to private."""
-        self._ghp('repos/delete/mytest', 'dustin', 'p').repos.delete('mytest')
-
-    def testFork(self):
-        """Test forking'"""
-        self._ghp('repos/fork/someuser/somerepo', 'dustin', 'p').repos.fork(
-            'someuser', 'somerepo')
-
-    def testAddCollaborator(self):
-        """Adding a collaborator."""
-        self._ghp('repos/collaborators/memcached/add/trondn',
-                  'dustin', 'p').repos.addCollaborator('memcached', 'trondn')
-
-    def testRemoveCollaborator(self):
-        """Removing a collaborator."""
-        self._ghp('repos/collaborators/memcached/remove/trondn',
-                  'dustin', 'p').repos.removeCollaborator('memcached', 'trondn')
-
-    def testAddDeployKey(self):
-        """Add a deploy key."""
-        self._ghp('repos/key/blah/add', 'dustin', 'p',
-                  title='title', key='key').repos.addDeployKey('blah', 'title', 'key')
-
-    def testRemoveDeployKey(self):
-        """Remove a deploy key."""
-        self._ghp('repos/key/blah/remove', 'dustin', 'p',
-                  id=5).repos.removeDeployKey('blah', 5)
-
-class CommitTest(BaseCase):
-
-    def testCommitList(self):
-        """Test commit list."""
-        cl = self._gh('http://github.com/api/v2/xml/commits/list/mojombo/grit/master',
-                      'data/commits.xml').commits.forBranch('mojombo', 'grit')
-        self.assertEquals(30, len(cl))
-
-        c = cl[0]
-        self.assertEquals("Regenerated gemspec for version 1.1.1", c.message)
-        self.assertEquals('4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde', c.id)
-        self.assertEquals('94490563ebaf733cbb3de4ad659eb58178c2e574', c.tree)
-        self.assertEquals('2009-03-31T09:54:51-07:00', c.committed_date)
-        self.assertEquals('2009-03-31T09:54:51-07:00', c.authored_date)
-        self.assertEquals('http://github.com/mojombo/grit/commit/4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde',
-                          c.url)
-        self.assertEquals(1, len(c.parents))
-        self.assertEquals('5071bf9fbfb81778c456d62e111440fdc776f76c', c.parents[0].id)
-        self.assertEquals('Tom Preston-Werner', c.author.name)
-        self.assertEquals('tom@mojombo.com', c.author.email)
-        self.assertEquals('Tom Preston-Werner', c.committer.name)
-        self.assertEquals('tom@mojombo.com', c.committer.email)
-
-    def testCommitListForFile(self):
-        """Test commit list for a file."""
-        cl = self._gh('http://github.com/api/v2/xml/commits/list/mojombo/grit/master/grit.gemspec',
-                      'data/commits.xml').commits.forFile('mojombo', 'grit', 'grit.gemspec')
-        self.assertEquals(30, len(cl))
-
-        c = cl[0]
-        self.assertEquals("Regenerated gemspec for version 1.1.1", c.message)
-        self.assertEquals('4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde', c.id)
-        self.assertEquals('94490563ebaf733cbb3de4ad659eb58178c2e574', c.tree)
-        self.assertEquals('2009-03-31T09:54:51-07:00', c.committed_date)
-        self.assertEquals('2009-03-31T09:54:51-07:00', c.authored_date)
-        self.assertEquals('http://github.com/mojombo/grit/commit/4ac4acab7fd9c7fd4c0e0f4ff5794b0347baecde',
-                          c.url)
-        self.assertEquals(1, len(c.parents))
-        self.assertEquals('5071bf9fbfb81778c456d62e111440fdc776f76c', c.parents[0].id)
-        self.assertEquals('Tom Preston-Werner', c.author.name)
-        self.assertEquals('tom@mojombo.com', c.author.email)
-        self.assertEquals('Tom Preston-Werner', c.committer.name)
-        self.assertEquals('tom@mojombo.com', c.committer.email)
-
-    def testIndividualCommit(self):
-        """Grab a single commit."""
-        h = '4c86fa592fcc7cb685c6e9d8b6aebe8dcbac6b3e'
-        c = self._gh('http://github.com/api/v2/xml/commits/show/dustin/memcached/' + h,
-                     'data/commit.xml').commits.show('dustin', 'memcached', h)
-        self.assertEquals(['internal_tests.c'], c.removed)
-        self.assertEquals(set(['cache.c', 'cache.h', 'testapp.c']), set(c.added))
-        self.assertEquals('Create a generic cache for objects of same size\n\n'
-                          'The suffix pool could be thread-local and use the generic cache',
-                          c.message)
-
-        self.assertEquals(6, len(c.modified))
-        self.assertEquals('.gitignore', c.modified[0].filename)
-        self.assertEquals(140, len(c.modified[0].diff))
-
-        self.assertEquals(['ee0c3d5ae74d0862b4d9990e2ad13bc79f8c34df'],
-                          [p.id for p in c.parents])
-        self.assertEquals('http://github.com/dustin/memcached/commit/' + h, c.url)
-        self.assertEquals('Trond Norbye', c.author.name)
-        self.assertEquals('Trond.Norbye@sun.com', c.author.email)
-        self.assertEquals(h, c.id)
-        self.assertEquals('2009-04-17T16:15:52-07:00', c.committed_date)
-        self.assertEquals('2009-03-27T10:30:16-07:00', c.authored_date)
-        self.assertEquals('94b644163f6381a9930e2d7c583fae023895b903', c.tree)
-        self.assertEquals('Dustin Sallings', c.committer.name)
-        self.assertEquals('dustin@spy.net', c.committer.email)
-
-    def testWatchRepo(self):
-        """Test watching a repo."""
-        self._ghp('repos/watch/dustin/py-github', 'dustin', 'p').repos.watch(
-            'dustin', 'py-github')
-
-    def testWatchRepo(self):
-        """Test watching a repo."""
-        self._ghp('repos/unwatch/dustin/py-github', 'dustin', 'p').repos.unwatch(
-            'dustin', 'py-github')
-
-class IssueTest(BaseCase):
-
-    def testListIssues(self):
-        """Test listing issues."""
-        il = self._gh('http://github.com/api/v2/xml/issues/list/schacon/simplegit/open',
-                      'data/issues.list.xml').issues.list('schacon', 'simplegit')
-        self.assertEquals(1, len(il))
-        i = il[0]
-
-        self.assertEquals('schacon', i.user)
-        self.assertEquals('2009-04-17T16:19:02-07:00', i.updated_at)
-        self.assertEquals('something', i.body)
-        self.assertEquals('new', i.title)
-        self.assertEquals(2, i.number)
-        self.assertEquals(0, i.votes)
-        self.assertEquals(1.0, i.position)
-        self.assertEquals('2009-04-17T16:18:50-07:00', i.created_at)
-        self.assertEquals('open', i.state)
-
-    def testShowIssue(self):
-        """Show an individual issue."""
-        i = self._gh('http://github.com/api/v2/xml/issues/show/dustin/py-github/1',
-                     'data/issues.show.xml').issues.show('dustin', 'py-github', 1)
-
-        self.assertEquals('dustin', i.user)
-        self.assertEquals('2009-04-17T18:37:04-07:00', i.updated_at)
-        self.assertEquals('http://develop.github.com/p/general.html', i.body)
-        self.assertEquals('Add auth tokens', i.title)
-        self.assertEquals(1, i.number)
-        self.assertEquals(0, i.votes)
-        self.assertEquals(1.0, i.position)
-        self.assertEquals('2009-04-17T17:00:58-07:00', i.created_at)
-        self.assertEquals('closed', i.state)
-
-    def testAddLabel(self):
-        """Adding a label to an issue."""
-        self._ghp('issues/label/add/dustin/py-github/todo/33', 'd', 'pw').issues.add_label(
-            'dustin', 'py-github', 33, 'todo')
-
-    def testRemoveLabel(self):
-        """Removing a label from an issue."""
-        self._ghp('issues/label/remove/dustin/py-github/todo/33',
-                  'd', 'pw').issues.remove_label(
-            'dustin', 'py-github', 33, 'todo')
-
-    def testCloseIssue(self):
-        """Closing an issue."""
-        self._ghp('issues/close/dustin/py-github/1', 'd', 'pw').issues.close(
-            'dustin', 'py-github', 1)
-
-    def testReopenIssue(self):
-        """Reopening an issue."""
-        self._ghp('issues/reopen/dustin/py-github/1', 'd', 'pw').issues.reopen(
-            'dustin', 'py-github', 1)
-
-    def testCreateIssue(self):
-        """Creating an issue."""
-        self._ghp('issues/open/dustin/py-github', 'd', 'pw',
-                  title='test title', body='').issues.new(
-            'dustin', 'py-github', title='test title')
-
-    def testEditIssue(self):
-        """Editing an existing issue."""
-        self._ghp('issues/edit/dustin/py-github/1', 'd', 'pw',
-                  title='new title', body='new body').issues.edit(
-            'dustin', 'py-github', 1, 'new title', 'new body')
-
-class ObjectTest(BaseCase):
-
-    def testTree(self):
-        """Test tree fetching."""
-        h = '1ddd3f99f0b96019042239375b3ad4d45796ffba'
-        tl = self._gh('http://github.com/api/v2/xml/tree/show/dustin/py-github/' + h,
-                      'data/tree.xml').objects.tree('dustin', 'py-github', h)
-        self.assertEquals(8, len(tl))
-        self.assertEquals('setup.py', tl['setup.py'].name)
-        self.assertEquals('6e290379ec58fa00ac9d1c2a78f0819a21397445',
-                          tl['setup.py'].sha)
-        self.assertEquals('100755', tl['setup.py'].mode)
-        self.assertEquals('blob', tl['setup.py'].type)
-
-        self.assertEquals('src', tl['src'].name)
-        self.assertEquals('5fb9175803334c82b3fd66f1b69502691b91cf4f',
-                          tl['src'].sha)
-        self.assertEquals('040000', tl['src'].mode)
-        self.assertEquals('tree', tl['src'].type)
-
-    def testBlob(self):
-        """Test blob fetching."""
-        h = '1ddd3f99f0b96019042239375b3ad4d45796ffba'
-        blob = self._gh('http://github.com/api/v2/xml/blob/show/dustin/py-github/'
-                        + h + '/setup.py',
-                        'data/blob.xml').objects.blob('dustin', 'py-github', h, 'setup.py')
-        self.assertEquals('setup.py', blob.name)
-        self.assertEquals(1842, blob.size)
-        self.assertEquals('6e290379ec58fa00ac9d1c2a78f0819a21397445', blob.sha)
-        self.assertEquals('100755', blob.mode)
-        self.assertEquals('text/plain', blob.mime_type)
-        self.assertEquals(1842, len(blob.data))
-        self.assertEquals(1641, blob.data.index('Production/Stable'))
-
-    def testRawBlob(self):
-        """Test raw blob fetching."""
-        h = '6e290379ec58fa00ac9d1c2a78f0819a21397445'
-        blob = self._gh('http://github.com/api/v2/xml/blob/show/dustin/py-github/' + h,
-                        'data/setup.py').objects.raw_blob('dustin', 'py-github', h)
-        self.assertEquals('e2dc8aea9ae8961f4f5923f9febfdd0a',
-                          hashlib.md5(blob).hexdigest())
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index cda6094e4d37cf2773bd8be7299b95c966bd744d..324a66ada36ad22312977cf488b9540143f7c5bb 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -249,6 +249,11 @@ BOXCAR_USERNAME = None
 BOXCAR_PASSWORD = None
 BOXCAR_PREFIX = None
 
+USE_PUSHOVER = False
+PUSHOVER_NOTIFY_ONSNATCH = False
+PUSHOVER_NOTIFY_ONDOWNLOAD = False
+PUSHOVER_USERKEY = None
+
 USE_LIBNOTIFY = False
 LIBNOTIFY_NOTIFY_ONSNATCH = False
 LIBNOTIFY_NOTIFY_ONDOWNLOAD = False
@@ -405,6 +410,7 @@ def initialize(consoleLogging=True):
                 NAMING_DATES, EXTRA_SCRIPTS, USE_TWITTER, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \
                 USE_NOTIFO, NOTIFO_USERNAME, NOTIFO_APISECRET, NOTIFO_NOTIFY_ONDOWNLOAD, NOTIFO_NOTIFY_ONSNATCH, \
                 USE_BOXCAR, BOXCAR_USERNAME, BOXCAR_PASSWORD, BOXCAR_NOTIFY_ONDOWNLOAD, BOXCAR_NOTIFY_ONSNATCH, \
+                USE_PUSHOVER, PUSHOVER_USERKEY, PUSHOVER_NOTIFY_ONDOWNLOAD, PUSHOVER_NOTIFY_ONSNATCH, \
                 USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_SYNOINDEX, \
                 USE_BANNER, USE_LISTVIEW, METADATA_XBMC, METADATA_MEDIABROWSER, METADATA_PS3, METADATA_SYNOLOGY, metadata_provider_dict, \
                 NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, \
@@ -497,6 +503,7 @@ def initialize(consoleLogging=True):
         QUALITY_DEFAULT = check_setting_int(CFG, 'General', 'quality_default', SD)
         STATUS_DEFAULT = check_setting_int(CFG, 'General', 'status_default', SKIPPED)
         VERSION_NOTIFY = check_setting_int(CFG, 'General', 'version_notify', 1)
+        
         SEASON_FOLDERS_FORMAT = check_setting_str(CFG, 'General', 'season_folders_format', 'Season %02d')
         SEASON_FOLDERS_DEFAULT = bool(check_setting_int(CFG, 'General', 'season_folders_default', 0))
 
@@ -627,6 +634,11 @@ def initialize(consoleLogging=True):
         BOXCAR_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_ondownload', 0))
         BOXCAR_USERNAME = check_setting_str(CFG, 'Boxcar', 'boxcar_username', '')
 
+        USE_PUSHOVER = bool(check_setting_int(CFG, 'Pushover', 'use_pushover', 0))
+        PUSHOVER_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Pushover', 'pushover_notify_onsnatch', 0))
+        PUSHOVER_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Pushover', 'pushover_notify_ondownload', 0))
+        PUSHOVER_USERKEY = check_setting_str(CFG, 'Pushover', 'pushover_userkey', '')
+
         USE_LIBNOTIFY = bool(check_setting_int(CFG, 'Libnotify', 'use_libnotify', 0))
         LIBNOTIFY_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_onsnatch', 0))
         LIBNOTIFY_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_ondownload', 0))
@@ -1162,6 +1174,12 @@ def save_config():
     new_config['Boxcar']['boxcar_notify_ondownload'] = int(BOXCAR_NOTIFY_ONDOWNLOAD)
     new_config['Boxcar']['boxcar_username'] = BOXCAR_USERNAME
 
+    new_config['Pushover'] = {}
+    new_config['Pushover']['use_pushover'] = int(USE_PUSHOVER)
+    new_config['Pushover']['pushover_notify_onsnatch'] = int(PUSHOVER_NOTIFY_ONSNATCH)
+    new_config['Pushover']['pushover_notify_ondownload'] = int(PUSHOVER_NOTIFY_ONDOWNLOAD)
+    new_config['Pushover']['pushover_userkey'] = PUSHOVER_USERKEY
+
     new_config['Libnotify'] = {}
     new_config['Libnotify']['use_libnotify'] = int(USE_LIBNOTIFY)
     new_config['Libnotify']['libnotify_notify_onsnatch'] = int(LIBNOTIFY_NOTIFY_ONSNATCH)
diff --git a/sickbeard/gh_api.py b/sickbeard/gh_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ed9762885a9d9edfb66c7d1503d560e51facd77
--- /dev/null
+++ b/sickbeard/gh_api.py
@@ -0,0 +1,59 @@
+# Author: Nic Wolfe <nic@wolfeden.ca>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+try:
+    import json
+except ImportError:
+    from lib import simplejson as json
+
+import urllib
+
+class GitHub(object):
+    """
+    Simple api wrapper for the Github API v3. Currently only supports the small thing that SB
+    needs it for - list of cimmots.
+    """
+    
+    def _access_API(self, path, params=None):
+        """
+        Access the API at the path given and with the optional params given.
+        
+        path: A list of the path elements to use (eg. ['repos', 'midgetspy', 'Sick-Beard', 'commits'])
+        params: Optional dict of name/value pairs for extra params to send. (eg. {'per_page': 10})
+        
+        Returns a deserialized json object of the result. Doesn't do any error checking (hope it works).
+        """
+        
+        url = 'https://api.github.com/' + '/'.join(path)
+        
+        if params and type(params) is dict:
+            url += '?' + '&'.join([str(x) + '=' + str(params[x]) for x in params.keys()])
+        
+        return json.load(urllib.urlopen(url)) 
+    
+    def commits(self, user, repo, branch='master'):
+        """
+        Uses the API to get a list of the 100 most recent commits from the specified user/repo/branch, starting from HEAD.
+        
+        user: The github username of the person whose repo you're querying
+        repo: The repo name to query
+        branch: Optional, the branch name to show commits from
+        
+        Returns a deserialized json object containing the commit info. See http://developer.github.com/v3/repos/commits/
+        """
+        return self._access_API(['repos', user, repo, 'commits'], {'per_page': 100, 'sha': branch})
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index 26b5a0aec25ff9f4f8e814315b918f33557e6250..62d5046b1c34bd5106c5c2ae1f4b99cf2e8bd14f 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -23,6 +23,7 @@ import stat
 import urllib, urllib2
 import re, socket
 import shutil
+import traceback
 
 from xml.dom.minidom import Node
 
@@ -148,6 +149,12 @@ def getURL (url, headers=[]):
     except socket.timeout:
         logger.log(u"Timed out while loading URL "+url, logger.WARNING)
         return None
+    except ValueError:
+        logger.log(u"Unknown error while loading URL "+url, logger.WARNING)
+        return None
+    except Exception:
+        logger.log(u"Unknown exception while loading URL "+url+": "+traceback.format_exc(), logger.WARNING)
+        return None
 
     return result
 
@@ -436,12 +443,25 @@ def chmodAsParent(childPath):
         return
     
     parentMode = stat.S_IMODE(os.stat(parentPath)[stat.ST_MODE])
+    
+    childPathStat = ek.ek(os.stat, childPath)
+    childPath_mode = stat.S_IMODE(childPathStat[stat.ST_MODE])
 
     if ek.ek(os.path.isfile, childPath):
         childMode = fileBitFilter(parentMode)
     else:
         childMode = parentMode
 
+    if childPath_mode == childMode:
+        return
+
+    childPath_owner = childPathStat.st_uid
+    user_id = os.geteuid()
+
+    if user_id !=0 and user_id != childPath_owner:
+        logger.log(u"Not running as root or owner of "+childPath+", not trying to set permissions", logger.DEBUG)
+        return
+
     try:
         ek.ek(os.chmod, childPath, childMode)
         logger.log(u"Setting permissions for %s to %o as parent directory has %o" % (childPath, childMode, parentMode), logger.DEBUG)
@@ -465,11 +485,19 @@ def fixSetGroupID(childPath):
 
     if parentMode & stat.S_ISGID:
         parentGID = parentStat[stat.ST_GID]
-        childGID = os.stat(childPath)[stat.ST_GID]
+        childStat = ek.ek(os.stat, childPath)
+        childGID = childStat[stat.ST_GID]
 
         if childGID == parentGID:
             return
 
+        childPath_owner = childStat.st_uid
+        user_id = os.geteuid()
+
+        if user_id !=0 and user_id != childPath_owner:
+            logger.log(u"Not running as root or owner of "+childPath+", not trying to set the set-group-ID", logger.DEBUG)
+            return
+
         try:
             ek.ek(os.chown, childPath, -1, parentGID)  #@UndefinedVariable - only available on UNIX
             logger.log(u"Respecting the set-group-ID bit on the parent directory for %s" % (childPath), logger.DEBUG)
diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py
index 03a4eae9c3f87942ed7af0071cd4bec739bad2d1..6e9228fa432f2e9422da43876694e1263d04f87c 100755
--- a/sickbeard/notifiers/__init__.py
+++ b/sickbeard/notifiers/__init__.py
@@ -26,6 +26,7 @@ import tweet
 from . import libnotify
 import notifo
 import boxcar
+import pushover
 import nmj
 import synoindex
 import trakt
@@ -41,6 +42,7 @@ prowl_notifier = prowl.ProwlNotifier()
 twitter_notifier = tweet.TwitterNotifier()
 notifo_notifier = notifo.NotifoNotifier()
 boxcar_notifier = boxcar.BoxcarNotifier()
+pushover_notifier = pushover.PushoverNotifier()
 libnotify_notifier = libnotify.LibnotifyNotifier()
 nmj_notifier = nmj.NMJNotifier()
 synoindex_notifier = synoindex.synoIndexNotifier()
@@ -60,6 +62,7 @@ notifiers = [
     nmj_notifier,
     synoindex_notifier,
     boxcar_notifier,
+    pushover_notifier,
     trakt_notifier,
     pytivo_notifier,
     nma_notifier,
diff --git a/sickbeard/notifiers/pushover.py b/sickbeard/notifiers/pushover.py
new file mode 100644
index 0000000000000000000000000000000000000000..5814688f58e7b3883b4dd3160991e03feda8c945
--- /dev/null
+++ b/sickbeard/notifiers/pushover.py
@@ -0,0 +1,143 @@
+# Author: Marvin Pinto <me@marvinp.ca>
+# Author: Dennis Lutter <lad1337@gmail.com>
+# Author: Aaron Bieber <deftly@gmail.com>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+import urllib, urllib2
+import time
+
+import sickbeard
+
+from sickbeard import logger
+from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD
+from sickbeard.exceptions import ex
+
+API_URL = "https://api.pushover.net/1/messages.json"
+API_KEY = "OKCXmkvHN1syU2e8xvpefTnyvVWGv5"
+
+class PushoverNotifier:
+
+    def test_notify(self, userKey=None):
+        return self._sendPushover("This is a test notification from SickBeard", 'Test', userKey )
+
+    def _sendPushover(self, msg, title, userKey=None ):
+        """
+        Sends a pushover notification to the address provided
+        
+        msg: The message to send (unicode)
+        title: The title of the message
+        userKey: The pushover user id to send the message to (or to subscribe with)
+        
+        returns: True if the message succeeded, False otherwise
+        """
+
+        if not userKey:
+            userKey = sickbeard.PUSHOVER_USERKEY
+        
+        # build up the URL and parameters
+        msg = msg.strip()
+        curUrl = API_URL
+
+        data = urllib.urlencode({
+            'token': API_KEY,
+            'title': title,
+            'user': userKey,
+            'message': msg.encode('utf-8'),
+            'timestamp': int(time.time())
+			})
+
+
+        # send the request to pushover
+        try:
+            req = urllib2.Request(curUrl)
+            handle = urllib2.urlopen(req, data)
+            handle.close()
+            
+        except urllib2.URLError, e:
+            # if we get an error back that doesn't have an error code then who knows what's really happening
+            if not hasattr(e, 'code'):
+                logger.log("Pushover notification failed." + ex(e), logger.ERROR)
+                return False
+            else:
+                logger.log("Pushover notification failed. Error code: " + str(e.code), logger.WARNING)
+
+            # HTTP status 404 if the provided email address isn't a Pushover user.
+            if e.code == 404:
+                logger.log("Username is wrong/not a pushover email. Pushover will send an email to it", logger.WARNING)
+                return False
+            
+            # For HTTP status code 401's, it is because you are passing in either an invalid token, or the user has not added your service.
+            elif e.code == 401:
+                
+                # If the user has already added your service, we'll return an HTTP status code of 401.
+                if subscribe:
+                    logger.log("Already subscribed to service", logger.ERROR)
+                    # i dont know if this is true or false ... its neither but i also dont know how we got here in the first place
+                    return False
+                
+                #HTTP status 401 if the user doesn't have the service added
+                else:
+                    subscribeNote = self._sendPushover(msg, title, userKey )
+                    if subscribeNote:
+                        logger.log("Subscription send", logger.DEBUG)
+                        return True
+                    else:
+                        logger.log("Subscription could not be send", logger.ERROR)
+                        return False
+            
+            # If you receive an HTTP status code of 400, it is because you failed to send the proper parameters
+            elif e.code == 400:
+                logger.log("Wrong data sent to pushover", logger.ERROR)
+                return False
+
+        logger.log("Pushover notification successful.", logger.DEBUG)
+        return True
+
+    def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]):
+        if sickbeard.PUSHOVER_NOTIFY_ONSNATCH:
+            self._notifyPushover(title, ep_name)
+            
+
+    def notify_download(self, ep_name, title=notifyStrings[NOTIFY_DOWNLOAD]):
+        if sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD:
+            self._notifyPushover(title, ep_name)
+
+    def _notifyPushover(self, title, message, userKey=None ):
+        """
+        Sends a pushover notification based on the provided info or SB config
+
+        title: The title of the notification to send
+        message: The message string to send
+        userKey: The userKey to send the notification to 
+        """
+
+        if not sickbeard.USE_PUSHOVER and not force:
+            logger.log("Notification for Pushover not enabled, skipping this notification", logger.DEBUG)
+            return False
+
+        # if no userKey was given then use the one from the config
+        if not userKey:
+            userKey = sickbeard.PUSHOVER_USERKEY
+
+        logger.log("Sending notification for " + message, logger.DEBUG)
+
+        # self._sendPushover(message, title, userKey)
+        self._sendPushover(message, title)
+        return True
+
+notifier = PushoverNotifier
diff --git a/sickbeard/notifiers/synoindex.py b/sickbeard/notifiers/synoindex.py
index a376222ec7ebdb25ae495245c74c704c5aabf26a..2eaeedbfb76d91a26bed0c9dabe1cd15e5be6db6 100755
--- a/sickbeard/notifiers/synoindex.py
+++ b/sickbeard/notifiers/synoindex.py
@@ -35,9 +35,39 @@ class synoIndexNotifier:
     def notify_download(self, ep_name):
         pass
 
-    def update_library(self, ep_obj):
+    def moveFolder(self, old_path, new_path):
+        self.moveObject(old_path, new_path)
+
+    def moveFile(self, old_file, new_file):
+        self.moveObject(old_file, new_file)
+
+    def moveObject(self, old_path, new_path):
+        if sickbeard.USE_SYNOINDEX:
+            synoindex_cmd = ['/usr/syno/bin/synoindex', '-N', ek.ek(os.path.abspath, new_path), ek.ek(os.path.abspath, old_path)]
+            logger.log(u"Executing command "+str(synoindex_cmd))
+            logger.log(u"Absolute path to command: "+ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG)
+            try:
+                p = subprocess.Popen(synoindex_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR)
+                out, err = p.communicate() #@UnusedVariable
+                logger.log(u"Script result: "+str(out), logger.DEBUG)
+            except OSError, e:
+                logger.log(u"Unable to run synoindex: "+ex(e))
+
+    def deleteFolder(self, cur_path):
+        self.makeObject('-D', cur_path)
+
+    def addFolder(self, cur_path):
+        self.makeObject('-A', cur_path)
+
+    def deleteFile(self, cur_file):
+        self.makeObject('-d', cur_file)
+
+    def addFile(self, cur_file):
+        self.makeObject('-a', cur_file)
+
+    def makeObject(self, cmd_arg, cur_path):
         if sickbeard.USE_SYNOINDEX:
-            synoindex_cmd = ['/usr/syno/bin/synoindex', '-a', ek.ek(os.path.abspath, ep_obj.location)]
+            synoindex_cmd = ['/usr/syno/bin/synoindex', cmd_arg, ek.ek(os.path.abspath, cur_path)]
             logger.log(u"Executing command "+str(synoindex_cmd))
             logger.log(u"Absolute path to command: "+ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG)
             try:
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index 7539779df6faa901867ef4106663f6374067a91b..f8070510281a4df5e85c74f4c397b19328a1031a 100755
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -53,7 +53,8 @@ class PostProcessor(object):
     EXISTS_SAME = 2
     EXISTS_SMALLER = 3
     DOESNT_EXIST = 4
-
+
+    IGNORED_FILESTRINGS = [ "/.AppleDouble/", ".DS_Store" ]
     def __init__(self, file_path, nzb_name = None):
         """
         Creates a new post processor with the given file path and optionally an NZB name.
@@ -112,7 +113,7 @@ class PostProcessor(object):
     
         # if the new file exists, return the appropriate code depending on the size
         if ek.ek(os.path.isfile, existing_file):
-    
+            
             # see if it's bigger than our old file
             if ek.ek(os.path.getsize, existing_file) > ek.ek(os.path.getsize, self.file_path):
                 self._log(u"File "+existing_file+" is larger than "+self.file_path, logger.DEBUG)
@@ -184,6 +185,8 @@ class PostProcessor(object):
             self._log(u"Deleting file "+cur_file, logger.DEBUG)
             if ek.ek(os.path.isfile, cur_file):
                 ek.ek(os.remove, cur_file)
+                # do the library update for synoindex
+                notifiers.synoindex_notifier.deleteFile(cur_file)
                 
     def _combined_file_operation (self, file_path, new_path, new_base_name, associated_files=False, action=None):
         """
@@ -697,6 +700,10 @@ class PostProcessor(object):
         if os.path.isdir( self.file_path ):
             self._log(u"File "+self.file_path+" seems to be a directory")
             return False
+        for ignore_file in self.IGNORED_FILESTRINGS:
+            if ignore_file in self.file_path:
+                self._log(u"File "+self.file_path+" is ignored type, skipping" )
+                return False
         # reset per-file stuff
         self.in_history = False
         
@@ -763,6 +770,8 @@ class PostProcessor(object):
             try:
                 ek.ek(os.mkdir, dest_path)
                 helpers.chmodAsParent(dest_path)
+                # do the library update for synoindex
+                notifiers.synoindex_notifier.addFolder(dest_path)
             except OSError, IOError:
                 raise exceptions.PostProcessingFailed("Unable to create the episode's destination folder: "+dest_path)
 
@@ -815,7 +824,7 @@ class PostProcessor(object):
         notifiers.plex_notifier.update_library()
 
         # do the library update for synoindex
-        notifiers.synoindex_notifier.update_library(ep_obj)
+        notifiers.synoindex_notifier.addFile(ep_obj.location)
 
         # do the library update for trakt
         notifiers.trakt_notifier.update_library(ep_obj)
diff --git a/sickbeard/providers/newzbin.py b/sickbeard/providers/newzbin.py
index c0b2360a666e7450b4919a04f0c7340a3d9f966c..c93ed52900a6bc203f5505a0d658508fb223875c 100644
--- a/sickbeard/providers/newzbin.py
+++ b/sickbeard/providers/newzbin.py
@@ -115,10 +115,11 @@ class NewzbinProvider(generic.NZBProvider):
 
     def _is_SDTV(self, attrs):
 
-        # Video Fmt: (XviD or DivX), NOT 720p, NOT 1080p
-        video_fmt = 'Video Fmt' in attrs and ('XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt']) \
+        # Video Fmt: (XviD, DivX, x264 or H.264), NOT 720p, NOT 1080p, NOT 1080i
+        video_fmt = 'Video Fmt' in attrs and ('XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt'] or 'x264' in attrs['Video Fmt'] or 'H.264' in attrs['Video Fmt']) \
                             and ('720p' not in attrs['Video Fmt']) \
-                            and ('1080p' not in attrs['Video Fmt'])
+                            and ('1080p' not in attrs['Video Fmt']) \
+                            and ('1080i' not in attrs['Video Fmt'])
 
         # Source: TV Cap or HDTV or (None)
         source = 'Source' not in attrs or 'TV Cap' in attrs['Source'] or 'HDTV' in attrs['Source']
@@ -130,11 +131,12 @@ class NewzbinProvider(generic.NZBProvider):
 
     def _is_SDDVD(self, attrs):
 
-        # Video Fmt: (XviD or DivX), NOT 720p, NOT 1080p
-        video_fmt = 'Video Fmt' in attrs and ('XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt']) \
+        # Video Fmt: (XviD, DivX, x264 or H.264), NOT 720p, NOT 1080p, NOT 1080i
+        video_fmt = 'Video Fmt' in attrs and ('XviD' in attrs['Video Fmt'] or 'DivX' in attrs['Video Fmt'] or 'x264' in attrs['Video Fmt'] or 'H.264' in attrs['Video Fmt']) \
                             and ('720p' not in attrs['Video Fmt']) \
-                            and ('1080p' not in attrs['Video Fmt'])
-
+                            and ('1080p' not in attrs['Video Fmt']) \
+                            and ('1080i' not in attrs['Video Fmt'])
+    						
         # Source: DVD
         source = 'Source' in attrs and 'DVD' in attrs['Source']
 
@@ -158,14 +160,17 @@ class NewzbinProvider(generic.NZBProvider):
 
     def _is_WEBDL(self, attrs):
 
-        # Video Fmt: H.264, 720p
-        video_fmt = 'Video Fmt' in attrs and ('H.264' in attrs['Video Fmt']) \
+        # Video Fmt: x264, H.264, 720p
+        video_fmt = 'Video Fmt' in attrs and ('x264' in attrs['Video Fmt'] or 'H.264' in attrs['Video Fmt']) \
                             and ('720p' in attrs['Video Fmt'])
 
+        # Source: Web-DL
+        source = 'Source' in attrs and 'Web-dl' in attrs['Source']
+
         # Subtitles: (None)
         subs = 'Subtitles' not in attrs
 
-        return video_fmt and subs
+        return video_fmt and source and subs
 
     def _is_720pBluRay(self, attrs):
 
diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py
index 14f7207d40784f1f337971c76ae641fbc45d7d0f..23464120b32dc4abcb20ab177d5c82eb34d0ce52 100644
--- a/sickbeard/providers/newznab.py
+++ b/sickbeard/providers/newznab.py
@@ -86,7 +86,7 @@ class NewznabProvider(generic.NZBProvider):
 				cur_params['rid'] = show.tvrid
 			# if we can't then fall back on a very basic name search
 			else:
-				cur_params['q'] = sanitizeSceneName(cur_exception)
+				cur_params['q'] = sanitizeSceneName(cur_exception).replace('.', '_')
 	
 			if season != None:
 				# air-by-date means &season=2010&q=2010.03, no other way to do it atm
@@ -117,7 +117,7 @@ class NewznabProvider(generic.NZBProvider):
 			params['rid'] = ep_obj.show.tvrid
 		# if we can't then fall back on a very basic name search
 		else:
-			params['q'] = sanitizeSceneName(ep_obj.show.name)
+			params['q'] = sanitizeSceneName(ep_obj.show.name).replace('.', '_')
 
 		if ep_obj.show.air_by_date:
 			date_str = str(ep_obj.airdate)
@@ -142,7 +142,7 @@ class NewznabProvider(generic.NZBProvider):
 					continue
 
 				cur_return = params.copy()
-				cur_return['q'] = sanitizeSceneName(cur_exception)
+				cur_return['q'] = sanitizeSceneName(cur_exception).replace('.', '_')
 				to_return.append(cur_return)
 
 		return to_return
@@ -178,6 +178,10 @@ class NewznabProvider(generic.NZBProvider):
 				  "limit": 100,
 				  "cat": '5030,5040'}
 
+		# hack this in for now
+		if self.getID() == 'nzbs_org':
+			params['cat'] += ',5070,5090'
+
 		if search_params:
 			params.update(search_params)
 
@@ -259,6 +263,10 @@ class NewznabCache(tvcache.TVCache):
 				  "age": sickbeard.USENET_RETENTION,
 				  "cat": '5040,5030'}
 
+		# hack this in for now
+		if self.provider.getID() == 'nzbs_org':
+			params['cat'] += ',5070,5090'
+
 		if self.provider.key:
 			params['apikey'] = self.provider.key
 
diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py
index 3f5bba7967c45bb08df3088deb228ce08377d037..521864a78dd7423d5991de2d1c2d51444c6497fd 100644
--- a/sickbeard/tvcache.py
+++ b/sickbeard/tvcache.py
@@ -340,7 +340,7 @@ class TVCache():
         if not episode:
             sqlResults = myDB.select("SELECT * FROM "+self.providerID)
         else:
-            sqlResults = myDB.select("SELECT * FROM "+self.providerID+" WHERE tvdbid = ? AND season = ? AND episodes LIKE ?", [episode.show.tvdbid, episode.season, "|"+str(episode.episode)+"|"])
+            sqlResults = myDB.select("SELECT * FROM "+self.providerID+" WHERE tvdbid = ? AND season = ? AND episodes LIKE ?", [episode.show.tvdbid, episode.season, "%|"+str(episode.episode)+"|%"])
 
         # for each cache entry
         for curResult in sqlResults:
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index f4f040f8041382ec7a517095bc9196ce71a4dbdd..6cfdbe537311dde64ad9eafaca601e272fdc7165 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -28,7 +28,7 @@ import urllib, urllib2
 import zipfile, tarfile
 
 from urllib2 import URLError
-from lib.pygithub import github
+import gh_api as github
 
 class CheckVersion():
     """
@@ -213,6 +213,8 @@ class GitUpdateManager(UpdateManager):
 
         self.git_url = 'http://code.google.com/p/sickbeard/downloads/list'
 
+        self.branch = self._find_git_branch()
+
     def _git_error(self):
         error_message = 'Unable to find your git executable - either delete your .git folder and run from source OR <a href="http://code.google.com/p/sickbeard/wiki/AdvancedSettings" onclick="window.open(this.href); return false;">set git_path in your config.ini</a> to enable updates.'
         sickbeard.NEWEST_VERSION_STRING = error_message
@@ -245,7 +247,7 @@ class GitUpdateManager(UpdateManager):
                 logger.log(u"Command "+cmd+" didn't work, couldn't find git.")
                 continue
             
-            if 'not found' in output or "not recognized as an internal or external command" in output:
+            if p.returncode != 0 or 'not found' in output or "not recognized as an internal or external command" in output:
                 logger.log(u"Unable to find git with command "+cmd, logger.DEBUG)
                 output = None
             elif 'fatal:' in output or err:
@@ -282,6 +284,17 @@ class GitUpdateManager(UpdateManager):
             
         return True
 
+    def _find_git_branch(self):
+
+        branch_info = self._run_git('symbolic-ref -q HEAD')
+
+        if not branch_info or not branch_info[0]:
+            return 'master'
+
+        branch = branch_info[0].strip().replace('refs/heads/', '', 1)
+
+        return branch or 'master'
+
 
     def _check_github_for_update(self):
         """
@@ -297,13 +310,13 @@ class GitUpdateManager(UpdateManager):
         gh = github.GitHub()
 
         # find newest commit
-        for curCommit in gh.commits.forBranch('midgetspy', 'Sick-Beard', version.SICKBEARD_VERSION):
+        for curCommit in gh.commits('midgetspy', 'Sick-Beard', self.branch):
             if not self._newest_commit_hash:
-                self._newest_commit_hash = curCommit.id
+                self._newest_commit_hash = curCommit['sha']
                 if not self._cur_commit_hash:
                     break
 
-            if curCommit.id == self._cur_commit_hash:
+            if curCommit['sha'] == self._cur_commit_hash:
                 break
 
             self._num_commits_behind += 1
@@ -313,7 +326,7 @@ class GitUpdateManager(UpdateManager):
     def set_newest_text(self):
 
         # if we're up to date then don't set this
-        if self._num_commits_behind == 35:
+        if self._num_commits_behind == 100:
             message = "or else you're ahead of master"
 
         elif self._num_commits_behind > 0:
@@ -336,8 +349,8 @@ class GitUpdateManager(UpdateManager):
         self._find_installed_version()
         try:
             self._check_github_for_update()
-        except Exception:
-            logger.log(u"Unable to contact github, can't check for update", logger.ERROR)
+        except Exception, e:
+            logger.log(u"Unable to contact github, can't check for update: "+repr(e), logger.ERROR)
             return False
 
         logger.log(u"After checking, cur_commit = "+str(self._cur_commit_hash)+", newest_commit = "+str(self._newest_commit_hash)+", num_commits_behind = "+str(self._num_commits_behind), logger.DEBUG)
@@ -353,7 +366,7 @@ class GitUpdateManager(UpdateManager):
         on the call's success.
         """
 
-        output, err = self._run_git('pull origin '+sickbeard.version.SICKBEARD_VERSION) #@UnusedVariable
+        output, err = self._run_git('pull origin '+self.branch) #@UnusedVariable
 
         if not output:
             return self._git_error()
diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py
index 5154cd85d3e615d4c4927b92b25c92a9b9cfc24f..6fda8f10393329b0267a36d157655aaa6104b25e 100644
--- a/sickbeard/webapi.py
+++ b/sickbeard/webapi.py
@@ -474,11 +474,7 @@ class TVDBShorthandWrapper(ApiCall):
 
     def run(self):
         """ internal function wrapper """
-        # how to add a var to a tuple
-        # http://stackoverflow.com/questions/1380860/add-variables-to-tuple
-        argstmp = (0, self.sid) # make a new tuple
-        args = argstmp + self.origArgs # add both
-        args = args[1:] # remove first fake element
+        args = (self.sid,) + self.origArgs
         if self.e:
             return CMD_Episode(args, self.kwargs).run()
         elif self.s:
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index 7190a4704eba985f053234c7b88032e3d14661a6..6d39b1e81008274341c555663fd8f218225bcaf0 100755
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -66,7 +66,10 @@ class PageTemplate (Template):
         self.sbHttpPort = sickbeard.WEB_PORT
         self.sbHttpsPort = sickbeard.WEB_PORT
         self.sbHttpsEnabled = sickbeard.ENABLE_HTTPS
-        self.sbHost = re.match("[^:]+", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
+        if cherrypy.request.headers['Host'][0] == '[':
+            self.sbHost = re.match("^\[.*\]", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
+        else:
+            self.sbHost = re.match("^[^:]+", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
         self.projectHomePage = "http://code.google.com/p/sickbeard/"
 
         if sickbeard.NZBS and sickbeard.NZBS_UID and sickbeard.NZBS_HASH:
@@ -1208,6 +1211,7 @@ class ConfigNotifications:
                           use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None, 
                           use_notifo=None, notifo_notify_onsnatch=None, notifo_notify_ondownload=None, notifo_username=None, notifo_apisecret=None,
                           use_boxcar=None, boxcar_notify_onsnatch=None, boxcar_notify_ondownload=None, boxcar_username=None,
+                          use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None, pushover_userkey=None,
                           use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None,
                           use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None,
                           use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None,
@@ -1333,6 +1337,20 @@ class ConfigNotifications:
         else:
             use_boxcar = 0
 
+        if pushover_notify_onsnatch == "on":
+            pushover_notify_onsnatch = 1
+        else:
+            pushover_notify_onsnatch = 0
+
+        if pushover_notify_ondownload == "on":
+            pushover_notify_ondownload = 1
+        else:
+            pushover_notify_ondownload = 0
+        if use_pushover == "on":
+            use_pushover = 1
+        else:
+            use_pushover = 0
+
         if use_nmj == "on":
             use_nmj = 1
         else:
@@ -1428,6 +1446,11 @@ class ConfigNotifications:
         sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD = boxcar_notify_ondownload
         sickbeard.BOXCAR_USERNAME = boxcar_username
 
+        sickbeard.USE_PUSHOVER = use_pushover
+        sickbeard.PUSHOVER_NOTIFY_ONSNATCH = pushover_notify_onsnatch
+        sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD = pushover_notify_ondownload
+        sickbeard.PUSHOVER_USERKEY = pushover_userkey
+
         sickbeard.USE_LIBNOTIFY = use_libnotify == "on"
         sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH = libnotify_notify_onsnatch == "on"
         sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD = libnotify_notify_ondownload == "on"
@@ -1581,11 +1604,12 @@ class NewHomeAddShows:
 
             try:
                 seriesXML = etree.ElementTree(etree.XML(urlData))
-            except Exception, e:
-                logger.log(u"Unable to parse XML for some reason: "+ex(e)+" from XML: "+urlData, logger.ERROR)
-                return ''
+                series = seriesXML.getiterator('Series')
 
-            series = seriesXML.getiterator('Series')
+            except Exception, e:
+                # use finalURL in log, because urlData can be too much information
+                logger.log(u"Unable to parse XML for some reason: "+ex(e)+" from XML: "+finalURL, logger.ERROR)
+                series = ''
 
             # add each result to our list
             for curSeries in series:
@@ -1966,6 +1990,8 @@ class Home:
             return "Error: Unsupported Request. Send jsonp request with 'callback' variable in the query stiring."
         cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
         cherrypy.response.headers['Content-Type'] = 'text/javascript'
+        cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
+        cherrypy.response.headers['Access-Control-Allow-Headers'] = 'x-requested-with'
 
         if sickbeard.started:
             return callback+'('+json.dumps({"msg": str(sickbeard.PID)})+');'
@@ -2042,6 +2068,16 @@ class Home:
         else:
             return "Error sending Boxcar notification"
 
+    @cherrypy.expose
+    def testPushover(self, userKey=None):
+        cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
+
+        result = notifiers.pushover_notifier.test_notify(userKey)
+        if result:
+            return "Pushover notification succeeded. Check your Pushover clients to make sure it worked"
+        else:
+            return "Error sending Pushover notification"
+
     @cherrypy.expose
     def twitterStep1(self):
         cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
@@ -2641,13 +2677,13 @@ class WebInterface:
             default_image_name = 'banner.png'
 
         default_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'data', 'images', default_image_name)
-        if show == None:
-            return cherrypy.lib.static.serve_file(default_image_path, content_type="image/jpeg")
+        if show is None:
+            return cherrypy.lib.static.serve_file(default_image_path, content_type="image/png")
         else:
             showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-        if showObj == None:
-            return cherrypy.lib.static.serve_file(default_image_path, content_type="image/jpeg")
+        if showObj is None:
+            return cherrypy.lib.static.serve_file(default_image_path, content_type="image/png")
 
         cache_obj = image_cache.ImageCache()
         
@@ -2668,7 +2704,7 @@ class WebInterface:
                 if im.mode == 'P': # Convert GIFs to RGB
                     im = im.convert('RGB')
                 if which == 'banner':
-                    size = 600, 112
+                    size = 606, 112
                 elif which == 'poster':
                     size = 136, 200
                 else:
@@ -2679,7 +2715,7 @@ class WebInterface:
                 cherrypy.response.headers['Content-Type'] = 'image/jpeg'
                 return buffer.getvalue()
         else:
-            return cherrypy.lib.static.serve_file(default_image_path, content_type="image/jpeg")
+            return cherrypy.lib.static.serve_file(default_image_path, content_type="image/png")
 
     @cherrypy.expose
     def setComingEpsLayout(self, layout):
diff --git a/tests/all_tests.py b/tests/all_tests.py
new file mode 100755
index 0000000000000000000000000000000000000000..ad9c9433e3aa57655be82ae823fc7fafac35dc15
--- /dev/null
+++ b/tests/all_tests.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# coding=UTF-8
+# Author: Dennis Lutter <lad1337@gmail.com>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+if __name__ == "__main__":
+    import glob
+    import unittest
+
+    test_file_strings = [ x for x in glob.glob('*_tests.py') if not x in __file__]
+    module_strings = [file_string[0:len(file_string) - 3] for file_string in test_file_strings]
+    suites = [unittest.defaultTestLoader.loadTestsFromName(file_string) for file_string in module_strings]
+    testSuite = unittest.TestSuite(suites)
+
+
+    print "=================="
+    print "STARTING - ALL TESTS"
+    print "=================="
+    print "this will include"
+    for includedfiles in test_file_strings:
+        print "- " + includedfiles
+
+
+    text_runner = unittest.TextTestRunner().run(testSuite)
diff --git a/tests/db_tests.py b/tests/db_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..55055fd525fe73f895c43ef478c9f8fe691cfe24
--- /dev/null
+++ b/tests/db_tests.py
@@ -0,0 +1,40 @@
+# coding=UTF-8
+# Author: Dennis Lutter <lad1337@gmail.com>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import test_lib as test
+
+
+class DBBasicTests(test.SickbeardTestDBCase):
+
+    def setUp(self):
+        super(DBBasicTests, self).setUp()
+        self.db = test.db.DBConnection()
+
+    def test_select(self):
+        self.db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000])
+
+
+if __name__ == '__main__':
+    print "=================="
+    print "STARTING - DB TESTS"
+    print "=================="
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(DBBasicTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/name_parser_tests.py b/tests/name_parser_tests.py
index e0fbef52b9a874fe38374c1eb8a8e2456ec8d576..81b6cc75572c1f354bc7f065265857fcf1193618 100644
--- a/tests/name_parser_tests.py
+++ b/tests/name_parser_tests.py
@@ -10,7 +10,7 @@ from sickbeard.name_parser import parser
 import sickbeard
 sickbeard.SYS_ENCODING = 'UTF-8'
 
-DEBUG = VERBOSE = True
+DEBUG = VERBOSE = False
 
 simple_test_cases = {
               'standard': {
diff --git a/tests/pp_tests.py b/tests/pp_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..2360ea9de8c864178b3f00079259d5b1fc616d35
--- /dev/null
+++ b/tests/pp_tests.py
@@ -0,0 +1,111 @@
+# coding=UTF-8
+# Author: Dennis Lutter <lad1337@gmail.com>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+import random
+import unittest
+
+import test_lib as test
+
+import sys, os.path
+
+from sickbeard.postProcessor import PostProcessor
+import sickbeard
+from sickbeard.tv import TVEpisode, TVShow
+
+
+class PPInitTests(unittest.TestCase):
+
+    def setUp(self):
+        self.pp = PostProcessor(test.FILEPATH)
+
+    def test_init_file_name(self):
+        self.assertEqual(self.pp.file_name, test.FILENAME)
+
+    def test_init_folder_name(self):
+        self.assertEqual(self.pp.folder_name, test.SHOWNAME)
+
+
+class PPPrivateTests(test.SickbeardTestDBCase):
+
+
+    def setUp(self):
+        super(PPPrivateTests, self).setUp()
+
+        sickbeard.showList = [TVShow(0000), TVShow(0001)]
+
+        self.pp = PostProcessor(test.FILEPATH)
+        self.show_obj = TVShow(0002)
+
+        self.db = test.db.DBConnection()
+        newValueDict = {"tvdbid": 1002,
+                        "name": test.SHOWNAME,
+                        "description": "description",
+                        "airdate": 1234,
+                        "hasnfo": 1,
+                        "hastbn": 1,
+                        "status": 404,
+                        "location": test.FILEPATH}
+        controlValueDict = {"showid": 0002,
+                            "season": test.SEASON,
+                            "episode": test.EPISODE}
+
+        # use a custom update/insert method to get the data into the DB
+        self.db.upsert("tv_episodes", newValueDict, controlValueDict)
+
+        self.ep_obj = TVEpisode(self.show_obj, test.SEASON, test.EPISODE, test.FILEPATH)
+
+    def test__find_ep_destination_folder(self):
+        self.show_obj.location = test.FILEDIR
+        self.ep_obj.show.seasonfolders = 1
+        sickbeard.SEASON_FOLDERS_FORMAT = 'Season %02d'
+        calculatedPath = self.pp._find_ep_destination_folder(self.ep_obj)
+        ecpectedPath = os.path.join(test.FILEDIR, "Season 0" + str(test.SEASON))
+        self.assertEqual(calculatedPath, ecpectedPath)
+
+
+class PPBasicTests(test.SickbeardTestDBCase):
+
+    def test_process(self):
+        show = TVShow(3)
+        show.name = test.SHOWNAME
+        show.location = test.SHOWDIR
+        show.saveToDB()
+
+        sickbeard.showList = [show]
+        ep = TVEpisode(show, test.SEASON, test.EPISODE)
+        ep.name = "some ep name"
+        ep.saveToDB()
+
+        pp = PostProcessor(test.FILEPATH)
+        self.assertTrue(pp.process())
+
+
+if __name__ == '__main__':
+    print "=================="
+    print "STARTING - PostProcessor TESTS"
+    print "=================="
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(PPInitTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(PPPrivateTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(PPBasicTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/scene_helpers_tests.py b/tests/scene_helpers_tests.py
index 36206d646bd68f05d52b9ab7c78216c3b0af2468..fb7e7d200af01f4b07dc524842ec69fd96b2d05e 100644
--- a/tests/scene_helpers_tests.py
+++ b/tests/scene_helpers_tests.py
@@ -1,4 +1,5 @@
 import unittest
+import test_lib as test
 
 import sys, os.path
 sys.path.append(os.path.abspath('..'))
@@ -8,26 +9,25 @@ from sickbeard import show_name_helpers, scene_exceptions, common, name_cache
 import sickbeard
 from sickbeard import db
 from sickbeard.databases import cache_db
+from sickbeard.tv import TVShow as Show
 
-class Show:
-    def __init__(self, name, tvdbid, tvrname):
-        self.name = name
-        self.tvdbid = tvdbid
-        self.tvrname = tvrname
 
-class SceneTests(unittest.TestCase):
-    
+class SceneTests(test.SickbeardTestDBCase):
+
     def _test_sceneToNormalShowNames(self, name, expected):
         result = show_name_helpers.sceneToNormalShowNames(name)
         self.assertTrue(len(set(expected).intersection(set(result))) == len(expected))
 
-        dot_result = show_name_helpers.sceneToNormalShowNames(name.replace(' ','.'))
-        dot_expected = [x.replace(' ','.') for x in expected]
+        dot_result = show_name_helpers.sceneToNormalShowNames(name.replace(' ', '.'))
+        dot_expected = [x.replace(' ', '.') for x in expected]
         self.assertTrue(len(set(dot_expected).intersection(set(dot_result))) == len(dot_expected))
-        
+
     def _test_allPossibleShowNames(self, name, tvdbid=0, tvrname=None, expected=[]):
-        
-        result = show_name_helpers.allPossibleShowNames(Show(name, tvdbid, tvrname))
+        s = Show(tvdbid)
+        s.name = name
+        s.tvrname = tvrname
+
+        result = show_name_helpers.allPossibleShowNames(s)
         self.assertTrue(len(set(expected).intersection(set(result))) == len(expected))
 
     def _test_filterBadReleases(self, name, expected):
@@ -38,14 +38,20 @@ class SceneTests(unittest.TestCase):
         self.assertTrue(show_name_helpers.isGoodResult(name, show))
 
     def test_isGoodName(self):
-        self._test_isGoodName('Show.Name.S01E02.Test-Test', Show('Show/Name', 0, ''))
-        self._test_isGoodName('Show.Name.S01E02.Test-Test', Show('Show. Name', 0, ''))
-        self._test_isGoodName('Show.Name.S01E02.Test-Test', Show('Show- Name', 0, ''))
-        self._test_isGoodName('Show.Name.Part.IV.Test-Test', Show('Show Name', 0, ''))
-        self._test_isGoodName('Show.Name.1x02.Test-Test', Show('Show Name', 0, ''))
-        self._test_isGoodName('Show.Name.S01.Test-Test', Show('Show Name', 0, ''))
-        self._test_isGoodName('Show.Name.E02.Test-Test', Show('Show: Name', 0, ''))
-        self._test_isGoodName('Show Name Season 2 Test', Show('Show: Name', 0, ''))
+        listOfcases = [('Show.Name.S01E02.Test-Test', 'Show/Name'),
+                        ('Show.Name.S01E02.Test-Test', 'Show. Name'),
+                        ('Show.Name.S01E02.Test-Test', 'Show- Name'),
+                        ('Show.Name.Part.IV.Test-Test', 'Show Name'),
+                        ('Show.Name.S01.Test-Test', 'Show Name'),
+                        ('Show.Name.E02.Test-Test', 'Show: Name'),
+                        ('Show Name Season 2 Test', 'Show: Name'),
+                        ]
+
+        for testCase in listOfcases:
+            scene_name, show_name = testCase
+            s = Show(0)
+            s.name = show_name
+            self._test_isGoodName(scene_name, s)
 
     def test_sceneToNormalShowNames(self):
         self._test_sceneToNormalShowNames('Show Name 2010', ['Show Name 2010', 'Show Name (2010)'])
@@ -56,7 +62,7 @@ class SceneTests(unittest.TestCase):
         self._test_sceneToNormalShowNames('Show and Name 2010', ['Show and Name 2010', 'Show & Name 2010', 'Show and Name (2010)', 'Show & Name (2010)'])
         self._test_sceneToNormalShowNames('show name us', ['show name us', 'show name (us)'])
         self._test_sceneToNormalShowNames('Show And Name', ['Show And Name', 'Show & Name'])
-        
+
         # failure cases
         self._test_sceneToNormalShowNames('Show Name 90210', ['Show Name 90210'])
         self._test_sceneToNormalShowNames('Show Name YA', ['Show Name YA'])
@@ -66,7 +72,7 @@ class SceneTests(unittest.TestCase):
         myDB = db.DBConnection("cache.db")
         myDB.action("INSERT INTO scene_exceptions (tvdb_id, show_name) VALUES (?,?)", [-1, 'Exception Test'])
         common.countryList['Full Country Name'] = 'FCN'
-        
+
         self._test_allPossibleShowNames('Show Name', expected=['Show Name'])
         self._test_allPossibleShowNames('Show Name', -1, expected=['Show Name', 'Exception Test'])
         self._test_allPossibleShowNames('Show Name', tvrname='TVRage Name', expected=['Show Name', 'TVRage Name'])
@@ -77,7 +83,6 @@ class SceneTests(unittest.TestCase):
         self._test_allPossibleShowNames('Show Name (FCN)', -1, 'TVRage Name', expected=['Show Name (FCN)', 'Show Name (Full Country Name)', 'Exception Test', 'TVRage Name'])
 
     def test_filterBadReleases(self):
-        
         self._test_filterBadReleases('Show.S02.German.Stuff-Grp', False)
         self._test_filterBadReleases('Show.S02.Some.Stuff-Core2HD', False)
         self._test_filterBadReleases('Show.S02.Some.German.Stuff-Grp', False)
@@ -85,14 +90,11 @@ class SceneTests(unittest.TestCase):
         self._test_filterBadReleases('Show.S02.This.Is.German', False)
 
 
+class SceneExceptionTestCase(test.SickbeardTestDBCase):
 
-
-print 'Loading exceptions...',
-db.upgradeDatabase(db.DBConnection("cache.db"), cache_db.InitialSchema)
-scene_exceptions.retrieve_exceptions()
-print 'done.'
-
-class SceneExceptionTestCase(unittest.TestCase):
+    def setUp(self):
+        super(SceneExceptionTestCase, self).setUp()
+        scene_exceptions.retrieve_exceptions()
 
     def test_sceneExceptionsEmpty(self):
         self.assertEqual(scene_exceptions.get_scene_exceptions(0), [])
@@ -104,7 +106,7 @@ class SceneExceptionTestCase(unittest.TestCase):
         self.assertEqual(scene_exceptions.get_scene_exception_by_name('Babylon5'), 70726)
         self.assertEqual(scene_exceptions.get_scene_exception_by_name('babylon 5'), 70726)
         self.assertEqual(scene_exceptions.get_scene_exception_by_name('Carlos 2010'), 164451)
-        
+
     def test_sceneExceptionByNameEmpty(self):
         self.assertEqual(scene_exceptions.get_scene_exception_by_name('nothing useful'), None)
 
@@ -112,17 +114,17 @@ class SceneExceptionTestCase(unittest.TestCase):
         # clear the exceptions
         myDB = db.DBConnection("cache.db")
         myDB.action("DELETE FROM scene_exceptions")
-        
+
         # put something in the cache
         name_cache.addNameToCache('Cached Name', 0)
-        
+
         # updating should clear the cache so our previously "Cached Name" won't be in there
         scene_exceptions.retrieve_exceptions()
         self.assertEqual(name_cache.retrieveNameFromCache('Cached Name'), None)
 
         # put something in the cache
         name_cache.addNameToCache('Cached Name', 0)
-        
+
         # updating should not clear the cache this time since our exceptions didn't change
         scene_exceptions.retrieve_exceptions()
         self.assertEqual(name_cache.retrieveNameFromCache('Cached Name'), 0)
@@ -130,7 +132,7 @@ class SceneExceptionTestCase(unittest.TestCase):
 
 if __name__ == '__main__':
     if len(sys.argv) > 1:
-        suite = unittest.TestLoader().loadTestsFromName('scene_helpers_tests.SceneExceptionTestCase.test_'+sys.argv[1])
+        suite = unittest.TestLoader().loadTestsFromName('scene_helpers_tests.SceneExceptionTestCase.test_' + sys.argv[1])
         unittest.TextTestRunner(verbosity=2).run(suite)
     else:
         suite = unittest.TestLoader().loadTestsFromTestCase(SceneTests)
diff --git a/tests/snatch_tests.py b/tests/snatch_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ae0ea3297c3914b8c42f02d4d80e354b3772b9f
--- /dev/null
+++ b/tests/snatch_tests.py
@@ -0,0 +1,111 @@
+# coding=UTF-8
+# Author: Dennis Lutter <lad1337@gmail.com>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+import random
+import unittest
+
+import test_lib as test
+
+import sys, os.path
+
+import sickbeard.search as search
+import sickbeard
+from sickbeard.tv import TVEpisode, TVShow
+import sickbeard.common as c
+
+tests = {"Dexter": {"a": 1, "q": c.HD, "s": 5, "e": [7], "b": 'Dexter.S05E07.720p.BluRay.X264-REWARD', "i": ['Dexter.S05E07.720p.BluRay.X264-REWARD', 'Dexter.S05E07.720p.X264-REWARD']},
+         "House": {"a": 1, "q": c.HD, "s": 4, "e": [5], "b": 'House.4x5.720p.BluRay.X264-REWARD', "i": ['Dexter.S05E04.720p.X264-REWARD', 'House.4x5.720p.BluRay.X264-REWARD']},
+         "Hells Kitchen": {"a": 1, "q": c.SD, "s": 6, "e": [14, 15], "b": 'Hells.Kitchen.s6e14e15.HDTV.XviD-ASAP', "i": ['Hells.Kitchen.S06E14.HDTV.XviD-ASAP', 'Hells.Kitchen.6x14.HDTV.XviD-ASAP', 'Hells.Kitchen.s6e14e15.HDTV.XviD-ASAP']}
+       }
+
+
+def _create_fake_xml(items):
+    xml = '<?xml version="1.0" encoding="UTF-8" ?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:newznab="http://www.newznab.com/DTD/2010/feeds/attributes/" encoding="utf-8"><channel>'
+    for item in items:
+        xml += '<item><title>' + item + '</title>\n'
+        xml += '<link>http://fantasy.com/' + item + '</link></item>'
+    xml += '</channel></rss>'
+    return xml
+
+
+searchItems = []
+
+
+class SearchTest(test.SickbeardTestDBCase):
+
+    def _fake_getURL(self, url, headers=None):
+        global searchItems
+        return _create_fake_xml(searchItems)
+
+    def _fake_isActive(self):
+        return True
+
+    def __init__(self, something):
+        for provider in sickbeard.providers.sortedProviderList():
+            provider.getURL = self._fake_getURL
+            #provider.isActive = self._fake_isActive
+
+        super(SearchTest, self).__init__(something)
+
+
+def test_generator(tvdbdid, show_name, curData, forceSearch):
+
+    def test(self):
+        global searchItems
+        searchItems = curData["i"]
+        show = TVShow(tvdbdid)
+        show.name = show_name
+        show.quality = curData["q"]
+        show.saveToDB()
+        sickbeard.showList.append(show)
+
+        for epNumber in curData["e"]:
+            episode = TVEpisode(show, curData["s"], epNumber)
+            episode.status = c.WANTED
+            episode.saveToDB()
+
+        bestResult = search.findEpisode(episode, forceSearch)
+        if not bestResult:
+            self.assertEqual(curData["b"], bestResult)
+        self.assertEqual(curData["b"], bestResult.name) #first is expected, second is choosen one
+    return test
+
+if __name__ == '__main__':
+    print "=================="
+    print "STARTING - Snatch TESTS"
+    print "=================="
+    print "######################################################################"
+    # create the test methods
+    tvdbdid = 1
+    for forceSearch in (True, False):
+        for name, curData in tests.items():
+            if not curData["a"]:
+                continue
+            fname = name.replace(' ', '_')
+            if forceSearch:
+                test_name = 'test_manual_%s_%s' % (fname, tvdbdid)
+            else:
+                test_name = 'test_%s_%s' % (fname, tvdbdid)
+
+            test = test_generator(tvdbdid, name, curData, forceSearch)
+            setattr(SearchTest, test_name, test)
+            tvdbdid += 1
+
+    suite = unittest.TestLoader().loadTestsFromTestCase(SearchTest)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/test_lib.py b/tests/test_lib.py
new file mode 100644
index 0000000000000000000000000000000000000000..b004146ead92921b6bd5d0fdc73537a7bec7652d
--- /dev/null
+++ b/tests/test_lib.py
@@ -0,0 +1,222 @@
+# coding=UTF-8
+# Author: Dennis Lutter <lad1337@gmail.com>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+import sqlite3
+
+import sys, os.path
+sys.path.append(os.path.abspath('..'))
+sys.path.append(os.path.abspath('../lib'))
+
+import sickbeard
+import shutil, time
+from sickbeard import encodingKludge as ek, providers, tvcache
+from sickbeard import db
+from sickbeard.databases import mainDB
+from sickbeard.databases import cache_db
+
+#=================
+# test globals
+#=================
+TESTDIR = os.path.abspath('.')
+TESTDBNAME = "sickbeard.db"
+TESTCACHEDBNAME = "cache.db"
+
+
+SHOWNAME = u"show name"
+SEASON = 4
+EPISODE = 2
+FILENAME = u"show name - s0" + str(SEASON) + "e0" + str(EPISODE) + ".mkv"
+FILEDIR = os.path.join(TESTDIR, SHOWNAME)
+FILEPATH = os.path.join(FILEDIR, FILENAME)
+
+SHOWDIR = os.path.join(TESTDIR, SHOWNAME+" final")
+
+#sickbeard.logger.sb_log_instance = sickbeard.logger.SBRotatingLogHandler(os.path.join(TESTDIR, 'sickbeard.log'), sickbeard.logger.NUM_LOGS, sickbeard.logger.LOG_SIZE)
+sickbeard.logger.SBRotatingLogHandler.log_file = os.path.join(os.path.join(TESTDIR, 'Logs'), 'test_sickbeard.log')
+
+#=================
+# prepare env functions
+#=================
+def createTestLogFolder():
+    if not os.path.isdir(sickbeard.LOG_DIR):
+        os.mkdir(sickbeard.LOG_DIR)
+
+# call env functions at apropriate time durin sickbeard var setup
+
+#=================
+# sickbeard globals
+#=================
+sickbeard.SYS_ENCODING = 'UTF-8'
+sickbeard.showList = []
+sickbeard.QUALITY_DEFAULT = 4
+sickbeard.SEASON_FOLDERS_DEFAULT = 1
+sickbeard.SEASON_FOLDERS_FORMAT = 'Season %02d'
+
+sickbeard.NAMING_SHOW_NAME = 1
+sickbeard.NAMING_EP_NAME = 1
+sickbeard.NAMING_EP_TYPE = 0
+sickbeard.NAMING_MULTI_EP_TYPE = 1
+sickbeard.NAMING_SEP_TYPE = 0
+sickbeard.NAMING_USE_PERIODS = 0
+sickbeard.NAMING_QUALITY = 0
+sickbeard.NAMING_DATES = 1
+
+sickbeard.PROVIDER_ORDER = ["sick_beard_index"]
+sickbeard.newznabProviderList = providers.getNewznabProviderList("Sick Beard Index|http://momo.sickbeard.com/||1!!!NZBs.org|http://beta.nzbs.org/||0")
+sickbeard.providerList = providers.makeProviderList()
+
+sickbeard.PROG_DIR = os.path.abspath('..')
+sickbeard.DATA_DIR = sickbeard.PROG_DIR
+sickbeard.LOG_DIR = os.path.join(TESTDIR, 'Logs')
+createTestLogFolder()
+sickbeard.logger.sb_log_instance.initLogging(False)
+
+#=================
+# dummy functions
+#=================
+def _dummy_saveConfig():
+    return True
+# this overrides the sickbeard save_config which gets called during a db upgrade
+# this might be considered a hack
+mainDB.sickbeard.save_config = _dummy_saveConfig
+
+# the real one tries to contact tvdb just stop it from getting more info on the ep
+def _fake_specifyEP(self, season, episode):
+    pass
+
+sickbeard.tv.TVEpisode.specifyEpisode = _fake_specifyEP
+
+
+#=================
+# test classes
+#=================
+class SickbeardTestDBCase(unittest.TestCase):
+    def setUp(self):
+        sickbeard.showList = []
+        setUp_test_db()
+        setUp_test_episode_file()
+        setUp_test_show_dir()
+
+    def tearDown(self):
+        sickbeard.showList = []
+        tearDown_test_db()
+        tearDown_test_episode_file()
+        tearDown_test_show_dir()
+
+
+class TestDBConnection(db.DBConnection, object):
+
+    def __init__(self, dbFileName=TESTDBNAME):
+        dbFileName = os.path.join(TESTDIR, dbFileName)
+        super(TestDBConnection, self).__init__(dbFileName)
+
+
+class TestCacheDBConnection(TestDBConnection, object):
+
+    def __init__(self, providerName):
+        db.DBConnection.__init__(self, os.path.join(TESTDIR, TESTCACHEDBNAME))
+
+        # Create the table if it's not already there
+        try:
+            sql = "CREATE TABLE "+providerName+" (name TEXT, season NUMERIC, episodes TEXT, tvrid NUMERIC, tvdbid NUMERIC, url TEXT, time NUMERIC, quality TEXT);"
+            self.connection.execute(sql)
+            self.connection.commit()
+        except sqlite3.OperationalError, e:
+            if str(e) != "table "+providerName+" already exists":
+                raise
+
+        # Create the table if it's not already there
+        try:
+            sql = "CREATE TABLE lastUpdate (provider TEXT, time NUMERIC);"
+            self.connection.execute(sql)
+            self.connection.commit()
+        except sqlite3.OperationalError, e:
+            if str(e) != "table lastUpdate already exists":
+                raise
+
+# this will override the normal db connection
+sickbeard.db.DBConnection = TestDBConnection
+sickbeard.tvcache.CacheDBConnection = TestCacheDBConnection
+
+
+#=================
+# test functions
+#=================
+def setUp_test_db():
+    """upgrades the db to the latest version
+    """
+    # upgrading the db
+    db.upgradeDatabase(db.DBConnection(), mainDB.InitialSchema)
+    # fix up any db problems
+    db.sanityCheckDatabase(db.DBConnection(), mainDB.MainSanityCheck)
+    
+    #and for cache.b too
+    db.upgradeDatabase(db.DBConnection("cache.db"), cache_db.InitialSchema)
+
+
+def tearDown_test_db():
+    """Deletes the test db
+        although this seams not to work on my system it leaves me with an zero kb file
+    """
+    # uncomment next line so leave the db intact beween test and at the end
+    #return False
+    if os.path.exists(os.path.join(TESTDIR, TESTDBNAME)):
+        os.remove(os.path.join(TESTDIR, TESTDBNAME))
+    if os.path.exists(os.path.join(TESTDIR, TESTCACHEDBNAME)):
+        os.remove(os.path.join(TESTDIR, TESTCACHEDBNAME))
+
+def setUp_test_episode_file():
+    if not os.path.exists(FILEDIR):
+        os.makedirs(FILEDIR)
+
+    f = open(FILEPATH, "w")
+    f.write("foo bar")
+    f.close()
+
+
+def tearDown_test_episode_file():
+    shutil.rmtree(FILEDIR)
+
+
+def setUp_test_show_dir():
+    if not os.path.exists(SHOWDIR):
+        os.makedirs(SHOWDIR)
+
+
+def tearDown_test_show_dir():
+    shutil.rmtree(SHOWDIR)
+
+tearDown_test_db()
+
+if __name__ == '__main__':
+    print "=================="
+    print "Dont call this directly"
+    print "=================="
+    print "you might want to call"
+
+    dirList = os.listdir(TESTDIR)
+    for fname in dirList:
+        if (fname.find("_test") > 0) and (fname.find("pyc") < 0):
+            print "- " + fname
+
+    print "=================="
+    print "or just call all_tests.py"
+
diff --git a/tests/tv_tests.py b/tests/tv_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..40734dfc0c1e75e3817d3e309dbb85283fd9b278
--- /dev/null
+++ b/tests/tv_tests.py
@@ -0,0 +1,117 @@
+# coding=UTF-8
+# Author: Dennis Lutter <lad1337@gmail.com>
+# URL: http://code.google.com/p/sickbeard/
+#
+# This file is part of Sick Beard.
+#
+# Sick Beard 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 3 of the License, or
+# (at your option) any later version.
+#
+# Sick Beard 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 Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
+
+import random
+import unittest
+import test_lib as test
+
+import sickbeard
+from sickbeard.tv import TVEpisode, TVShow
+from sickbeard import exceptions
+
+
+class TVShowTests(test.SickbeardTestDBCase):
+
+    def setUp(self):
+        super(TVShowTests, self).setUp()
+        sickbeard.showList = []
+
+    def test_init_tvdbid(self):
+        show = TVShow(0001, "en")
+        self.assertEqual(show.tvdbid, 0001)
+
+    def test_change_tvdbid(self):
+        show = TVShow(0001, "en")
+        show.name = "show name"
+        show.tvrname = "show name"
+        show.network = "cbs"
+        show.genre = "crime"
+        show.runtime = 40
+        show.status = "5"
+        show.airs = "monday"
+        show.startyear = 1987
+
+        show.saveToDB()
+        show.loadFromDB(skipNFO=True)
+
+        show.tvdbid = 0002
+        show.saveToDB()
+        show.loadFromDB(skipNFO=True)
+
+        self.assertEqual(show.tvdbid, 0002)
+
+    def test_set_name(self):
+        show = TVShow(0001, "en")
+        show.name = "newName"
+        show.saveToDB()
+        show.loadFromDB(skipNFO=True)
+        self.assertEqual(show.name, "newName")
+
+
+class TVEpisodeTests(test.SickbeardTestDBCase):
+
+    def setUp(self):
+        super(TVEpisodeTests, self).setUp()
+        sickbeard.showList = []
+
+    def test_init_empty_db(self):
+        show = TVShow(0001, "en")
+        ep = TVEpisode(show, 1, 1)
+        ep.name = "asdasdasdajkaj"
+        ep.saveToDB()
+        ep.loadFromDB(1, 1)
+        self.assertEqual(ep.name, "asdasdasdajkaj")
+
+
+class TVTests(test.SickbeardTestDBCase):
+
+    def setUp(self):
+        super(TVTests, self).setUp()
+        sickbeard.showList = []
+
+    def test_getEpisode(self):
+        show = TVShow(0001, "en")
+        show.name = "show name"
+        show.tvrname = "show name"
+        show.network = "cbs"
+        show.genre = "crime"
+        show.runtime = 40
+        show.status = "5"
+        show.airs = "monday"
+        show.startyear = 1987
+        show.saveToDB()
+        sickbeard.showList = [show]
+        #TODO: implement
+
+
+if __name__ == '__main__':
+    print "=================="
+    print "STARTING - TV TESTS"
+    print "=================="
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(TVShowTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(TVEpisodeTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(TVTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
+
+