diff --git a/SickBeard.py b/SickBeard.py index 7b67dc411c660063beee651d1765dad628fc989b..e40ee3de2399f51e7f8a7e75dd9c7230e60c4727 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. -# Check needed software dependencies to nudge users to fix their setup +# Check needed software dependencies to nudge users to fix their setup import sys if sys.version_info < (2, 5): print "Sorry, requires Python 2.5, 2.6 or 2.7." @@ -52,6 +52,7 @@ from sickbeard import db from sickbeard.tv import TVShow from sickbeard import logger from sickbeard.version import SICKBEARD_VERSION +from sickbeard.databases.mainDB import MAX_DB_VERSION from sickbeard.webserveInit import initWebServer @@ -261,6 +262,11 @@ def main(): sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) + if db.DBConnection().checkDBVersion() > MAX_DB_VERSION: + print 'Your database version has been incremented past what this version of Sick Beard supports.' + print 'Have you used other forks of Sick Beard with this same database file?' + sys.exit(1) + # Initialize the config and our threads sickbeard.initialize(consoleLogging=consoleLogging) diff --git a/data/css/style.css b/data/css/style.css index 967959eade1fe70f7ff162f08f4030b0efc7d20d..0187128b0237d8a88dcb214e82341b3ba365b77e 100644 --- a/data/css/style.css +++ b/data/css/style.css @@ -531,7 +531,9 @@ displayShow.tmpl + manage_backlogOverview.tmpl .wanted { background-color: #ffb0b0; } - +.snatched { + background-color: #ebc1ea; +} /* ======================================================================= manage_backlogOverview.tmpl ========================================================================== */ @@ -1056,12 +1058,20 @@ span.quality { } span.Custom { background: none repeat scroll 0 0 #449; - /* blue */ + /* purplish blue */ +} +span.HD { + background: none repeat scroll 0 0 #008fbb; + /* greenish blue */ } -span.HD,span.WEB-DL,span.BluRay { +span.HD720p { background: none repeat scroll 0 0 #494; /* green */ } +span.HD1080p { + background: none repeat scroll 0 0 #499; + /* blue */ +} span.SD { background: none repeat scroll 0 0 #944; /* red */ @@ -1070,6 +1080,10 @@ span.Any { background: none repeat scroll 0 0 #444; /* black */ } +span.RawHD { + background: none repeat scroll 0 0 #999944; + /* dark orange */ +} /* unused boolean tags */ span.false { color: #933; diff --git a/data/interfaces/default/apiBuilder.tmpl b/data/interfaces/default/apiBuilder.tmpl index c454e7bccbb843690f67220cbfcf1b965465a4f4..1234074cb6a26fa88ccae02debd4ecb9638c474c 100644 --- a/data/interfaces/default/apiBuilder.tmpl +++ b/data/interfaces/default/apiBuilder.tmpl @@ -190,14 +190,19 @@ addList("show.setquality", "$curShow.name", "&tvdbid=$curShow.tvdbid", "quality" //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", "HD", "&initial=hdtv|fullhdtv|hdwebdl|fullhdwebdl|hdbluray|fullhdbluray"); +addOption("quality", "HD720p", "&initial=hdtv|hdwebdl|hdbluray"); +addOption("quality", "HD1080p", "&initial=fullhdtv|fullhdwebdl|fullhdbluray"); +addOption("quality", "ANY", "&initial=sdtv|sddvd|hdtv|fullhdtv|hdwebdl|fullhdwebdl|hdbluray|fullhdbluray|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", "RawHD TV", "&initial=rawhdtv", "quality-archive"); +addList("quality", "1080p HD TV", "&initial=fullhdtv", "quality-archive"); addList("quality", "720p Web-DL", "&initial=hdwebdl", "quality-archive"); +addList("quality", "1080p Web-DL", "&initial=fullhdwebdl", "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"); @@ -211,10 +216,12 @@ 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", "RawHD TV", "&archive=rawhdtv"); +addList("quality-archive", "1080p HD TV", "&archive=fullhdtv"); addList("quality-archive", "720p Web-DL", "&archive=hdwebdl"); +addList("quality-archive", "1080p Web-DL", "&archive=fullhdwebdl"); 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"); diff --git a/data/interfaces/default/comingEpisodes.tmpl b/data/interfaces/default/comingEpisodes.tmpl index 0afd8412323184de65025d576cac3b2fc8170702..40396d4b223f5ef20bbcd32a12a9468995fe656e 100644 --- a/data/interfaces/default/comingEpisodes.tmpl +++ b/data/interfaces/default/comingEpisodes.tmpl @@ -43,7 +43,7 @@ return false; }, format: function(s) { - return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); + return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('best',0).replace('custom',7); }, type: 'numeric' }); diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl index 826cf0ad58be0af72711b9e42b7046f5ca8ef2c2..2144a0fdd2f717654d27c525322b8ab31a668c5f 100755 --- a/data/interfaces/default/config_notifications.tmpl +++ b/data/interfaces/default/config_notifications.tmpl @@ -66,6 +66,13 @@ <span class="component-desc">Fall back to a full library update if per-show fails?</span> </label> </div> + <div class="field-pair"> + <input type="checkbox" name="xbmc_update_onlyfirst" id="xbmc_update_onlyfirst" #if $sickbeard.XBMC_UPDATE_ONLYFIRST then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="xbmc_update_onlyfirst"> + <span class="component-title">Only Update First Host</span> + <span class="component-desc">Only send library update to the first host?</span> + </label> + </div> <div class="field-pair"> <label class="nocheck clearfix"> <span class="component-title">XBMC IP:Port</span> @@ -77,7 +84,7 @@ </label> <label class="nocheck clearfix"> <span class="component-title"> </span> - <span class="component-desc">(multiple host strings must be separated by commas, <br/> the library updates will only be sent to the first host listed)</span> + <span class="component-desc">(multiple host strings must be separated by commas)</span> </label> </div> <div class="field-pair"> @@ -263,6 +270,91 @@ </fieldset> </div><!-- /nmj component-group //--> + <div class="component-group clearfix"> + <div class="component-group-desc"> + <img class="notifier-icon" src="$sbRoot/images/notifiers/nmj.png" alt="" title="Networked Media Jukebox V2" /> + <h3><a href="http://www.popcornhour.com/" onclick="window.open(this.href, '_blank'); return false;">NMJv2</a></h3> + <p>The Networked Media Jukebox, or NMJv2, is the official media jukebox interface made available for the Popcorn Hour 300 & 400-series.</p> + </div> + <fieldset class="component-group-list"> + <div class="field-pair"> + <input type="checkbox" class="enabler" name="use_nmjv2" id="use_nmjv2" #if $sickbeard.USE_NMJv2 then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_nmjv2"> + <span class="component-title">Enable</span> + <span class="component-desc">Should Sick Beard send update commands to NMJv2?</span> + </label> + </div> + + <div id="content_use_nmjv2"> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Popcorn IP address</span> + <input type="text" name="nmjv2_host" id="nmjv2_host" value="$sickbeard.NMJv2_HOST" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">IP of Popcorn 300/400-series (eg. 192.168.1.100)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Database Location</span> + <span class="component-desc"> + <input type="radio" name="nmjv2_dbloc" value="local" class="radio" id="nmjv2_dbloc_a" #if $sickbeard.NMJv2_DBLOC=="local" then "checked=\"checked\"" else ""# />PCH Local Media<br /> + </span> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc"> + <input type="radio" name="nmjv2_dbloc" value="network" class="radio" id="nmjv2_dbloc_b" #if $sickbeard.NMJv2_DBLOC=="network" then "checked=\"checked\"" else ""# />PCH Network Media<br /> + </span> + </label> + <label class="nocheck clearfix"> + <span class="component-title">Database Instance</span> + <span class="component-desc"> + <select id="NMJv2db_instance"> + <option value="0">#1 </option> + <option value="1">#2 </option> + <option value="2">#3 </option> + <option value="3">#4 </option> + <option value="4">#5 </option> + <option value="5">#6 </option> + <option value="6">#7 </option> + </select> + </span> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Adjust this value if the wrong database is selected.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Find Database</span> + <input type="button" class="btn" value="Find Database" id="settingsNMJv2" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">The Popcorn Hour device must be powered on.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">NMJv2 Database</span> + <input type="text" name="nmjv2_database" id="nmjv2_database" value="$sickbeard.NMJv2_DATABASE" size="35" #if $sickbeard.NMJv2_DATABASE then "readonly=\"readonly\"" else ""# /></label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Automatically filled via the 'Find Database' buttons.</span> + </label> + </div> + <div class="testNotification" id="testNMJv2-result">Click below to test.</div> + <input type="button" class="btn" value="Test NMJv2" id="testNMJv2" /> + <input type="submit" class="btn config_submitter" value="Save Changes" /> + </div><!-- /content_use_nmjv2 //--> + + </fieldset> + </div><!-- /nmjv2 component-group //--> + <div class="component-group clearfix"> <div class="component-group-desc"> @@ -273,7 +365,7 @@ <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_synoindex" id="use_synoindex" #if $sickbeard.USE_SYNOINDEX then "checked=\"checked\"" else ""# /> + <input type="checkbox" class="enabler" name="use_synoindex" id="use_synoindex" #if $sickbeard.USE_SYNOINDEX then "checked=\"checked\"" else ""# /> <label class="clearfix" for="use_synoindex"> <span class="component-title">Enable</span> <span class="component-desc">Should Sick Beard send notifications to the synoindex daemon?<br /><br /> @@ -301,7 +393,7 @@ </div> <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_pytivo" id="use_pytivo" #if $sickbeard.USE_PYTIVO then "checked=\"checked\"" else ""# /> + <input type="checkbox" class="enabler" name="use_pytivo" id="use_pytivo" #if $sickbeard.USE_PYTIVO then "checked=\"checked\"" else ""# /> <label class="clearfix" for="use_pytivo"> <span class="component-title">Enable</span> <span class="component-desc">Should Sick Beard send notifications to pyTivo?<br /><br /></span> @@ -366,7 +458,7 @@ </div> <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_growl" id="use_growl" #if $sickbeard.USE_GROWL then "checked=\"checked\"" else ""# /> + <input type="checkbox" class="enabler" name="use_growl" id="use_growl" #if $sickbeard.USE_GROWL then "checked=\"checked\"" else ""# /> <label class="clearfix" for="use_growl"> <span class="component-title">Enable</span> <span class="component-desc">Should Sick Beard send Growl notifications?</span> @@ -375,7 +467,7 @@ <div id="content_use_growl"> <div class="field-pair"> - <input type="checkbox" name="growl_notify_onsnatch" id="growl_notify_onsnatch" #if $sickbeard.GROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <input type="checkbox" name="growl_notify_onsnatch" id="growl_notify_onsnatch" #if $sickbeard.GROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> <label class="clearfix" for="growl_notify_onsnatch"> <span class="component-title">Notify on Snatch</span> <span class="component-desc">Send notification when we start a download?</span> @@ -429,7 +521,7 @@ </div> <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_prowl" id="use_prowl" #if $sickbeard.USE_PROWL then "checked=\"checked\"" else ""# /> + <input type="checkbox" class="enabler" name="use_prowl" id="use_prowl" #if $sickbeard.USE_PROWL then "checked=\"checked\"" else ""# /> <label class="clearfix" for="use_prowl"> <span class="component-title">Enable</span> <span class="component-desc">Should Sick Beard send Prowl notifications?</span> @@ -438,7 +530,7 @@ <div id="content_use_prowl"> <div class="field-pair"> - <input type="checkbox" name="prowl_notify_onsnatch" id="prowl_notify_onsnatch" #if $sickbeard.PROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <input type="checkbox" name="prowl_notify_onsnatch" id="prowl_notify_onsnatch" #if $sickbeard.PROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> <label class="clearfix" for="prowl_notify_onsnatch"> <span class="component-title">Notify on Snatch</span> <span class="component-desc">Send notification when we start a download?</span> @@ -553,7 +645,7 @@ </div> <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_libnotify" id="use_libnotify" #if $sickbeard.USE_LIBNOTIFY then "checked=\"checked\"" else ""# /> + <input type="checkbox" class="enabler" name="use_libnotify" id="use_libnotify" #if $sickbeard.USE_LIBNOTIFY then "checked=\"checked\"" else ""# /> <label class="clearfix" for="use_libnotify"> <span class="component-title">Enable</span> <span class="component-desc">Should Sick Beard send Libnotify notifications?</span> @@ -562,7 +654,7 @@ <div id="content_use_libnotify"> <div class="field-pair"> - <input type="checkbox" name="libnotify_notify_onsnatch" id="libnotify_notify_onsnatch" #if $sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <input type="checkbox" name="libnotify_notify_onsnatch" id="libnotify_notify_onsnatch" #if $sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> <label class="clearfix" for="libnotify_notify_onsnatch"> <span class="component-title">Notify on Snatch</span> <span class="component-desc">Send notification when we start a download?</span> @@ -689,7 +781,7 @@ </div> <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_nma" id="use_nma" #if $sickbeard.USE_NMA then "checked=\"checked\"" else ""# /> + <input type="checkbox" class="enabler" name="use_nma" id="use_nma" #if $sickbeard.USE_NMA then "checked=\"checked\"" else ""# /> <label class="clearfix" for="use_nma"> <span class="component-title">Enable</span> <span class="component-desc">Should Sick Beard send NMA notifications?</span> @@ -698,7 +790,7 @@ <div id="content_use_nma"> <div class="field-pair"> - <input type="checkbox" name="nma_notify_onsnatch" id="nma_notify_onsnatch" #if $sickbeard.NMA_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <input type="checkbox" name="nma_notify_onsnatch" id="nma_notify_onsnatch" #if $sickbeard.NMA_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> <label class="clearfix" for="nma_notify_onsnatch"> <span class="component-title">Notify on Snatch</span> <span class="component-desc">Send notification when we start a download?</span> @@ -775,7 +867,7 @@ <div id="content_use_twitter"> <div class="field-pair"> - <input type="checkbox" name="twitter_notify_onsnatch" id="twitter_notify_onsnatch" #if $sickbeard.TWITTER_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <input type="checkbox" name="twitter_notify_onsnatch" id="twitter_notify_onsnatch" #if $sickbeard.TWITTER_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> <label class="clearfix" for="twitter_notify_onsnatch"> <span class="component-title">Notify on Snatch</span> <span class="component-desc">Send notification when we start a download?</span> diff --git a/data/interfaces/default/config_providers.tmpl b/data/interfaces/default/config_providers.tmpl index 439913e3e209769868cdb8bbbf174f4742349f65..5231036b30b5661ee129f5565bf2c86cb2c6eec8 100755 --- a/data/interfaces/default/config_providers.tmpl +++ b/data/interfaces/default/config_providers.tmpl @@ -11,16 +11,17 @@ <script type="text/javascript" src="$sbRoot/js/configProviders.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script> +#if $sickbeard.USE_NZBS <script type="text/javascript" charset="utf-8"> <!-- \$(document).ready(function(){ - var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#; #for $curNewznabProvider in $sickbeard.newznabProviderList: - \$(this).addProvider('$curNewznabProvider.getID()', '$curNewznabProvider.name', '$curNewznabProvider.url', '$curNewznabProvider.key', $int($curNewznabProvider.default), show_nzb_providers); + \$(this).addProvider('$curNewznabProvider.getID()', '$curNewznabProvider.name', '$curNewznabProvider.url', '$curNewznabProvider.key', $int($curNewznabProvider.default)); #end for }); //--> </script> +#end if <div id="config"> <div id="config-content"> @@ -28,7 +29,7 @@ <form id="configForm" action="saveProviders" method="post"> <div id="config-components"> - + <div id="core-component-group1" class="component-group clearfix"> <div class="component-group-desc"> @@ -78,14 +79,14 @@ <h3>Configure Built-In Providers</h3> <p>Check with provider's website on how to obtain an API key if needed.</p> </div> - + <fieldset class="component-group-list"> <div class="field-pair"> <label class="clearfix" for="editAProvider"> <span class="component-title jumbo">Configure Provider:</span> <span class="component-desc"> #set $provider_config_list = [] - #for $cur_provider in ("nzbs_r_us", "tvtorrents", "btn", "binnewz", "t411"): + #for $cur_provider in ("nzbs_r_us", "omgwtfnzbs", "tvtorrents", "torrentleech", "btn", "binnewz", "t411"): #set $cur_provider_obj = $sickbeard.providers.getProviderClass($cur_provider) #if $cur_provider_obj.providerType == $GenericProvider.NZB and not $sickbeard.USE_NZBS: #continue @@ -95,9 +96,9 @@ $provider_config_list.append($cur_provider_obj) #end for - #if $provider_config_list: + #if $provider_config_list: <select id="editAProvider" class="input-medium" > - #for $cur_provider in $provider_config_list + [$curProvider for $curProvider in $sickbeard.newznabProviderList if $curProvider.default and $curProvider.needs_auth]: + #for $cur_provider in $provider_config_list + [$curProvider for $curProvider in $sickbeard.newznabProviderList if $curProvider.default and $curProvider.needs_auth and $sickbeard.USE_NZBS]: <option value="$cur_provider.getID()">$cur_provider.name</option> #end for </select> @@ -130,17 +131,32 @@ <div class="providerDiv" id="nzbs_r_usDiv"> <div class="field-pair"> <label class="clearfix"> - <span class="component-title">NZBs'R'US UID</span> + <span class="component-title">NZBs'R'US User ID</span> <input class="component-desc" type="text" name="nzbs_r_us_uid" value="$sickbeard.NZBSRUS_UID" size="10" /> </label> </div> <div class="field-pair"> <label class="clearfix"> - <span class="component-title">NZBs'R'US Hash</span> + <span class="component-title">NZBs'R'US API Key</span> <input class="component-desc" type="text" name="nzbs_r_us_hash" value="$sickbeard.NZBSRUS_HASH" size="40" /> </label> </div> </div><!-- /nzbs_r_usDiv //--> + + <div class="providerDiv" id="omgwtfnzbsDiv"> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">omgwtfnzbs User ID</span> + <input class="component-desc" type="text" name="omgwtfnzbs_uid" value="$sickbeard.OMGWTFNZBS_UID" size="10" /> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">omgwtfnzbs API Key</span> + <input class="component-desc" type="text" name="omgwtfnzbs_key" value="$sickbeard.OMGWTFNZBS_KEY" size="40" /> + </label> + </div> + </div><!-- /omgwtfnzbsDiv //--> <div class="providerDiv" id="binnewzDiv"> <p> @@ -161,7 +177,16 @@ <input class="component-desc" type="text" name="tvtorrents_hash" value="$sickbeard.TVTORRENTS_HASH" size="40" /> </label> </div> - </div><!-- /tvtorrentsDiv //--> + </div><!-- /torrentleechDiv //--> + + <div class="providerDiv" id="torrentleechDiv"> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">TorrentLeech RSS key (enable in profile):</span> + <input class="component-desc" type="text" name="torrentleech_key" value="$sickbeard.TORRENTLEECH_KEY" size="40" /> + </label> + </div> + </div><!-- /torrentleechDiv //--> <div class="providerDiv" id="t411Div"> <div class="field-pair"> @@ -190,9 +215,10 @@ <!-- end div for editing providers --> <input type="submit" class="btn config_submitter" value="Save Changes" /><br/> - + </fieldset> </div><!-- /component-group2 //--> +#if $sickbeard.USE_NZBS: <div id="core-component-group3" class="component-group clearfix"> @@ -243,12 +269,12 @@ </div> <div id="newznab_update_div" style="display: none;"> <input type="button" class="btn btn-danger newznab_delete" id="newznab_delete" value="Delete" /> - </div> + </div> </div> </fieldset> </div><!-- /component-group3 //--> - +#end if <div class="component-group-save"> <input type="submit" class="btn config_submitter" value="Save Changes" /> </div><br /> diff --git a/data/interfaces/default/displayShow.tmpl b/data/interfaces/default/displayShow.tmpl index 2cbf3da6faa86b68049c262415c9ae681ca4bc04..436c253bc0aa250920239a9e5f03d423878cc13e 100644 --- a/data/interfaces/default/displayShow.tmpl +++ b/data/interfaces/default/displayShow.tmpl @@ -78,10 +78,10 @@ $qualityPresetStrings[$show.quality] #else: #if $anyQualities: -initially download: <b><%=", ".join([Quality.qualityStrings[x] for x in anyQualities])%></b> #if $bestQualities then " + " else ""# +initially download: <b><%=", ".join([Quality.qualityStrings[x] for x in sorted(anyQualities)])%></b> #if $bestQualities then " + " else ""# #end if #if $bestQualities: -replace with: <b><%=", ".join([Quality.qualityStrings[x] for x in bestQualities])%></b> +replace with: <b><%=", ".join([Quality.qualityStrings[x] for x in sorted(bestQualities)])%></b> #end if #end if </td></tr> @@ -102,7 +102,7 @@ replace with: <b><%=", ".join([Quality.qualityStrings[x] for x in bestQualities] <div class="float-left"> Change selected episodes to <select id="statusSelect"> -#for $curStatus in [$WANTED, $SKIPPED, $ARCHIVED, $IGNORED] + $Quality.DOWNLOADED: +#for $curStatus in [$WANTED, $SKIPPED, $ARCHIVED, $IGNORED] + sorted($Quality.DOWNLOADED): #if $curStatus == $DOWNLOADED: #continue #end if diff --git a/data/interfaces/default/history.tmpl b/data/interfaces/default/history.tmpl index ded4e5a086ec059060cbe502f704e83c113fee06..5da3b876c716a43f120678065a84508df9a75db1 100644 --- a/data/interfaces/default/history.tmpl +++ b/data/interfaces/default/history.tmpl @@ -70,11 +70,13 @@ #set $provider = $providers.getProviderClass($generic.GenericProvider.makeID($hItem["provider"])) #if $provider != None: <img src="$sbRoot/images/providers/<%=provider.imageName()%>" width="16" height="16" alt="$provider.name" title="$provider.name"/> + #else: + <img src="$sbRoot/images/providers/missing.png" width="16" height="16" alt="missing provider" title="missing provider"/> #end if #end if #end if </td> - <td align="center"><span class="quality $Quality.qualityStrings[$curQuality]">$Quality.qualityStrings[$curQuality]</span></td> + <td align="center"><span class="quality $Quality.qualityStrings[$curQuality].replace("720p","HD720p").replace("1080p","HD1080p").replace("RawHD TV", "RawHD").replace("HD TV", "HD720p")">$Quality.qualityStrings[$curQuality]</span></td> </tr> #end for </tbody> diff --git a/data/interfaces/default/home.tmpl b/data/interfaces/default/home.tmpl index 51b9a4fff6116410da44cdd017b052391bf3b204..c604285b252a6a7212fb91cb9db1136aa682be13 100644 --- a/data/interfaces/default/home.tmpl +++ b/data/interfaces/default/home.tmpl @@ -40,7 +40,7 @@ return false; }, format: function(s) { - return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); + return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('best',0).replace('custom',7); }, type: 'numeric' }); diff --git a/data/interfaces/default/home_newShow.tmpl b/data/interfaces/default/home_newShow.tmpl index b4aaa0690c3dffc4cf897b2a026aa9937bc260bc..73f4c7631afcd3126e1350378b3d9ce03531f482 100644 --- a/data/interfaces/default/home_newShow.tmpl +++ b/data/interfaces/default/home_newShow.tmpl @@ -34,7 +34,6 @@ <select name="tvdbLang" id="tvdbLangSelect"> <option value="fr" selected="selected">fr</option> </select><b>*</b> - <input type="button" id="searchName" value="Search" class="btn" /><br /><br /> <b>*</b> This will only affect the language of the retrieved metadata file contents and episode filenames.<br /> diff --git a/data/interfaces/default/inc_qualityChooser.tmpl b/data/interfaces/default/inc_qualityChooser.tmpl index 0f574fff3a5eb96a7739cb6461527394ed96a5ed..5956b97b24d4c8f31c856d25e02dac53b5be10c1 100644 --- a/data/interfaces/default/inc_qualityChooser.tmpl +++ b/data/interfaces/default/inc_qualityChooser.tmpl @@ -8,8 +8,8 @@ #set $selected = None <select id="qualityPreset"> <option value="0">Custom</option> -#for $curPreset in sorted($qualityPresets): -<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""#>$qualityPresetStrings[$curPreset]</option> +#for $curPreset in sorted($qualityPresets, reverse=True): +<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""# #if $qualityPresetStrings[$curPreset].endswith("0p") then "style=\"padding-left: 15px;\"" else ""#>$qualityPresetStrings[$curPreset]</option> #end for </select> </span> @@ -36,7 +36,7 @@ <div style="text-align: center;" class="float-left"> <h4>Archive</h4> - #set $bestQualityList = filter(lambda x: x > $Quality.SDTV, $Quality.qualityStrings) + #set $bestQualityList = filter(lambda x: x > $Quality.SDTV and x < $Quality.UNKNOWN, $Quality.qualityStrings) <select id="bestQualities" name="bestQualities" multiple="multiple" size="$len($bestQualityList)"> #for $curQuality in sorted($bestQualityList): <option value="$curQuality" #if $curQuality in $bestQualities then "selected=\"selected\"" else ""#>$Quality.qualityStrings[$curQuality]</option> diff --git a/data/interfaces/default/manage.tmpl b/data/interfaces/default/manage.tmpl index 4d7035c4b9ea03a2376c233a2593425f1567c260..4be920bdb933a77ae23425d7cc9bd975fea5627a 100644 --- a/data/interfaces/default/manage.tmpl +++ b/data/interfaces/default/manage.tmpl @@ -26,7 +26,7 @@ return false; }, format: function(s) { - return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); + return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('best',0).replace('custom',7); }, type: 'numeric' }); diff --git a/data/interfaces/default/manage_massEdit.tmpl b/data/interfaces/default/manage_massEdit.tmpl index e69b9177c818c9b813a402fc61b50cb0f3fab76e..735bbfae7f8e839423404bf9c97698d91a874f6d 100644 --- a/data/interfaces/default/manage_massEdit.tmpl +++ b/data/interfaces/default/manage_massEdit.tmpl @@ -60,7 +60,7 @@ </div> <div style="width: 50%; text-align: center;" class="float-left"> <h4>Archive</h4> - #set $bestQualityList = filter(lambda x: x > $common.Quality.SDTV, $common.Quality.qualityStrings) + #set $bestQualityList = filter(lambda x: x > $common.Quality.SDTV and x < $common.Quality.UNKNOWN, $common.Quality.qualityStrings) <select id="bestQualities" name="bestQualities" multiple="multiple" size="len($bestQualityList)"> #for $curQuality in sorted($bestQualityList): <option value="$curQuality" #if $curQuality in $bestQualities then "selected=\"selected\"" else ""#>$common.Quality.qualityStrings[$curQuality]</option> diff --git a/data/js/ajaxEpSearch.js b/data/js/ajaxEpSearch.js index cda70b6c55f187d19811586b779b60d3382e3010..5effba384148796186ff09c0d69ff3953d331448 100644 --- a/data/js/ajaxEpSearch.js +++ b/data/js/ajaxEpSearch.js @@ -1,50 +1,50 @@ -(function(){ - - $.ajaxEpSearch = { - defaults: { - size: 16, - colorRow: false, - loadingImage: 'loading16_dddddd.gif', - noImage: 'no16.png', - yesImage: 'yes16.png' - } - }; - - $.fn.ajaxEpSearch = function(options){ - options = $.extend({}, $.ajaxEpSearch.defaults, options); - - $('.epSearch').click(function(){ - var parent = $(this).parent(); - - // put the ajax spinner (for non white bg) placeholder while we wait - parent.empty(); - parent.append($("<img/>").attr({"src": sbRoot+"/images/"+options.loadingImage, "height": options.size, "alt": "", "title": "loading"})); - - $.getJSON($(this).attr('href'), function(data){ - // if they failed then just put the red X - if (data.result == 'failure') { - img_name = options.noImage; - img_result = 'failed'; - - // if the snatch was successful then apply the corresponding class and fill in the row appropriately - } else { - img_name = options.yesImage; - img_result = 'success'; - // color the row - if (options.colorRow) - parent.parent().removeClass('skipped wanted qual good unaired').addClass('good'); - - // update the status column if it exists - parent.siblings('.status_column').html(data.result); - } - - // put the corresponding image as the result for the the row - parent.empty(); - parent.append($("<img/>").attr({"src": sbRoot+"/images/"+img_name, "height": options.size, "alt": img_result, "title": img_result})); - }); - - // fon't follow the link - return false; - }); - } +(function () { + + $.ajaxEpSearch = { + defaults: { + size: 16, + colorRow: false, + loadingImage: 'loading16_dddddd.gif', + noImage: 'no16.png', + yesImage: 'yes16.png' + } + }; + + $.fn.ajaxEpSearch = function (options) { + options = $.extend({}, $.ajaxEpSearch.defaults, options); + + $('.epSearch').click(function () { + var parent = $(this).parent(); + + // put the ajax spinner (for non white bg) placeholder while we wait + parent.empty(); + parent.append($("<img/>").attr({"src": sbRoot + "/images/" + options.loadingImage, "height": options.size, "alt": "", "title": "loading"})); + + $.getJSON($(this).attr('href'), function (data) { + // if they failed then just put the red X + if (data.result == 'failure') { + img_name = options.noImage; + img_result = 'failed'; + + // if the snatch was successful then apply the corresponding class and fill in the row appropriately + } else { + img_name = options.yesImage; + img_result = 'success'; + // color the row + if (options.colorRow) { + parent.parent().removeClass('skipped wanted qual good unaired snatched').addClass('snatched'); + } + // update the status column if it exists + parent.siblings('.status_column').html(data.result); + } + + // put the corresponding image as the result for the the row + parent.empty(); + parent.append($("<img/>").attr({"src": sbRoot + "/images/" + img_name, "height": options.size, "alt": img_result, "title": img_result})); + }); + + // fon't follow the link + return false; + }); + }; })(); diff --git a/data/js/configNotifications.js b/data/js/configNotifications.js index a9ed18fe89ab90b22dee734f49f2e014562cd9e4..363d231ec79b7087335b3f9d58935743a6b44d68 100644 --- a/data/js/configNotifications.js +++ b/data/js/configNotifications.js @@ -1,89 +1,91 @@ -$(document).ready(function(){ - var loading = '<img src="'+sbRoot+'/images/loading16.gif" height="16" width="16" />'; +$(document).ready(function () { + var loading = '<img src="' + sbRoot + '/images/loading16.gif" height="16" width="16" />'; - $('#testGrowl').click(function(){ + $('#testGrowl').click(function () { $('#testGrowl-result').html(loading); var growl_host = $("#growl_host").val(); var growl_password = $("#growl_password").val(); - var growl_result = $.get(sbRoot+"/home/testGrowl", {'host': growl_host, 'password': growl_password}, - function (data){ $('#testGrowl-result').html(data); }); + $.get(sbRoot + "/home/testGrowl", {'host': growl_host, 'password': growl_password}, + function (data) { $('#testGrowl-result').html(data); }); }); - $('#testProwl').click(function(){ + $('#testProwl').click(function () { $('#testProwl-result').html(loading); var prowl_api = $("#prowl_api").val(); var prowl_priority = $("#prowl_priority").val(); - var prowl_result = $.get(sbRoot+"/home/testProwl", {'prowl_api': prowl_api, 'prowl_priority': prowl_priority}, - function (data){ $('#testProwl-result').html(data); }); + $.get(sbRoot + "/home/testProwl", {'prowl_api': prowl_api, 'prowl_priority': prowl_priority}, + function (data) { $('#testProwl-result').html(data); }); }); - $('#testXBMC').click(function(){ + $('#testXBMC').click(function () { + $("#testXBMC").attr("disabled", true); $('#testXBMC-result').html(loading); var xbmc_host = $("#xbmc_host").val(); var xbmc_username = $("#xbmc_username").val(); var xbmc_password = $("#xbmc_password").val(); - - $.get(sbRoot+"/home/testXBMC", {'host': xbmc_host, 'username': xbmc_username, 'password': xbmc_password}, - function (data){ $('#testXBMC-result').html(data); }); + $.get(sbRoot + "/home/testXBMC", {'host': xbmc_host, 'username': xbmc_username, 'password': xbmc_password}) + .done(function (data) { + $('#testXBMC-result').html(data); + $("#testXBMC").attr("disabled", false); + }); }); - $('#testPLEX').click(function(){ + $('#testPLEX').click(function () { $('#testPLEX-result').html(loading); var plex_host = $("#plex_host").val(); var plex_username = $("#plex_username").val(); var plex_password = $("#plex_password").val(); - - $.get(sbRoot+"/home/testPLEX", {'host': plex_host, 'username': plex_username, 'password': plex_password}, - function (data){ $('#testPLEX-result').html(data);}); + $.get(sbRoot + "/home/testPLEX", {'host': plex_host, 'username': plex_username, 'password': plex_password}, + function (data) { $('#testPLEX-result').html(data); }); }); - $('#testNotifo').click(function(){ + $('#testNotifo').click(function () { $('#testNotifo-result').html(loading); var notifo_username = $("#notifo_username").val(); var notifo_apisecret = $("#notifo_apisecret").val(); - $.get(sbRoot+"/home/testNotifo", {'username': notifo_username, 'apisecret': notifo_apisecret}, - function (data){ $('#testNotifo-result').html(data); }); + $.get(sbRoot + "/home/testNotifo", {'username': notifo_username, 'apisecret': notifo_apisecret}, + function (data) { $('#testNotifo-result').html(data); }); }); - $('#testBoxcar').click(function(){ + $('#testBoxcar').click(function () { $('#testBoxcar-result').html(loading); var boxcar_username = $("#boxcar_username").val(); - $.get(sbRoot+"/home/testBoxcar", {'username': boxcar_username}, - function (data){ $('#testBoxcar-result').html(data); }); + $.get(sbRoot + "/home/testBoxcar", {'username': boxcar_username}, + function (data) { $('#testBoxcar-result').html(data); }); }); - $('#testPushover').click(function(){ + $('#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); }); + $.get(sbRoot + "/home/testPushover", {'userKey': pushover_userkey}, + function (data) { $('#testPushover-result').html(data); }); }); - $('#testLibnotify').click(function(){ + $('#testLibnotify').click(function () { $('#testLibnotify-result').html(loading); - $.get(sbRoot+"/home/testLibnotify", - function(message){ $('#testLibnotify-result').html(message); }); + $.get(sbRoot + "/home/testLibnotify", + function (data) { $('#testLibnotify-result').html(data); }); }); - - $('#twitterStep1').click(function(){ + + $('#twitterStep1').click(function () { $('#testTwitter-result').html(loading); - var twitter1_result = $.get(sbRoot+"/home/twitterStep1", function (data){window.open(data)}) - .complete(function() { $('#testTwitter-result').html('<b>Step1:</b> Confirm Authorization'); }); + $.get(sbRoot + "/home/twitterStep1", function (data) {window.open(data); }) + .done(function () { $('#testTwitter-result').html('<b>Step1:</b> Confirm Authorization'); }); }); - $('#twitterStep2').click(function(){ + $('#twitterStep2').click(function () { $('#testTwitter-result').html(loading); var twitter_key = $("#twitter_key").val(); - $.get(sbRoot+"/home/twitterStep2", {'key': twitter_key}, - function (data){ $('#testTwitter-result').html(data); }); + $.get(sbRoot + "/home/twitterStep2", {'key': twitter_key}, + function (data) { $('#testTwitter-result').html(data); }); }); - $('#testTwitter').click(function(){ - $.get(sbRoot+"/home/testTwitter", - function (data){ $('#testTwitter-result').html(data); }); + $('#testTwitter').click(function () { + $.get(sbRoot + "/home/testTwitter", + function (data) { $('#testTwitter-result').html(data); }); }); - $('#settingsNMJ').click(function(){ + $('#settingsNMJ').click(function () { if (!$('#nmj_host').val()) { alert('Please fill in the Popcorn IP address'); $('#nmj_host').focus(); @@ -91,55 +93,99 @@ $(document).ready(function(){ } $('#testNMJ-result').html(loading); var nmj_host = $('#nmj_host').val(); - - $.get(sbRoot+"/home/settingsNMJ", {'host': nmj_host}, + + $.get(sbRoot + "/home/settingsNMJ", {'host': nmj_host}, + function (data) { + if (data === null) { + $('#nmj_database').removeAttr('readonly'); + $('#nmj_mount').removeAttr('readonly'); + } + var JSONData = $.parseJSON(data); + $('#testNMJ-result').html(JSONData.message); + $('#nmj_database').val(JSONData.database); + $('#nmj_mount').val(JSONData.mount); + + if (JSONData.database) { + $('#nmj_database').attr('readonly', true); + } else { + $('#nmj_database').removeAttr('readonly'); + } + if (JSONData.mount) { + $('#nmj_mount').attr('readonly', true); + } else { + $('#nmj_mount').removeAttr('readonly'); + } + }); + }); + + $('#testNMJ').click(function () { + $('#testNMJ-result').html(loading); + var nmj_host = $("#nmj_host").val(); + var nmj_database = $("#nmj_database").val(); + var nmj_mount = $("#nmj_mount").val(); + + $.get(sbRoot + "/home/testNMJ", {'host': nmj_host, 'database': nmj_database, 'mount': nmj_mount}, + function (data) { $('#testNMJ-result').html(data); }); + }); + + $('#settingsNMJv2').click(function () { + if (!$('#nmjv2_host').val()) { + alert('Please fill in the Popcorn IP address'); + $('#nmjv2_host').focus(); + return; + } + $('#testNMJv2-result').html(loading); + var nmjv2_host = $('#nmjv2_host').val(); + var nmjv2_dbloc; + var radios = document.getElementsByName("nmjv2_dbloc"); + for (var i = 0; i < radios.length; i++) { + if (radios[i].checked) { + nmjv2_dbloc=radios[i].value; + break; + } + } + + var nmjv2_dbinstance=$('#NMJv2db_instance').val(); + $.get(sbRoot + "/home/settingsNMJv2", {'host': nmjv2_host, 'dbloc': nmjv2_dbloc, 'instance': nmjv2_dbinstance}, function (data){ if (data == null) { - $('#nmj_database').removeAttr('readonly'); - $('#nmj_mount').removeAttr('readonly'); + $('#nmjv2_database').removeAttr('readonly'); } var JSONData = $.parseJSON(data); - $('#testNMJ-result').html(JSONData.message); - $('#nmj_database').val(JSONData.database); - $('#nmj_mount').val(JSONData.mount); + $('#testNMJv2-result').html(JSONData.message); + $('#nmjv2_database').val(JSONData.database); - if (JSONData.database) - $('#nmj_database').attr('readonly', true); - else - $('#nmj_database').removeAttr('readonly'); - - if (JSONData.mount) - $('#nmj_mount').attr('readonly', true); - else - $('#nmj_mount').removeAttr('readonly'); + if (JSONData.database) { + $('#nmjv2_database').attr('readonly', true); + } else { + $('#nmjv2_database').removeAttr('readonly'); + } }); }); - $('#testNMJ').click(function(){ - $('#testNMJ-result').html(loading); - var nmj_host = $("#nmj_host").val(); - var nmj_database = $("#nmj_database").val(); - var nmj_mount = $("#nmj_mount").val(); + $('#testNMJv2').click(function () { + $('#testNMJv2-result').html(loading); + var nmjv2_host = $("#nmjv2_host").val(); - $.get(sbRoot+"/home/testNMJ", {'host': nmj_host, 'database': nmj_database, 'mount': nmj_mount}, - function (data){ $('#testNMJ-result').html(data); }); + $.get(sbRoot + "/home/testNMJv2", {'host': nmjv2_host}, + function (data){ $('#testNMJv2-result').html(data); }); }); - $('#testTrakt').click(function(){ + $('#testTrakt').click(function () { $('#testTrakt-result').html(loading); var trakt_api = $("#trakt_api").val(); var trakt_username = $("#trakt_username").val(); var trakt_password = $("#trakt_password").val(); - $.get(sbRoot+"/home/testTrakt", {'api': trakt_api, 'username': trakt_username, 'password': trakt_password}, - function (data){ $('#testTrakt-result').html(data); }); + $.get(sbRoot + "/home/testTrakt", {'api': trakt_api, 'username': trakt_username, 'password': trakt_password}, + function (data) { $('#testTrakt-result').html(data); }); }); - $('#testNMA').click(function(){ + $('#testNMA').click(function () { $('#testNMA-result').html(loading); var nma_api = $("#nma_api").val(); var nma_priority = $("#nma_priority").val(); - var nma_result = $.get(sbRoot+"/home/testNMA", {'nma_api': nma_api, 'nma_priority': nma_priority}, - function (data){ $('#testNMA-result').html(data); }); + $.get(sbRoot + "/home/testNMA", {'nma_api': nma_api, 'nma_priority': nma_priority}, + function (data) { $('#testNMA-result').html(data); }); }); }); diff --git a/data/js/configProviders.js b/data/js/configProviders.js index b54f63e3df511d733e025a15ed1f3409dc32002c..07817fb1ca28a96975e94f5f0ad810add85b8c17 100644 --- a/data/js/configProviders.js +++ b/data/js/configProviders.js @@ -13,7 +13,7 @@ $(document).ready(function(){ }); } - $.fn.addProvider = function (id, name, url, key, isDefault, showProvider) { + $.fn.addProvider = function (id, name, url, key, isDefault) { if (url.match('/$') == null) url = url + '/' @@ -27,7 +27,7 @@ $(document).ready(function(){ $(this).populateNewznabSection(); } - if ($('#providerOrderList > #'+id).length == 0 && showProvider != false) { + if ($('#providerOrderList > #'+id).length == 0) { var toAdd = '<li class="ui-state-default" id="'+id+'"> <input type="checkbox" id="enable_'+id+'" class="provider_enabler" CHECKED> <a href="'+url+'" class="imgLink" target="_new"><img src="'+sbRoot+'/images/providers/newznab.gif" alt="'+name+'" width="16" height="16"></a> '+name+'</li>' $('#providerOrderList').append(toAdd); @@ -136,16 +136,15 @@ $(document).ready(function(){ }); - $('#newznab_key').change(function(){ + $('#newznab_key,#newznab_url').change(function(){ var selectedProvider = $('#editANewznabProvider :selected').val(); - - if (selectedProvider == "addNewznab") - return; - + + if (selectedProvider == "addNewznab") + return; + var url = $('#newznab_url').val(); var key = $('#newznab_key').val(); - $(this).updateProvider(selectedProvider, url, key); @@ -209,4 +208,4 @@ $(document).ready(function(){ $("#providerOrderList").disableSelection(); -}); +}); \ No newline at end of file diff --git a/data/js/displayShow.js b/data/js/displayShow.js index c821231e1f09bd82ee54d462e6addd34cc1f63fa..9c0160f0934909de495314237f82047fcd579791 100644 --- a/data/js/displayShow.js +++ b/data/js/displayShow.js @@ -1,9 +1,7 @@ $(document).ready(function(){ $('#sbRoot').ajaxEpSearch({'colorRow': true}); - - $("td.status_column:contains('Snatched')").parent().css("background-color", "#EBC1EA"); - + $('#seasonJump').change(function() { var id = $(this).val(); if (id && id != 'jump') { diff --git a/data/js/rootDirs.js b/data/js/rootDirs.js index a8593270a0062aeb5f3131848b7cdfd76ebcb4b9..0e56ead8fd78042700c457db175d100936fdfd4a 100644 --- a/data/js/rootDirs.js +++ b/data/js/rootDirs.js @@ -1,27 +1,44 @@ -$(document).ready(function(){ - - function logMsg(msg) { - if (window.console && window.logMsg) - console.log(msg) +// Avoid `console` errors in browsers that lack a console. +(function() { + var method; + var noop = function noop() {}; + var methods = [ + 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', + 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', + 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', + 'timeStamp', 'trace', 'warn' + ]; + var length = methods.length; + var console = (window.console = window.console || {}); + + while (length--) { + method = methods[length]; + + // Only stub undefined methods. + if (!console[method]) { + console[method] = noop; + } } +}()); + +$(document).ready(function() { - function addRootDir(path){ + function addRootDir(path) { // check if it's the first one var is_default = false; if (!$('#whichDefaultRootDir').val().length) is_default = true; $('#rootDirs').append('<option value="'+path+'">'+path+'</option>'); - + syncOptionIDs(); - + if (is_default) setDefault($('#rootDirs option').attr('id')); refreshRootDirs(); - $.get(sbRoot+'/config/general/saveRootDirs', { rootDirString: $('#rootDirText').val() }); - + } function editRootDir(path) { @@ -43,7 +60,7 @@ $(document).ready(function(){ $('#addRootDir').click(function(){$(this).nFileBrowser(addRootDir)}); $('#editRootDir').click(function(){$(this).nFileBrowser(editRootDir, {initialDir: $("#rootDirs option:selected").val()})}); - $('#deleteRootDir').click(function(){ + $('#deleteRootDir').click(function() { if ($("#rootDirs option:selected").length) { var toDelete = $("#rootDirs option:selected"); @@ -56,15 +73,15 @@ $(document).ready(function(){ if (newDefault) { - logMsg('new default when deleting') - + console.log('new default when deleting'); + // we deleted the default so this isn't valid anymore $("#whichDefaultRootDir").val(''); // if we're deleting the default and there are options left then pick a new default if ($("#rootDirs option").length) setDefault($('#rootDirs option').attr('id')); - + } else if ($("#whichDefaultRootDir").val().length) { var old_default_num = $("#whichDefaultRootDir").val().substr(3); if (old_default_num > deleted_num) @@ -80,12 +97,12 @@ $(document).ready(function(){ if ($("#rootDirs option:selected").length) setDefault($("#rootDirs option:selected").attr('id')); refreshRootDirs(); - $.get(sbRoot+'/config/general/saveRootDirs', 'rootDirString='+$('#rootDirText').val()); + $.get(sbRoot+'/config/general/saveRootDirs', {rootDirString: $('#rootDirText').val()}); }); function setDefault(which, force){ - logMsg('setting default to '+which) + console.log('setting default to '+which); if (which != undefined && !which.length) return @@ -102,7 +119,7 @@ $(document).ready(function(){ var old_default = $('#'+$('#whichDefaultRootDir').val()); old_default.text(old_default.text().substring(1)); } - + $('#whichDefaultRootDir').val(which); } @@ -118,7 +135,7 @@ $(document).ready(function(){ if (!$("#rootDirs").length) return - + var do_disable = 'true'; // re-sync option ids @@ -148,11 +165,11 @@ $(document).ready(function(){ dir_text += '|' + $(this).val() }); log_str += 'def: '+ $('#whichDefaultRootDir').val(); - logMsg(log_str) + console.log(log_str); $('#rootDirText').val(dir_text); $('#rootDirText').change(); - logMsg('rootDirText: '+$('#rootDirText').val()) + console.log('rootDirText: '+$('#rootDirText').val()); } $('#rootDirs').click(refreshRootDirs); diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 561422015e887b22fd00afa00f9d020b96ed6d35..f6e815939f60b13c5da407f850e135bb6644f3f8 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -30,7 +30,7 @@ from threading import Lock # apparently py2exe won't build these unless they're imported somewhere from sickbeard import providers, metadata -from providers import ezrss, tvtorrents, btn, nzbsrus, newznab, womble, binnewz, t411 +from providers import ezrss, tvtorrents, torrentleech, btn, nzbsrus, newznab, womble, nzbx, omgwtfnzbs, binnewz, t411 from sickbeard.config import CheckSection, check_setting_int, check_setting_str, ConfigMigrator from sickbeard import searchCurrent, searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser @@ -159,6 +159,9 @@ TVTORRENTS = False TVTORRENTS_DIGEST = None TVTORRENTS_HASH = None +TORRENTLEECH = False +TORRENTLEECH_KEY = None + BTN = False BTN_API_KEY = None @@ -178,6 +181,13 @@ NZBS_HASH = None WOMBLE = False +NZBX = False +NZBX_COMPLETION = 100 + +OMGWTFNZBS = False +OMGWTFNZBS_UID = None +OMGWTFNZBS_KEY = None + NZBSRUS = False NZBSRUS_UID = None NZBSRUS_HASH = None @@ -211,6 +221,7 @@ XBMC_NOTIFY_ONSNATCH = False XBMC_NOTIFY_ONDOWNLOAD = False XBMC_UPDATE_LIBRARY = False XBMC_UPDATE_FULL = False +XBMC_UPDATE_ONLYFIRST = False XBMC_HOST = '' XBMC_USERNAME = None XBMC_PASSWORD = None @@ -273,6 +284,11 @@ NMJ_MOUNT = None USE_SYNOINDEX = False +USE_NMJv2 = False +NMJv2_HOST = None +NMJv2_DATABASE = None +NMJv2_DBLOC = None + USE_TRAKT = False TRAKT_USERNAME = None TRAKT_PASSWORD = None @@ -318,13 +334,13 @@ def initialize(consoleLogging=True): USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, \ SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_HOST, \ NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, currentSearchScheduler, backlogSearchScheduler, \ - USE_XBMC, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_UPDATE_FULL, \ + USE_XBMC, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, \ XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \ USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_API, \ USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_UPDATE_LIBRARY, \ PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, \ showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, showList, loadingShowList, \ - NZBS, NZBS_UID, NZBS_HASH, EZRSS, TVTORRENTS, TVTORRENTS_DIGEST, TVTORRENTS_HASH, BTN, BTN_API_KEY, TORRENT_DIR, USENET_RETENTION, SOCKET_TIMEOUT, \ + NZBS, NZBS_UID, NZBS_HASH, EZRSS, TVTORRENTS, TVTORRENTS_DIGEST, TVTORRENTS_HASH, BTN, BTN_API_KEY, TORRENTLEECH, TORRENTLEECH_KEY, TORRENT_DIR, USENET_RETENTION, SOCKET_TIMEOUT, \ BINNEWZ, \ T411, T411_USERNAME, T411_PASSWORD, \ SEARCH_FREQUENCY, DEFAULT_SEARCH_FREQUENCY, BACKLOG_SEARCH_FREQUENCY, \ @@ -338,12 +354,12 @@ def initialize(consoleLogging=True): showQueueScheduler, searchQueueScheduler, ROOT_DIRS, CACHE_DIR, ACTUAL_CACHE_DIR, TVDB_API_PARMS, \ NAMING_PATTERN, NAMING_MULTI_EP, NAMING_FORCE_FOLDERS, NAMING_ABD_PATTERN, NAMING_CUSTOM_ABD, \ RENAME_EPISODES, properFinderScheduler, PROVIDER_ORDER, autoPostProcesserScheduler, \ - NZBSRUS, NZBSRUS_UID, NZBSRUS_HASH, WOMBLE, providerList, newznabProviderList, \ + NZBSRUS, NZBSRUS_UID, NZBSRUS_HASH, WOMBLE, NZBX, NZBX_COMPLETION, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_KEY, providerList, newznabProviderList, \ 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_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_NMJv2, NMJv2_HOST, NMJv2_DATABASE, NMJv2_DBLOC, 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, \ COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, METADATA_WDTV, METADATA_TIVO, IGNORE_WORDS, CREATE_MISSING_SHOW_DIRS, \ @@ -544,6 +560,10 @@ def initialize(consoleLogging=True): BTN = bool(check_setting_int(CFG, 'BTN', 'btn', 0)) BTN_API_KEY = check_setting_str(CFG, 'BTN', 'btn_api_key', '') + CheckSection(CFG, 'TorrentLeech') + TORRENTLEECH = bool(check_setting_int(CFG, 'TorrentLeech', 'torrentleech', 0)) + TORRENTLEECH_KEY = check_setting_str(CFG, 'TorrentLeech', 'torrentleech_key', '') + CheckSection(CFG, 'NZBs') NZBS = bool(check_setting_int(CFG, 'NZBs', 'nzbs', 0)) NZBS_UID = check_setting_str(CFG, 'NZBs', 'nzbs_uid', '') @@ -575,6 +595,15 @@ def initialize(consoleLogging=True): CheckSection(CFG, 'Womble') WOMBLE = bool(check_setting_int(CFG, 'Womble', 'womble', 1)) + CheckSection(CFG, 'nzbX') + NZBX = bool(check_setting_int(CFG, 'nzbX', 'nzbx', 0)) + NZBX_COMPLETION = check_setting_int(CFG, 'nzbX', 'nzbx_completion', 100) + + CheckSection(CFG, 'omgwtfnzbs') + OMGWTFNZBS = bool(check_setting_int(CFG, 'omgwtfnzbs', 'omgwtfnzbs', 0)) + OMGWTFNZBS_UID = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_uid', '') + OMGWTFNZBS_KEY = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_key', '') + CheckSection(CFG, 'SABnzbd') SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '') SAB_PASSWORD = check_setting_str(CFG, 'SABnzbd', 'sab_password', '') @@ -593,6 +622,7 @@ def initialize(consoleLogging=True): XBMC_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_ondownload', 0)) XBMC_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_library', 0)) XBMC_UPDATE_FULL = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_full', 0)) + XBMC_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_onlyfirst', 0)) XBMC_HOST = check_setting_str(CFG, 'XBMC', 'xbmc_host', '') XBMC_USERNAME = check_setting_str(CFG, 'XBMC', 'xbmc_username', '') XBMC_PASSWORD = check_setting_str(CFG, 'XBMC', 'xbmc_password', '') @@ -659,6 +689,12 @@ def initialize(consoleLogging=True): NMJ_DATABASE = check_setting_str(CFG, 'NMJ', 'nmj_database', '') NMJ_MOUNT = check_setting_str(CFG, 'NMJ', 'nmj_mount', '') + CheckSection(CFG, 'NMJv2') + USE_NMJv2 = bool(check_setting_int(CFG, 'NMJv2', 'use_nmjv2', 0)) + NMJv2_HOST = check_setting_str(CFG, 'NMJv2', 'nmjv2_host', '') + NMJv2_DATABASE = check_setting_str(CFG, 'NMJv2', 'nmjv2_database', '') + NMJ_DBLOC = check_setting_str(CFG, 'NMJv2', 'nmjv2_dbloc', '') + CheckSection(CFG, 'Synology') USE_SYNOINDEX = bool(check_setting_int(CFG, 'Synology', 'use_synoindex', 0)) @@ -1026,6 +1062,10 @@ def save_config(): new_config['BTN']['btn'] = int(BTN) new_config['BTN']['btn_api_key'] = BTN_API_KEY + new_config['TorrentLeech'] = {} + new_config['TorrentLeech']['torrentleech'] = int(TORRENTLEECH) + new_config['TorrentLeech']['torrentleech_key'] = TORRENTLEECH_KEY + new_config['NZBs'] = {} new_config['NZBs']['nzbs'] = int(NZBS) new_config['NZBs']['nzbs_uid'] = NZBS_UID @@ -1057,6 +1097,15 @@ def save_config(): new_config['Womble'] = {} new_config['Womble']['womble'] = int(WOMBLE) + new_config['nzbX'] = {} + new_config['nzbX']['nzbx'] = int(NZBX) + new_config['nzbX']['nzbx_completion'] = int(NZBX_COMPLETION) + + new_config['omgwtfnzbs'] = {} + new_config['omgwtfnzbs']['omgwtfnzbs'] = int(OMGWTFNZBS) + new_config['omgwtfnzbs']['omgwtfnzbs_uid'] = OMGWTFNZBS_UID + new_config['omgwtfnzbs']['omgwtfnzbs_key'] = OMGWTFNZBS_KEY + new_config['SABnzbd'] = {} new_config['SABnzbd']['sab_username'] = SAB_USERNAME new_config['SABnzbd']['sab_password'] = SAB_PASSWORD @@ -1075,6 +1124,7 @@ def save_config(): new_config['XBMC']['xbmc_notify_ondownload'] = int(XBMC_NOTIFY_ONDOWNLOAD) new_config['XBMC']['xbmc_update_library'] = int(XBMC_UPDATE_LIBRARY) new_config['XBMC']['xbmc_update_full'] = int(XBMC_UPDATE_FULL) + new_config['XBMC']['xbmc_update_onlyfirst'] = int(XBMC_UPDATE_ONLYFIRST) new_config['XBMC']['xbmc_host'] = XBMC_HOST new_config['XBMC']['xbmc_username'] = XBMC_USERNAME new_config['XBMC']['xbmc_password'] = XBMC_PASSWORD @@ -1144,6 +1194,12 @@ def save_config(): new_config['Synology'] = {} new_config['Synology']['use_synoindex'] = int(USE_SYNOINDEX) + new_config['NMJv2'] = {} + new_config['NMJv2']['use_nmjv2'] = int(USE_NMJv2) + new_config['NMJv2']['nmjv2_host'] = NMJv2_HOST + new_config['NMJv2']['nmjv2_database'] = NMJv2_DATABASE + new_config['NMJv2']['nmjv2_dbloc'] = NMJv2_DBLOC + new_config['Trakt'] = {} new_config['Trakt']['use_trakt'] = int(USE_TRAKT) new_config['Trakt']['trakt_username'] = TRAKT_USERNAME diff --git a/sickbeard/common.py b/sickbeard/common.py index 3da19925091fb20a17d1ec7a21519898fcf7ddc3..3c82b0e6e9661e3d3af7245d4918ea29019c80f9 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -17,12 +17,13 @@ # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. import os.path -import operator, platform +import operator +import platform import re from sickbeard import version -USER_AGENT = 'Sick Beard/alpha2-'+version.SICKBEARD_VERSION.replace(' ','-')+' ('+platform.system()+' '+platform.release()+')' +USER_AGENT = 'Sick Beard/alpha2-' + version.SICKBEARD_VERSION.replace(' ', '-') + ' (' + platform.system() + ' ' + platform.release() + ')' mediaExtensions = ['avi', 'mkv', 'mpg', 'mpeg', 'wmv', 'ogm', 'mp4', 'iso', 'img', 'divx', @@ -68,25 +69,31 @@ multiEpStrings[NAMING_EXTEND] = "Extend" multiEpStrings[NAMING_LIMITED_EXTEND] = "Extend (Limited)" multiEpStrings[NAMING_LIMITED_EXTEND_E_PREFIXED] = "Extend (Limited, E-prefixed)" -class Quality: - NONE = 0 - SDTV = 1 - SDDVD = 1<<1 # 2 - HDTV = 1<<2 # 4 - HDWEBDL = 1<<3 # 8 - HDBLURAY = 1<<4 # 16 - FULLHDBLURAY = 1<<5 # 32 +class Quality: + NONE = 0 # 0 + SDTV = 1 # 1 + SDDVD = 1 << 1 # 2 + HDTV = 1 << 2 # 4 + RAWHDTV = 1 << 3 # 8 -- 720p/1080i mpeg2 (trollhd releases) + FULLHDTV = 1 << 4 # 16 -- 1080p HDTV (QCF releases) + HDWEBDL = 1 << 5 # 32 + FULLHDWEBDL = 1 << 6 # 64 -- 1080p web-dl + HDBLURAY = 1 << 7 # 128 + FULLHDBLURAY = 1 << 8 # 256 # put these bits at the other end of the spectrum, far enough out that they shouldn't interfere - UNKNOWN = 1<<15 + UNKNOWN = 1 << 15 # 32768 qualityStrings = {NONE: "N/A", UNKNOWN: "Unknown", SDTV: "SD TV", SDDVD: "SD DVD", HDTV: "HD TV", + RAWHDTV: "RawHD TV", + FULLHDTV: "1080p HD TV", HDWEBDL: "720p WEB-DL", + FULLHDWEBDL: "1080p WEB-DL", HDBLURAY: "720p BluRay", FULLHDBLURAY: "1080p BluRay"} @@ -97,7 +104,7 @@ class Quality: def _getStatusStrings(status): toReturn = {} for x in Quality.qualityStrings.keys(): - toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")" + toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status] + " (" + Quality.qualityStrings[x] + ")" return toReturn @staticmethod @@ -108,7 +115,7 @@ class Quality: anyQuality = reduce(operator.or_, anyQualities) if bestQualities: bestQuality = reduce(operator.or_, bestQualities) - return anyQuality | (bestQuality<<16) + return anyQuality | (bestQuality << 16) @staticmethod def splitQuality(quality): @@ -117,50 +124,56 @@ class Quality: for curQual in Quality.qualityStrings.keys(): if curQual & quality: anyQualities.append(curQual) - if curQual<<16 & quality: + if curQual << 16 & quality: bestQualities.append(curQual) return (sorted(anyQualities), sorted(bestQualities)) @staticmethod def nameQuality(name): - name = os.path.basename(name) # if we have our exact text then assume we put it there - for x in Quality.qualityStrings: + for x in sorted(Quality.qualityStrings, reverse=True): if x == Quality.UNKNOWN: continue - regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W' + regex = '\W' + Quality.qualityStrings[x].replace(' ', '\W') + '\W' regex_match = re.search(regex, name, re.I) if regex_match: return x checkName = lambda list, func: func([re.search(x, name, re.I) for x in list]) - if checkName(["(pdtv|hdtv|dsr|tvrip).(xvid|x264)"], all) and not checkName(["(720|1080)[pi]"], all): + if checkName(["(pdtv|hdtv|dsr|tvrip|webrip).(xvid|x264)"], all) and not checkName(["(720|1080)[pi]"], all): return Quality.SDTV elif checkName(["(dvdrip|bdrip)(.ws)?.(xvid|divx|x264)"], any) and not checkName(["(720|1080)[pi]"], all): return Quality.SDDVD - elif checkName(["720p", "hdtv", "x264"], all) or checkName(["hr.ws.pdtv.x264"], any): + elif checkName(["720p", "hdtv", "x264"], all) or checkName(["hr.ws.pdtv.x264"], any) and not checkName(["(1080)[pi]"], all): return Quality.HDTV - elif checkName(["720p", "web.dl"], all) or checkName(["720p", "itunes", "h.?264"], all): + elif checkName(["720p|1080i", "hdtv", "mpeg2"], all): + return Quality.RAWHDTV + elif checkName(["1080p", "hdtv", "x264"], all): + return Quality.FULLHDTV + elif checkName(["720p", "web.dl|webrip"], all) or checkName(["720p", "itunes", "h.?264"], all): return Quality.HDWEBDL - elif checkName(["720p", "bluray", "x264"], all) or checkName(["720p", "hddvd", "x264"], all): + elif checkName(["1080p", "web.dl|webrip"], all) or checkName(["1080p", "itunes", "h.?264"], all): + return Quality.FULLHDWEBDL + elif checkName(["720p", "bluray|hddvd", "x264"], all): return Quality.HDBLURAY - elif checkName(["1080p", "bluray", "x264"], all) or checkName(["1080p", "hddvd", "x264"], all): + elif checkName(["1080p", "bluray|hddvd", "x264"], all): return Quality.FULLHDBLURAY else: return Quality.UNKNOWN @staticmethod def assumeQuality(name): - if name.lower().endswith((".avi", ".mp4")): return Quality.SDTV elif name.lower().endswith(".mkv"): return Quality.HDTV + elif name.lower().endswith(".ts"): + return Quality.RAWHDTV else: return Quality.UNKNOWN @@ -174,15 +187,15 @@ class Quality: @staticmethod def splitCompositeStatus(status): + """Returns a tuple containing (status, quality)""" if status == UNKNOWN: return (UNKNOWN, Quality.UNKNOWN) - - """Returns a tuple containing (status, quality)""" + for x in sorted(Quality.qualityStrings.keys(), reverse=True): - if status > x*100: - return (status-x*100, x) + if status > x * 100: + return (status - x * 100, x) - return (Quality.NONE, status) + return (status, Quality.NONE) @staticmethod def statusFromName(name, assume=True): @@ -199,22 +212,29 @@ Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qu Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()] Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()] -HD = Quality.combineQualities([Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY], []) SD = Quality.combineQualities([Quality.SDTV, Quality.SDDVD], []) -ANY = Quality.combineQualities([Quality.SDTV, Quality.SDDVD, Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY, Quality.UNKNOWN], []) +HD = Quality.combineQualities([Quality.HDTV, Quality.FULLHDTV, Quality.HDWEBDL, Quality.FULLHDWEBDL, Quality.HDBLURAY, Quality.FULLHDBLURAY], []) # HD720p + HD1080p +HD720p = Quality.combineQualities([Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY], []) +HD1080p = Quality.combineQualities([Quality.FULLHDTV, Quality.FULLHDWEBDL, Quality.FULLHDBLURAY], []) +ANY = Quality.combineQualities([Quality.SDTV, Quality.SDDVD, Quality.HDTV, Quality.FULLHDTV, Quality.HDWEBDL, Quality.FULLHDWEBDL, Quality.HDBLURAY, Quality.FULLHDBLURAY, Quality.UNKNOWN], []) # SD + HD + +# legacy template, cant remove due to reference in mainDB upgrade? BEST = Quality.combineQualities([Quality.SDTV, Quality.HDTV, Quality.HDWEBDL], [Quality.HDTV]) -qualityPresets = (SD, HD, ANY) +qualityPresets = (SD, HD, HD720p, HD1080p, ANY) qualityPresetStrings = {SD: "SD", HD: "HD", + HD720p: "HD720p", + HD1080p: "HD1080p", ANY: "Any"} + class StatusStrings: def __init__(self): self.statusStrings = {UNKNOWN: "Unknown", UNAIRED: "Unaired", SNATCHED: "Snatched", - DOWNLOADED: "Downloaded", + DOWNLOADED: "Downloaded", SKIPPED: "Skipped", SNATCHED_PROPER: "Snatched (Proper)", WANTED: "Wanted", @@ -227,7 +247,7 @@ class StatusStrings: if quality == Quality.NONE: return self.statusStrings[status] else: - return self.statusStrings[status]+" ("+Quality.qualityStrings[quality]+")" + return self.statusStrings[status] + " (" + Quality.qualityStrings[quality] + ")" else: return self.statusStrings[name] @@ -236,6 +256,7 @@ class StatusStrings: statusStrings = StatusStrings() + class Overview: UNAIRED = UNAIRED # 1 QUAL = 2 @@ -243,11 +264,15 @@ class Overview: GOOD = 4 SKIPPED = SKIPPED # 5 + # For both snatched statuses. Note: SNATCHED/QUAL have same value and break dict. + SNATCHED = SNATCHED_PROPER # 9 + overviewStrings = {SKIPPED: "skipped", WANTED: "wanted", QUAL: "qual", GOOD: "good", - UNAIRED: "unaired"} + UNAIRED: "unaired", + SNATCHED: "snatched"} # Get our xml namespaces correct for lxml XML_NSMAP = {'xsi': 'http://www.w3.org/2001/XMLSchema-instance', @@ -258,7 +283,6 @@ countryList = {'Australia': 'AU', 'Canada': 'CA', 'USA': 'US' } - showLanguages = {'en':'english', 'fr':'french', '':'unknown' diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py index da2a9c761a87300e3119e9524a9c97162635f672..01f8b032f5e44d78a7d501d58446aeeaddd49154 100644 --- a/sickbeard/databases/mainDB.py +++ b/sickbeard/databases/mainDB.py @@ -23,17 +23,17 @@ from sickbeard import db, common, helpers, logger from sickbeard.providers.generic import GenericProvider from sickbeard import encodingKludge as ek -from sickbeard.name_parser.parser import NameParser, InvalidNameException +from sickbeard.name_parser.parser import NameParser, InvalidNameException -class MainSanityCheck(db.DBSanityCheck): +MAX_DB_VERSION = 12 +class MainSanityCheck(db.DBSanityCheck): def check(self): self.fix_duplicate_shows() self.fix_duplicate_episodes() self.fix_orphan_episodes() def fix_duplicate_shows(self): - sqlResults = self.connection.select("SELECT show_id, tvdb_id, COUNT(tvdb_id) as count FROM tv_shows GROUP BY tvdb_id HAVING count > 1") for cur_duplicate in sqlResults: @@ -41,7 +41,7 @@ class MainSanityCheck(db.DBSanityCheck): logger.log(u"Duplicate show detected! tvdb_id: " + str(cur_duplicate["tvdb_id"]) + u" count: " + str(cur_duplicate["count"]), logger.DEBUG) cur_dupe_results = self.connection.select("SELECT show_id, tvdb_id FROM tv_shows WHERE tvdb_id = ? LIMIT ?", - [cur_duplicate["tvdb_id"], int(cur_duplicate["count"])-1] + [cur_duplicate["tvdb_id"], int(cur_duplicate["count"]) - 1] ) for cur_dupe_id in cur_dupe_results: @@ -52,15 +52,14 @@ class MainSanityCheck(db.DBSanityCheck): logger.log(u"No duplicate show, check passed") def fix_duplicate_episodes(self): - sqlResults = self.connection.select("SELECT showid, season, episode, COUNT(showid) as count FROM tv_episodes GROUP BY showid, season, episode HAVING count > 1") for cur_duplicate in sqlResults: - logger.log(u"Duplicate episode detected! showid: " + str(cur_duplicate["showid"]) + u" season: "+str(cur_duplicate["season"]) + u" episode: "+str(cur_duplicate["episode"]) + u" count: " + str(cur_duplicate["count"]), logger.DEBUG) + logger.log(u"Duplicate episode detected! showid: " + str(cur_duplicate["showid"]) + u" season: " + str(cur_duplicate["season"]) + u" episode: " + str(cur_duplicate["episode"]) + u" count: " + str(cur_duplicate["count"]), logger.DEBUG) cur_dupe_results = self.connection.select("SELECT episode_id FROM tv_episodes WHERE showid = ? AND season = ? and episode = ? ORDER BY episode_id DESC LIMIT ?", - [cur_duplicate["showid"], cur_duplicate["season"], cur_duplicate["episode"], int(cur_duplicate["count"])-1] + [cur_duplicate["showid"], cur_duplicate["season"], cur_duplicate["episode"], int(cur_duplicate["count"]) - 1] ) for cur_dupe_id in cur_dupe_results: @@ -71,17 +70,17 @@ class MainSanityCheck(db.DBSanityCheck): logger.log(u"No duplicate episode, check passed") def fix_orphan_episodes(self): - sqlResults = self.connection.select("SELECT episode_id, showid, tv_shows.tvdb_id FROM tv_episodes LEFT JOIN tv_shows ON tv_episodes.showid=tv_shows.tvdb_id WHERE tv_shows.tvdb_id is NULL") for cur_orphan in sqlResults: logger.log(u"Orphan episode detected! episode_id: " + str(cur_orphan["episode_id"]) + " showid: " + str(cur_orphan["showid"]), logger.DEBUG) - logger.log(u"Deleting orphan episode with episode_id: "+str(cur_orphan["episode_id"])) + logger.log(u"Deleting orphan episode with episode_id: " + str(cur_orphan["episode_id"])) self.connection.action("DELETE FROM tv_episodes WHERE episode_id = ?", [cur_orphan["episode_id"]]) else: logger.log(u"No orphan episode, check passed") + def backupDatabase(version): helpers.backupVersionedFile(db.dbFilename(), version) @@ -90,6 +89,7 @@ def backupDatabase(version): # ====================== # Add new migrations at the bottom of the list; subclass the previous migration. + class InitialSchema (db.SchemaUpgrade): def test(self): return self.hasTable("tv_shows") @@ -104,6 +104,7 @@ class InitialSchema (db.SchemaUpgrade): for query in queries: self.connection.action(query) + class AddTvrId (InitialSchema): def test(self): return self.hasColumn("tv_shows", "tvr_id") @@ -111,6 +112,7 @@ class AddTvrId (InitialSchema): def execute(self): self.addColumn("tv_shows", "tvr_id") + class AddTvrName (AddTvrId): def test(self): return self.hasColumn("tv_shows", "tvr_name") @@ -118,6 +120,7 @@ class AddTvrName (AddTvrId): def execute(self): self.addColumn("tv_shows", "tvr_name", "TEXT", "") + class AddAirdateIndex (AddTvrName): def test(self): return self.hasTable("idx_tv_episodes_showid_airdate") @@ -125,6 +128,7 @@ class AddAirdateIndex (AddTvrName): def execute(self): self.connection.action("CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate);") + class NumericProviders (AddAirdateIndex): def test(self): return self.connection.tableInfo("history")['provider']['type'] == 'TEXT' @@ -157,12 +161,12 @@ class NumericProviders (AddAirdateIndex): args = [curResult["action"], curResult["date"], curResult["showid"], curResult["season"], curResult["episode"], curResult["quality"], curResult["resource"], provider] self.connection.action(sql, args) + class NewQualitySettings (NumericProviders): def test(self): return self.hasTable("db_version") def execute(self): - backupDatabase(0) # old stuff that's been removed from common but we need it to upgrade @@ -229,7 +233,6 @@ class NewQualitySettings (NumericProviders): if didUpdate: os.remove(db.dbFilename(suffix='v0')) - ### Update show qualities toUpdate = self.connection.select("SELECT * FROM tv_shows") for curUpdate in toUpdate: @@ -246,13 +249,12 @@ class NewQualitySettings (NumericProviders): elif int(curUpdate["quality"]) == BEST: newQuality = common.BEST else: - logger.log(u"Unknown show quality: "+str(curUpdate["quality"]), logger.WARNING) + logger.log(u"Unknown show quality: " + str(curUpdate["quality"]), logger.WARNING) newQuality = None if newQuality: self.connection.action("UPDATE tv_shows SET quality = ? WHERE show_id = ?", [newQuality, curUpdate["show_id"]]) - ### Update history toUpdate = self.connection.select("SELECT * FROM history") for curUpdate in toUpdate: @@ -282,6 +284,7 @@ class NewQualitySettings (NumericProviders): self.connection.action("CREATE TABLE db_version (db_version INTEGER);") self.connection.action("INSERT INTO db_version (db_version) VALUES (?)", [1]) + class DropOldHistoryTable(NewQualitySettings): def test(self): return self.checkDBVersion() >= 2 @@ -290,12 +293,12 @@ class DropOldHistoryTable(NewQualitySettings): self.connection.action("DROP TABLE history_old") self.incDBVersion() + class UpgradeHistoryForGenericProviders(DropOldHistoryTable): def test(self): return self.checkDBVersion() >= 3 def execute(self): - providerMap = {'NZBs': 'NZBs.org', 'BinReq': 'Bin-Req', 'NZBsRUS': '''NZBs'R'US''', @@ -306,6 +309,7 @@ class UpgradeHistoryForGenericProviders(DropOldHistoryTable): self.incDBVersion() + class AddAirByDateOption(UpgradeHistoryForGenericProviders): def test(self): return self.checkDBVersion() >= 4 @@ -314,24 +318,27 @@ class AddAirByDateOption(UpgradeHistoryForGenericProviders): self.connection.action("ALTER TABLE tv_shows ADD air_by_date NUMERIC") self.incDBVersion() + class ChangeSabConfigFromIpToHost(AddAirByDateOption): def test(self): return self.checkDBVersion() >= 5 - + def execute(self): sickbeard.SAB_HOST = 'http://' + sickbeard.SAB_HOST + '/sabnzbd/' self.incDBVersion() + class FixSabHostURL(ChangeSabConfigFromIpToHost): def test(self): return self.checkDBVersion() >= 6 - + def execute(self): if sickbeard.SAB_HOST.endswith('/sabnzbd/'): - sickbeard.SAB_HOST = sickbeard.SAB_HOST.replace('/sabnzbd/','/') + sickbeard.SAB_HOST = sickbeard.SAB_HOST.replace('/sabnzbd/', '/') sickbeard.save_config() self.incDBVersion() + class AddLang (FixSabHostURL): def test(self): return self.hasColumn("tv_shows", "lang") @@ -349,10 +356,10 @@ class AddCustomSearchNames (AddLang): class PopulateRootDirs (AddCustomSearchNames): def test(self): return self.checkDBVersion() >= 7 - + def execute(self): dir_results = self.connection.select("SELECT location FROM tv_shows") - + dir_counts = {} for cur_dir in dir_results: cur_root_dir = ek.ek(os.path.dirname, ek.ek(os.path.normpath, cur_dir["location"])) @@ -360,31 +367,30 @@ class PopulateRootDirs (AddCustomSearchNames): dir_counts[cur_root_dir] = 1 else: dir_counts[cur_root_dir] += 1 - - logger.log(u"Dir counts: "+str(dir_counts), logger.DEBUG) - + + logger.log(u"Dir counts: " + str(dir_counts), logger.DEBUG) + if not dir_counts: self.incDBVersion() return - + default_root_dir = dir_counts.values().index(max(dir_counts.values())) - - new_root_dirs = str(default_root_dir)+'|'+'|'.join(dir_counts.keys()) - logger.log(u"Setting ROOT_DIRS to: "+new_root_dirs, logger.DEBUG) - + + new_root_dirs = str(default_root_dir) + '|' + '|'.join(dir_counts.keys()) + logger.log(u"Setting ROOT_DIRS to: " + new_root_dirs, logger.DEBUG) + sickbeard.ROOT_DIRS = new_root_dirs - + sickbeard.save_config() - + self.incDBVersion() - -class SetNzbTorrentSettings(PopulateRootDirs): + +class SetNzbTorrentSettings(PopulateRootDirs): def test(self): return self.checkDBVersion() >= 8 - - def execute(self): + def execute(self): use_torrents = False use_nzbs = False @@ -392,33 +398,32 @@ class SetNzbTorrentSettings(PopulateRootDirs): if cur_provider.isEnabled(): if cur_provider.providerType == GenericProvider.NZB: use_nzbs = True - logger.log(u"Provider "+cur_provider.name+" is enabled, enabling NZBs in the upgrade") + logger.log(u"Provider " + cur_provider.name + " is enabled, enabling NZBs in the upgrade") break elif cur_provider.providerType == GenericProvider.TORRENT: use_torrents = True - logger.log(u"Provider "+cur_provider.name+" is enabled, enabling Torrents in the upgrade") + logger.log(u"Provider " + cur_provider.name + " is enabled, enabling Torrents in the upgrade") break sickbeard.USE_TORRENTS = use_torrents sickbeard.USE_NZBS = use_nzbs - + sickbeard.save_config() - + self.incDBVersion() + class FixAirByDateSetting(SetNzbTorrentSettings): - def test(self): return self.checkDBVersion() >= 9 def execute(self): - shows = self.connection.select("SELECT * FROM tv_shows") - + for cur_show in shows: if cur_show["genre"] and "talk show" in cur_show["genre"].lower(): self.connection.action("UPDATE tv_shows SET air_by_date = ? WHERE tvdb_id = ?", [1, cur_show["tvdb_id"]]) - + self.incDBVersion() class AddAudioLang (FixAirByDateSetting): @@ -438,9 +443,8 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode): def test(self): return self.checkDBVersion() >= 10 - - def execute(self): + def execute(self): backupDatabase(10) if not self.hasColumn("tv_episodes", "file_size"): @@ -450,12 +454,12 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode): self.addColumn("tv_episodes", "release_name", "TEXT", "") ep_results = self.connection.select("SELECT episode_id, location, file_size FROM tv_episodes") - + logger.log(u"Adding file size to all episodes in DB, please be patient") for cur_ep in ep_results: if not cur_ep["location"]: continue - + # if there is no size yet then populate it for us if (not cur_ep["file_size"] or not int(cur_ep["file_size"])) and ek.ek(os.path.isfile, cur_ep["location"]): cur_size = ek.ek(os.path.getsize, cur_ep["location"]) @@ -463,19 +467,19 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode): # check each snatch to see if we can use it to get a release name from history_results = self.connection.select("SELECT * FROM history WHERE provider != -1 ORDER BY date ASC") - + logger.log(u"Adding release name to all episodes still in history") for cur_result in history_results: # find the associated download, if there isn't one then ignore it download_results = self.connection.select("SELECT resource FROM history WHERE provider = -1 AND showid = ? AND season = ? AND episode = ? AND date > ?", [cur_result["showid"], cur_result["season"], cur_result["episode"], cur_result["date"]]) if not download_results: - logger.log(u"Found a snatch in the history for "+cur_result["resource"]+" but couldn't find the associated download, skipping it", logger.DEBUG) + logger.log(u"Found a snatch in the history for " + cur_result["resource"] + " but couldn't find the associated download, skipping it", logger.DEBUG) continue nzb_name = cur_result["resource"] file_name = ek.ek(os.path.basename, download_results[0]["resource"]) - + # take the extension off the filename, it's not needed if '.' in file_name: file_name = file_name.rpartition('.')[0] @@ -484,20 +488,20 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode): ep_results = self.connection.select("SELECT episode_id, status FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND location != ''", [cur_result["showid"], cur_result["season"], cur_result["episode"]]) if not ep_results: - logger.log(u"The episode "+nzb_name+" was found in history but doesn't exist on disk anymore, skipping", logger.DEBUG) + logger.log(u"The episode " + nzb_name + " was found in history but doesn't exist on disk anymore, skipping", logger.DEBUG) continue - # get the status/quality of the existing ep and make sure it's what we expect + # get the status/quality of the existing ep and make sure it's what we expect ep_status, ep_quality = common.Quality.splitCompositeStatus(int(ep_results[0]["status"])) if ep_status != common.DOWNLOADED: continue - + if ep_quality != int(cur_result["quality"]): - continue + continue # make sure this is actually a real release name and not a season pack or something for cur_name in (nzb_name, file_name): - logger.log(u"Checking if "+cur_name+" is actually a good release name", logger.DEBUG) + logger.log(u"Checking if " + cur_name + " is actually a good release name", logger.DEBUG) try: np = NameParser(False) parse_result = np.parse(cur_name) @@ -511,44 +515,43 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode): # check each snatch to see if we can use it to get a release name from empty_results = self.connection.select("SELECT episode_id, location FROM tv_episodes WHERE release_name = ''") - + logger.log(u"Adding release name to all episodes with obvious scene filenames") for cur_result in empty_results: - + ep_file_name = ek.ek(os.path.basename, cur_result["location"]) ep_file_name = os.path.splitext(ep_file_name)[0] - + # I only want to find real scene names here so anything with a space in it is out if ' ' in ep_file_name: continue - + try: np = NameParser(False) parse_result = np.parse(ep_file_name) except InvalidNameException: continue - + if not parse_result.release_group: continue - - logger.log(u"Name "+ep_file_name+" gave release group of "+parse_result.release_group+", seems valid", logger.DEBUG) + + logger.log(u"Name " + ep_file_name + " gave release group of " + parse_result.release_group + ", seems valid", logger.DEBUG) self.connection.action("UPDATE tv_episodes SET release_name = ? WHERE episode_id = ?", [ep_file_name, cur_result["episode_id"]]) self.incDBVersion() -class RenameSeasonFolders(AddSizeAndSceneNameFields): +class RenameSeasonFolders(AddSizeAndSceneNameFields): def test(self): return self.checkDBVersion() >= 11 - + def execute(self): - # rename the column self.connection.action("ALTER TABLE tv_shows RENAME TO tmp_tv_shows") self.connection.action("CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, location TEXT, show_name TEXT, tvdb_id NUMERIC, network TEXT, genre TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, tvr_id NUMERIC, tvr_name TEXT, air_by_date NUMERIC, lang TEXT, custom_search_names TEXT, audio_lang TEXT)") sql = "INSERT INTO tv_shows(show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, tvr_id, tvr_name, air_by_date, lang, custom_search_names, audio_lang) SELECT show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, seasonfolders, paused, startyear, tvr_id, tvr_name, air_by_date, lang, custom_search_names, audio_lang FROM tmp_tv_shows" self.connection.action(sql) - + # flip the values to be opposite of what they were before self.connection.action("UPDATE tv_shows SET flatten_folders = 2 WHERE flatten_folders = 1") self.connection.action("UPDATE tv_shows SET flatten_folders = 1 WHERE flatten_folders = 0") @@ -556,3 +559,112 @@ class RenameSeasonFolders(AddSizeAndSceneNameFields): self.connection.action("DROP TABLE tmp_tv_shows") self.incDBVersion() + + +class Add1080pAndRawHDQualities(RenameSeasonFolders): + """Add support for 1080p related qualities along with RawHD + + Quick overview of what the upgrade needs to do: + + quality | old | new + -------------------------- + hdwebdl | 1<<3 | 1<<5 + hdbluray | 1<<4 | 1<<7 + fullhdbluray | 1<<5 | 1<<8 + -------------------------- + rawhdtv | | 1<<3 + fullhdtv | | 1<<4 + fullhdwebdl | | 1<<6 + """ + + def test(self): + return self.checkDBVersion() >= 12 + + def _update_status(self, old_status): + (status, quality) = common.Quality.splitCompositeStatus(old_status) + return common.Quality.compositeStatus(status, self._update_quality(quality)) + + def _update_quality(self, old_quality): + """Update bitwise flags to reflect new quality values + + Check flag bits (clear old then set their new locations) starting + with the highest bits so we dont overwrite data we need later on + """ + + result = old_quality + # move fullhdbluray from 1<<5 to 1<<8 if set + if(result & (1<<5)): + result = result & ~(1<<5) + result = result | (1<<8) + # move hdbluray from 1<<4 to 1<<7 if set + if(result & (1<<4)): + result = result & ~(1<<4) + result = result | (1<<7) + # move hdwebdl from 1<<3 to 1<<5 if set + if(result & (1<<3)): + result = result & ~(1<<3) + result = result | (1<<5) + + return result + + def _update_composite_qualities(self, status): + """Unpack, Update, Return new quality values + + Unpack the composite archive/initial values. + Update either qualities if needed. + Then return the new compsite quality value. + """ + + best = (status & (0xffff << 16)) >> 16 + initial = status & (0xffff) + + best = self._update_quality(best) + initial = self._update_quality(initial) + + result = ((best << 16) | initial) + return result + + def execute(self): + backupDatabase(self.checkDBVersion()) + + # update the default quality so we dont grab the wrong qualities after migration + sickbeard.QUALITY_DEFAULT = self._update_composite_qualities(sickbeard.QUALITY_DEFAULT) + sickbeard.save_config() + + # upgrade previous HD to HD720p -- shift previous qualities to new placevalues + old_hd = common.Quality.combineQualities([common.Quality.HDTV, common.Quality.HDWEBDL >> 2, common.Quality.HDBLURAY >> 3], []) + new_hd = common.Quality.combineQualities([common.Quality.HDTV, common.Quality.HDWEBDL, common.Quality.HDBLURAY], []) + + # update ANY -- shift existing qualities and add new 1080p qualities, note that rawHD was not added to the ANY template + old_any = common.Quality.combineQualities([common.Quality.SDTV, common.Quality.SDDVD, common.Quality.HDTV, common.Quality.HDWEBDL >> 2, common.Quality.HDBLURAY >> 3, common.Quality.UNKNOWN], []) + new_any = common.Quality.combineQualities([common.Quality.SDTV, common.Quality.SDDVD, common.Quality.HDTV, common.Quality.FULLHDTV, common.Quality.HDWEBDL, common.Quality.FULLHDWEBDL, common.Quality.HDBLURAY, common.Quality.FULLHDBLURAY, common.Quality.UNKNOWN], []) + + # update qualities (including templates) + shows = self.connection.select("SELECT * FROM tv_shows") + for cur_show in shows: + if cur_show["quality"] == old_hd: + new_quality = new_hd + elif cur_show["quality"] == old_any: + new_quality = new_any + else: + new_quality = self._update_composite_qualities(cur_show["quality"]) + self.connection.action("UPDATE tv_shows SET quality = ? WHERE tvdb_id = ?", [new_quality, cur_show["tvdb_id"]]) + + # update status that are are within the old hdwebdl (1<<3 which is 8) and better -- exclude unknown (1<<15 which is 32768) + episodes = self.connection.select("SELECT * FROM tv_episodes WHERE status/100 < 32768 AND status/100 >= 8") + for cur_episode in episodes: + self.connection.action("UPDATE tv_episodes SET status = ? WHERE episode_id = ?", [self._update_status(cur_episode["status"]), cur_episode["episode_id"]]) + + # make two seperate passes through the history since snatched and downloaded (action & quality) may not always coordinate together + + # update previous history so it shows the correct action + historyAction = self.connection.select("SELECT * FROM history WHERE action/100 < 32768 AND action/100 >= 8") + for cur_entry in historyAction: + self.connection.action("UPDATE history SET action = ? WHERE showid = ? AND date = ?", [self._update_status(cur_entry["action"]), cur_entry["showid"], cur_entry["date"]]) + + # update previous history so it shows the correct quality + historyQuality = self.connection.select("SELECT * FROM history WHERE quality < 32768 AND quality >= 8") + for cur_entry in historyQuality: + self.connection.action("UPDATE history SET quality = ? WHERE showid = ? AND date = ?", [self._update_quality(cur_entry["quality"]), cur_entry["showid"], cur_entry["date"]]) + + self.incDBVersion() diff --git a/sickbeard/db.py b/sickbeard/db.py index 2d00745b045ee88796905c5cc53b59c0cb886cef..3bb56fe453fb9651b4cb5aafec2a140ab25f2159 100644 --- a/sickbeard/db.py +++ b/sickbeard/db.py @@ -54,6 +54,13 @@ class DBConnection: else: self.connection.row_factory = sqlite3.Row + def checkDBVersion(self): + result = self.select("SELECT db_version FROM db_version") + if result: + return int(result[0]["db_version"]) + else: + return 0 + def action(self, query, args=None): with db_lock: @@ -183,11 +190,7 @@ class SchemaUpgrade (object): self.connection.action("UPDATE %s SET %s = ?" % (table, column), (default,)) def checkDBVersion(self): - result = self.connection.select("SELECT db_version FROM db_version") - if result: - return int(result[0]["db_version"]) - else: - return 0 + return self.connection.checkDBVersion() def incDBVersion(self): curVersion = self.checkDBVersion() diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index cec047891433aec48b511d528d35fabcdc153e8e..15ba0c38c7c1662a7a8fa3e1404af4a3e6ab5c85 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -37,6 +37,7 @@ from sickbeard.common import USER_AGENT, mediaExtensions, XML_NSMAP from sickbeard import db from sickbeard import encodingKludge as ek +from sickbeard import notifiers from lib.tvdb_api import tvdb_api, tvdb_exceptions @@ -198,6 +199,8 @@ def makeDir (dir): if not ek.ek(os.path.isdir, dir): try: ek.ek(os.makedirs, dir) + # do the library update for synoindex + notifiers.synoindex_notifier.addFolder(dir) except OSError: return False return True @@ -463,6 +466,8 @@ def make_dirs(path): ek.ek(os.mkdir, sofar) # use normpath to remove end separator, otherwise checks permissions against itself chmodAsParent(ek.ek(os.path.normpath, sofar)) + # do the library update for synoindex + notifiers.synoindex_notifier.addFolder(sofar) except (OSError, IOError), e: logger.log(u"Failed creating " + sofar + " : " + ex(e), logger.ERROR) return False @@ -525,6 +530,8 @@ def delete_empty_folders(check_empty_dir, keep_dir=None): logger.log(u"Deleting empty folder: " + check_empty_dir) # need shutil.rmtree when ignore_items is really implemented ek.ek(os.rmdir, check_empty_dir) + # do the library update for synoindex + notifiers.synoindex_notifier.deleteFolder(check_empty_dir) except (WindowsError, OSError), e: logger.log(u"Unable to delete " + check_empty_dir + ": " + repr(e) + " / " + str(e), logger.WARNING) break diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index b11e5a315c1fdb3827f9b84ff055462713275766..becde5c1abe0e6bcd95c71a539faa2da8c937c51 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -189,7 +189,6 @@ class NameParser(object): if type(number) == int: return number - # good lord I'm lazy if number.lower() == 'i': return 1 if number.lower() == 'ii': return 2 diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index c78aaabfc6f83a8e8996f18d81e53eaf64e60b66..fb28b7ddcd52eb9d364ba90277f35b4b2a355ffa 100755 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -21,6 +21,7 @@ import sickbeard import xbmc import plex import nmj +import nmjv2 import synoindex import pytivo @@ -42,6 +43,7 @@ xbmc_notifier = xbmc.XBMCNotifier() plex_notifier = plex.PLEXNotifier() nmj_notifier = nmj.NMJNotifier() synoindex_notifier = synoindex.synoIndexNotifier() +nmjv2_notifier = nmjv2.NMJv2Notifier() pytivo_notifier = pytivo.pyTivoNotifier() # devices growl_notifier = growl.GrowlNotifier() @@ -60,6 +62,7 @@ notifiers = [ xbmc_notifier, plex_notifier, nmj_notifier, + nmjv2_notifier, synoindex_notifier, pytivo_notifier, growl_notifier, diff --git a/sickbeard/notifiers/xbmc.py b/sickbeard/notifiers/xbmc.py index 05c63d106f8a72c53f78a2c6d68655e4fe45e237..63528c644244aacb4946cae8804d4d8908ba8461 100644 --- a/sickbeard/notifiers/xbmc.py +++ b/sickbeard/notifiers/xbmc.py @@ -42,12 +42,16 @@ except ImportError: class XBMCNotifier: - def _get_json_version(self, host, username, password): + sb_logo_url = 'http://www.sickbeard.com/xbmc-notify.png' + + def _get_xbmc_version(self, host, username, password): """Returns XBMC JSON-RPC API version (odd # = dev, even # = stable) Sends a request to the XBMC host using the JSON-RPC to determine if the legacy API or if the JSON-RPC API functions should be used. + Fallback to testing legacy HTTPAPI before assuming it is just a badly configured host. + Args: host: XBMC webserver host:port username: XBMC webserver username @@ -80,7 +84,14 @@ class XBMCNotifier: if result: return result["result"]["version"] else: - return False + # fallback to legacy HTTPAPI method + testCommand = {'command': 'Help'} + request = self._send_to_xbmc(testCommand, host, username, password) + if request: + # return a fake version number, so it uses the legacy method + return 1 + else: + return False def _notify_xbmc(self, message, title="Sick Beard", host=None, username=None, password=None, force=False): """Internal wrapper for the notify_snatch and notify_download functions @@ -118,7 +129,7 @@ class XBMCNotifier: for curHost in [x.strip() for x in host.split(",")]: logger.log(u"Sending XBMC notification to '" + curHost + "' - " + message, logger.MESSAGE) - xbmcapi = self._get_json_version(curHost, username, password) + xbmcapi = self._get_xbmc_version(curHost, username, password) if xbmcapi: if (xbmcapi <= 4): logger.log(u"Detected XBMC version <= 11, using XBMC HTTP API", logger.DEBUG) @@ -128,12 +139,13 @@ class XBMCNotifier: result += curHost + ':' + str(notifyResult) else: logger.log(u"Detected XBMC version >= 12, using XBMC JSON API", logger.DEBUG) - command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"%s","message":"%s"},"id":1}' % (title.encode("utf-8"), message.encode("utf-8")) + command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"%s","message":"%s", "image": "%s"},"id":1}' % (title.encode("utf-8"), message.encode("utf-8"), self.sb_logo_url) notifyResult = self._send_to_xbmc_json(command, curHost, username, password) if notifyResult: result += curHost + ':' + notifyResult["result"].decode(sickbeard.SYS_ENCODING) else: - logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.DEBUG) + logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR) + result += curHost + ':False' return result @@ -257,7 +269,7 @@ class XBMCNotifier: return False for path in paths: - # Don't need it double-encoded, gawd this is dumb + # we do not need it double-encoded, gawd this is dumb unEncPath = urllib.unquote(path.text).decode(sickbeard.SYS_ENCODING) logger.log(u"XBMC Updating " + showName + " on " + host + " at " + unEncPath, logger.DEBUG) updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video, %s)' % (unEncPath)} @@ -402,7 +414,7 @@ class XBMCNotifier: return False logger.log(u"XBMC Updating " + showName + " on " + host + " at " + path, logger.DEBUG) - updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (json.dumps(path)) + updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (json.dumps("\"" + path + "\"")) # yes we really have to wrap the path in escaped " request = self._send_to_xbmc_json(updateCommand, host) if not request: logger.log(u"Update of show directory failed on " + showName + " on " + host + " at " + path, logger.ERROR) @@ -462,32 +474,37 @@ class XBMCNotifier: logger.log(u"No XBMC hosts specified, check your settings", logger.DEBUG) return False - # only send update to first host in the list -- workaround for xbmc sql backend users - host = sickbeard.XBMC_HOST.split(",")[0].strip() - - logger.log(u"Sending request to update library for XBMC host: '" + host + "'", logger.MESSAGE) - - xbmcapi = self._get_json_version(host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD) - if xbmcapi: - if (xbmcapi <= 4): - # try to update for just the show, if it fails, do full update if enabled - if not self._update_library(host, showName) and sickbeard.XBMC_UPDATE_FULL: - logger.log(u"Single show update failed, falling back to full update", logger.WARNING) - return self._update_library(host) + if sickbeard.XBMC_UPDATE_ONLYFIRST: + # only send update to first host in the list if requested -- workaround for xbmc sql backend users + host = sickbeard.XBMC_HOST.split(",")[0].strip() + else: + host = sickbeard.XBMC_HOST + + result = 0 + for curHost in [x.strip() for x in host.split(",")]: + logger.log(u"Sending request to update library for XBMC host: '" + curHost + "'", logger.MESSAGE) + + xbmcapi = self._get_xbmc_version(curHost, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD) + if xbmcapi: + if (xbmcapi <= 4): + # try to update for just the show, if it fails, do full update if enabled + if not self._update_library(curHost, showName) and sickbeard.XBMC_UPDATE_FULL: + logger.log(u"Single show update failed, falling back to full update", logger.WARNING) + self._update_library(curHost) else: - return True + # try to update for just the show, if it fails, do full update if enabled + if not self._update_library_json(curHost, showName) and sickbeard.XBMC_UPDATE_FULL: + logger.log(u"Single show update failed, falling back to full update", logger.WARNING) + self._update_library_json(curHost) else: - # try to update for just the show, if it fails, do full update if enabled - if not self._update_library_json(host, showName) and sickbeard.XBMC_UPDATE_FULL: - logger.log(u"Single show update failed, falling back to full update", logger.WARNING) - return self._update_library_json(host) - else: - return True + logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR) + result = result + 1 + + # needed for the 'update xbmc' submenu command + # as it only cares of the final result vs the individual ones + if result == 0: + return True else: - logger.log(u"Failed to detect XBMC version for '" + host + "', check configuration and try again.", logger.DEBUG) return False - return True - - notifier = XBMCNotifier diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 00cbaad4270d0c85dfd207adaa076fbb83fbd1f7..d73bd2c0d37128691df2816baa6ddcb292811a81 100755 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -700,13 +700,13 @@ class PostProcessor(object): self._log(u"Processing " + self.file_path + " (" + str(self.nzb_name) + ")") - 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 + 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 @@ -767,6 +767,8 @@ class PostProcessor(object): self._log(u"Show directory doesn't exist, creating it", logger.DEBUG) try: ek.ek(os.mkdir, ep_obj.show._location) + # do the library update for synoindex + notifiers.synoindex_notifier.addFolder(ep_obj.show._location) except (OSError, IOError): raise exceptions.PostProcessingFailed("Unable to create the show directory: " + ep_obj.show._location) @@ -852,10 +854,10 @@ class PostProcessor(object): ep_obj.createMetaFiles() ep_obj.saveToDB() - # do the library update for XBMC + # do the library update for XBMC notifiers.xbmc_notifier.update_library(ep_obj.show.name) - # do the library update for Plex + # do the library update for Plex notifiers.plex_notifier.update_library() # do the library update for NMJ @@ -866,8 +868,8 @@ class PostProcessor(object): # do the library update for pyTivo notifiers.pytivo_notifier.update_library(ep_obj) - - # do the library update for Trakt + + # do the library update for Trakt notifiers.trakt_notifier.update_library(ep_obj) self._run_extra_scripts(ep_obj) diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index 57c41569d90da55df1320343479633feaaa47267..267f8ed576cf43c9cf313f406e7811e327896898 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -94,6 +94,10 @@ def processDir (dirName, nzbName=None, recurse=False): remainingFolders = filter(lambda x: ek.ek(os.path.isdir, ek.ek(os.path.join, dirName, x)), fileList) + # If nzbName is set and there's more than one videofile in the folder, files will be lost (overwritten). + if nzbName != None and len(videoFiles) >= 2: + nzbName = None + # process any files in the dir for cur_video_file_path in videoFiles: diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index 867501d52871bc748b87525395766806bd086911..7300cf488d391e391a0df9c97e27abf020404dc7 100755 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -18,9 +18,12 @@ __all__ = ['ezrss', 'tvtorrents', + 'torrentleech', 'nzbsrus', 'womble', 'btn', + 'nzbx', + 'omgwtfnzbs', 'binnewz', 't411' ] @@ -29,6 +32,7 @@ import sickbeard from os import sys + def sortedProviderList(): initialList = sickbeard.providerList + sickbeard.newznabProviderList @@ -48,10 +52,12 @@ def sortedProviderList(): return newList + def makeProviderList(): return [x.provider for x in [getProviderModule(y) for y in __all__] if x] + def getNewznabProviderList(data): defaultList = [makeNewznabProvider(x) for x in getDefaultNewznabProviders().split('!!!')] @@ -76,7 +82,7 @@ def getNewznabProviderList(data): providerDict[curDefault.name].name = curDefault.name providerDict[curDefault.name].url = curDefault.url providerDict[curDefault.name].needs_auth = curDefault.needs_auth - + return filter(lambda x: x, providerList) @@ -95,21 +101,23 @@ def makeNewznabProvider(configString): return newProvider + def getDefaultNewznabProviders(): - return 'Sick Beard Index|http://lolo.sickbeard.com/|0|0!!!NZBs.org|http://beta.nzbs.org/||0!!!NZBGeek|https://index.nzbgeek.info/||0!!!NZBFinder|http://www.nzbfinder.ws/||0!!!Usenet-Crawler|http://www.usenet-crawler.com/||0' + return 'Sick Beard Index|http://lolo.sickbeard.com/|0|0!!!NZBs.org|http://nzbs.org/||0!!!NZBGeek|https://index.nzbgeek.info/||0!!!NZBFinder|http://www.nzbfinder.ws/||0!!!Usenet-Crawler|http://www.usenet-crawler.com/||0' def getProviderModule(name): name = name.lower() prefix = "sickbeard.providers." - if name in __all__ and prefix+name in sys.modules: - return sys.modules[prefix+name] + if name in __all__ and prefix + name in sys.modules: + return sys.modules[prefix + name] else: - raise Exception("Can't find "+prefix+name+" in "+repr(sys.modules)) + raise Exception("Can't find " + prefix + name + " in " + repr(sys.modules)) + -def getProviderClass(id): +def getProviderClass(providerID): - providerMatch = [x for x in sickbeard.providerList+sickbeard.newznabProviderList if x.getID() == id] + providerMatch = [x for x in sickbeard.providerList + sickbeard.newznabProviderList if x.getID() == providerID] if len(providerMatch) != 1: return None diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py index fe3d1b4b2430cb9342b445e93b021b6ab6a699ba..aeddbb215ca181f8cacce5db63d61f7940fb96a1 100644 --- a/sickbeard/providers/btn.py +++ b/sickbeard/providers/btn.py @@ -1,5 +1,5 @@ # coding=utf-8 -# Author: Dani�l Heimans +# Author: Dani�l Heimans # URL: http://code.google.com/p/sickbeard # # This file is part of Sick Beard. @@ -59,7 +59,7 @@ class BTNProvider(generic.TorrentProvider): return result - def _doSearch(self, search_params, show=None): + def _doSearch(self, search_params, show=None, season=None): params = {} apikey = sickbeard.BTN_API_KEY diff --git a/sickbeard/providers/ezrss.py b/sickbeard/providers/ezrss.py index ca8cd72b3c912e092155e487b85a2a811cbe7cca..9900d777b4c1e3305006b70aee45405a5c5c6934 100644 --- a/sickbeard/providers/ezrss.py +++ b/sickbeard/providers/ezrss.py @@ -99,7 +99,7 @@ class EZRSSProvider(generic.TorrentProvider): return [params] - def _doSearch(self, search_params, show=None): + def _doSearch(self, search_params, show=None, season= None): params = {"mode": "rss"} diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index b2ca02a836cbc6dcedd9af390d8324a54efae733..4848c976a7dae844e045107a59cc633b4eca7457 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -107,8 +107,6 @@ class GenericProvider: if not headers: headers = [] - - result = helpers.getURL(url, headers) if result is None: diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py index b1e07e770f494cb7a9c4a39eec6a7bb457785223..d0735bd75f471be6c3ce01cf723c0383e8d6aa75 100644 --- a/sickbeard/providers/newznab.py +++ b/sickbeard/providers/newznab.py @@ -318,7 +318,6 @@ class NewznabCache(tvcache.TVCache): cat = '5020' params = {"t": "tvsearch", - "age": sickbeard.USENET_RETENTION, "cat": cat} # hack this in for now diff --git a/sickbeard/providers/nzbsrus.py b/sickbeard/providers/nzbsrus.py index c90365f30768bd1d576459f6f440f772664ea85c..9d140c5686fc62c09f106b7855316c38e965c3e4 100644 --- a/sickbeard/providers/nzbsrus.py +++ b/sickbeard/providers/nzbsrus.py @@ -16,62 +16,107 @@ # 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 - +import generic import sickbeard -from sickbeard import exceptions, logger +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree -from sickbeard import tvcache +from sickbeard import exceptions, logger +from sickbeard import tvcache, show_name_helpers -import generic class NZBsRUSProvider(generic.NZBProvider): - def __init__(self): - - generic.NZBProvider.__init__(self, "NZBs'R'US") - - self.cache = NZBsRUSCache(self) - - self.url = 'https://www.nzbsrus.com/' - - def isEnabled(self): - return sickbeard.NZBSRUS - - def _checkAuth(self): - if sickbeard.NZBSRUS_UID in (None, "") or sickbeard.NZBSRUS_HASH in (None, ""): - raise exceptions.AuthException("NZBs'R'US authentication details are empty, check your config") + def __init__(self): + generic.NZBProvider.__init__(self, "NZBs'R'US") + self.cache = NZBsRUSCache(self) + self.url = 'https://www.nzbsrus.com/' + self.supportsBacklog = True + + def isEnabled(self): + return sickbeard.NZBSRUS + + def _checkAuth(self): + if sickbeard.NZBSRUS_UID in (None, "") or sickbeard.NZBSRUS_HASH in (None, ""): + raise exceptions.AuthException("NZBs'R'US authentication details are empty, check your config") + + def _get_season_search_strings(self, show, season): + return [x for x in show_name_helpers.makeSceneSeasonSearchString(show, season)] + + def _get_episode_search_strings(self, ep_obj): + return [x for x in show_name_helpers.makeSceneSearchString(ep_obj)] + + def _doSearch(self, search, show=None, season=None): + params = {'uid': sickbeard.NZBSRUS_UID, + 'key': sickbeard.NZBSRUS_HASH, + 'xml': 1, + 'age': sickbeard.USENET_RETENTION, + 'lang0': 1, # English only from CouchPotato + 'lang1': 1, + 'lang3': 1, + 'c91': 1, # TV:HD + 'c104': 1, # TV:SD-x264 + 'c75': 1, # TV:XviD + 'searchtext': search} + + if not params['age']: + params['age'] = 500 + + searchURL = self.url + 'api.php?' + urllib.urlencode(params) + logger.log(u"NZBS'R'US search url: " + searchURL, logger.DEBUG) + + data = self.getURL(searchURL) + if not data: + return [] + + if not data.startswith('<?xml'): # Error will be a single line of text + logger.log(u"NZBs'R'US error: " + data, logger.ERROR) + return [] + + root = etree.fromstring(data) + if root is None: + logger.log(u"Error trying to parse NZBS'R'US XML data.", logger.ERROR) + logger.log(u"RSS data: " + data, logger.DEBUG) + return [] + return root.findall('./results/result') + + def _get_title_and_url(self, element): + if element.find('title'): # RSS feed + title = element.find('title').text + url = element.find('link').text.replace('&', '&') + else: # API item + title = element.find('name').text + nzbID = element.find('id').text + key = element.find('key').text + url = self.url + 'nzbdownload_rss.php' + '/' + \ + nzbID + '/' + sickbeard.NZBSRUS_UID + '/' + key + '/' + return (title, url) class NZBsRUSCache(tvcache.TVCache): - def __init__(self, provider): - - tvcache.TVCache.__init__(self, provider) - - # only poll NZBs'R'US every 15 minutes max - self.minTime = 15 - - - def _getRSSData(self): - - url = self.provider.url + 'rssfeed.php?' - urlArgs = {'cat': '91,75', - 'i': sickbeard.NZBSRUS_UID, - 'h': sickbeard.NZBSRUS_HASH} - - url += urllib.urlencode(urlArgs) + def __init__(self, provider): + tvcache.TVCache.__init__(self, provider) + # only poll NZBs'R'US every 15 minutes max + self.minTime = 15 - logger.log(u"NZBs'R'US cache update URL: "+ url, logger.DEBUG) + def _getRSSData(self): + url = self.provider.url + 'rssfeed.php?' + urlArgs = {'cat': '91,75,104', # HD,XviD,SD-x264 + 'i': sickbeard.NZBSRUS_UID, + 'h': sickbeard.NZBSRUS_HASH} - data = self.provider.getURL(url) + url += urllib.urlencode(urlArgs) + logger.log(u"NZBs'R'US cache update URL: " + url, logger.DEBUG) - return data + data = self.provider.getURL(url) + return data - def _checkAuth(self, data): - return data != 'Invalid Link' + def _checkAuth(self, data): + return data != 'Invalid Link' -provider = NZBsRUSProvider() \ No newline at end of file +provider = NZBsRUSProvider() diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py index 059c5f256d5a2774c6f8f9eaa5f9aec5859fd8d5..1a7a7aac39eda99204061f97f941546402c63ad8 100644 --- a/sickbeard/show_name_helpers.py +++ b/sickbeard/show_name_helpers.py @@ -174,6 +174,8 @@ def makeSceneSearchString (episode): myDB = db.DBConnection() numseasonsSQlResult = myDB.select("SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0", [episode.show.tvdbid]) numseasons = int(numseasonsSQlResult[0][0]) + numepisodesSQlResult = myDB.select("SELECT COUNT(episode) as numepisodes FROM tv_episodes WHERE showid = ? and season != 0", [episode.show.tvdbid]) + numepisodes = int(numepisodesSQlResult[0][0]) # see if we should use dates instead of episodes if episode.show.air_by_date and episode.airdate != datetime.date.fromordinal(1): @@ -182,8 +184,9 @@ def makeSceneSearchString (episode): epStrings = ["S%02iE%02i" % (int(episode.season), int(episode.episode)), "%ix%02i" % (int(episode.season), int(episode.episode))] - # for single-season shows just search for the show name - if numseasons == 1: + # for single-season shows just search for the show name -- if total ep count (exclude s0) is less than 11 + # due to the amount of qualities and releases, it is easy to go over the 50 result limit on rss feeds otherwise + if numseasons == 1 and numepisodes < 11: epStrings = [''] showNames = set(makeSceneShowSearchStrings(episode.show)) diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py index 97f3960635949eecca7e3718cdadffba15029c61..6cc667e5ed03cfa824b4729b2dbb69daffe68960 100644 --- a/sickbeard/show_queue.py +++ b/sickbeard/show_queue.py @@ -32,13 +32,13 @@ from sickbeard import generic_queue from sickbeard import name_cache from sickbeard.exceptions import ex + class ShowQueue(generic_queue.GenericQueue): def __init__(self): generic_queue.GenericQueue.__init__(self) self.queue_name = "SHOWQUEUE" - def _isInQueue(self, show, actions): return show in [x.show for x in self.queue if x.action_id in actions] @@ -68,7 +68,7 @@ class ShowQueue(generic_queue.GenericQueue): return self._isBeingSomethinged(show, (ShowQueueActions.RENAME,)) def _getLoadingShowList(self): - return [x for x in self.queue+[self.currentItem] if x != None and x.isLoading] + return [x for x in self.queue + [self.currentItem] if x != None and x.isLoading] loadingShowList = property(_getLoadingShowList) @@ -102,7 +102,7 @@ class ShowQueue(generic_queue.GenericQueue): return queueItemObj = QueueItemRefresh(show) - + self.add_item(queueItemObj) return queueItemObj @@ -122,13 +122,14 @@ class ShowQueue(generic_queue.GenericQueue): return queueItemObj + class ShowQueueActions: - REFRESH=1 - ADD=2 - UPDATE=3 - FORCEUPDATE=4 - RENAME=5 - + REFRESH = 1 + ADD = 2 + UPDATE = 3 + FORCEUPDATE = 4 + RENAME = 5 + names = {REFRESH: 'Refresh', ADD: 'Add', UPDATE: 'Update', @@ -136,6 +137,7 @@ class ShowQueueActions: RENAME: 'Rename', } + class ShowQueueItem(generic_queue.QueueItem): """ Represents an item in the queue waiting to be executed @@ -149,9 +151,9 @@ class ShowQueueItem(generic_queue.QueueItem): def __init__(self, action_id, show): generic_queue.QueueItem.__init__(self, ShowQueueActions.names[action_id], action_id) self.show = show - + def isInQueue(self): - return self in sickbeard.showQueueScheduler.action.queue+[sickbeard.showQueueScheduler.action.currentItem] #@UndefinedVariable + return self in sickbeard.showQueueScheduler.action.queue + [sickbeard.showQueueScheduler.action.currentItem] #@UndefinedVariable def _getName(self): return str(self.show.tvdbid) @@ -179,7 +181,7 @@ class QueueItemAdd(ShowQueueItem): # this will initialize self.show to None ShowQueueItem.__init__(self, ShowQueueActions.ADD, self.show) - + def _getName(self): """ Returns the show name if there is a show object created, if not returns @@ -206,7 +208,7 @@ class QueueItemAdd(ShowQueueItem): ShowQueueItem.execute(self) - logger.log(u"Starting to add show "+self.showDir) + logger.log(u"Starting to add show " + self.showDir) try: # make sure the tvdb ids are valid @@ -214,9 +216,9 @@ class QueueItemAdd(ShowQueueItem): ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy() if self.lang: ltvdb_api_parms['language'] = self.lang - - logger.log(u"TVDB: "+repr(ltvdb_api_parms)) - + + logger.log(u"TVDB: " + repr(ltvdb_api_parms)) + t = tvdb_api.Tvdb(**ltvdb_api_parms) s = t[self.tvdb_id] @@ -233,8 +235,8 @@ class QueueItemAdd(ShowQueueItem): self._finishEarly() return except tvdb_exceptions.tvdb_exception, e: - logger.log(u"Error contacting TVDB: "+ex(e), logger.ERROR) - ui.notifications.error("Unable to add show", "Unable to look up the show in "+self.showDir+" on TVDB, not using the NFO. Delete .nfo and add manually in the correct language.") + logger.log(u"Error contacting TVDB: " + ex(e), logger.ERROR) + ui.notifications.error("Unable to add show", "Unable to look up the show in " + self.showDir + " on TVDB, not using the NFO. Delete .nfo and add manually in the correct language.") self._finishEarly() return @@ -250,16 +252,16 @@ class QueueItemAdd(ShowQueueItem): self.show.location = self.showDir self.show.quality = self.quality if self.quality else sickbeard.QUALITY_DEFAULT self.show.flatten_folders = self.flatten_folders if self.flatten_folders != None else sickbeard.FLATTEN_FOLDERS_DEFAULT - self.show.paused = False - + self.show.paused = 0 + # be smartish about this if self.show.genre and "talk show" in self.show.genre.lower(): self.show.air_by_date = 1 except tvdb_exceptions.tvdb_exception, e: - logger.log(u"Unable to add show due to an error with TVDB: "+ex(e), logger.ERROR) + logger.log(u"Unable to add show due to an error with TVDB: " + ex(e), logger.ERROR) if self.show: - ui.notifications.error("Unable to add "+str(self.show.name)+" due to an error with TVDB") + ui.notifications.error("Unable to add " + str(self.show.name) + " due to an error with TVDB") else: ui.notifications.error("Unable to add show due to an error with TVDB") self._finishEarly() @@ -272,7 +274,7 @@ class QueueItemAdd(ShowQueueItem): return except Exception, e: - logger.log(u"Error trying to add show: "+ex(e), logger.ERROR) + logger.log(u"Error trying to add show: " + ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) self._finishEarly() raise @@ -292,7 +294,7 @@ class QueueItemAdd(ShowQueueItem): self.show.writeMetadata() self.show.populateCache() - + except Exception, e: logger.log(u"Error with TVDB, not creating episode list: " + ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) @@ -305,8 +307,8 @@ class QueueItemAdd(ShowQueueItem): # if they gave a custom status then change all the eps to it if self.default_status != SKIPPED: - logger.log(u"Setting all episodes to the specified default status: "+str(self.default_status)) - myDB = db.DBConnection(); + logger.log(u"Setting all episodes to the specified default status: " + str(self.default_status)) + myDB = db.DBConnection() myDB.action("UPDATE tv_episodes SET status = ? WHERE status = ? AND showid = ? AND season != 0", [self.default_status, SKIPPED, self.show.tvdbid]) # if they started with WANTED eps then run the backlog @@ -336,7 +338,7 @@ class QueueItemRefresh(ShowQueueItem): ShowQueueItem.execute(self) - logger.log(u"Performing refresh on "+self.show.name) + logger.log(u"Performing refresh on " + self.show.name) self.show.refreshDir() self.show.writeMetadata() @@ -395,13 +397,13 @@ class QueueItemUpdate(ShowQueueItem): ShowQueueItem.execute(self) - logger.log(u"Beginning update of "+self.show.name) + logger.log(u"Beginning update of " + self.show.name) logger.log(u"Retrieving show info from TVDB", logger.DEBUG) try: self.show.loadFromTVDB(cache=not self.force) except tvdb_exceptions.tvdb_error, e: - logger.log(u"Unable to contact TVDB, aborting: "+ex(e), logger.WARNING) + logger.log(u"Unable to contact TVDB, aborting: " + ex(e), logger.WARNING) return # get episode list from DB @@ -413,7 +415,7 @@ class QueueItemUpdate(ShowQueueItem): try: TVDBEpList = self.show.loadEpisodesFromTVDB(cache=not self.force) except tvdb_exceptions.tvdb_exception, e: - logger.log(u"Unable to get info from TVDB, the show info will not be refreshed: "+ex(e), logger.ERROR) + logger.log(u"Unable to get info from TVDB, the show info will not be refreshed: " + ex(e), logger.ERROR) TVDBEpList = None if TVDBEpList == None: @@ -424,14 +426,14 @@ class QueueItemUpdate(ShowQueueItem): # for each ep we found on TVDB delete it from the DB list for curSeason in TVDBEpList: for curEpisode in TVDBEpList[curSeason]: - logger.log(u"Removing "+str(curSeason)+"x"+str(curEpisode)+" from the DB list", logger.DEBUG) + logger.log(u"Removing " + str(curSeason) + "x" + str(curEpisode) + " from the DB list", logger.DEBUG) if curSeason in DBEpList and curEpisode in DBEpList[curSeason]: del DBEpList[curSeason][curEpisode] # for the remaining episodes in the DB list just delete them from the DB for curSeason in DBEpList: for curEpisode in DBEpList[curSeason]: - logger.log(u"Permanently deleting episode "+str(curSeason)+"x"+str(curEpisode)+" from the database", logger.MESSAGE) + logger.log(u"Permanently deleting episode " + str(curSeason) + "x" + str(curEpisode) + " from the database", logger.MESSAGE) curEp = self.show.getEpisode(curSeason, curEpisode) try: curEp.deleteEpisode() @@ -447,6 +449,7 @@ class QueueItemUpdate(ShowQueueItem): sickbeard.showQueueScheduler.action.refreshShow(self.show, True) #@UndefinedVariable + class QueueItemForceUpdate(QueueItemUpdate): def __init__(self, show=None): ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show) diff --git a/sickbeard/tv.py b/sickbeard/tv.py index bbacf27c74801c95d25a0f35039017d750aea498..892ea95e4f7e35180d1f101240c65dd865b7ab56 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -927,8 +927,10 @@ class TVShow(object): epStatus, curQuality = Quality.splitCompositeStatus(epStatus) + if epStatus in (SNATCHED, SNATCHED_PROPER): + return Overview.SNATCHED # if they don't want re-downloads then we call it good if they have anything - if maxBestQuality == None: + elif maxBestQuality == None: return Overview.GOOD # if they have one but it's not the best they want then mark it as qual elif curQuality < maxBestQuality: @@ -960,6 +962,7 @@ class TVEpisode(object): self._file_size = 0 self._audio_langs = '' self._release_name = '' + # setting any of the above sets the dirty flag self.dirty = True diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index afa7cee3be8a7d6d9f1415b07040dc04fe79e40f..daa90785d053ee85c61d23838f5963180659ac25 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -459,7 +459,7 @@ class SourceUpdateManager(GitUpdateManager): logger.log(u"Unable to retrieve new version from "+tar_download_url+", can't update", logger.ERROR) return False - download_name = data.geturl().split('/')[-1] + download_name = data.geturl().split('/')[-1].split('?')[0] tar_download_path = os.path.join(sickbeard.PROG_DIR, download_name) diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py index 691730da9dd8e36cf2562da6024fe4adf70bb754..091a51b4c17267ca08b98cf4d37222c2ec5cf69d 100644 --- a/sickbeard/webapi.py +++ b/sickbeard/webapi.py @@ -35,7 +35,7 @@ from sickbeard.exceptions import ex from sickbeard import encodingKludge as ek from sickbeard import search_queue from sickbeard.common import SNATCHED, SNATCHED_PROPER, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED, UNKNOWN -from common import ANY, Quality, qualityPresetStrings, statusStrings +from common import Quality, qualityPresetStrings, statusStrings from sickbeard import image_cache from lib.tvdb_api import tvdb_api, tvdb_exceptions try: @@ -67,7 +67,7 @@ result_type_map = {RESULT_SUCCESS: "success", class Api: """ api class that returns json results """ - version = 3 # use an int since float-point is unpredictible + version = 4 # use an int since float-point is unpredictible intent = 4 @cherrypy.expose @@ -605,11 +605,13 @@ def _getQualityMap(): return {Quality.SDTV: 'sdtv', Quality.SDDVD: 'sddvd', Quality.HDTV: 'hdtv', + Quality.RAWHDTV: 'rawhdtv', + Quality.FULLHDTV: 'fullhdtv', Quality.HDWEBDL: 'hdwebdl', + Quality.FULLHDWEBDL: 'fullhdwebdl', Quality.HDBLURAY: 'hdbluray', Quality.FULLHDBLURAY: 'fullhdbluray', - Quality.UNKNOWN: 'unknown', - ANY: 'any'} + Quality.UNKNOWN: 'unknown'} def _getRootDirs(): @@ -1533,8 +1535,8 @@ class CMD_SickBeardSetDefaults(ApiCall): def __init__(self, args, kwargs): # required # optional - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"]) self.future_show_paused, args = self.check_params(args, kwargs, "future_show_paused", None, False, "bool", []) self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", None, False, "bool", []) self.status, args = self.check_params(args, kwargs, "status", None, False, "string", ["wanted", "skipped", "archived", "ignored"]) @@ -1547,11 +1549,13 @@ class CMD_SickBeardSetDefaults(ApiCall): quality_map = {'sdtv': Quality.SDTV, 'sddvd': Quality.SDDVD, 'hdtv': Quality.HDTV, + 'rawhdtv': Quality.RAWHDTV, + 'fullhdtv': Quality.FULLHDTV, 'hdwebdl': Quality.HDWEBDL, + 'fullhdwebdl': Quality.FULLHDWEBDL, 'hdbluray': Quality.HDBLURAY, 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN, - 'any': ANY } + 'unknown': Quality.UNKNOWN} iqualityID = [] aqualityID = [] @@ -1683,8 +1687,8 @@ class CMD_ShowAddExisting(ApiCall): self.location, args = self.check_params(args, kwargs, "location", None, True, "string", []) self.tvdbid, args = self.check_params(args, kwargs, "tvdbid", None, True, "int", []) # optional - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"]) self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", []) # super, missing, help ApiCall.__init__(self, args, kwargs) @@ -1713,11 +1717,13 @@ class CMD_ShowAddExisting(ApiCall): quality_map = {'sdtv': Quality.SDTV, 'sddvd': Quality.SDDVD, 'hdtv': Quality.HDTV, + 'rawhdtv': Quality.RAWHDTV, + 'fullhdtv': Quality.FULLHDTV, 'hdwebdl': Quality.HDWEBDL, + 'fullhdwebdl': Quality.FULLHDWEBDL, 'hdbluray': Quality.HDBLURAY, 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN, - 'any': ANY } + 'unknown': Quality.UNKNOWN} #use default quality as a failsafe newQuality = int(sickbeard.QUALITY_DEFAULT) @@ -1762,8 +1768,8 @@ class CMD_ShowAddNew(ApiCall): self.tvdbid, args = self.check_params(args, kwargs, "tvdbid", None, True, "int", []) # optional self.location, args = self.check_params(args, kwargs, "location", None, False, "string", []) - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"]) self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", []) self.status, args = self.check_params(args, kwargs, "status", None, False, "string", ["wanted", "skipped", "archived", "ignored"]) self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys()) @@ -1791,11 +1797,13 @@ class CMD_ShowAddNew(ApiCall): quality_map = {'sdtv': Quality.SDTV, 'sddvd': Quality.SDDVD, 'hdtv': Quality.HDTV, + 'rawhdtv': Quality.RAWHDTV, + 'fullhdtv': Quality.FULLHDTV, 'hdwebdl': Quality.HDWEBDL, + 'fullhdwebdl': Quality.FULLHDWEBDL, 'hdbluray': Quality.HDBLURAY, 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN, - 'any': ANY } + 'unknown': Quality.UNKNOWN} # use default quality as a failsafe newQuality = int(sickbeard.QUALITY_DEFAULT) @@ -2148,49 +2156,29 @@ class CMD_ShowSetQuality(ApiCall): # optional # this for whatever reason removes hdbluray not sdtv... which is just wrong. reverting to previous code.. plus we didnt use the new code everywhere. # self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", _getQualityMap().values()[1:]) - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"]) # super, missing, help ApiCall.__init__(self, args, kwargs) def run(self): - """ set the quality for a show in sickbeard """ + """ set the quality for a show in sickbeard by taking in a deliminated + string of qualities, map to their value and combine for new values + """ showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.tvdbid)) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") - """ - take in a deliminated string of quality, - map that to the # it corresponds to, - then combine qualities to make a new quality - - "self.initial": [ - "sdtv", - "sddvd", - "hdtv", - "hdwebdl", - "hdbluray", - "unknown" - ], - "iqualityID": [ - 1, - 2, - 4, - 8, - 16, - 32768 - ], - "newQuality": 32799 - """ - quality_map = {'sdtv': Quality.SDTV, 'sddvd': Quality.SDDVD, 'hdtv': Quality.HDTV, + 'rawhdtv': Quality.RAWHDTV, + 'fullhdtv': Quality.FULLHDTV, 'hdwebdl': Quality.HDWEBDL, + 'fullhdwebdl': Quality.FULLHDWEBDL, 'hdbluray': Quality.HDBLURAY, 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN, - 'any': ANY } + 'unknown': Quality.UNKNOWN} #use default quality as a failsafe newQuality = int(sickbeard.QUALITY_DEFAULT) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index bf294978dfd556c9d00dfd7eb6eac887948d0b30..f9ff6310b6d6e4c8f166df13888c1499bae9e547 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -78,7 +78,7 @@ class PageTemplate (Template): if sickbeard.NZBS and sickbeard.NZBS_UID and sickbeard.NZBS_HASH: logger.log(u"NZBs.org has been replaced, please check the config to configure the new provider!", logger.ERROR) - ui.notifications.error("NZBs.org Config Update", "NZBs.org has a new site. Please <a href=\""+sickbeard.WEB_ROOT+"/config/providers\">update your config</a> with the api key from <a href=\"http://beta.nzbs.org/login\">http://beta.nzbs.org</a> and then disable the old NZBs.org provider.") + ui.notifications.error("NZBs.org Config Update", "NZBs.org has a new site. Please <a href=\""+sickbeard.WEB_ROOT+"/config/providers\">update your config</a> with the api key from <a href=\"http://nzbs.org/login\">http://nzbs.org</a> and then disable the old NZBs.org provider.") if "X-Forwarded-Host" in cherrypy.request.headers: self.sbHost = cherrypy.request.headers['X-Forwarded-Host'] @@ -215,19 +215,19 @@ class Manage: status_list = [int(whichStatus)] if status_list[0] == SNATCHED: status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER - + cur_show_results = myDB.select("SELECT season, episode, name FROM tv_episodes WHERE showid = ? AND season != 0 AND status IN ("+','.join(['?']*len(status_list))+")", [int(tvdb_id)] + status_list) - + result = {} for cur_result in cur_show_results: cur_season = int(cur_result["season"]) cur_episode = int(cur_result["episode"]) - + if cur_season not in result: result[cur_season] = {} - + result[cur_season][cur_episode] = cur_result["name"] - + return json.dumps(result) @cherrypy.expose @@ -240,7 +240,7 @@ class Manage: status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER else: status_list = [] - + t = PageTemplate(file="manage_episodeStatuses.tmpl") t.submenu = ManageMenu t.whichStatus = whichStatus @@ -248,7 +248,7 @@ class Manage: # if we have no status then this is as far as we need to go if not status_list: return _munge(t) - + myDB = db.DBConnection() status_results = myDB.select("SELECT show_name, tv_shows.tvdb_id as tvdb_id FROM tv_episodes, tv_shows WHERE tv_episodes.status IN ("+','.join(['?']*len(status_list))+") AND season != 0 AND tv_episodes.showid = tv_shows.tvdb_id ORDER BY show_name", status_list) @@ -261,11 +261,11 @@ class Manage: ep_counts[cur_tvdb_id] = 1 else: ep_counts[cur_tvdb_id] += 1 - + show_names[cur_tvdb_id] = cur_status_result["show_name"] if cur_tvdb_id not in sorted_show_ids: sorted_show_ids.append(cur_tvdb_id) - + t.show_names = show_names t.ep_counts = ep_counts t.sorted_show_ids = sorted_show_ids @@ -273,26 +273,26 @@ class Manage: @cherrypy.expose def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs): - + status_list = [int(oldStatus)] if status_list[0] == SNATCHED: status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER to_change = {} - + # make a list of all shows and their associated args for arg in kwargs: tvdb_id, what = arg.split('-') - + # we don't care about unchecked checkboxes if kwargs[arg] != 'on': continue - + if tvdb_id not in to_change: to_change[tvdb_id] = [] - + to_change[tvdb_id].append(what) - + myDB = db.DBConnection() for cur_tvdb_id in to_change: @@ -304,19 +304,19 @@ class Manage: to_change[cur_tvdb_id] = all_eps Home().setStatus(cur_tvdb_id, '|'.join(to_change[cur_tvdb_id]), newStatus, direct=True) - + redirect('/manage/episodeStatuses') @cherrypy.expose def backlogShow(self, tvdb_id): - + show_obj = helpers.findCertainShow(sickbeard.showList, int(tvdb_id)) - + if show_obj: sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj]) #@UndefinedVariable - + redirect("/manage/backlogOverview") - + @cherrypy.expose def backlogOverview(self): @@ -338,6 +338,7 @@ class Manage: epCounts[Overview.QUAL] = 0 epCounts[Overview.GOOD] = 0 epCounts[Overview.UNAIRED] = 0 + epCounts[Overview.SNATCHED] = 0 sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC", [curShow.tvdbid]) @@ -386,11 +387,11 @@ class Manage: root_dir_list = [] for curShow in showList: - + cur_root_dir = ek.ek(os.path.dirname, curShow._location) if cur_root_dir not in root_dir_list: - root_dir_list.append(cur_root_dir) - + root_dir_list.append(cur_root_dir) + # if we know they're not all the same then no point even bothering if paused_all_same: # if we had a value already and this value is different then they're not all the same @@ -446,7 +447,7 @@ class Manage: logger.log(u"For show "+showObj.name+" changing dir from "+showObj._location+" to "+new_show_dir) else: new_show_dir = showObj._location - + if paused == 'keep': new_paused = showObj.paused else: @@ -461,7 +462,7 @@ class Manage: if quality_preset == 'keep': anyQualities, bestQualities = Quality.splitQuality(showObj.quality) - + curErrors += Home().editShow(curShow, new_show_dir, anyQualities, bestQualities, new_flatten_folders, new_paused, directCall=True) if curErrors: @@ -631,7 +632,7 @@ class ConfigGeneral: @cherrypy.expose def saveRootDirs(self, rootDirString=None): sickbeard.ROOT_DIRS = rootDirString - + @cherrypy.expose def saveAddShowDefaults(self, defaultFlattenFolders, defaultStatus, anyQualities, bestQualities, audio_langs ): @@ -646,7 +647,7 @@ class ConfigGeneral: bestQualities = [] newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities)) - + sickbeard.STATUS_DEFAULT = int(defaultStatus) sickbeard.QUALITY_DEFAULT = int(newQuality) sickbeard.AUDIO_SHOW_DEFAULT = audio_langs @@ -667,14 +668,14 @@ class ConfigGeneral: from hashlib import md5 except ImportError: from md5 import md5 - + # Create some values to seed md5 t = str(time.time()) r = str(random.random()) - + # Create the md5 instance and give it the current time m = md5(t) - + # Update the md5 instance with the random variable m.update(r) @@ -727,14 +728,14 @@ class ConfigGeneral: sickbeard.USE_API = use_api sickbeard.API_KEY = api_key - + if enable_https == "on": enable_https = 1 else: enable_https = 0 - + sickbeard.ENABLE_HTTPS = enable_https - + if not config.change_HTTPS_CERT(https_cert): results += ["Unable to create directory " + os.path.normpath(https_cert) + ", https cert dir not changed."] @@ -898,7 +899,7 @@ class ConfigPostProcessing: sickbeard.metadata_provider_dict['Sony PS3'].set_config(sony_ps3_data) sickbeard.metadata_provider_dict['WDTV'].set_config(wdtv_data) sickbeard.metadata_provider_dict['TIVO'].set_config(tivo_data) - + if self.isNamingValid(naming_pattern, naming_multi_ep) != "invalid": sickbeard.NAMING_PATTERN = naming_pattern sickbeard.NAMING_MULTI_EP = int(naming_multi_ep) @@ -933,16 +934,16 @@ class ConfigPostProcessing: result = naming.test_name(pattern, multi, abd) - result = ek.ek(os.path.join, result['dir'], result['name']) + result = ek.ek(os.path.join, result['dir'], result['name']) return result - + @cherrypy.expose def isNamingValid(self, pattern=None, multi=None, abd=False): if pattern == None: return "invalid" - - # air by date shows just need one check, we don't need to worry about season folders + + # air by date shows just need one check, we don't need to worry about season folders if abd: is_valid = naming.check_valid_abd_naming(pattern) require_season_folders = False @@ -950,7 +951,7 @@ class ConfigPostProcessing: else: # check validity of single and multi ep cases for the whole path is_valid = naming.check_valid_naming(pattern, multi) - + # check validity of single and multi ep cases for only the file name require_season_folders = naming.check_force_season_folders(pattern, multi) @@ -961,7 +962,7 @@ class ConfigPostProcessing: else: return "invalid" - + class ConfigProviders: @cherrypy.expose @@ -1031,9 +1032,11 @@ class ConfigProviders: @cherrypy.expose def saveProviders(self, nzbmatrix_username=None, nzbmatrix_apikey=None, - nzbs_r_us_uid=None, nzbs_r_us_hash=None, newznab_string=None, + nzbs_r_us_uid=None, nzbs_r_us_hash=None, newznab_string='', + omgwtfnzbs_uid=None, omgwtfnzbs_key=None, tvtorrents_digest=None, tvtorrents_hash=None, - btn_api_key=None, binnewz_language=None, + torrentleech_key=None, + btn_api_key=None, binnewz_language=None, newzbin_username=None, newzbin_password=None,t411_language=None,t411_username=None,t411_password=None, provider_order=None): @@ -1092,10 +1095,16 @@ class ConfigProviders: sickbeard.BINREQ = curEnabled elif curProvider == 'womble_s_index': sickbeard.WOMBLE = curEnabled + elif curProvider == 'nzbx': + sickbeard.NZBX = curEnabled + elif curProvider == 'omgwtfnzbs': + sickbeard.OMGWTFNZBS = curEnabled elif curProvider == 'ezrss': sickbeard.EZRSS = curEnabled elif curProvider == 'tvtorrents': sickbeard.TVTORRENTS = curEnabled + elif curProvider == 'torrentleech': + sickbeard.TORRENTLEECH = curEnabled elif curProvider == 'btn': sickbeard.BTN = curEnabled elif curProvider == 'binnewz': @@ -1105,11 +1114,13 @@ class ConfigProviders: elif curProvider in newznabProviderDict: newznabProviderDict[curProvider].enabled = bool(curEnabled) else: - logger.log(u"don't know what "+curProvider+" is, skipping") + logger.log(u"don't know what " + curProvider + " is, skipping") sickbeard.TVTORRENTS_DIGEST = tvtorrents_digest.strip() sickbeard.TVTORRENTS_HASH = tvtorrents_hash.strip() + sickbeard.TORRENTLEECH_KEY = torrentleech_key.strip() + sickbeard.BTN_API_KEY = btn_api_key.strip() sickbeard.BINNEWZ_LANGUAGE = binnewz_language @@ -1121,6 +1132,9 @@ class ConfigProviders: sickbeard.NZBSRUS_UID = nzbs_r_us_uid.strip() sickbeard.NZBSRUS_HASH = nzbs_r_us_hash.strip() + sickbeard.OMGWTFNZBS_UID = omgwtfnzbs_uid.strip() + sickbeard.OMGWTFNZBS_KEY = omgwtfnzbs_key.strip() + sickbeard.PROVIDER_ORDER = provider_list sickbeard.save_config() @@ -1135,6 +1149,7 @@ class ConfigProviders: redirect("/config/providers/") + class ConfigNotifications: @cherrypy.expose @@ -1144,20 +1159,21 @@ class ConfigNotifications: return _munge(t) @cherrypy.expose - def saveNotifications(self, use_xbmc=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None, + def saveNotifications(self, use_xbmc=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None, xbmc_update_onlyfirst=None, xbmc_update_library=None, xbmc_update_full=None, xbmc_host=None, xbmc_username=None, xbmc_password=None, use_plex=None, plex_notify_onsnatch=None, plex_notify_ondownload=None, plex_update_library=None, plex_server_host=None, plex_host=None, plex_username=None, plex_password=None, - use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None, growl_host=None, growl_password=None, - use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_api=None, prowl_priority=0, - use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None, + use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None, growl_host=None, growl_password=None, + use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_api=None, prowl_priority=0, + 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_nmjv2=None, nmjv2_host=None, nmjv2_dbloc=None, nmjv2_database=None, use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None, - use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None, pytivo_update_library=None, + use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None, pytivo_update_library=None, pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None, use_nma=None, nma_notify_onsnatch=None, nma_notify_ondownload=None, nma_api=None, nma_priority=0 ): @@ -1183,6 +1199,11 @@ class ConfigNotifications: else: xbmc_update_full = 0 + if xbmc_update_onlyfirst == "on": + xbmc_update_onlyfirst = 1 + else: + xbmc_update_onlyfirst = 0 + if use_xbmc == "on": use_xbmc = 1 else: @@ -1222,7 +1243,7 @@ class ConfigNotifications: use_growl = 1 else: use_growl = 0 - + if prowl_notify_onsnatch == "on": prowl_notify_onsnatch = 1 else: @@ -1303,6 +1324,11 @@ class ConfigNotifications: else: use_synoindex = 0 + if use_nmjv2 == "on": + use_nmjv2 = 1 + else: + use_nmjv2 = 0 + if use_trakt == "on": use_trakt = 1 else: @@ -1312,7 +1338,7 @@ class ConfigNotifications: use_pytivo = 1 else: use_pytivo = 0 - + if pytivo_notify_onsnatch == "on": pytivo_notify_onsnatch = 1 else: @@ -1348,6 +1374,7 @@ class ConfigNotifications: sickbeard.XBMC_NOTIFY_ONDOWNLOAD = xbmc_notify_ondownload sickbeard.XBMC_UPDATE_LIBRARY = xbmc_update_library sickbeard.XBMC_UPDATE_FULL = xbmc_update_full + sickbeard.XBMC_UPDATE_ONLYFIRST = xbmc_update_onlyfirst sickbeard.XBMC_HOST = xbmc_host sickbeard.XBMC_USERNAME = xbmc_username sickbeard.XBMC_PASSWORD = xbmc_password @@ -1404,6 +1431,11 @@ class ConfigNotifications: sickbeard.USE_SYNOINDEX = use_synoindex + sickbeard.USE_NMJv2 = use_nmjv2 + sickbeard.NMJv2_HOST = nmjv2_host + sickbeard.NMJv2_DATABASE = nmjv2_database + sickbeard.NMJv2_DBLOC = nmjv2_dbloc + sickbeard.USE_TRAKT = use_trakt sickbeard.TRAKT_USERNAME = trakt_username sickbeard.TRAKT_PASSWORD = trakt_password @@ -1411,7 +1443,7 @@ class ConfigNotifications: sickbeard.USE_PYTIVO = use_pytivo sickbeard.PYTIVO_NOTIFY_ONSNATCH = pytivo_notify_onsnatch == "off" - sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = pytivo_notify_ondownload == "off" + sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = pytivo_notify_ondownload == "off" sickbeard.PYTIVO_UPDATE_LIBRARY = pytivo_update_library sickbeard.PYTIVO_HOST = pytivo_host sickbeard.PYTIVO_SHARE_NAME = pytivo_share_name @@ -1422,7 +1454,7 @@ class ConfigNotifications: sickbeard.NMA_NOTIFY_ONDOWNLOAD = nma_notify_ondownload sickbeard.NMA_API = nma_api sickbeard.NMA_PRIORITY = nma_priority - + sickbeard.save_config() if len(results) > 0: @@ -1448,7 +1480,7 @@ class Config: general = ConfigGeneral() search = ConfigSearch() - + postProcessing = ConfigPostProcessing() providers = ConfigProviders() @@ -1581,16 +1613,16 @@ class NewHomeAddShows: def massAddTable(self, rootDir=None): t = PageTemplate(file="home_massAddTable.tmpl") t.submenu = HomeMenu() - + myDB = db.DBConnection() if not rootDir: - return "No folders selected." + return "No folders selected." elif type(rootDir) != list: root_dirs = [rootDir] else: root_dirs = rootDir - + root_dirs = [urllib.unquote_plus(x) for x in root_dirs] default_index = int(sickbeard.ROOT_DIRS.split('|')[0]) @@ -1599,9 +1631,9 @@ class NewHomeAddShows: if tmp in root_dirs: root_dirs.remove(tmp) root_dirs = [tmp]+root_dirs - + dir_list = [] - + for root_dir in root_dirs: try: file_list = ek.ek(os.listdir, root_dir) @@ -1613,36 +1645,36 @@ class NewHomeAddShows: cur_path = ek.ek(os.path.normpath, ek.ek(os.path.join, root_dir, cur_file)) if not ek.ek(os.path.isdir, cur_path): continue - + cur_dir = { 'dir': cur_path, 'display_dir': '<b>'+ek.ek(os.path.dirname, cur_path)+os.sep+'</b>'+ek.ek(os.path.basename, cur_path), } - + # see if the folder is in XBMC already dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path]) - + if dirResults: cur_dir['added_already'] = True else: cur_dir['added_already'] = False - + dir_list.append(cur_dir) - + tvdb_id = '' show_name = '' for cur_provider in sickbeard.metadata_provider_dict.values(): (tvdb_id, show_name) = cur_provider.retrieveShowMetadata(cur_path) if tvdb_id and show_name: break - + cur_dir['existing_info'] = (tvdb_id, show_name) - + if tvdb_id and helpers.findCertainShow(sickbeard.showList, tvdb_id): - cur_dir['added_already'] = True + cur_dir['added_already'] = True t.dirList = dir_list - + return _munge(t) @cherrypy.expose @@ -1653,38 +1685,38 @@ class NewHomeAddShows: """ t = PageTemplate(file="home_newShow.tmpl") t.submenu = HomeMenu() - + show_dir, tvdb_id, show_name = self.split_extra_show(show_to_add) - + if tvdb_id and show_name: use_provided_info = True else: use_provided_info = False - + # tell the template whether we're giving it show name & TVDB ID t.use_provided_info = use_provided_info - - # use the given show_dir for the tvdb search if available + + # use the given show_dir for the tvdb search if available if not show_dir: t.default_show_name = '' elif not show_name: t.default_show_name = ek.ek(os.path.basename, ek.ek(os.path.normpath, show_dir)).replace('.',' ') else: t.default_show_name = show_name - + # carry a list of other dirs if given if not other_shows: other_shows = [] elif type(other_shows) != list: other_shows = [other_shows] - + if use_provided_info: t.provided_tvdb_id = tvdb_id t.provided_tvdb_name = show_name - + t.provided_show_dir = show_dir t.other_shows = other_shows - + return _munge(t) @cherrypy.expose @@ -1695,52 +1727,52 @@ class NewHomeAddShows: Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are provided then it forwards back to newShow, if not it goes to /home. """ - + # grab our list of other dirs if given if not other_shows: other_shows = [] elif type(other_shows) != list: other_shows = [other_shows] - - def finishAddShow(): + + def finishAddShow(): # if there are no extra shows then go home if not other_shows: redirect('/home') - + # peel off the next one next_show_dir = other_shows[0] rest_of_show_dirs = other_shows[1:] - + # go to add the next show return self.newShow(next_show_dir, rest_of_show_dirs) - + # if we're skipping then behave accordingly if skipShow: return finishAddShow() - + # sanity check on our inputs if (not rootDir and not fullShowPath) or not whichSeries: return "Missing params, no tvdb id or folder:"+repr(whichSeries)+" and "+repr(rootDir)+"/"+repr(fullShowPath) - + # figure out what show we're adding and where series_pieces = whichSeries.partition('|') if len(series_pieces) < 3: return "Error with show selection." - + tvdb_id = int(series_pieces[0]) show_name = series_pieces[2] - + # use the whole path if it's given, or else append the show name to the root dir to get the full show path if fullShowPath: show_dir = ek.ek(os.path.normpath, fullShowPath) else: show_dir = ek.ek(os.path.join, rootDir, helpers.sanitizeFileName(show_name)) - + # blanket policy - if the dir exists you should have used "add existing show" numbnuts if ek.ek(os.path.isdir, show_dir) and not fullShowPath: ui.notifications.error("Unable to add show", "Folder "+show_dir+" exists already") redirect('/home/addShows/existingShows') - + # don't create show dir if config says not to if sickbeard.ADD_SHOWS_WO_DIR: logger.log(u"Skipping initial creation of "+show_dir+" due to config.ini setting") @@ -1758,7 +1790,7 @@ class NewHomeAddShows: flatten_folders = 1 else: flatten_folders = 0 - + if not anyQualities: anyQualities = [] if not bestQualities: @@ -1768,22 +1800,22 @@ class NewHomeAddShows: if type(bestQualities) != list: bestQualities = [bestQualities] newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities)) - + # add the show sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, int(defaultStatus), newQuality, flatten_folders, tvdbLang, audio_lang) #@UndefinedVariable ui.notifications.message('Show added', 'Adding the specified show into '+show_dir) return finishAddShow() - + @cherrypy.expose def existingShows(self): """ - Prints out the page to add existing shows from a root dir + Prints out the page to add existing shows from a root dir """ t = PageTemplate(file="home_addExistingShow.tmpl") t.submenu = HomeMenu() - + return _munge(t) def split_extra_show(self, extra_show): @@ -1795,7 +1827,7 @@ class NewHomeAddShows: show_dir = split_vals[0] tvdb_id = split_vals[1] show_name = '|'.join(split_vals[2:]) - + return (show_dir, tvdb_id, show_name) @cherrypy.expose @@ -1810,14 +1842,14 @@ class NewHomeAddShows: shows_to_add = [] elif type(shows_to_add) != list: shows_to_add = [shows_to_add] - + shows_to_add = [urllib.unquote_plus(x) for x in shows_to_add] - + if promptForSettings == "on": promptForSettings = 1 else: promptForSettings = 0 - + tvdb_id_given = [] dirs_only = [] # separate all the ones with TVDB IDs @@ -1834,7 +1866,7 @@ class NewHomeAddShows: # if they want me to prompt for settings then I will just carry on to the newShow page if promptForSettings and shows_to_add: return self.newShow(shows_to_add[0], shows_to_add[1:]) - + # if they don't want me to prompt for settings then I can just add all the nfo shows now num_added = 0 for cur_show in tvdb_id_given: @@ -1843,7 +1875,7 @@ class NewHomeAddShows: # add the show sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, SKIPPED, sickbeard.QUALITY_DEFAULT, sickbeard.FLATTEN_FOLDERS_DEFAULT) #@UndefinedVariable num_added += 1 - + if num_added: ui.notifications.message("Shows Added", "Automatically added "+str(num_added)+" from their existing metadata files") @@ -2119,6 +2151,25 @@ class Home: else: return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}' + @cherrypy.expose + def testNMJv2(self, host=None): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + + result = notifiers.nmjv2_notifier.test_notify(urllib.unquote_plus(host)) + if result: + return "Test notice sent successfully to " + urllib.unquote_plus(host) + else: + return "Test notice failed to " + urllib.unquote_plus(host) + + @cherrypy.expose + def settingsNMJv2(self, host=None, dbloc=None, instance=None): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + result = notifiers.nmjv2_notifier.notify_settings(urllib.unquote_plus(host), dbloc, instance) + if result: + return '{"message": "NMJ Database found at: %(host)s", "database": "%(database)s"}' % {"host": host, "database": sickbeard.NMJv2_DATABASE} + else: + return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {"dbloc": dbloc} + @cherrypy.expose def testTrakt(self, api=None, username=None, password=None): cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" @@ -2132,7 +2183,7 @@ class Home: @cherrypy.expose def testNMA(self, nma_api=None, nma_priority=0): cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" - + result = notifiers.nma_notifier.test_notify(nma_api, nma_priority) if result: return "Test NMA notice sent successfully" @@ -2250,6 +2301,7 @@ class Home: epCounts[Overview.QUAL] = 0 epCounts[Overview.GOOD] = 0 epCounts[Overview.UNAIRED] = 0 + epCounts[Overview.SNATCHED] = 0 for curResult in sqlResults: @@ -2461,17 +2513,20 @@ class Home: # just give it some time time.sleep(3) - redirect("/home/displayShow?show="+str(showObj.tvdbid)) + redirect("/home/displayShow?show=" + str(showObj.tvdbid)) @cherrypy.expose def updateXBMC(self, showName=None): - # TODO: configure that each host can have different options / username / pw - # only send update to first host in the list -- workaround for xbmc sql backend users - firstHost = sickbeard.XBMC_HOST.split(",")[0].strip() + if sickbeard.XBMC_UPDATE_ONLYFIRST: + # only send update to first host in the list -- workaround for xbmc sql backend users + host = sickbeard.XBMC_HOST.split(",")[0].strip() + else: + host = sickbeard.XBMC_HOST + if notifiers.xbmc_notifier.update_library(showName=showName): - ui.notifications.message("Library update command sent to XBMC host: " + firstHost) + ui.notifications.message("Library update command sent to XBMC host(s): " + host) else: - ui.notifications.error("Unable to contact XBMC host: " + firstHost) + ui.notifications.error("Unable to contact one or more XBMC host(s): " + host) redirect('/home') @cherrypy.expose @@ -2529,7 +2584,7 @@ class Home: ep_segment = str(epObj.airdate)[:7] else: ep_segment = epObj.season - + if ep_segment not in segment_list: segment_list.append(ep_segment) @@ -2673,18 +2728,18 @@ class Home: if eps == None: redirect("/home/displayShow?show=" + show) - + for curEp in eps.split('|'): epInfo = curEp.split('x') - + # this is probably the worst possible way to deal with double eps but I've kinda painted myself into a corner here with this stupid database ep_result = myDB.select("SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND 5=5", [show, epInfo[0], epInfo[1]]) if not ep_result: logger.log(u"Unable to find an episode for "+curEp+", skipping", logger.WARNING) continue related_eps_result = myDB.select("SELECT * FROM tv_episodes WHERE location = ? AND episode != ?", [ep_result[0]["location"], epInfo[1]]) - + root_ep_obj = show_obj.getEpisode(int(epInfo[0]), int(epInfo[1])) for cur_related_ep in related_eps_result: related_ep_obj = show_obj.getEpisode(int(cur_related_ep["season"]), int(cur_related_ep["episode"])) @@ -2698,7 +2753,7 @@ class Home: @cherrypy.expose def searchEpisode(self, show=None, season=None, episode=None): - # retrieve the episode object and fail if we can't get one + # retrieve the episode object and fail if we can't get one ep_obj = _getEpisode(show, season, episode) if isinstance(ep_obj, str): return json.dumps({'result': 'failure'}) @@ -2718,13 +2773,13 @@ class Home: return json.dumps({'result': 'failure'}) class UI: - + @cherrypy.expose def add_message(self): - + ui.notifications.message('Test 1', 'This is test number 1') ui.notifications.error('Test 2', 'This is test number 2') - + return "ok" @cherrypy.expose @@ -2803,32 +2858,32 @@ class WebInterface: def setComingEpsLayout(self, layout): if layout not in ('poster', 'banner', 'list'): layout = 'banner' - + sickbeard.COMING_EPS_LAYOUT = layout - + redirect("/comingEpisodes") @cherrypy.expose def toggleComingEpsDisplayPaused(self): - + sickbeard.COMING_EPS_DISPLAY_PAUSED = not sickbeard.COMING_EPS_DISPLAY_PAUSED - + redirect("/comingEpisodes") @cherrypy.expose def setComingEpsSort(self, sort): if sort not in ('date', 'network', 'show'): sort = 'date' - + sickbeard.COMING_EPS_SORT = sort - + redirect("/comingEpisodes") @cherrypy.expose def comingEpisodes(self, layout="None"): myDB = db.DBConnection() - + today = datetime.date.today().toordinal() next_week = (datetime.date.today() + datetime.timedelta(days=7)).toordinal() recently = (datetime.date.today() - datetime.timedelta(days=3)).toordinal() @@ -2882,7 +2937,7 @@ class WebInterface: t.layout = layout else: t.layout = sickbeard.COMING_EPS_LAYOUT - + return _munge(t) @@ -2893,11 +2948,11 @@ class WebInterface: config = Config() home = Home() - + api = Api() browser = browser.WebFileBrowser() errorlogs = ErrorLogs() - + ui = UI()