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"> </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) + +