diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index d2189bcdf49877d145658397429240e438aa65ad..2310d85bb4c384222613e33fc38b7484648cc4e2 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -265,7 +265,7 @@ home.mako color: #fff; } -.show { +.show-container { margin: 12px; width: 188px; height: 352px; diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index 119a4f9e7371b2df5098d29f3be9c7b34a144516..dd7d31b8f57060db1a9899d235fe0527e1ced587 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -627,7 +627,7 @@ home.mako margin: 0 auto; } -.show { +.show-container { margin: 12px; width: 188px; height: 352px; @@ -644,28 +644,28 @@ home.mako border-top-right-radius: 5px; } -.show .ui-progressbar { +.show-container .ui-progressbar { height: 7px !important; top: -2px; } -.show .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { +.show-container .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { border-bottom-right-radius: 0px; } -.show .ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { +.show-container .ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { border-bottom-left-radius: 0px; } -.show .ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { +.show-container .ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { border-top-right-radius: 0px; } -.show .ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { +.show-container .ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { border-top-left-radius: 0px; } -.show .ui-widget-content { +.show-container .ui-widget-content { border-top: 1px solid #111; border-bottom: 1px solid #111; border-left: 0px; @@ -677,10 +677,10 @@ home.mako .ui-progressbar .progress-20 { border: none; } -.show .progress-20, -.show .progress-40, -.show .progress-60, -.show .progress-80 { +.show-container .progress-20, +.show-container .progress-40, +.show-container .progress-60, +.show-container .progress-80 { border-radius: 0px; height: 7px } diff --git a/gui/slick/images/network/fx (us).png b/gui/slick/images/network/fx (us).png new file mode 100644 index 0000000000000000000000000000000000000000..62f98632041d66906180e5f1cddab1bda6bd0ee1 Binary files /dev/null and b/gui/slick/images/network/fx (us).png differ diff --git a/gui/slick/images/providers/elitetorrent.png b/gui/slick/images/providers/elitetorrent.png new file mode 100644 index 0000000000000000000000000000000000000000..bc84adbfed862f969ba72eae59d659f4d9a7f6b6 Binary files /dev/null and b/gui/slick/images/providers/elitetorrent.png differ diff --git a/gui/slick/js/core.js b/gui/slick/js/core.js index d36035d44fc9e44f6ff1d9e2fe609a160ed29885..496c0cc2f8e822e0d653bc55d824d08706a5ba37 100644 --- a/gui/slick/js/core.js +++ b/gui/slick/js/core.js @@ -19,6 +19,7 @@ var configSuccess = function(){ window.location.href = srRoot + '/config/providers/'; }); $('#email_show').trigger('notify'); + $('#prowl_show').trigger('notify'); }; var SICKRAGE = { @@ -276,106 +277,8 @@ var SICKRAGE = { $('#backupDir').fileBrowser({ title: 'Select backup folder to save to', key: 'backupPath' }); $('#backupFile').fileBrowser({ title: 'Select backup files to restore', key: 'backupFile', includeFiles: 1 }); $('#config-components').tabs(); - - $(".enabler").each(function(){ - if (!$(this).prop('checked')) { $('#content_'+$(this).attr('id')).hide(); } - }); - - $(".enabler").on('click', function() { - if ($(this).prop('checked')){ - $('#content_'+$(this).attr('id')).fadeIn("fast", "linear"); - } else { - $('#content_'+$(this).attr('id')).fadeOut("fast", "linear"); - } - }); - - $(".viewIf").on('click', function() { - if ($(this).prop('checked')) { - $('.hide_if_'+$(this).attr('id')).css('display','none'); - $('.show_if_'+$(this).attr('id')).fadeIn("fast", "linear"); - } else { - $('.show_if_'+$(this).attr('id')).css('display','none'); - $('.hide_if_'+$(this).attr('id')).fadeIn("fast", "linear"); - } - }); - - $(".datePresets").on('click', function() { - var def = $('#date_presets').val(); - if ($(this).prop('checked') && '%x' == def) { // jshint ignore:line - def = '%a, %b %d, %Y'; - $('#date_use_system_default').html('1'); - } else if (!$(this).prop('checked') && '1' == $('#date_use_system_default').html()){ // jshint ignore:line - def = '%x'; - } - - $('#date_presets').attr('name', 'date_preset_old'); - $('#date_presets').attr('id', 'date_presets_old'); - - $('#date_presets_na').attr('name', 'date_preset'); - $('#date_presets_na').attr('id', 'date_presets'); - - $('#date_presets_old').attr('name', 'date_preset_na'); - $('#date_presets_old').attr('id', 'date_presets_na'); - - if (def) { $('#date_presets').val(def); } - }); - - // bind 'myForm' and provide a simple callback function - $('#configForm').ajaxForm({ - beforeSubmit: function(){ - $('.config_submitter .config_submitter_refresh').each(function(){ - $(this).attr("disabled", "disabled"); - $(this).after('<span><img src="' + srRoot + '/images/loading16' + themeSpinner + '.gif"> Saving...</span>'); - $(this).hide(); - }); - }, - success: function(){ - setTimeout(function () { - "use strict"; - configSuccess(); - }, 2000); - } - }); - - $('#api_key').on('click', function(){ - $('#api_key').select(); - }); - - $("#generate_new_apikey").on('click', function(){ - $.get(srRoot + '/config/general/generateApiKey', function(data){ - if (data.error !== undefined) { - alert(data.error); - return; - } - $('#api_key').val(data); - }); - }); - - $('#branchCheckout').on('click', function() { - var url = srRoot+'/home/branchCheckout?branch='+$("#branchVersion").val(); - var checkDBversion = srRoot + "/home/getDBcompare"; - $.getJSON(checkDBversion, function(data){ - if (data.status.toLowerCase() === "success") { - if (data.message.toLowerCase() === "equal") { - //Checkout Branch - window.location.href = url; - } - if (data.message.toLowerCase() === "upgrade") { - if ( confirm("Changing branch will upgrade your database.\nYou won't be able to downgrade afterward.\nDo you want to continue?") ) { - //Checkout Branch - window.location.href = url; - } - } - if (data.message.toLowerCase() === "downgrade") { - alert("Can't switch branch as this will result in a database downgrade."); - } - } - }); - }); }, notifications: function() { - $('#config-components').tabs(); - $('#testGrowl').on('click', function () { var growl = {}; growl.host = $.trim($('#growl_host').val()); @@ -388,7 +291,10 @@ var SICKRAGE = { $('#growl_host').removeClass('warning'); $(this).prop('disabled', true); $('#testGrowl-result').html(loading); - $.get(srRoot + '/home/testGrowl', {'host': growl.host, 'password': growl.password}).done(function (data) { + $.get(srRoot + '/home/testGrowl', { + 'host': growl.host, + 'password': growl.password + }).done(function (data) { $('#testGrowl-result').html(data); $('#testGrowl').prop('disabled', false); }); @@ -406,7 +312,10 @@ var SICKRAGE = { $('#prowl_api').removeClass('warning'); $(this).prop('disabled', true); $('#testProwl-result').html(loading); - $.get(srRoot + '/home/testProwl', {'prowl_api': prowl.api, 'prowl_priority': prowl.priority}).done(function (data) { + $.get(srRoot + '/home/testProwl', { + 'prowl_api': prowl.api, + 'prowl_priority': prowl.priority + }).done(function (data) { $('#testProwl-result').html(data); $('#testProwl').prop('disabled', false); }); @@ -425,7 +334,11 @@ var SICKRAGE = { $('#kodi_host').removeClass('warning'); $(this).prop('disabled', true); $('#testKODI-result').html(loading); - $.get(srRoot + '/home/testKODI', {'host': kodi.host, 'username': kodi.username, 'password': kodi.password}).done(function (data) { + $.get(srRoot + '/home/testKODI', { + 'host': kodi.host, + 'username': kodi.username, + 'password': kodi.password + }).done(function (data) { $('#testKODI-result').html(data); $('#testKODI').prop('disabled', false); }); @@ -445,7 +358,11 @@ var SICKRAGE = { $('#plex_host').removeClass('warning'); $(this).prop('disabled', true); $('#testPMC-result').html(loading); - $.get(srRoot + '/home/testPMC', {'host': plex.client.host, 'username': plex.client.username, 'password': plex.client.password}).done(function (data) { + $.get(srRoot + '/home/testPMC', { + 'host': plex.client.host, + 'username': plex.client.username, + 'password': plex.client.password + }).done(function (data) { $('#testPMC-result').html(data); $('#testPMC').prop('disabled', false); }); @@ -466,7 +383,12 @@ var SICKRAGE = { $('#plex_server_host').removeClass('warning'); $(this).prop('disabled', true); $('#testPMS-result').html(loading); - $.get(srRoot + '/home/testPMS', {'host': plex.server.host, 'username': plex.username, 'password': plex.password, 'plex_server_token': plex.server.token}).done(function (data) { + $.get(srRoot + '/home/testPMS', { + 'host': plex.server.host, + 'username': plex.username, + 'password': plex.password, + 'plex_server_token': plex.server.token + }).done(function (data) { $('#testPMS-result').html(data); $('#testPMS').prop('disabled', false); }); @@ -493,7 +415,10 @@ var SICKRAGE = { $('#emby_host,#emby_apikey').removeClass('warning'); $(this).prop('disabled', true); $('#testEMBY-result').html(loading); - $.get(srRoot + '/home/testEMBY', {'host': emby.host, 'emby_apikey': emby.apikey}).done(function (data) { + $.get(srRoot + '/home/testEMBY', { + 'host': emby.host, + 'emby_apikey': emby.apikey + }).done(function (data) { $('#testEMBY-result').html(data); $('#testEMBY').prop('disabled', false); }); @@ -510,7 +435,9 @@ var SICKRAGE = { $('#boxcar2_accesstoken').removeClass('warning'); $(this).prop('disabled', true); $('#testBoxcar2-result').html(loading); - $.get(srRoot + '/home/testBoxcar2', {'accesstoken': boxcar2.accesstoken}).done(function (data) { + $.get(srRoot + '/home/testBoxcar2', { + 'accesstoken': boxcar2.accesstoken + }).done(function (data) { $('#testBoxcar2-result').html(data); $('#testBoxcar2').prop('disabled', false); }); @@ -537,7 +464,10 @@ var SICKRAGE = { $('#pushover_userkey,#pushover_apikey').removeClass('warning'); $(this).prop('disabled', true); $('#testPushover-result').html(loading); - $.get(srRoot + '/home/testPushover', {'userKey': pushover.userkey, 'apiKey': pushover.apikey}).done(function (data) { + $.get(srRoot + '/home/testPushover', { + 'userKey': pushover.userkey, + 'apiKey': pushover.apikey + }).done(function (data) { $('#testPushover-result').html(data); $('#testPushover').prop('disabled', false); }); @@ -569,7 +499,9 @@ var SICKRAGE = { } $('#twitter_key').removeClass('warning'); $('#testTwitter-result').html(loading); - $.get(srRoot + '/home/twitterStep2', {'key': twitter.key}, function(data) { + $.get(srRoot + '/home/twitterStep2', { + 'key': twitter.key + }, function(data) { $('#testTwitter-result').html(data); }); }); @@ -626,7 +558,11 @@ var SICKRAGE = { $('#nmj_host').removeClass('warning'); $(this).prop('disabled', true); $('#testNMJ-result').html(loading); - $.get(srRoot + '/home/testNMJ', {'host': nmj.host, 'database': nmj.database, 'mount': nmj.mount}).done(function (data) { + $.get(srRoot + '/home/testNMJ', { + 'host': nmj.host, + 'database': nmj.database, + 'mount': nmj.mount + }).done(function (data) { $('#testNMJ-result').html(data); $('#testNMJ').prop('disabled', false); }); @@ -651,7 +587,11 @@ var SICKRAGE = { } nmjv2.dbinstance=$('#NMJv2db_instance').val(); - $.get(srRoot + '/home/settingsNMJv2', {'host': nmjv2.host,'dbloc': nmjv2.dbloc,'instance': nmjv2.dbinstance}, function (data){ + $.get(srRoot + '/home/settingsNMJv2', { + 'host': nmjv2.host, + 'dbloc': nmjv2.dbloc, + 'instance': nmjv2.dbinstance + }, function (data){ if (data === null) { $('#nmjv2_database').removeAttr('readonly'); } @@ -678,7 +618,9 @@ var SICKRAGE = { $('#nmjv2_host').removeClass('warning'); $(this).prop('disabled', true); $('#testNMJv2-result').html(loading); - $.get(srRoot + '/home/testNMJv2', {'host': nmjv2.host}) .done(function (data) { + $.get(srRoot + '/home/testNMJv2', { + 'host': nmjv2.host + }) .done(function (data) { $('#testNMJv2-result').html(data); $('#testNMJv2').prop('disabled', false); }); @@ -705,24 +647,22 @@ var SICKRAGE = { $('#freemobile_id,#freemobile_apikey').removeClass('warning'); $(this).prop('disabled', true); $('#testFreeMobile-result').html(loading); - $.get(srRoot + '/home/testFreeMobile', {'freemobile_id': freemobile.id, 'freemobile_apikey': freemobile.apikey}).done(function (data) { + $.get(srRoot + '/home/testFreeMobile', { + 'freemobile_id': freemobile.id, + 'freemobile_apikey': freemobile.apikey + }).done(function (data) { $('#testFreeMobile-result').html(data); $('#testFreeMobile').prop('disabled', false); }); }); $('#TraktGetPin').on('click', function () { - var trakt = {}; - trakt.pinUrl = $('#trakt_pin_url').val(); - window.open(trakt.pinUrl, "popUp", "toolbar=no, scrollbars=no, resizable=no, top=200, left=200, width=650, height=550"); + window.open($('#trakt_pin_url').val(), "popUp", "toolbar=no, scrollbars=no, resizable=no, top=200, left=200, width=650, height=550"); $('#trakt_pin').removeClass('hide'); }); $('#trakt_pin').on('keyup change', function(){ - var trakt = {}; - trakt.pin = $('#trakt_pin').val(); - - if (trakt.pin.length !== 0) { + if ($('#trakt_pin').val().length !== 0) { $('#TraktGetPin').addClass('hide'); $('#authTrakt').removeClass('hide'); } else { @@ -735,7 +675,9 @@ var SICKRAGE = { var trakt = {}; trakt.pin = $('#trakt_pin').val(); if (trakt.pin.length !== 0) { - $.get(srRoot + '/home/getTraktToken', { "trakt_pin": trakt.pin }).done(function (data) { + $.get(srRoot + '/home/getTraktToken', { + 'trakt_pin': trakt.pin + }).done(function (data) { $('#testTrakt-result').html(data); $('#authTrakt').addClass('hide'); $('#trakt_pin').addClass('hide'); @@ -767,7 +709,10 @@ var SICKRAGE = { $('#trakt_blacklist_name').removeClass('warning'); $(this).prop('disabled', true); $('#testTrakt-result').html(loading); - $.get(srRoot + '/home/testTrakt', {'username': trakt.username, 'blacklist_name': trakt.trendingBlacklist}).done(function (data) { + $.get(srRoot + '/home/testTrakt', { + 'username': trakt.username, + 'blacklist_name': trakt.trendingBlacklist + }).done(function (data) { $('#testTrakt-result').html(data); $('#testTrakt').prop('disabled', false); }); @@ -830,7 +775,10 @@ var SICKRAGE = { $('#nma_api').removeClass('warning'); $(this).prop('disabled', true); $('#testNMA-result').html(loading); - $.get(srRoot + '/home/testNMA', {'nma_api': nma.api, 'nma_priority': nma.priority}).done(function (data) { + $.get(srRoot + '/home/testNMA', { + 'nma_api': nma.api, + 'nma_priority': nma.priority + }).done(function (data) { $('#testNMA-result').html(data); $('#testNMA').prop('disabled', false); }); @@ -847,7 +795,9 @@ var SICKRAGE = { $('#pushalot_authorizationtoken').removeClass('warning'); $(this).prop('disabled', true); $('#testPushalot-result').html(loading); - $.get(srRoot + '/home/testPushalot', {'authorizationToken': pushalot.authToken}).done(function (data) { + $.get(srRoot + '/home/testPushalot', { + 'authorizationToken': pushalot.authToken + }).done(function (data) { $('#testPushalot-result').html(data); $('#testPushalot').prop('disabled', false); }); @@ -864,7 +814,9 @@ var SICKRAGE = { $('#pushbullet_api').removeClass('warning'); $(this).prop('disabled', true); $('#testPushbullet-result').html(loading); - $.get(srRoot + '/home/testPushbullet', {'api': pushbullet.api}).done(function (data) { + $.get(srRoot + '/home/testPushbullet', { + 'api': pushbullet.api + }).done(function (data) { $('#testPushbullet-result').html(data); $('#testPushbullet').prop('disabled', false); }); @@ -884,8 +836,10 @@ var SICKRAGE = { return false; } - $.get(srRoot + "/home/getPushbulletDevices", {'api': pushbullet.api}, function (data) { - pushbullet.devices = jQuery.parseJSON(data).devices; + $.get(srRoot + "/home/getPushbulletDevices", { + 'api': pushbullet.api + }, function (data) { + pushbullet.devices = $.parseJSON(data).devices; pushbullet.currentDevice = $("#pushbullet_device").val(); $("#pushbullet_device_list").html(''); for (var i = 0, len = pushbullet.devices.length; i < len; i++) { @@ -897,14 +851,8 @@ var SICKRAGE = { } } } - if (pushbullet.currentDevice === '') { - $("#pushbullet_device_list").prepend('<option value="" selected>All devices</option>'); - } else { - $("#pushbullet_device_list").prepend('<option value="">All devices</option>'); - } - if(msg) { - $('#testPushbullet-result').html(msg); - } + $("#pushbullet_device_list").prepend('<option value="" '+ (pushbullet.currentDevice === '' ? 'selected' : '') + '>All devices</option>'); + if(msg) { $('#testPushbullet-result').html(msg); } }); $("#pushbullet_device_list").on('change', function(){ @@ -925,11 +873,18 @@ var SICKRAGE = { var key = parseInt($('#email_show').val(), 10); $('#email_show_list').val(key >= 0 ? notify_data[key.toString()].list : ''); // jshint ignore:line }); + $('#prowl_show').on('change', function() { + var key = parseInt($('#prowl_show').val(), 10); + $('#prowl_show_list').val(key >= 0 ? notify_data[key.toString()].prowl_notify_list : ''); // jshint ignore:line + }); // Update the internal data struct anytime settings are saved to the server $('#email_show').on('notify', function() { loadShowNotifyLists(); }); + $('#prowl_show').on('notify', function() { + loadShowNotifyLists(); + }); function loadShowNotifyLists() { $.get(srRoot + "/home/loadShowNotifyLists", function(data) { @@ -937,14 +892,30 @@ var SICKRAGE = { list = $.parseJSON(data); // @TODO The line below this is the same as the $('#email_show') function above notify_data = list; // jshint ignore:line if (list._size === 0) { return; } - html = '<option value="-1">-- Select --</option>'; + + // Convert the 'list' object to a js array of objects so that we can sort it + var _list = []; for (s in list) { if (s.charAt(0) !== '_') { - html += '<option value="' + list[s].id + '">' + $('<div/>').text(list[s].name).html() + '</option>'; + _list.push(list[s]); + } + } + var sortedList = _list.sort(function(a,b) { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; + }); + html = '<option value="-1">-- Select --</option>'; + for (s in sortedList) { + if (sortedList[s].id && sortedList[s].name) { + html += '<option value="' + sortedList[s].id + '">' + $('<div/>').text(sortedList[s].name).html() + '</option>'; } } $('#email_show').html(html); $('#email_show_list').val(''); + + $('#prowl_show').html(html); + $('#prowl_show_list').val(''); }); } // Load the per show notify lists everytime this page is loaded @@ -959,6 +930,15 @@ var SICKRAGE = { loadShowNotifyLists(); }); }); + $('#prowl_show_save').on('click', function() { + $.post(srRoot + "/home/saveShowNotifyList", { + 'show': $('#prowl_show').val(), + 'prowlAPIs': $('#prowl_show_list').val() + }, function() { + // Reload the per show notify lists to reflect changes + loadShowNotifyLists(); + }); + }); // show instructions for plex when enabled $('#use_plex').on('click', function() { @@ -1106,7 +1086,7 @@ var SICKRAGE = { $.get(srRoot + '/config/postProcessing/testNaming', { 'pattern': pattern, - 'sports': 'True' + 'sports': 'True' // @TODO does this actually need to be a string or can it be a boolean? }, function (data) { if (data) { $('#naming_sports_example').text(data + '.ext'); @@ -1118,7 +1098,7 @@ var SICKRAGE = { $.get(srRoot + '/config/postProcessing/isNamingValid', { 'pattern': pattern, - 'sports': 'True' + 'sports': 'True' // @TODO does this actually need to be a string or can it be a boolean? }, function (data) { if (data === "invalid") { $('#naming_sports_pattern').qtip('option', { @@ -1206,6 +1186,8 @@ var SICKRAGE = { }); } + // @TODO all of these setup funcitons should be able to be rolled into a generic jQuery function + function setupNaming() { // if it is a custom selection then show the text box if ($('#name_presets :selected').val().toLowerCase() === "custom...") { @@ -1258,6 +1240,9 @@ var SICKRAGE = { } }); + // @TODO all of these on change funcitons should be able to be rolled into a generic jQuery function or maybe we could + // move all of the setup functions into these handlers? + $('#name_presets').on('change', function(){ setupNaming(); }); @@ -1290,6 +1275,9 @@ var SICKRAGE = { setupAnimeNaming(); }); + // @TODO We might be able to change these from typewatch to _ debounce like we've done on the log page + // The main reason for doing this would be to use only open source stuff that's still being maintained + $('#naming_multi_ep').on('change', fillExamples); $('#naming_pattern').on('focusout', fillExamples); $('#naming_pattern').on('keyup', function() { @@ -1344,6 +1332,8 @@ var SICKRAGE = { $('#naming_custom').show(); $('#naming_pattern').focus(); }); + + // @TODO We should see if these can be added with the on click or if we need to even call them on load setupNaming(); setupAbdNaming(); setupSportsNaming(); @@ -1739,6 +1729,9 @@ var SICKRAGE = { placeholder: 'ui-state-highlight', update: function() { $(this).refreshServiceList(); + }, + create: function() { + $(this).refreshServiceList(); } }); @@ -1759,6 +1752,16 @@ var SICKRAGE = { $('table').trigger('filterReset'); }); + // Handle filtering in the poster layout + $('#filterShowName').on('input', _.debounce(function (e) { + $('#container, #container-anime').isotope({ + filter: function () { + var name = $('div.show-title', this).text(); + return (name.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1); + } + }); + }, 500)); + // This needs to be refined to work a little faster. $('.progressbar').each(function(){ var percentage = $(this).data('progress-percentage'); @@ -1786,112 +1789,84 @@ var SICKRAGE = { 6: function(node) { return $(node).find("img").attr("alt"); } }, widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter', 'columnSelector'], - headers: (function(){ - if(metaToBool('sickbeard.FILTER_ROW')){ - return { - 0: { sorter: 'realISODate' }, - 1: { sorter: 'realISODate' }, - 2: { sorter: 'loadingNames' }, - 4: { sorter: 'quality' }, - 5: { sorter: 'eps' }, - 6: { filter : 'parsed' } - }; - } else { - return { - 0: { sorter: 'realISODate' }, - 1: { sorter: 'realISODate' }, - 2: { sorter: 'loadingNames' }, - 4: { sorter: 'quality' }, - 5: { sorter: 'eps' } - }; - } - }()), - widgetOptions: (function(){ - if(metaToBool('sickbeard.FILTER_ROW')){ - return { - filter_columnFilters: true, // jshint ignore:line - filter_hideFilters : true, // jshint ignore:line - filter_saveFilters : true, // jshint ignore:line - filter_functions : { // jshint ignore:line - 5:function(e, n, f) { - var test = false; - var pct = Math.floor((n % 1) * 1000); - if (f === '') { - test = true; - } else { - var result = f.match(/(<|<=|>=|>)\s(\d+)/i); - if (result) { - if (result[1] === "<") { - if (pct < parseInt(result[2])) { - test = true; - } - } else if (result[1] === "<=") { - if (pct <= parseInt(result[2])) { - test = true; - } - } else if (result[1] === ">=") { - if (pct >= parseInt(result[2])) { - test = true; - } - } else if (result[1] === ">") { - if (pct > parseInt(result[2])) { - test = true; - } - } + headers: { + 0: { sorter: 'realISODate' }, + 1: { sorter: 'realISODate' }, + 2: { sorter: 'loadingNames' }, + 4: { sorter: 'quality' }, + 5: { sorter: 'eps' }, + 6: { filter: 'parsed' } + }, + widgetOptions: { + filter_columnFilters: true, // jshint ignore:line + filter_hideFilters: true, // jshint ignore:line + filter_saveFilters: true, // jshint ignore:line + filter_functions: { // jshint ignore:line + 5: function(e, n, f) { + var test = false; + var pct = Math.floor((n % 1) * 1000); + if (f === '') { + test = true; + } else { + var result = f.match(/(<|<=|>=|>)\s(\d+)/i); + if (result) { + if (result[1] === "<") { + if (pct < parseInt(result[2])) { + test = true; } - - result = f.match(/(\d+)\s(-|to)\s(\d+)/i); - if (result) { - if ((result[2] === "-") || (result[2] === "to")) { - if ((pct >= parseInt(result[1])) && (pct <= parseInt(result[3]))) { - test = true; - } - } + } else if (result[1] === "<=") { + if (pct <= parseInt(result[2])) { + test = true; + } + } else if (result[1] === ">=") { + if (pct >= parseInt(result[2])) { + test = true; } + } else if (result[1] === ">") { + if (pct > parseInt(result[2])) { + test = true; + } + } + } - result = f.match(/(=)?\s?(\d+)\s?(=)?/i); - if (result) { - if ((result[1] === "=") || (result[3] === "=")) { - if (parseInt(result[2]) === pct) { - test = true; - } - } + result = f.match(/(\d+)\s(-|to)\s(\d+)/i); + if (result) { + if ((result[2] === "-") || (result[2] === "to")) { + if ((pct >= parseInt(result[1])) && (pct <= parseInt(result[3]))) { + test = true; } + } + } - if (!isNaN(parseFloat(f)) && isFinite(f)) { - if (parseInt(f) === pct) { - test = true; - } + result = f.match(/(=)?\s?(\d+)\s?(=)?/i); + if (result) { + if ((result[1] === "=") || (result[3] === "=")) { + if (parseInt(result[2]) === pct) { + test = true; } } - return test; } - }, - 'columnSelector_mediaquery': false - }; - } else { - return { - 'filter_columnFilters': false - }; - } - }()), + + if (!isNaN(parseFloat(f)) && isFinite(f)) { + if (parseInt(f) === pct) { + test = true; + } + } + } + return test; + } + }, + 'columnSelector_mediaquery': false + }, sortStable: true, sortAppend: [[2,0]] }); - if ($("#showListTableShows").find("tbody").find("tr").size() > 0){ - $.tablesorter.filter.bindSearch( "#showListTableShows", $('.search') ); - } - - if(metaToBool('sickbeard.ANIME_SPLIT_HOME')){ - if($("#showListTableAnime").find("tbody").find("tr").size() > 0){ - $.tablesorter.filter.bindSearch( "#showListTableAnime", $('.search') ); - } - } - + // @TODO we need to check if doing a $('') with a comma would be quicker than a seperate + // isotope function for each as it does here $.each([$('#container'), $('#container-anime')], function (){ this.isotope({ - itemSelector: '.show', + itemSelector: '.show-container', sortBy : getMeta('sickbeard.POSTER_SORTBY'), sortAscending: getMeta('sickbeard.POSTER_SORTDIR'), layoutMode: 'masonry', @@ -2050,7 +2025,7 @@ var SICKRAGE = { }); // show/hide different types of rows when the checkboxes are changed - $("#checkboxControls input").change(function () { + $("#checkboxControls input").on('change', function () { var whichClass = $(this).attr('id'); $(this).showHideRows(whichClass); }); @@ -2110,7 +2085,7 @@ var SICKRAGE = { 'sceneSeason': sceneSeason, 'sceneEpisode': sceneEpisode }, function(data) { - // Set the values we get back + // Set the values we get back if (data.sceneSeason === null || data.sceneEpisode === null) { $('#sceneSeasonXEpisode_' + showId + '_' + forSeason + '_' + forEpisode).val(''); } else { @@ -2140,7 +2115,7 @@ var SICKRAGE = { 'sceneAbsolute': sceneAbsolute }, function(data) { - // Set the values we get back + // Set the values we get back if (data.sceneAbsolute === null) { $('#sceneAbsolute_' + showId + '_' + forAbsolute).val(''); } else { @@ -2157,7 +2132,7 @@ var SICKRAGE = { } $('.sceneSeasonXEpisode').on('change', function() { - // Strip non-numeric characters + // Strip non-numeric characters $(this).val($(this).val().replace(/[^0-9xX]*/g, '')); var forSeason = $(this).attr('data-for-season'); var forEpisode = $(this).attr('data-for-episode'); @@ -2171,7 +2146,7 @@ var SICKRAGE = { }); $('.sceneAbsolute').on('change', function() { - // Strip non-numeric characters + // Strip non-numeric characters $(this).val($(this).val().replace(/[^0-9xX]*/g, '')); var forAbsolute = $(this).attr('data-for-absolute'); @@ -2192,7 +2167,9 @@ var SICKRAGE = { }); }); $.fn.generateStars = function() { - return this.each(function(i,e){$(e).html($('<span/>').width($(e).text()*12));}); + return this.each(function(i,e){ + $(e).html($('<span/>').width($(e).text()*12)); + }); }; $('.imdbstars').generateStars(); @@ -2449,7 +2426,7 @@ var SICKRAGE = { if (!clicked) { $.getJSON(srRoot + '/manage/showSubtitleMissed', { 'indexer_id': indexerId, - whichSubs: $('#selectSubLang').val() + 'whichSubs': $('#selectSubLang').val() }, function(data) { $.each(data, function(season, eps) { $.each(eps, function(episode, data) { @@ -2470,6 +2447,8 @@ var SICKRAGE = { } }); + // @TODO these two should be able to be merged by using a generic class for the selector + // selects all visible episode checkboxes. $('.selectAllShows').on('click', function(){ $('.allCheck').each(function(){ @@ -2590,21 +2569,12 @@ var SICKRAGE = { 8: { sorter: false }, 9: { sorter: false } }, - widgetOptions: (function() { - if (metaToBool('sickbeard.FILTER_ROW')) { - return { - 'filter_columnFilters': true, - 'filter_hideFilters': true, - 'filter_saveFilters': true, - 'columnSelector_mediaquery': false - }; - } else { - return { - 'filter_columnFilters': false, - 'columnSelector_mediaquery': false - }; - } - }()) + widgetOptions: { + 'filter_columnFilters': true, + 'filter_hideFilters': true, + 'filter_saveFilters': true, + 'columnSelector_mediaquery': false + } }); $('#srRoot').ajaxEpSearch(); diff --git a/gui/slick/js/core.min.js b/gui/slick/js/core.min.js index 93decfd76f1258eb1f4a317e1d6679e7406c6869..ec512de8b35ca25d9fc76f1969820616877f9338 100644 Binary files a/gui/slick/js/core.min.js and b/gui/slick/js/core.min.js differ diff --git a/gui/slick/js/vender.min.js b/gui/slick/js/vender.min.js index b7dc4f9eb8b4ddfc8589f789c0bf2878517c747c..9167a17446d294abb22ed0d82b51ddf6341d7d2a 100644 Binary files a/gui/slick/js/vender.min.js and b/gui/slick/js/vender.min.js differ diff --git a/gui/slick/views/apiBuilder.mako b/gui/slick/views/apiBuilder.mako index c174c1b588ae715d579acc5ca5e3d82d4ebfbbd6..a1aa2b7ae2f6c80dec14c4ddaa3ccb79fe8a34e0 100644 --- a/gui/slick/views/apiBuilder.mako +++ b/gui/slick/views/apiBuilder.mako @@ -34,7 +34,6 @@ <meta data-var="sickbeard.COMING_EPS_LAYOUT" data-content="${sickbeard.COMING_EPS_LAYOUT}"> <meta data-var="sickbeard.COMING_EPS_SORT" data-content="${sickbeard.COMING_EPS_SORT}"> <meta data-var="sickbeard.DATE_PRESET" data-content="${sickbeard.DATE_PRESET}"> - <meta data-var="sickbeard.FILTER_ROW" data-content="${sickbeard.FILTER_ROW}"> <meta data-var="sickbeard.FUZZY_DATING" data-content="${sickbeard.FUZZY_DATING}"> <meta data-var="sickbeard.HISTORY_LAYOUT" data-content="${sickbeard.HISTORY_LAYOUT}"> <meta data-var="sickbeard.HOME_LAYOUT" data-content="${sickbeard.HOME_LAYOUT}"> diff --git a/gui/slick/views/config_general.mako b/gui/slick/views/config_general.mako index 0d74ea107e9cfd7cec3aaa5c07175684e1ce04b0..d0f4f429b2561ed9432430f5bf201600fa2790fc 100644 --- a/gui/slick/views/config_general.mako +++ b/gui/slick/views/config_general.mako @@ -271,18 +271,6 @@ </span> </label> </div> - <div class="field-pair"> - <label for="filter_row"> - <span class="component-title">Filter Row</span> - <span class="component-desc"> - <input type="checkbox" name="filter_row" id="filter_row" ${('', 'checked="checked"')[bool(sickbeard.FILTER_ROW)]}/> - <p>Add a filter row to the show display on the home page</p> - <p>Supports =, >, >=, <=, <, xx to yy , xx - yy</p> - <p><b>Note:</b> =, >, >=, <=, < should be first, followed by a space, then the value.</p> - <p>Examples: '> 90', '= 100', '0 to 99'</p> - </span> - </label> - </div> <div class="field-pair"> <label for="coming_eps_missed_range"> <span class="component-title">Missed episodes range</span> diff --git a/gui/slick/views/config_notifications.mako b/gui/slick/views/config_notifications.mako index fa45a445132e4b0851360a3771934fc4878eb003..69f41f94d3ea1f41285f0afa1ca61469978525ea 100644 --- a/gui/slick/views/config_notifications.mako +++ b/gui/slick/views/config_notifications.mako @@ -409,7 +409,7 @@ <div class="field-pair"> <label for="nmj_database"> <span class="component-title">NMJ database</span> - <input type="text" name="nmj_database" id="nmj_database" value="${sickbeard.NMJ_DATABASE}" class="form-control input-sm input250" ${(' readonly="readonly"', '')[sickbeard.NMJ_DATABASE == True]}/> + <input type="text" name="nmj_database" id="nmj_database" value="${sickbeard.NMJ_DATABASE}" class="form-control input-sm input250" ${(' readonly="readonly"', '')[sickbeard.NMJ_DATABASE is True]}/> </label> <label> <span class="component-title"> </span> @@ -419,7 +419,7 @@ <div class="field-pair"> <label for="nmj_mount"> <span class="component-title">NMJ mount url</span> - <input type="text" name="nmj_mount" id="nmj_mount" value="${sickbeard.NMJ_MOUNT}" class="form-control input-sm input250" ${(' readonly="readonly"', '')[sickbeard.NMJ_MOUNT == True]}/> + <input type="text" name="nmj_mount" id="nmj_mount" value="${sickbeard.NMJ_MOUNT}" class="form-control input-sm input250" ${(' readonly="readonly"', '')[sickbeard.NMJ_MOUNT is True]}/> </label> <label> <span class="component-title"> </span> @@ -506,7 +506,7 @@ <div class="field-pair"> <label for="nmjv2_database"> <span class="component-title">NMJv2 database</span> - <input type="text" name="nmjv2_database" id="nmjv2_database" value="${sickbeard.NMJv2_DATABASE}" class="form-control input-sm input250" ${(' readonly="readonly"', '')[sickbeard.NMJv2_DATABASE == True]}/> + <input type="text" name="nmjv2_database" id="nmjv2_database" value="${sickbeard.NMJv2_DATABASE}" class="form-control input-sm input250" ${(' readonly="readonly"', '')[sickbeard.NMJv2_DATABASE is True]}/> </label> <label> <span class="component-title"> </span> @@ -793,14 +793,46 @@ </span> </label> </div> + <div class="field-pair"> + <label for="prowl_message_title"> + <span class="component-title">Prowl Message Title:</span> + <input type="text" name="prowl_message_title" id="prowl_message_title" value="${sickbeard.PROWL_MESSAGE_TITLE}" class="form-control input-sm input250" /> + </label> + </div> <div class="field-pair"> <label for="prowl_api"> - <span class="component-title">Prowl API key:</span> + <span class="component-title">Global Prowl API key(s):</span> <input type="text" name="prowl_api" id="prowl_api" value="${sickbeard.PROWL_API}" class="form-control input-sm input250" /> </label> <label> <span class="component-title"> </span> - <span class="component-desc">get your key at: <a href="${anon_url('https://www.prowlapp.com/api_settings.php')}" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">https://www.prowlapp.com/api_settings.php</a></span> + <span class="component-desc">Prowl API(s) listed here, separated by commas if applicable, will<br /> receive notifications for <b>all</b> shows. + Your Prowl API key is available at: + <a href="${anon_url('https://www.prowlapp.com/api_settings.php')}" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"> + https://www.prowlapp.com/api_settings.php</a><br /> + (This field may be blank except when testing.)</span> + </label> + </div> + <div class="field-pair"> + <label for="prowl_show"> + <span class="component-title">Show notification list</span> + <select name="prowl_show" id="prowl_show" class="form-control input-sm"> + <option value="-1">-- Select a Show --</option> + </select> + </label> + <label> + <span class="component-title"> </span> + <input type="text" name="prowl_show_list" id="prowl_show_list" class="form-control input-sm input350" /> + </label> + <label> + <span class="component-title"> </span> + <span class="component-desc">Configure per-show notifications here by entering Prowl API key(s), separated by commas, + after selecting a show in the drop-down box. Be sure to activate the 'Save for this show' + button below after each entry.</span> + </label> + <label> + <span class="component-title"> </span> + <input id="prowl_show_save" class="btn" type="button" value="Save for this show" /> </label> </div> <div class="field-pair"> @@ -1114,7 +1146,7 @@ </label> <label> <span class="component-title"> </span> - <span class="component-desc">(multiple keys must be seperated by commas, up to a maximum of 5)</span> + <span class="component-desc">(multiple keys must be separated by commas, up to a maximum of 5)</span> </label> </div> <div class="field-pair"> @@ -1560,7 +1592,7 @@ </label> <label> <span class="component-title"> </span> - <span class="component-desc">method in which to download episodes for new show's.</span> + <span class="component-desc">method in which to download episodes for new shows.</span> </label> </div> <div class="field-pair"> @@ -1595,7 +1627,7 @@ <span class="component-title">Start paused</span> <span class="component-desc"> <input type="checkbox" name="trakt_start_paused" id="trakt_start_paused" ${('', 'checked="checked"')[bool(sickbeard.TRAKT_START_PAUSED)]}/> - <p>show's grabbed from your trakt watchlist start paused.</p> + <p>shows grabbed from your trakt watchlist start paused.</p> </span> </label> </div> @@ -1728,7 +1760,8 @@ </label> <label> <span class="component-title"> </span> - <span class="component-desc">all emails here receive notifications for <b>all</b> shows.</span> + <span class="component-desc">Email addresses listed here, separated by commas if applicable, will<br /> receive notifications for <b>all</b> shows.<br /> + (This field may be blank except when testing.)</span> </label> </div> <div class="field-pair"> @@ -1744,7 +1777,9 @@ </label> <label> <span class="component-title"> </span> - <span class="component-desc">configure per show notifications here.</span> + <span class="component-desc">Configure per-show notifications here by entering email address(es), separated by commas, + after selecting a show in the drop-down box. Be sure to activate the 'Save for this show' + button below after each entry.</span> </label> <label> <span class="component-title"> </span> diff --git a/gui/slick/views/config_postProcessing.mako b/gui/slick/views/config_postProcessing.mako index cb202f50dcabc8b674a29b9b7851f9e19c31f463..51ce0a4c1fd014150e86ef64a76dac7cf40c1223 100644 --- a/gui/slick/views/config_postProcessing.mako +++ b/gui/slick/views/config_postProcessing.mako @@ -1,6 +1,7 @@ <%inherit file="/layouts/main.mako"/> <%! import os.path + import datetime import sickbeard from sickbeard.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from sickbeard.common import Quality, qualityPresets, statusStrings, qualityPresetStrings, cpu_presets, multiEpStrings @@ -378,6 +379,21 @@ <td>%E_N</td> <td>Episode_Name</td> </tr> + <tr> + <td class="align-right"><b>Air Date:</b></td> + <td>%M</td> + <td>${datetime.date.today().month}</td> + </tr> + <tr class="even"> + <td> </td> + <td>%D</td> + <td>${datetime.date.today().day}</td> + </tr> + <tr> + <td> </td> + <td>%Y</td> + <td>${datetime.date.today().year}</td> + </tr> <tr> <td class="align-right"><b>Quality:</b></td> <td>%QN</td> diff --git a/gui/slick/views/config_providers.mako b/gui/slick/views/config_providers.mako index ca7ccfbb831e6373d72dcaac255ed00a728be03f..40808db9b0d223fc52864c26f4467b9b0cfc0a23 100644 --- a/gui/slick/views/config_providers.mako +++ b/gui/slick/views/config_providers.mako @@ -76,10 +76,14 @@ $('#config-components').tabs(); continue curName = curProvider.getID() + if hasattr(curProvider, 'custom_url'): + curURL = curProvider.custom_url or curProvider.url + else: + curURL = curProvider.url %> <li class="ui-state-default ${('nzb-provider', 'torrent-provider')[bool(curProvider.providerType == "torrent")]}" id="${curName}"> - <input type="checkbox" id="enable_${curName}" class="provider_enabler" ${('', 'checked="checked"')[curProvider.isEnabled() == True]}/> - <a href="${anon_url(curProvider.url)}" class="imgLink" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"><img src="${srRoot}/images/providers/${curProvider.imageName()}" alt="${curProvider.name}" title="${curProvider.name}" width="16" height="16" style="vertical-align:middle;"/></a> + <input type="checkbox" id="enable_${curName}" class="provider_enabler" ${('', 'checked="checked"')[curProvider.isEnabled() is True]}/> + <a href="${anon_url(curURL)}" class="imgLink" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"><img src="${srRoot}/images/providers/${curProvider.imageName()}" alt="${curProvider.name}" title="${curProvider.name}" width="16" height="16" style="vertical-align:middle;"/></a> <span style="vertical-align:middle;">${curProvider.name}</span> ${('*', '')[bool(curProvider.supportsBacklog)]} <span class="ui-icon ui-icon-arrowthick-2-n-s pull-right" style="vertical-align:middle;"></span> @@ -301,6 +305,24 @@ $('#config-components').tabs(); % for curTorrentProvider in [curProvider for curProvider in sickbeard.providers.sortedProviderList() if curProvider.providerType == GenericProvider.TORRENT]: <div class="providerDiv" id="${curTorrentProvider.getID()}Div"> + + % if hasattr(curTorrentProvider, 'custom_url'): + <div class="field-pair"> + <label for="${curTorrentProvider.getID()}_custom_url"> + <span class="component-title">Custom URL:</span> + <span class="component-desc"> + <input type="text" name="${curTorrentProvider.getID()}_custom_url" id="${curTorrentProvider.getID()}_custom_url" value="${curTorrentProvider.custom_url}" class="form-control input-sm input350" /> + </span> + </label> + <label> + <span class="component-title"> </span> + <span class="component-desc"> + <p>The URL should include the protocol (and port if applicable). Examples: http://192.168.1.4/ or http://localhost:3000/</p> + </span> + </label> + </div> + % endif + % if hasattr(curTorrentProvider, 'api_key'): <div class="field-pair"> <label for="${curTorrentProvider.getID()}_api_key"> diff --git a/gui/slick/views/config_search.mako b/gui/slick/views/config_search.mako index 8beab44b50b58c03bfb4e521465188ef95c2a202..61e3054dfd68e15001d3933a3efe8f28235f4c52 100644 --- a/gui/slick/views/config_search.mako +++ b/gui/slick/views/config_search.mako @@ -318,7 +318,7 @@ </label> </div> - % if sickbeard.ALLOW_HIGH_PRIORITY == True: + % if sickbeard.ALLOW_HIGH_PRIORITY is True: <div class="field-pair"> <label for="sab_forced"> <span class="component-title">Use forced priority</span> diff --git a/gui/slick/views/config_subtitles.mako b/gui/slick/views/config_subtitles.mako index 63fd4faf61aa43b7d2d8c9c60455e5fef26a6806..b84bfc6b946110f26ffeba8f490f54a0acbe3bd1 100644 --- a/gui/slick/views/config_subtitles.mako +++ b/gui/slick/views/config_subtitles.mako @@ -121,7 +121,7 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } <div class="field-pair"> <label class="nocheck"> <span class="component-title">Extra Scripts</span> - <input type="text" name="subtitles_extra_scripts" value="<%'|'.join(sickbeard.SUBTITLES_EXTRA_SCRIPTS)%>" class="form-control input-sm input350" /> + <input type="text" name="subtitles_extra_scripts" value="${'|'.join(sickbeard.SUBTITLES_EXTRA_SCRIPTS)}" class="form-control input-sm input350" /> </label> <label class="nocheck"> <span class="component-title"> </span> @@ -162,7 +162,7 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } <ul id="service_order_list"> % for curService in sickbeard.subtitles.sortedServiceList(): <li class="ui-state-default" id="${curService['name']}"> - <input type="checkbox" id="enable_${curService['name']}" class="service_enabler" ${('', 'checked="checked"')[curService['enabled'] == True]}/> + <input type="checkbox" id="enable_${curService['name']}" class="service_enabler" ${('', 'checked="checked"')[curService['enabled'] is True]}/> <a href="${anon_url(curService['url'])}" class="imgLink" target="_new"> <img src="${srRoot}/images/subtitles/${curService['image']}" alt="${curService['url']}" title="${curService['url']}" width="16" height="16" style="vertical-align:middle;"/> </a> @@ -212,6 +212,7 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } <br><input type="submit" class="btn config_submitter" value="Save Changes" /><br> </fieldset> </div><!-- /component-group3 //--> + <br><input type="submit" class="btn config_submitter" value="Save Changes" /><br> </div><!-- /config-components //--> </form> diff --git a/gui/slick/views/displayShow.mako b/gui/slick/views/displayShow.mako index b8da99c0f5e6a59ac9ade423dcac79608dac2e51..23fd582196121cf9e83f8f05437af381a446130d 100644 --- a/gui/slick/views/displayShow.mako +++ b/gui/slick/views/displayShow.mako @@ -334,7 +334,7 @@ <tr style="height: 60px;"> <th class="row-seasonheader displayShowTable" colspan="13" style="vertical-align: bottom; width: auto;"> <h3 style="display: inline;"><a name="season-${epResult["season"]}"></a>${("Specials", "Season " + str(epResult["season"]))[int(epResult["season"]) > 0]}</h3> - % if sickbeard.DISPLAY_ALL_SEASONS == False: + % if sickbeard.DISPLAY_ALL_SEASONS is False: <button id="showseason-${epResult['season']}" type="button" class="btn btn-xs pull-right" data-toggle="collapse" data-target="#collapseSeason-${epResult['season']}">Show Episodes</button> <script type="text/javascript"> $(function() { @@ -374,7 +374,7 @@ <tr style="height: 60px;"> <th class="row-seasonheader displayShowTable" colspan="13" style="vertical-align: bottom; width: auto;"> <h3 style="display: inline;"><a name="season-${epResult["season"]}"></a>${("Specials", "Season " + str(epResult["season"]))[bool(int(epResult["season"]))]}</h3> - % if sickbeard.DISPLAY_ALL_SEASONS == False: + % if sickbeard.DISPLAY_ALL_SEASONS is False: <button id="showseason-${epResult['season']}" type="button" class="btn btn-xs pull-right" data-toggle="collapse" data-target="#collapseSeason-${epResult['season']}">Show Episodes</button> <script type="text/javascript"> $(function() { @@ -410,7 +410,7 @@ </tr> % endif </tbody> - % if sickbeard.DISPLAY_ALL_SEASONS == False: + % if sickbeard.DISPLAY_ALL_SEASONS is False: <tbody class="collapse${("", " in")[curSeason == -1]}" id="collapseSeason-${epResult['season']}"> % else: <tbody> @@ -428,7 +428,7 @@ <td align="center"> <% text = str(epResult['episode']) - if epLoc != '' and epLoc != None: + if epLoc != '' and epLoc is not None: text = '<span title="' + epLoc + '" class="addQTip">' + text + "</span>" %> ${text} @@ -459,7 +459,7 @@ style="padding: 0; text-align: center; max-width: 60px;" /> </td> <td class="col-name"> - % if epResult["description"] != "" and epResult["description"] != None: + % if epResult["description"] != "" and epResult["description"] is not None: <img src="${srRoot}/images/info32.png" width="16" height="16" class="plotInfo" alt="" id="plot_info_${str(show.indexerid)}_${str(epResult["season"])}_${str(epResult["episode"])}" /> % else: <img src="${srRoot}/images/info32.png" width="16" height="16" class="plotInfoNone" alt="" /> diff --git a/gui/slick/views/editShow.mako b/gui/slick/views/editShow.mako index b1901f664591d2fde71c109595223dd1e0fd51cd..299168570c6e184e613eb6e108dfe120d375dc2f 100644 --- a/gui/slick/views/editShow.mako +++ b/gui/slick/views/editShow.mako @@ -106,7 +106,7 @@ <label for="subtitles"> <span class="component-title">Subtitles</span> <span class="component-desc"> - <input type="checkbox" id="subtitles" name="subtitles" ${('', 'checked="checked"')[show.subtitles == 1 and sickbeard.USE_SUBTITLES == True]} ${('disabled="disabled"', '')[bool(sickbeard.USE_SUBTITLES)]}/> search for subtitles + <input type="checkbox" id="subtitles" name="subtitles" ${('', 'checked="checked"')[show.subtitles == 1 and sickbeard.USE_SUBTITLES is True]} ${('disabled="disabled"', '')[bool(sickbeard.USE_SUBTITLES)]}/> search for subtitles </span> </label> </div> diff --git a/gui/slick/views/history.mako b/gui/slick/views/history.mako index 8f9de80b3a77af59b13c1a3c22e72b6a34d08403..8895b5b044e84ddc132f883bc49daa0c6fe38929 100644 --- a/gui/slick/views/history.mako +++ b/gui/slick/views/history.mako @@ -87,7 +87,7 @@ % if hItem["provider"] > 0: % if curStatus in [SNATCHED, FAILED]: <% provider = providers.getProviderClass(generic.GenericProvider.makeID(hItem["provider"])) %> - % if provider != None: + % if provider is not None: <img src="${srRoot}/images/providers/${provider.imageName()}" width="16" height="16" style="vertical-align:middle;" /> <span style="vertical-align:middle;">${provider.name}</span> % else: <img src="${srRoot}/images/providers/missing.png" width="16" height="16" style="vertical-align:middle;" title="missing provider"/> <span style="vertical-align:middle;">Missing Provider</span> @@ -142,7 +142,7 @@ <% curStatus, curQuality = Quality.splitCompositeStatus(int(action["action"])) %> % if curStatus in [SNATCHED, FAILED]: <% provider = providers.getProviderClass(generic.GenericProvider.makeID(action["provider"])) %> - % if provider != None: + % if provider is not None: <img src="${srRoot}/images/providers/${provider.imageName()}" width="16" height="16" style="vertical-align:middle;" alt="${provider.name}" style="cursor: help;" title="${provider.name}: ${os.path.basename(action["resource"])}"/> % else: <img src="${srRoot}/images/providers/missing.png" width="16" height="16" style="vertical-align:middle;" alt="missing provider" title="missing provider"/> diff --git a/gui/slick/views/home.mako b/gui/slick/views/home.mako index 9b02a63f37a2bb581488952e6639657a60231760..431137155ad592f82290d61f807952451c1ea06e 100644 --- a/gui/slick/views/home.mako +++ b/gui/slick/views/home.mako @@ -19,24 +19,21 @@ <div id="HomeLayout" class="pull-right hidden-print" style="margin-top: -40px;"> % if sickbeard.HOME_LAYOUT != 'poster': + <span> <button id="popover" type="button" class="btn btn-inline">Select Columns <b class="caret"></b></button> - % endif - <span> Layout: - <select name="layout" class="form-control form-control-inline input-sm" onchange="location = this.options[this.selectedIndex].value;"> - <option value="${srRoot}/setHomeLayout/?layout=poster" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'poster']}>Poster</option> - <option value="${srRoot}/setHomeLayout/?layout=small" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'small']}>Small Poster</option> - <option value="${srRoot}/setHomeLayout/?layout=banner" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'banner']}>Banner</option> - <option value="${srRoot}/setHomeLayout/?layout=simple" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'simple']}>Simple</option> - </select> - % if sickbeard.HOME_LAYOUT != 'poster': - Search: - <input class="search form-control form-control-inline input-sm input200" type="search" data-column="2" placeholder="Search Show Name"> - <button type="button" class="resetsorting btn btn-inline">Reset Search</button> - % endif </span> + + <span> + <button type="button" class="resetsorting btn btn-inline">Clear Filter(s)</button> + </span> + % endif % if sickbeard.HOME_LAYOUT == 'poster': + <span> + <input id="filterShowName" class="form-control form-control-inline input-sm input200" type="search" placeholder="Filter Show Name"> + </span> + <span> Sort By: <select id="postersort" class="form-control form-control-inline input-sm"> <option value="name" data-sort="${srRoot}/setPosterSortBy/?sort=name" ${('', 'selected="selected"')[sickbeard.POSTER_SORTBY == 'name']}>Name</option> @@ -46,15 +43,23 @@ </select> </span> - <span> Sort Order: + <span> Direction: <select id="postersortdirection" class="form-control form-control-inline input-sm"> - <option value="true" data-sort="${srRoot}/setPosterSortDir/?direction=1" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 1]}>Asc</option> - <option value="false" data-sort="${srRoot}/setPosterSortDir/?direction=0" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 0]}>Desc</option> + <option value="true" data-sort="${srRoot}/setPosterSortDir/?direction=1" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 1]}>A ➜ Z</option> + <option value="false" data-sort="${srRoot}/setPosterSortDir/?direction=0" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 0]}>Z ➜ A</option> </select> </span> - - % endif + + + <span> Layout: + <select name="layout" class="form-control form-control-inline input-sm" onchange="location = this.options[this.selectedIndex].value;"> + <option value="${srRoot}/setHomeLayout/?layout=poster" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'poster']}>Poster</option> + <option value="${srRoot}/setHomeLayout/?layout=small" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'small']}>Small Poster</option> + <option value="${srRoot}/setHomeLayout/?layout=banner" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'banner']}>Banner</option> + <option value="${srRoot}/setHomeLayout/?layout=simple" ${('', 'selected="selected"')[sickbeard.HOME_LAYOUT == 'simple']}>Simple</option> + </select> + </span> </div> % for curShowlist in showlists: @@ -67,8 +72,8 @@ <div id="${('container', 'container-anime')[curListType == 'Anime' and sickbeard.HOME_LAYOUT == 'poster']}" class="clearfix"> <div class="posterview"> % for curLoadingShow in sickbeard.showQueueScheduler.action.loadingShowList: - % if curLoadingShow.show == None: - <div class="show" data-name="0" data-date="010101" data-network="0" data-progress="101"> + % if curLoadingShow.show is None: + <div class="show-container" data-name="0" data-date="010101" data-network="0" data-progress="101"> <img alt="" title="${curLoadingShow.show_name}" class="show-image" style="border-bottom: 1px solid #111;" src="${srRoot}/images/poster.png" /> <div class="show-details"> <div class="show-add">Loading... (${curLoadingShow.show_name})</div> @@ -141,7 +146,7 @@ elif 'nded' in display_status: data_date = '5000000100.0' %> - <div class="show" id="show${curShow.indexerid}" data-name="${curShow.name}" data-date="${data_date}" data-network="${curShow.network}" data-progress="${progressbar_percent}"> + <div class="show-container" id="show${curShow.indexerid}" data-name="${curShow.name}" data-date="${data_date}" data-network="${curShow.network}" data-progress="${progressbar_percent}"> <div class="show-image"> <a href="${srRoot}/home/displayShow?show=${curShow.indexerid}"><img alt="" class="show-image" src="${srRoot}/showPoster/?show=${curShow.indexerid}&which=poster_thumb" /></a> </div> @@ -244,14 +249,14 @@ <tbody class="tablesorter-infoOnly"> % for curLoadingShow in sickbeard.showQueueScheduler.action.loadingShowList: - % if curLoadingShow.show != None and curLoadingShow.show in sickbeard.showList: + % if curLoadingShow.show is not None and curLoadingShow.show in sickbeard.showList: <% continue %> % endif <tr> <td align="center">(loading)</td> <td></td> <td> - % if curLoadingShow.show == None: + % if curLoadingShow.show is None: <span title="">Loading... (${curLoadingShow.show_name})</span> % else: <a href="displayShow?show=${curLoadingShow.show.indexerid}">${curLoadingShow.show.name}</a> diff --git a/gui/slick/views/home_addShows.mako b/gui/slick/views/home_addShows.mako index 16da9f75c1dac61cf22c99dbb32ae7c387ede771..a42f588421e63b2d443f5d2360babae47b8f3141 100644 --- a/gui/slick/views/home_addShows.mako +++ b/gui/slick/views/home_addShows.mako @@ -21,7 +21,7 @@ </a> <br><br> - % if sickbeard.USE_TRAKT == True: + % if sickbeard.USE_TRAKT is True: <a href="${srRoot}/home/addShows/trendingShows/" id="btnNewShow" class="btn btn-large"> <div class="button"><div class="icon-addtrendingshow"></div></div> <div class="buttontext"> diff --git a/gui/slick/views/inc_defs.mako b/gui/slick/views/inc_defs.mako index eaa54116836d691c2ff9eecc16cf25c501a2f502..f8ff29646e906f4cc13b9d163d4f77cec5711fe6 100644 --- a/gui/slick/views/inc_defs.mako +++ b/gui/slick/views/inc_defs.mako @@ -42,7 +42,7 @@ cssClass = "Custom" qualityString = "Custom" - if overrideClass == None: + if overrideClass is None: cssClass = "quality " + cssClass else: cssClass = overrideClass diff --git a/gui/slick/views/layouts/main.mako b/gui/slick/views/layouts/main.mako index d1bcc84bf59e367f31a8cefa795a3b9d5ad8df53..19fcb9965b463ad3cd2721cec972f892edd7e1da 100644 --- a/gui/slick/views/layouts/main.mako +++ b/gui/slick/views/layouts/main.mako @@ -49,7 +49,6 @@ <meta data-var="sickbeard.COMING_EPS_LAYOUT" data-content="${sickbeard.COMING_EPS_LAYOUT}"> <meta data-var="sickbeard.COMING_EPS_SORT" data-content="${sickbeard.COMING_EPS_SORT}"> <meta data-var="sickbeard.DATE_PRESET" data-content="${sickbeard.DATE_PRESET}"> - <meta data-var="sickbeard.FILTER_ROW" data-content="${sickbeard.FILTER_ROW}"> <meta data-var="sickbeard.FUZZY_DATING" data-content="${sickbeard.FUZZY_DATING}"> <meta data-var="sickbeard.HISTORY_LAYOUT" data-content="${sickbeard.HISTORY_LAYOUT}"> <meta data-var="sickbeard.HOME_LAYOUT" data-content="${sickbeard.HOME_LAYOUT}"> @@ -221,7 +220,7 @@ <li><a href="${srRoot}/home/updateCheck?pid=${sbPID}"><i class="menu-icon-update"></i> Check For Updates</a></li> <li><a href="${srRoot}/home/restart/?pid=${sbPID}" class="confirm restart"><i class="menu-icon-restart"></i> Restart</a></li> <li><a href="${srRoot}/home/shutdown/?pid=${sbPID}" class="confirm shutdown"><i class="menu-icon-shutdown"></i> Shutdown</a></li> - % if srLogin != True: + % if srLogin is not True: <li><a href="${srRoot}/logout" class="confirm logout"><i class="menu-icon-shutdown"></i> Logout</a></li> % endif <li role="separator" class="divider"></li> diff --git a/gui/slick/views/manage_backlogOverview.mako b/gui/slick/views/manage_backlogOverview.mako index 637c9d1143f8c681b1f8242e1d685a78bc0e9fdd..449418457d9ecb1f435f3f0cd25912fc4d3894a7 100644 --- a/gui/slick/views/manage_backlogOverview.mako +++ b/gui/slick/views/manage_backlogOverview.mako @@ -19,13 +19,14 @@ <h1 class="title">${title}</h1> % endif -<% totalWanted = 0 %> -<% totalQual = 0 %> - -% for curShow in sickbeard.showList: - <% totalWanted = totalWanted + showCounts[curShow.indexerid][Overview.WANTED] %> - <% totalQual = totalQual + showCounts[curShow.indexerid][Overview.QUAL] %> -% endfor +<% + totalWanted = 0 + totalQual = 0 + backLogShows = sorted([x for x in sickbeard.showList if showCounts[x.indexerid][Overview.QUAL] + showCounts[x.indexerid][Overview.WANTED]], key=lambda x: x.name) + for curShow in backLogShows: + totalWanted += showCounts[curShow.indexerid][Overview.WANTED] + totalQual += showCounts[curShow.indexerid][Overview.QUAL] +%> <div class="h2footer pull-right"> <span class="listing-key wanted">Wanted: <b>${totalWanted}</b></span> @@ -35,20 +36,14 @@ <div class="float-left"> Jump to Show <select id="pickShow" class="form-control form-control-inline input-sm"> - % for curShow in sorted(sickbeard.showList, key=lambda x: x.name): - % if showCounts[curShow.indexerid][Overview.QUAL] + showCounts[curShow.indexerid][Overview.WANTED] != 0: + % for curShow in backLogShows: <option value="${curShow.indexerid}">${curShow.name}</option> - % endif % endfor </select> </div> <table class="sickbeardTable" cellspacing="0" border="0" cellpadding="0"> -% for curShow in sorted(sickbeard.showList, key=lambda x: x.name): - - % if showCounts[curShow.indexerid][Overview.QUAL] + showCounts[curShow.indexerid][Overview.WANTED] == 0: - <% continue %> - % endif +% for curShow in backLogShows: <tr class="seasonheader" id="show-${curShow.indexerid}"> <td colspan="3" class="align-left"> <br><h2><a href="${srRoot}/home/displayShow?show=${curShow.indexerid}">${curShow.name}</a></h2> @@ -63,17 +58,11 @@ Jump to Show <tr class="seasoncols"><th>Episode</th><th>Name</th><th class="nowrap">Airdate</th></tr> % for curResult in showSQLResults[curShow.indexerid]: - <% whichStr = str(curResult['season']) + 'x' + str(curResult['episode']) %> - % try: - <% overview = showCats[curShow.indexerid][whichStr] %> - % except Exception: - <% continue %> - % endtry - - % if overview not in (Overview.QUAL, Overview.WANTED): - <% continue %> - % endif - + <% + whichStr = 'S%02dE%02d' % (curResult['season'], curResult['episode']) + if whichStr not in showCats[curShow.indexerid] or showCats[curShow.indexerid][whichStr] not in (Overview.QUAL, Overview.WANTED): + continue + %> <tr class="seasonstyle ${Overview.overviewStrings[showCats[curShow.indexerid][whichStr]]}"> <td class="tableleft" align="center">${whichStr}</td> <td class="tableright" align="center" class="nowrap"> @@ -81,7 +70,7 @@ Jump to Show </td> <td> <% airDate = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(curResult['airdate'], curShow.airs, curShow.network)) %> - % if int(curResult['airdate']) != 1: + % if int(curResult['airdate']) > 1: <time datetime="${airDate.isoformat('T')}" class="date">${sbdatetime.sbdatetime.sbfdatetime(airDate)}</time> % else: Never @@ -90,7 +79,6 @@ Jump to Show </tr> % endfor % endfor - </table> </div> </%block> diff --git a/gui/slick/views/manage_failedDownloads.mako b/gui/slick/views/manage_failedDownloads.mako index bac45ec04062b86ec34972a6f6ae6754fe47775e..920e04f8a3054b4f2b94905664d58b7079af0821 100644 --- a/gui/slick/views/manage_failedDownloads.mako +++ b/gui/slick/views/manage_failedDownloads.mako @@ -54,7 +54,7 @@ </td> <td align="center"> <% provider = providers.getProviderClass(generic.GenericProvider.makeID(hItem["provider"])) %> - % if provider != None: + % if provider is not None: <img src="${srRoot}/images/providers/${provider.imageName()}" width="16" height="16" alt="${provider.name}" title="${provider.name}"/> % else: <img src="${srRoot}/images/providers/missing.png" width="16" height="16" alt="missing provider" title="missing provider"/> diff --git a/gui/slick/views/manage_manageSearches.mako b/gui/slick/views/manage_manageSearches.mako index e3caf400a321675cff1cb5f4b58377af69d2cd8d..3a2ee26f97c85aea44892082c6521d4ae3a77477 100644 --- a/gui/slick/views/manage_manageSearches.mako +++ b/gui/slick/views/manage_manageSearches.mako @@ -44,6 +44,17 @@ ${('Not in progress', 'In Progress')[dailySearchStatus]}<br> % endif <br> +<h3>Subtitle Search:</h3> +<a class="btn ${('disabled', '')[bool(sickbeard.USE_SUBTITLES)]}" href="${srRoot}/manage/manageSearches/forceSubtitlesFinder"><i class="icon-exclamation-sign"></i> Force</a> +% if not sickbeard.USE_SUBTITLES: + Subtitle search disabled <br> +% elif not subtitlesFinderStatus: + Not in progress<br> +% else: + In Progress<br> +% endif +<br> + <h3>Search Queue:</h3> Backlog: <i>${queueLength['backlog']} pending items</i></br> Daily: <i>${queueLength['daily']} pending items</i></br> diff --git a/gui/slick/views/manage_massEdit.mako b/gui/slick/views/manage_massEdit.mako index 7d44bb7c46aeb6a570269ba9256007ffd17a8a80..0892016a97e75f1e78b853719456eb3d2fefc9cf 100644 --- a/gui/slick/views/manage_massEdit.mako +++ b/gui/slick/views/manage_massEdit.mako @@ -9,7 +9,7 @@ <%block name="scripts"> <% - if quality_value != None: + if quality_value is not None: initial_quality = int(quality_value) else: initial_quality = common.SD @@ -90,7 +90,7 @@ <span class="component-title">Preferred Quality</span> <span class="component-desc"> <% - if quality_value != None: + if quality_value is not None: initial_quality = int(quality_value) else: initial_quality = common.SD @@ -101,7 +101,7 @@ <select id="qualityPreset" name="quality_preset" class="form-control form-control-inline input-sm"> <option value="keep">< Keep ></option> <% selected = None %> - <option value="0" ${('', 'selected="selected"')[quality_value != None and quality_value not in common.qualityPresets]}>Custom</option> + <option value="0" ${('', 'selected="selected"')[quality_value is not None and quality_value not in common.qualityPresets]}>Custom</option> % for curPreset in sorted(common.qualityPresets): <option value="${curPreset}" ${('', 'selected="selected"')[quality_value == curPreset]}>${common.qualityPresetStrings[curPreset]}</option> % endfor @@ -137,9 +137,9 @@ <span class="component-title">Archive on first match</span> <span class="component-desc"> <select id="edit_archive_firstmatch" name="archive_firstmatch" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[archive_firstmatch_value == None]}>< Keep ></option> - <option value="enable" ${('', 'selected="selected"')[archive_firstmatch_value == 1]}>Yes</option> - <option value="disable" ${('', 'selected="selected"')[archive_firstmatch_value == 0]}>No</option> + <option value="keep" ${('', 'selected="selected"')[archive_firstmatch_value is None]}>< Keep ></option> + <option value="enable" ${('', 'selected="selected"')[archive_firstmatch_value is 1]}>Yes</option> + <option value="disable" ${('', 'selected="selected"')[archive_firstmatch_value is 0]}>No</option> </select><br> Archive episode after the first best match is found from your archive quality list. </span> @@ -151,7 +151,7 @@ <span class="component-title">Season folders (<span class="separator">*</span>)</span> <span class="component-desc"> <select id="" name="flatten_folders" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[flatten_folders_value == None]}>< Keep ></option> + <option value="keep" ${('', 'selected="selected"')[flatten_folders_value is None]}>< Keep ></option> <option value="enable" ${('', 'selected="selected"')[flatten_folders_value == 0]}>Yes</option> <option value="disable" ${('', 'selected="selected"')[flatten_folders_value == 1]}>No</option> </select><br> @@ -165,7 +165,7 @@ <span class="component-title">Paused</span> <span class="component-desc"> <select id="edit_paused" name="paused" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[paused_value == None]}>< Keep ></option> + <option value="keep" ${('', 'selected="selected"')[paused_value is None]}>< Keep ></option> <option value="enable" ${('', 'selected="selected"')[paused_value == 1]}>Yes</option> <option value="disable" ${('', 'selected="selected"')[paused_value == 0]}>No</option> </select><br/ > @@ -194,7 +194,7 @@ <span class="component-title">Scene Numbering</span> <span class="component-desc"> <select id="edit_scene" name="scene" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[scene_value == None]}>< Keep ></option> + <option value="keep" ${('', 'selected="selected"')[scene_value is None]}>< Keep ></option> <option value="enable" ${('', 'selected="selected"')[scene_value == 1]}>Yes</option> <option value="disable" ${('', 'selected="selected"')[scene_value == 0]}>No</option> </select><br> @@ -208,7 +208,7 @@ <span class="component-title">Anime</span> <span class="component-desc"> <select id="edit_anime" name="anime" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[anime_value == None]}>< Keep ></option> + <option value="keep" ${('', 'selected="selected"')[anime_value is None]}>< Keep ></option> <option value="enable" ${('', 'selected="selected"')[anime_value == 1]}>Yes</option> <option value="disable" ${('', 'selected="selected"')[anime_value == 0]}>No</option> </select><br> @@ -222,7 +222,7 @@ <span class="component-title">Sports</span> <span class="component-desc"> <select id="edit_sports" name="sports" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[sports_value == None]}>< Keep ></option> + <option value="keep" ${('', 'selected="selected"')[sports_value is None]}>< Keep ></option> <option value="enable" ${('', 'selected="selected"')[sports_value == 1]}>Yes</option> <option value="disable" ${('', 'selected="selected"')[sports_value == 0]}>No</option> </select><br> @@ -237,7 +237,7 @@ <span class="component-title">Air by date</span> <span class="component-desc"> <select id="edit_air_by_date" name="air_by_date" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[air_by_date_value == None]}>< Keep ></option> + <option value="keep" ${('', 'selected="selected"')[air_by_date_value is None]}>< Keep ></option> <option value="enable" ${('', 'selected="selected"')[air_by_date_value == 1]}>Yes</option> <option value="disable" ${('', 'selected="selected"')[air_by_date_value == 0]}>No</option> </select><br> @@ -252,7 +252,7 @@ <span class="component-title">Subtitles</span> <span class="component-desc"> <select id="edit_subtitles" name="subtitles" class="form-control form-control-inline input-sm"> - <option value="keep" ${('', 'selected="selected"')[subtitles_value == None]}>< Keep ></option> + <option value="keep" ${('', 'selected="selected"')[subtitles_value is None]}>< Keep ></option> <option value="enable" ${('', 'selected="selected"')[subtitles_value == 1]}>Yes</option> <option value="disable" ${('', 'selected="selected"')[subtitles_value == 0]}>No</option> </select><br> diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 765cb7626f51adc6a439087e0f43185bb0fce420..0ddd3a8a710040211449176321a59bc7fbd19229 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -376,6 +376,7 @@ PROWL_NOTIFY_ONDOWNLOAD = False PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = False PROWL_API = None PROWL_PRIORITY = 0 +PROWL_MESSAGE_TITLE = 'SickRage' USE_TWITTER = False TWITTER_NOTIFY_ONSNATCH = False @@ -508,7 +509,6 @@ TIMEZONE_DISPLAY = None THEME_NAME = None POSTER_SORTBY = None POSTER_SORTDIR = None -FILTER_ROW = True USE_SUBTITLES = False SUBTITLES_LANGUAGES = [] @@ -586,7 +586,7 @@ def initialize(consoleLogging=True): NEWZNAB_DATA, NZBS, NZBS_UID, NZBS_HASH, INDEXER_DEFAULT, INDEXER_TIMEOUT, USENET_RETENTION, TORRENT_DIR, \ QUALITY_DEFAULT, FLATTEN_FOLDERS_DEFAULT, SUBTITLES_DEFAULT, STATUS_DEFAULT, STATUS_DEFAULT_AFTER, \ GROWL_NOTIFY_ONSNATCH, GROWL_NOTIFY_ONDOWNLOAD, GROWL_NOTIFY_ONSUBTITLEDOWNLOAD, TWITTER_NOTIFY_ONSNATCH, TWITTER_NOTIFY_ONDOWNLOAD, TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD, USE_FREEMOBILE, FREEMOBILE_ID, FREEMOBILE_APIKEY, FREEMOBILE_NOTIFY_ONSNATCH, FREEMOBILE_NOTIFY_ONDOWNLOAD, FREEMOBILE_NOTIFY_ONSUBTITLEDOWNLOAD, \ - USE_GROWL, GROWL_HOST, GROWL_PASSWORD, USE_PROWL, PROWL_NOTIFY_ONSNATCH, PROWL_NOTIFY_ONDOWNLOAD, PROWL_NOTIFY_ONSUBTITLEDOWNLOAD, PROWL_API, PROWL_PRIORITY, \ + USE_GROWL, GROWL_HOST, GROWL_PASSWORD, USE_PROWL, PROWL_NOTIFY_ONSNATCH, PROWL_NOTIFY_ONDOWNLOAD, PROWL_NOTIFY_ONSUBTITLEDOWNLOAD, PROWL_API, PROWL_PRIORITY, PROWL_MESSAGE_TITLE, \ USE_PYTIVO, PYTIVO_NOTIFY_ONSNATCH, PYTIVO_NOTIFY_ONDOWNLOAD, PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD, PYTIVO_UPDATE_LIBRARY, PYTIVO_HOST, PYTIVO_SHARE_NAME, PYTIVO_TIVO_NAME, \ USE_NMA, NMA_NOTIFY_ONSNATCH, NMA_NOTIFY_ONDOWNLOAD, NMA_NOTIFY_ONSUBTITLEDOWNLOAD, NMA_API, NMA_PRIORITY, \ USE_PUSHALOT, PUSHALOT_NOTIFY_ONSNATCH, PUSHALOT_NOTIFY_ONDOWNLOAD, PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD, PUSHALOT_AUTHORIZATIONTOKEN, \ @@ -605,7 +605,7 @@ def initialize(consoleLogging=True): USE_EMAIL, EMAIL_HOST, EMAIL_PORT, EMAIL_TLS, EMAIL_USER, EMAIL_PASSWORD, EMAIL_FROM, EMAIL_NOTIFY_ONSNATCH, EMAIL_NOTIFY_ONDOWNLOAD, EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD, EMAIL_LIST, \ USE_LISTVIEW, METADATA_KODI, METADATA_KODI_12PLUS, METADATA_MEDIABROWSER, METADATA_PS3, metadata_provider_dict, \ NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, SYNC_FILES, POSTPONE_IF_SYNC_FILES, POSTPONE_IF_NO_SUBS, dailySearchScheduler, NFO_RENAME, \ - GUI_NAME, HOME_LAYOUT, HISTORY_LAYOUT, DISPLAY_SHOW_SPECIALS, COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, COMING_EPS_MISSED_RANGE, FUZZY_DATING, TRIM_ZERO, DATE_PRESET, TIME_PRESET, TIME_PRESET_W_SECONDS, THEME_NAME, FILTER_ROW, \ + GUI_NAME, HOME_LAYOUT, HISTORY_LAYOUT, DISPLAY_SHOW_SPECIALS, COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, COMING_EPS_MISSED_RANGE, FUZZY_DATING, TRIM_ZERO, DATE_PRESET, TIME_PRESET, TIME_PRESET_W_SECONDS, THEME_NAME, \ POSTER_SORTBY, POSTER_SORTDIR, HISTORY_LIMIT, CREATE_MISSING_SHOW_DIRS, ADD_SHOWS_WO_DIR, \ METADATA_WDTV, METADATA_TIVO, METADATA_MEDE8ER, IGNORE_WORDS, TRACKERS_LIST, IGNORED_SUBS_LIST, REQUIRE_WORDS, CALENDAR_UNPROTECTED, CALENDAR_ICONS, NO_RESTART, \ USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, SUBTITLES_MULTI, EMBEDDED_SUBTITLES_ALL, SUBTITLES_EXTRA_SCRIPTS, subtitlesFinderScheduler, \ @@ -1018,6 +1018,7 @@ def initialize(consoleLogging=True): PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_onsubtitledownload', 0)) PROWL_API = check_setting_str(CFG, 'Prowl', 'prowl_api', '', censor_log=True) PROWL_PRIORITY = check_setting_str(CFG, 'Prowl', 'prowl_priority', "0") + PROWL_MESSAGE_TITLE = check_setting_str(CFG, 'Prowl', 'prowl_message_title', "SickRage") USE_TWITTER = bool(check_setting_int(CFG, 'Twitter', 'use_twitter', 0)) TWITTER_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Twitter', 'twitter_notify_onsnatch', 0)) @@ -1209,7 +1210,6 @@ def initialize(consoleLogging=True): TIMEZONE_DISPLAY = check_setting_str(CFG, 'GUI', 'timezone_display', 'local') POSTER_SORTBY = check_setting_str(CFG, 'GUI', 'poster_sortby', 'name') POSTER_SORTDIR = check_setting_int(CFG, 'GUI', 'poster_sortdir', 1) - FILTER_ROW = bool(check_setting_int(CFG, 'GUI', 'filter_row', 1)) DISPLAY_ALL_SEASONS = bool(check_setting_int(CFG, 'General', 'display_all_seasons', 1)) # initialize NZB and TORRENT providers @@ -1226,6 +1226,9 @@ def initialize(consoleLogging=True): curProvider.providerType == GenericProvider.TORRENT]: curTorrentProvider.enabled = bool(check_setting_int(CFG, curTorrentProvider.getID().upper(), curTorrentProvider.getID(), 0)) + if hasattr(curTorrentProvider, 'custom_url'): + curTorrentProvider.custom_url = check_setting_str(CFG, curTorrentProvider.getID().upper(), + curTorrentProvider.getID() + '_custom_url', '', censor_log=True) if hasattr(curTorrentProvider, 'api_key'): curTorrentProvider.api_key = check_setting_str(CFG, curTorrentProvider.getID().upper(), curTorrentProvider.getID() + '_api_key', '', censor_log=True) @@ -1787,6 +1790,9 @@ def save_config(): curProvider.providerType == GenericProvider.TORRENT]: new_config[curTorrentProvider.getID().upper()] = {} new_config[curTorrentProvider.getID().upper()][curTorrentProvider.getID()] = int(curTorrentProvider.enabled) + if hasattr(curTorrentProvider, 'custom_url'): + new_config[curTorrentProvider.getID().upper()][ + curTorrentProvider.getID() + '_custom_url'] = curTorrentProvider.custom_url if hasattr(curTorrentProvider, 'digest'): new_config[curTorrentProvider.getID().upper()][ curTorrentProvider.getID() + '_digest'] = curTorrentProvider.digest @@ -1980,6 +1986,7 @@ def save_config(): new_config['Prowl']['prowl_notify_onsubtitledownload'] = int(PROWL_NOTIFY_ONSUBTITLEDOWNLOAD) new_config['Prowl']['prowl_api'] = PROWL_API new_config['Prowl']['prowl_priority'] = PROWL_PRIORITY + new_config['Prowl']['prowl_message_title'] = PROWL_MESSAGE_TITLE new_config['Twitter'] = {} new_config['Twitter']['use_twitter'] = int(USE_TWITTER) @@ -2125,7 +2132,6 @@ def save_config(): new_config['GUI']['timezone_display'] = TIMEZONE_DISPLAY new_config['GUI']['poster_sortby'] = POSTER_SORTBY new_config['GUI']['poster_sortdir'] = POSTER_SORTDIR - new_config['GUI']['filter_row'] = int(FILTER_ROW) new_config['Subtitles'] = {} new_config['Subtitles']['use_subtitles'] = int(USE_SUBTITLES) @@ -2182,13 +2188,13 @@ def launchBrowser(protocol='http', startPort=None, web_root='/'): def getEpList(epIDs, showid=None): - if epIDs == None or len(epIDs) == 0: + if epIDs is None or len(epIDs) == 0: return [] query = "SELECT * FROM tv_episodes WHERE indexerid in (%s)" % (",".join(['?'] * len(epIDs)),) params = epIDs - if showid != None: + if showid is not None: query += " AND showid = ?" params.append(showid) diff --git a/sickbeard/common.py b/sickbeard/common.py index b9ae20bb365b84496b67eb17394c2589a65079cd..a23afacfe6f39bf424f9e473c0ab1082204450ee 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -54,24 +54,16 @@ if SPOOF_USER_AGENT: shuffle(user_agents) USER_AGENT = user_agents[0] -mediaExtensions = ['avi', 'mkv', 'mpg', 'mpeg', 'wmv', - 'ogm', 'mp4', 'iso', 'img', 'divx', - 'm2ts', 'm4v', 'ts', 'flv', 'f4v', - 'mov', 'rmvb', 'vob', 'dvr-ms', 'wtv', - 'ogv', '3gp', 'webm', 'tp'] - -subtitleExtensions = ['srt', 'sub', 'ass', 'idx', 'ssa'] - cpu_presets = {'HIGH': 5, 'NORMAL': 2, 'LOW': 1 } -### Other constants +# Other constants MULTI_EP_RESULT = -1 SEASON_RESULT = -2 -### Notification Types +# Notification Types NOTIFY_SNATCH = 1 NOTIFY_DOWNLOAD = 2 NOTIFY_SUBTITLE_DOWNLOAD = 3 @@ -85,7 +77,7 @@ notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD] = "Subtitle Download Finished" notifyStrings[NOTIFY_GIT_UPDATE] = "SickRage Updated" notifyStrings[NOTIFY_GIT_UPDATE_TEXT] = "SickRage Updated To Commit#: " -### Episode statuses +# Episode statuses UNKNOWN = -1 # should never happen UNAIRED = 1 # episodes that haven't aired yet SNATCHED = 2 # qualified with quality @@ -572,6 +564,7 @@ statusStrings = StatusStrings( } ) + # pylint: disable=R0903 class Overview(object): UNAIRED = UNAIRED # 1 @@ -590,11 +583,6 @@ class Overview(object): UNAIRED: "unaired", SNATCHED: "snatched"} - -# Get our xml namespaces correct for lxml -XML_NSMAP = {'xsi': 'http://www.w3.org/2001/XMLSchema-instance', - 'xsd': 'http://www.w3.org/2001/XMLSchema'} - countryList = {'Australia': 'AU', 'Canada': 'CA', 'USA': 'US' diff --git a/sickbeard/config.py b/sickbeard/config.py index 4bc496076ab559335b4dba553cac2b5f6238037d..f6766920a604dbeb0782f927e802cd8c4896fc7f 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -120,7 +120,7 @@ def change_LOG_DIR(log_dir, web_log): else: return False - if sickbeard.WEB_LOG != web_log_value or log_dir_changed == True: + if sickbeard.WEB_LOG != web_log_value or log_dir_changed is True: sickbeard.WEB_LOG = web_log_value return True @@ -282,7 +282,7 @@ def change_VERSION_NOTIFY(version_notify): if not version_notify: sickbeard.NEWEST_VERSION_STRING = None - if oldSetting == False and version_notify == True: + if oldSetting is False and version_notify is True: sickbeard.versionCheckScheduler.forceRun() def change_DOWNLOAD_PROPERS(download_propers): @@ -595,7 +595,8 @@ def check_setting_str(config, cfg_name, item_name, def_val, silent=True, censor_ config[cfg_name][item_name] = helpers.encrypt(my_val, encryption_version) if censor_log or (cfg_name, item_name) in logger.censoredItems.iteritems(): - logger.censoredItems[cfg_name, item_name] = my_val + if not item_name.endswith('custom_url'): + logger.censoredItems[cfg_name, item_name] = my_val if not silent: logger.log(item_name + " -> " + my_val, logger.DEBUG) diff --git a/sickbeard/db.py b/sickbeard/db.py index 95d2add4e8e613c46cdcd142bf77493017ffdfc4..92d5e08189b2a6bcfe2717286c783a42941ed56a 100644 --- a/sickbeard/db.py +++ b/sickbeard/db.py @@ -183,7 +183,7 @@ class DBConnection(object): :param fetchone: Boolean to indicate one result must be fetched (to walk results for instance) :return: query results """ - if query == None: + if query is None: return sqlResult = None @@ -192,7 +192,7 @@ class DBConnection(object): with db_locks[self.filename]: while attempt < 5: try: - if args == None: + if args is None: logger.log(self.filename + ": " + query, logger.DB) else: logger.log(self.filename + ": " + query + " with args " + str(args), logger.DB) @@ -229,7 +229,7 @@ class DBConnection(object): sqlResults = self.action(query, args, fetchall=True) - if sqlResults == None: + if sqlResults is None: return [] return sqlResults @@ -244,7 +244,7 @@ class DBConnection(object): """ sqlResults = self.action(query, args, fetchone=True) - if sqlResults == None: + if sqlResults is None: return [] return sqlResults diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index 035501b454909ae3764c8fc8877007ecdd3e8550..c3a77d1906dae914984d8b7f37cbffe5147cd598 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -51,12 +51,11 @@ from socket import timeout as SocketTimeout from sickbeard import logger, classes from sickbeard.common import USER_AGENT -from sickbeard.common import mediaExtensions -from sickbeard.common import subtitleExtensions from sickbeard import db from sickbeard.notifiers.synoindex import notifier as synoindex_notifier from sickbeard import clients from sickbeard.subtitles import isValidLanguage +from sickrage.helper.common import media_extensions, subtitle_extensions from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex, MultipleShowObjectsException from cachecontrol import CacheControl, caches @@ -105,7 +104,7 @@ def remove_extension(name): if name and "." in name: # pylint: disable=W0612 base_name, sep, extension = name.rpartition('.') # @UnusedVariable - if base_name and extension.lower() in ['nzb', 'torrent'] + mediaExtensions: + if base_name and extension.lower() in ['nzb', 'torrent'] + media_extensions: name = base_name return name @@ -168,7 +167,7 @@ def remove_non_release_groups(name): elif remove_type == 'searchre': _name = re.sub(r'(?i)' + remove_string, '', _name) - return _name.strip('.- []{}') + return _name def replaceExtension(filename, newExt): @@ -244,7 +243,7 @@ def isMediaFile(filename): if re.search('extras?$', sepFile[0], re.I): return False - if sepFile[2].lower() in mediaExtensions: + if sepFile[2].lower() in media_extensions: return True else: return False @@ -649,7 +648,7 @@ def rename_ep_file(cur_path, new_path, old_path_length=0): cur_file_ext = cur_path[old_path_length:] cur_file_name = cur_path[:old_path_length] - if cur_file_ext[1:] in subtitleExtensions: + if cur_file_ext[1:] in subtitle_extensions: # Extract subtitle language from filename sublang = os.path.splitext(cur_file_name)[1][1:] diff --git a/sickbeard/history.py b/sickbeard/history.py index accf006f380ab2a278bf2c277443a123561717f0..e0a4b1f44f7fc841dc967057f1a8503181a49c8c 100644 --- a/sickbeard/history.py +++ b/sickbeard/history.py @@ -62,7 +62,7 @@ def logSnatch(searchResult): version = searchResult.version providerClass = searchResult.provider - if providerClass != None: + if providerClass is not None: provider = providerClass.name else: provider = "unknown" diff --git a/sickbeard/image_cache.py b/sickbeard/image_cache.py index 10c1d2e416d9c50f56a03c43f4ee1a7416fd1ab9..7ab282fee4c344aba20106db3e39662aab38a6fd 100644 --- a/sickbeard/image_cache.py +++ b/sickbeard/image_cache.py @@ -289,7 +289,7 @@ class ImageCache: cur_file_name = os.path.abspath(cur_provider.get_poster_path(show_obj)) cur_file_type = self.which_type(cur_file_name) - if cur_file_type == None: + if cur_file_type is None: logger.log(u"Unable to retrieve image type, not using the image from " + str(cur_file_name), logger.WARNING) continue diff --git a/sickbeard/naming.py b/sickbeard/naming.py index 1e3c0fe62f76a3856769f1f6668275474785360f..9b8db1d06a1492501059e061a365060d259ac407 100644 --- a/sickbeard/naming.py +++ b/sickbeard/naming.py @@ -123,15 +123,15 @@ def check_force_season_folders(pattern=None, multi=None, anime_type=None): :return: true if season folders need to be forced on or false otherwise. """ - if pattern == None: + if pattern is None: pattern = sickbeard.NAMING_PATTERN - if anime_type == None: + if anime_type is None: anime_type = sickbeard.NAMING_ANIME valid = not validate_name(pattern, None, anime_type, file_only=True) - if multi != None: + if multi is not None: valid = valid or not validate_name(pattern, multi, anime_type, file_only=True) return valid @@ -143,16 +143,16 @@ def check_valid_naming(pattern=None, multi=None, anime_type=None): :return: true if the naming is valid, false if not. """ - if pattern == None: + if pattern is None: pattern = sickbeard.NAMING_PATTERN - if anime_type == None: + if anime_type is None: anime_type = sickbeard.NAMING_ANIME logger.log(u"Checking whether the pattern " + pattern + " is valid for a single episode", logger.DEBUG) valid = validate_name(pattern, None, anime_type) - if multi != None: + if multi is not None: logger.log(u"Checking whether the pattern " + pattern + " is valid for a multi episode", logger.DEBUG) valid = valid and validate_name(pattern, multi, anime_type) @@ -165,7 +165,7 @@ def check_valid_abd_naming(pattern=None): :return: true if the naming is valid, false if not. """ - if pattern == None: + if pattern is None: pattern = sickbeard.NAMING_PATTERN logger.log(u"Checking whether the pattern " + pattern + " is valid for an air-by-date episode", logger.DEBUG) @@ -180,7 +180,7 @@ def check_valid_sports_naming(pattern=None): :return: true if the naming is valid, false if not. """ - if pattern == None: + if pattern is None: pattern = sickbeard.NAMING_PATTERN logger.log(u"Checking whether the pattern " + pattern + " is valid for an sports episode", logger.DEBUG) @@ -263,7 +263,7 @@ def generate_sample_ep(multi=None, abd=False, sports=False, anime_type=None): else: ep._release_name = 'Show.Name.S02E03.HDTV.XviD-RLSGROUP' - if multi != None: + if multi is not None: ep._name = "Ep Name (1)" if anime_type != 3: diff --git a/sickbeard/notifiers/emailnotify.py b/sickbeard/notifiers/emailnotify.py index 03dbdbc1e332aa5fa714b6a7eea58af7c4bbd870..34249c92de064129a725b9fcab8a7d0e73b216ab 100644 --- a/sickbeard/notifiers/emailnotify.py +++ b/sickbeard/notifiers/emailnotify.py @@ -20,9 +20,12 @@ # # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## import smtplib import traceback +import ast from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formatdate @@ -61,7 +64,7 @@ class EmailNotifier(object): show = self._parseEp(ep_name) to = self._generate_recipients(show) if len(to) == 0: - logger.log(u'Skipping email notify because there are no configured recipients', logger.WARNING) + logger.log(u'Skipping email notify because there are no configured recipients', logger.DEBUG) else: try: msg = MIMEMultipart('alternative') @@ -100,7 +103,7 @@ class EmailNotifier(object): show = self._parseEp(ep_name) to = self._generate_recipients(show) if len(to) == 0: - logger.log(u'Skipping email notify because there are no configured recipients', logger.WARNING) + logger.log(u'Skipping email notify because there are no configured recipients', logger.DEBUG) else: try: msg = MIMEMultipart('alternative') @@ -139,7 +142,7 @@ class EmailNotifier(object): show = self._parseEp(ep_name) to = self._generate_recipients(show) if len(to) == 0: - logger.log(u'Skipping email notify because there are no configured recipients', logger.WARNING) + logger.log(u'Skipping email notify because there are no configured recipients', logger.DEBUG) else: try: msg = MIMEMultipart('alternative') @@ -169,20 +172,28 @@ class EmailNotifier(object): def _generate_recipients(self, show): addrs = [] + myDB = db.DBConnection() # Grab the global recipients - for addr in sickbeard.EMAIL_LIST.split(','): - if len(addr.strip()) > 0: - addrs.append(addr) - - # Grab the recipients for the show - myDB = db.DBConnection() - for s in show: - for subs in myDB.select("SELECT notify_list FROM tv_shows WHERE show_name = ?", (s,)): - if subs['notify_list']: - for addr in subs['notify_list'].split(','): - if len(addr.strip()) > 0: - addrs.append(addr) + if sickbeard.EMAIL_LIST: + for addr in sickbeard.EMAIL_LIST.split(','): + if len(addr.strip()) > 0: + addrs.append(addr) + + # Grab the per-show-notification recipients + if show is not None: + for s in show: + for subs in myDB.select("SELECT notify_list FROM tv_shows WHERE show_name = ?", (s,)): + if subs['notify_list']: + if subs['notify_list'][0] == '{': + entries = dict(ast.literal_eval(subs['notify_list'])) + for addr in entries['emails'].split(','): + if len(addr.strip()) > 0: + addrs.append(addr) + else: # Legacy + for addr in subs['notify_list'].split(','): + if len(addr.strip()) > 0: + addrs.append(addr) addrs = set(addrs) logger.log(u'Notification recipients: %s' % addrs, logger.DEBUG) diff --git a/sickbeard/notifiers/prowl.py b/sickbeard/notifiers/prowl.py index 20c40c47b18ae842ba0400a1cd602eab3c95b8e1..8f1bde7513000c7c962459a29398ba97c36bfb4c 100644 --- a/sickbeard/notifiers/prowl.py +++ b/sickbeard/notifiers/prowl.py @@ -17,6 +17,8 @@ # # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## import socket from httplib import HTTPSConnection, HTTPException @@ -31,51 +33,103 @@ except ImportError: pass import sickbeard +import time +import ast -from sickbeard import logger, common - +from sickbeard import logger, common, db +from sickrage.helper.encoding import ss class ProwlNotifier(object): def test_notify(self, prowl_api, prowl_priority): - return self._sendProwl(prowl_api, prowl_priority, event="Test", - message="Testing Prowl settings from SickRage", force=True) + return self._send_prowl(prowl_api, prowl_priority, event="Test", message="Testing Prowl settings from SickRage", force=True) def notify_snatch(self, ep_name): + ep_name = ss(ep_name) if sickbeard.PROWL_NOTIFY_ONSNATCH: - self._sendProwl(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH], - message=ep_name) + show = self._parse_episode(ep_name) + recipients = self._generate_recipients(show) + if len(recipients) == 0: + logger.log('Skipping prowl notify because there are no configured recipients', logger.DEBUG) + else: + for api in recipients: + self._send_prowl(prowl_api=api, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH], + message=ep_name+" :: "+time.strftime(sickbeard.DATE_PRESET+" "+sickbeard.TIME_PRESET)) def notify_download(self, ep_name): + ep_name = ss(ep_name) if sickbeard.PROWL_NOTIFY_ONDOWNLOAD: - self._sendProwl(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], - message=ep_name) + show = self._parse_episode(ep_name) + recipients = self._generate_recipients(show) + if len(recipients) == 0: + logger.log('Skipping prowl notify because there are no configured recipients', logger.DEBUG) + else: + for api in recipients: + self._send_prowl(prowl_api=api, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], + message=ep_name+" :: "+time.strftime(sickbeard.DATE_PRESET+" "+sickbeard.TIME_PRESET)) def notify_subtitle_download(self, ep_name, lang): + ep_name = ss(ep_name) if sickbeard.PROWL_NOTIFY_ONSUBTITLEDOWNLOAD: - self._sendProwl(prowl_api=None, prowl_priority=None, - event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], message=ep_name + ": " + lang) - + show = self._parse_episode(ep_name) + recipients = self._generate_recipients(show) + if len(recipients) == 0: + logger.log('Skipping prowl notify because there are no configured recipients', logger.DEBUG) + else: + for api in recipients: + self._send_prowl(prowl_api=api, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], + message=ep_name+" ["+lang+"] :: "+time.strftime(sickbeard.DATE_PRESET+" "+sickbeard.TIME_PRESET)) + def notify_git_update(self, new_version="??"): if sickbeard.USE_PROWL: update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] - self._sendProwl(prowl_api=None, prowl_priority=None, - event=title, message=update_text + new_version) - - def _sendProwl(self, prowl_api=None, prowl_priority=None, event=None, message=None, force=False): + self._send_prowl(prowl_api=None, prowl_priority=None, + event=title, message=update_text + new_version) + + + @staticmethod + def _generate_recipients(show=None): + apis = [] + mydb = db.DBConnection() + + # Grab the global recipient(s) + if sickbeard.PROWL_API: + for api in sickbeard.PROWL_API.split(','): + if len(api.strip()) > 0: + apis.append(api) + + # Grab the per-show-notification recipients + if show is not None: + for value in show: + for subs in mydb.select("SELECT notify_list FROM tv_shows WHERE show_name = ?", (value,)): + if subs['notify_list']: + if subs['notify_list'][0] == '{': # legacy format handling + entries = dict(ast.literal_eval(subs['notify_list'])) + for api in entries['prowlAPIs'].split(','): + if len(api.strip()) > 0: + apis.append(api) + + apis = set(apis) + return apis + + @staticmethod + def _send_prowl(prowl_api=None, prowl_priority=None, event=None, message=None, force=False): if not sickbeard.USE_PROWL and not force: return False if prowl_api is None: prowl_api = sickbeard.PROWL_API + if len(prowl_api) == 0: + return False if prowl_priority is None: prowl_priority = sickbeard.PROWL_PRIORITY - title = "SickRage" + title = sickbeard.PROWL_MESSAGE_TITLE - logger.log(u"PROWL: Sending notice with details: event=\"%s\", message=\"%s\", priority=%s, api=%s" % (event, message, prowl_priority, prowl_api), logger.DEBUG) + logger.log(u"PROWL: Sending notice with details: title=\"%s\" event=\"%s\", message=\"%s\", priority=%s, api=%s" + % (title, event, message, prowl_priority, prowl_api), logger.DEBUG) http_handler = HTTPSConnection("api.prowlapp.com") @@ -106,5 +160,14 @@ class ProwlNotifier(object): logger.log(u"Prowl notification failed.", logger.ERROR) return False + @staticmethod + def _parse_episode(ep_name): + ep_name = ss(ep_name) + + sep = " - " + titles = ep_name.split(sep) + titles.sort(key=len, reverse=True) + logger.log("TITLES: %s" % titles, logger.DEBUG) + return titles notifier = ProwlNotifier diff --git a/sickbeard/notifiers/pushover.py b/sickbeard/notifiers/pushover.py index f4f102b0bbaa3ef526f2282a815b6a35c0345445..1dc232f16ed34ec2d9172aaa9b695d978070f1c6 100644 --- a/sickbeard/notifiers/pushover.py +++ b/sickbeard/notifiers/pushover.py @@ -47,13 +47,13 @@ class PushoverNotifier(object): returns: True if the message succeeded, False otherwise """ - if userKey == None: + if userKey is None: userKey = sickbeard.PUSHOVER_USERKEY - if apiKey == None: + if apiKey is None: apiKey = sickbeard.PUSHOVER_APIKEY - if sound == None: + if sound is None: sound = sickbeard.PUSHOVER_SOUND logger.log(u"Pushover API KEY in use: " + apiKey, logger.DEBUG) diff --git a/sickbeard/nzbSplitter.py b/sickbeard/nzbSplitter.py index 09cb3df81ab386d9a3a29ce469d48acafb91d9e2..311443aa5077f21f403276a03dcd8f446daf44a6 100644 --- a/sickbeard/nzbSplitter.py +++ b/sickbeard/nzbSplitter.py @@ -145,7 +145,7 @@ def splitResult(result): return False # bust it up - season = parse_result.season_number if parse_result.season_number != None else 1 + season = parse_result.season_number if parse_result.season_number is not None else 1 separateNZBs, xmlns = getSeasonNZBs(result.name, urlData, season) @@ -167,8 +167,8 @@ def splitResult(result): return False # make sure the result is sane - if (parse_result.season_number != None and parse_result.season_number != season) or ( - parse_result.season_number == None and season != 1): + if (parse_result.season_number is not None and parse_result.season_number != season) or ( + parse_result.season_number is None and season != 1): logger.log( u"Found " + newNZB + " inside " + result.name + " but it doesn't seem to belong to the same season, ignoring it", logger.WARNING) diff --git a/sickbeard/nzbget.py b/sickbeard/nzbget.py index 809133a97952190fb636fe75c17ae3c4543133b0..5e12b521aac631e9e36b8b9290dd5dbaf6860891 100644 --- a/sickbeard/nzbget.py +++ b/sickbeard/nzbget.py @@ -47,8 +47,8 @@ def sendNZB(nzb, proper=False): else: nzbgetXMLrpc = "http://%(username)s:%(password)s@%(host)s/xmlrpc" - if sickbeard.NZBGET_HOST == None: - logger.log(u"No NZBget host found in configuration. Please configure it.", logger.ERROR) + if sickbeard.NZBGET_HOST is None: + logger.log(u"No NZBget host found in configuration. Please configure it.", logger.WARNING) return False url = nzbgetXMLrpc % {"host": sickbeard.NZBGET_HOST, "username": sickbeard.NZBGET_USERNAME, @@ -59,17 +59,17 @@ def sendNZB(nzb, proper=False): if nzbGetRPC.writelog("INFO", "SickRage connected to drop of %s any moment now." % (nzb.name + ".nzb")): logger.log(u"Successful connected to NZBget", logger.DEBUG) else: - logger.log(u"Successful connected to NZBget, but unable to send a message", logger.ERROR) + logger.log(u"Successful connected to NZBget, but unable to send a message", logger.WARNING) except httplib.socket.error: logger.log( u"Please check your NZBget host and port (if it is running). NZBget is not responding to this combination", - logger.ERROR) + logger.WARNING) return False except xmlrpclib.ProtocolError, e: if e.errmsg == "Unauthorized": - logger.log(u"NZBget username or password is incorrect.", logger.ERROR) + logger.log(u"NZBget username or password is incorrect.", logger.WARNING) else: logger.log(u"Protocol Error: " + e.errmsg, logger.ERROR) return False @@ -116,7 +116,7 @@ def sendNZB(nzb, proper=False): if nzb.resultType == "nzb": genProvider = GenericProvider("") data = genProvider.getURL(nzb.url) - if data == None: + if data is None: return False nzbcontent64 = standard_b64encode(data) nzbget_result = nzbGetRPC.append(nzb.name + ".nzb", category, addToTop, nzbcontent64) @@ -146,8 +146,8 @@ def sendNZB(nzb, proper=False): logger.log(u"NZB sent to NZBget successfully", logger.DEBUG) return True else: - logger.log(u"NZBget could not add %s to the queue" % (nzb.name + ".nzb"), logger.ERROR) + logger.log(u"NZBget could not add %s to the queue" % (nzb.name + ".nzb"), logger.WARNING) return False except Exception: - logger.log(u"Connect Error to NZBget: could not add %s to the queue" % (nzb.name + ".nzb"), logger.ERROR) + logger.log(u"Connect Error to NZBget: could not add %s to the queue" % (nzb.name + ".nzb"), logger.WARNING) return False diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index ab01e21d2618b89ca3eb6e9186c8b4645c126c0c..de0602842985001cc4cd24ab4d0dc15b5864c63e 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -36,6 +36,7 @@ from sickbeard import notifiers from sickbeard import show_name_helpers from sickbeard import failed_history from sickbeard.name_parser.parser import NameParser, InvalidNameException, InvalidShowException +from sickrage.helper.common import subtitle_extensions from sickrage.helper.encoding import ek from sickrage.helper.exceptions import EpisodeNotFoundException, EpisodePostProcessingFailedException, ex from sickrage.helper.exceptions import ShowDirectoryNotFoundException @@ -192,18 +193,29 @@ class PostProcessor(object): checklist = glob.glob(ek(os.path.join, ek(os.path.dirname, globbable_file_path), '*')) # get a list of all the files in the folder for filefound in checklist: # loop through all the files in the folder, and check if they are the same name even when the cases don't match + file_name = filefound.rpartition('.')[0] file_extension = filefound.rpartition('.')[2] + is_subtitle = None + + if file_extension in subtitle_extensions: + is_subtitle = True + if not base_name_only: new_file_name = file_name + '.' - if new_file_name.lower() == base_name.lower().replace('[[]', '[').replace('[]]', ']'): # if there's no difference in the filename add it to the filelist - filelist.append(filefound) - elif file_extension in common.subtitleExtensions: + sub_file_name = file_name.rpartition('.')[0] + '.' + else: + new_file_name = file_name + sub_file_name = file_name.rpartition('.')[0] + + if is_subtitle and sub_file_name.lower() == base_name.lower().replace('[[]', '[').replace('[]]', ']'): language_extensions = tuple('.' + c for c in language_converters['opensubtitles'].codes) if file_name.lower().endswith(language_extensions) and (len(filefound.rsplit('.', 2)[1]) is 2 or 3): filelist.append(filefound) elif file_name.lower().endswith('pt-br') and len(filefound.rsplit('.', 2)[1]) is 5: filelist.append(filefound) + elif new_file_name.lower() == base_name.lower().replace('[[]', '[').replace('[]]', ']'): # if there's no difference in the filename add it to the filelist + filelist.append(filefound) for associated_file_path in filelist: # only add associated to list @@ -211,7 +223,7 @@ class PostProcessor(object): continue # only list it if the only non-shared part is the extension or if it is a subtitle - if subtitles_only and not associated_file_path[len(associated_file_path) - 3:] in common.subtitleExtensions: + if subtitles_only and not associated_file_path[len(associated_file_path) - 3:] in subtitle_extensions: continue # Exclude .rar files from associated list @@ -221,7 +233,7 @@ class PostProcessor(object): # Add the extensions that the user doesn't allow to the 'extensions_to_delete' list if sickbeard.MOVE_ASSOCIATED_FILES and sickbeard.ALLOWED_EXTENSIONS: allowed_extensions = sickbeard.ALLOWED_EXTENSIONS.split(",") - if not associated_file_path[-3:] in allowed_extensions and not associated_file_path[-3:] in common.subtitleExtensions: + if not associated_file_path[-3:] in allowed_extensions and not associated_file_path[-3:] in subtitle_extensions: if ek(os.path.isfile, associated_file_path): extensions_to_delete.append(associated_file_path) @@ -324,7 +336,7 @@ class PostProcessor(object): cur_extension = cur_file_path[old_base_name_length + 1:] # check if file have subtitles language - if os.path.splitext(cur_extension)[1][1:] in common.subtitleExtensions: + if os.path.splitext(cur_extension)[1][1:] in subtitle_extensions: cur_lang = os.path.splitext(cur_extension)[0] if cur_lang: cur_lang = cur_lang.lower() @@ -333,7 +345,7 @@ class PostProcessor(object): cur_extension = cur_lang + os.path.splitext(cur_extension)[1] # replace .nfo with .nfo-orig to avoid conflicts - if cur_extension == 'nfo' and sickbeard.NFO_RENAME == True: + if cur_extension == 'nfo' and sickbeard.NFO_RENAME is True: cur_extension = 'nfo-orig' # If new base name then convert name @@ -343,7 +355,7 @@ class PostProcessor(object): else: new_file_name = helpers.replaceExtension(cur_file_name, cur_extension) - if sickbeard.SUBTITLES_DIR and cur_extension[-3:] in common.subtitleExtensions: + if sickbeard.SUBTITLES_DIR and cur_extension[-3:] in subtitle_extensions: subs_new_path = ek(os.path.join, new_path, sickbeard.SUBTITLES_DIR) dir_exists = helpers.makeDir(subs_new_path) if not dir_exists: @@ -474,7 +486,7 @@ class PostProcessor(object): # search the database for a possible match and return immediately if we find one myDB = db.DBConnection() for curName in names: - search_name = re.sub(r"[\.\-\ ]", "_", curName) + search_name = re.sub(r"[\.\- ]", "_", curName) sql_results = myDB.select("SELECT * FROM history WHERE resource LIKE ?", [search_name]) if len(sql_results) == 0: @@ -492,7 +504,7 @@ class PostProcessor(object): self.in_history = True self.version = version - to_return = (show, season, [], quality, version) + to_return = (str(show), season, [], quality, version) self._log("Found result in history: " + str(to_return), logger.DEBUG) return to_return @@ -510,7 +522,7 @@ class PostProcessor(object): # remember whether it's a proper if parse_result.extra_info: - self.is_proper = re.search(r'(^|[\. _-])(proper|repack)([\. _-]|$)', parse_result.extra_info, re.I) != None + self.is_proper = re.search(r'(^|[\. _-])(proper|repack)([\. _-]|$)', parse_result.extra_info, re.I) is not None # if the result is complete then remember that for later # if the result is complete then set release name @@ -648,7 +660,7 @@ class PostProcessor(object): if cur_version is not None: version = cur_version - if cur_season != None: + if cur_season is not None: season = cur_season if cur_episodes: episodes = cur_episodes @@ -685,12 +697,12 @@ class PostProcessor(object): continue # if there's no season then we can hopefully just use 1 automatically - elif season == None and show: + elif season is None and show: myDB = db.DBConnection() numseasonsSQlResult = myDB.select( "SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and indexer = ? and season != 0", [show.indexerid, show.indexer]) - if int(numseasonsSQlResult[0][0]) == 1 and season == None: + if int(numseasonsSQlResult[0][0]) == 1 and season is None: self._log( u"Don't have a season number, but this show appears to only have 1 season, setting season number to 1...", logger.DEBUG) @@ -727,7 +739,7 @@ class PostProcessor(object): raise EpisodePostProcessingFailedException() # associate all the episodes together under a single root episode - if root_ep == None: + if root_ep is None: root_ep = curEp root_ep.relatedEps = [] elif curEp not in root_ep.relatedEps: @@ -841,6 +853,11 @@ class PostProcessor(object): return True _, old_ep_quality = common.Quality.splitCompositeStatus(ep_obj.status) + self._log(u"old_ep_quality = %s, new_ep_quality %s" % (common.Quality.qualityStrings[old_ep_quality], common.Quality.qualityStrings[new_ep_quality]), logger.DEBUG) + + if old_ep_quality == common.Quality.UNKNOWN and new_ep_quality != common.Quality.UNKNOWN: + self._log(u"Old episode has an unknown quality, so any known quality is better", logger.DEBUG) + return True # if SR downloaded this on purpose we likely have a priority download if self.in_history or ep_obj.status in common.Quality.SNATCHED + common.Quality.SNATCHED_PROPER + common.Quality.SNATCHED_BEST: @@ -905,7 +922,7 @@ class PostProcessor(object): if not show: self._log(u"This show isn't in your list, you need to add it to SR before post-processing an episode") raise EpisodePostProcessingFailedException() - elif season == None or not episodes: + elif season is None or not episodes: self._log(u"Not enough information to determine what episode this is. Quitting post-processing") return False @@ -921,7 +938,7 @@ class PostProcessor(object): else: new_ep_quality = self._get_quality(ep_obj) - logger.log(u"Quality of the episode we're processing: %s" % new_ep_quality, logger.DEBUG) + logger.log(u"Quality of the episode we're processing: %s" % common.Quality.qualityStrings[new_ep_quality], logger.DEBUG) # see if this is a priority download (is it snatched, in history, PROPER, or BEST) priority_download = self._is_priority(ep_obj, new_ep_quality) diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index d201a5885db93295c840d7872f01f64916935a64..ec6ddd064b93a5d5a86c8a7e7975c009e8350dbb 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -28,7 +28,8 @@ from sickbeard import logger from sickbeard.name_parser.parser import NameParser, InvalidNameException, InvalidShowException from sickbeard import common from sickbeard import failedProcessor -from sickrage.helper.encoding import ek, ss +from sickrage.helper.common import subtitle_extensions +from sickrage.helper.encoding import ek from sickrage.helper.exceptions import EpisodePostProcessingFailedException, ex, FailedPostProcessingFailedException from unrar2 import RarFile @@ -547,15 +548,15 @@ def process_media(processPath, videoFiles, nzbName, process_method, force, is_pr try: processor = postProcessor.PostProcessor(cur_video_file_path, nzbName, process_method, is_priority) - # This feature prevents PP for files that do not have subtitle associated with the video file + # This feature prevents PP for files that do not have subtitle associated with the video file if sickbeard.POSTPONE_IF_NO_SUBS: associatedFiles = processor.list_associated_files(cur_video_file_path, subtitles_only=True) - if not [associatedFile for associatedFile in associatedFiles if associatedFile[-3:] in common.subtitleExtensions]: + if not [associatedFile for associatedFile in associatedFiles if associatedFile[-3:] in subtitle_extensions]: result.output += logHelper(u"No subtitles associated. Postponing the post-process of this file: %s" % cur_video_file, logger.DEBUG) continue else: - result.output += logHelper(u"Found subtitles associated. Continuing the post-process of this file: %s" % cur_video_file) - + result.output += logHelper(u"Found subtitles associated. Continuing the post-process of this file: %s" % cur_video_file) + result.result = processor.process() process_fail_message = "" except EpisodePostProcessingFailedException, e: diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index d863cb052e839bd47f648cc5e858ab5cfa677b8b..60116727e358a89c64080c7c41c3c43f82b0a153 100644 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -26,7 +26,7 @@ from sickbeard import logger from sickbeard.providers import btn, newznab, womble, thepiratebay, torrentleech, kat, iptorrents, torrentz, \ omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, hounddawgs, nextgen, speedcd, nyaatorrents, animenzb, bluetigers, cpasbien, fnt, xthor, torrentbytes, \ freshontv, titansoftv, libertalia, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, torrentproject, extratorrent, \ - scenetime, btdigg, strike, transmitthenet, tvchaosuk, bitcannon, pretome, gftracker, hdspace, newpct + scenetime, btdigg, strike, transmitthenet, tvchaosuk, bitcannon, pretome, gftracker, hdspace, newpct, elitetorrent __all__ = ['womble', 'btn', @@ -73,7 +73,8 @@ __all__ = ['womble', 'pretome', 'gftracker', 'hdspace', - 'newpct' + 'newpct', + 'elitetorrent' ] diff --git a/sickbeard/providers/alpharatio.py b/sickbeard/providers/alpharatio.py index d2b096faeaf39bf4d6f50670703fe57d38e57467..0e58915fa0e4e706f6c9e0334d592e71f2fb31dd 100644 --- a/sickbeard/providers/alpharatio.py +++ b/sickbeard/providers/alpharatio.py @@ -85,7 +85,7 @@ class AlphaRatioProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (search_string, self.categories) @@ -125,12 +125,12 @@ class AlphaRatioProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/bitcannon.py b/sickbeard/providers/bitcannon.py index 54ea2276879e6a796187d287a8b9addf0ce57d7a..ebdd16551107fca7e2a21f9d60234da964f5529e 100644 --- a/sickbeard/providers/bitcannon.py +++ b/sickbeard/providers/bitcannon.py @@ -75,7 +75,7 @@ class BitCannonProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue @@ -83,7 +83,7 @@ class BitCannonProvider(generic.TorrentProvider): download_url = 'magnet:?xt=urn:btih:%s&dn=%s&tr=%s' % (info_hash, quote_plus(title.encode('utf-8')), u'&tr='.join([quote_plus(x.encode('utf-8')) for x in trackers])) item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/bitsoup.py b/sickbeard/providers/bitsoup.py index 162f7b3bc1fb63f8c236a443f5b28f2b7a4c9faf..e25855c9deee2c8029bee140220fb234dc4fc253 100644 --- a/sickbeard/providers/bitsoup.py +++ b/sickbeard/providers/bitsoup.py @@ -89,7 +89,7 @@ class BitSoupProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) self.search_params['search'] = search_string @@ -128,12 +128,12 @@ class BitSoupProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/bluetigers.py b/sickbeard/providers/bluetigers.py index bbe6e3f6e4b6324ba79b42e31d2032c8e96f10f7..561503a401732b3df83c43ef7da75950493d16fc 100644 --- a/sickbeard/providers/bluetigers.py +++ b/sickbeard/providers/bluetigers.py @@ -1,4 +1,4 @@ -# -*- coding: latin-1 -*- +# coding=utf-8 # Author: raver2046 <raver2046@gmail.com> # URL: http://code.google.com/p/sickbeard/ # @@ -18,11 +18,11 @@ # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. import traceback +import requests import re from requests.auth import AuthBase from sickbeard.providers import generic -import requests from sickbeard.bs4_parser import BS4Parser from sickbeard import logger @@ -39,7 +39,6 @@ class BLUETIGERSProvider(generic.TorrentProvider): self.password = None self.ratio = None self.token = None - self.tokenLastUpdate = None self.cache = BLUETIGERSCache(self) @@ -67,13 +66,16 @@ class BLUETIGERSProvider(generic.TorrentProvider): } response = self.getURL(self.urls['login'], post_data=login_params, timeout=30) - if not response: - logger.log(u"Unable to connect to provider", logger.WARNING) - return False - if re.search('/account-logout.php', response): - return True - else: + if not response: + check_login = self.getURL(self.urls['base_url'], timeout=30) + if re.search('account-logout.php', check_login): + return True + else: + logger.log(u"Unable to connect to provider", logger.WARNING) + return False + + if re.search('account-login.php', response): logger.log(u"Invalid username or password. Check your settings", logger.WARNING) return False @@ -83,8 +85,7 @@ class BLUETIGERSProvider(generic.TorrentProvider): results = [] items = {'Season': [], 'Episode': [], 'RSS': []} - - # check for auth + if not self._doLogin(): return results @@ -92,7 +93,7 @@ class BLUETIGERSProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) self.search_params['search'] = search_string @@ -112,7 +113,7 @@ class BLUETIGERSProvider(generic.TorrentProvider): if result_linkz: for link in result_linkz: title = link.text - download_url = self.urls['base_url'] + "/" + link['href'] + download_url = self.urls['base_url'] + link['href'] download_url = download_url.replace("torrents-details", "download") # FIXME size = -1 @@ -124,12 +125,12 @@ class BLUETIGERSProvider(generic.TorrentProvider): # Filter unseeded torrent # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/btdigg.py b/sickbeard/providers/btdigg.py index 553e7e320bfcd1a66395b6c964a350d9e31e5ca2..4eda679d1cdf9e9371d3566cdcf4b02ad4aaaa8f 100644 --- a/sickbeard/providers/btdigg.py +++ b/sickbeard/providers/btdigg.py @@ -55,11 +55,11 @@ class BTDIGGProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s" % search_string, logger.DEBUG) search_params['q'] = search_string.encode('utf-8') - search_params['order'] = '1' if mode is not 'RSS' else '2' + search_params['order'] = '1' if mode != 'RSS' else '2' searchURL = self.urls['api'] + '?' + urlencode(search_params) logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) @@ -83,12 +83,12 @@ class BTDIGGProvider(generic.TorrentProvider): # Filter unseeded torrent (Unsupported) # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s" % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/cpasbien.py b/sickbeard/providers/cpasbien.py index 67641af0006547fd78cb73d998bf3508d14bd042..e91160f8db116325b9629173d66b48db37b3678e 100644 --- a/sickbeard/providers/cpasbien.py +++ b/sickbeard/providers/cpasbien.py @@ -49,7 +49,7 @@ class CpasbienProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.url + '/recherche/'+search_string.replace('.', '-') + '.html' @@ -101,7 +101,7 @@ class CpasbienProvider(generic.TorrentProvider): continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/elitetorrent.py b/sickbeard/providers/elitetorrent.py new file mode 100644 index 0000000000000000000000000000000000000000..63a9492e2c50d32af0d90572d04160f15075148b --- /dev/null +++ b/sickbeard/providers/elitetorrent.py @@ -0,0 +1,186 @@ +# coding=utf-8 +# Author: CristianBB +# +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import traceback +import re +from six.moves import urllib + +from sickbeard import logger +from sickbeard import tvcache +from sickbeard.providers import generic +from sickbeard.bs4_parser import BS4Parser + + +class elitetorrentProvider(generic.TorrentProvider): + def __init__(self): + + generic.TorrentProvider.__init__(self, "EliteTorrent") + + self.supportsBacklog = True + self.onlyspasearch = None + self.minseed = None + self.minleech = None + self.cache = elitetorrentCache(self) + + self.urls = { + 'base_url': 'http://www.elitetorrent.net', + 'search': 'http://www.elitetorrent.net/torrents.php' + } + + self.url = self.urls['base_url'] + + """ + Search query: + http://www.elitetorrent.net/torrents.php?cat=4&modo=listado&orden=fecha&pag=1&buscar=fringe + + cat = 4 => Shows + modo = listado => display results mode + orden = fecha => order + buscar => Search show + pag = 1 => page number + """ + + self.search_params = { + 'cat': 4, + 'modo': 'listado', + 'orden': 'fecha', + 'pag': 1, + 'buscar': '' + + } + + def _doSearch(self, search_strings, search_mode='eponly', epcount=0, age=0, epObj=None): + + results = [] + items = {'Season': [], 'Episode': [], 'RSS': []} + + lang_info = '' if not epObj or not epObj.show else epObj.show.lang + + for mode in search_strings.keys(): + logger.log(u"Search Mode: %s" % mode, logger.DEBUG) + + # Only search if user conditions are true + if self.onlyspasearch and lang_info != 'es' and mode is not 'RSS': + logger.log(u"Show info is not spanish, skipping provider search", logger.DEBUG) + continue + + for search_string in search_strings[mode]: + if mode is not 'RSS': + logger.log(u"Search string: %s " % search_string, logger.DEBUG) + + search_string = re.sub(r'S0*(\d*)E(\d*)', r'\1x\2', search_string) + self.search_params['buscar'] = search_string.strip() if mode is not 'RSS' else '' + + searchURL = self.urls['search'] + '?' + urllib.parse.urlencode(self.search_params) + logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + + data = self.getURL(searchURL, timeout=30) + + if not data: + continue + + try: + with BS4Parser(data, features=["html5lib", "permissive"]) as html: + torrent_table = html.find('table', class_='fichas-listado') + + if torrent_table is None: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue + + torrent_rows = torrent_table.findAll('tr') + if torrent_rows is None: + logger.log(u"Torrent table does not have any rows", logger.DEBUG) + continue + + for row in torrent_rows[1:]: + try: + seeders_raw = row.find('td', class_='semillas').text + leechers_raw = row.find('td', class_='clientes').text + + download_url = self.urls['base_url'] + row.findAll('a')[0].get('href', '') + title = self._processTitle(row.findAll('a')[1].text) + seeders = seeders_raw if seeders_raw.isnumeric() else 0 + leechers = leechers_raw if leechers_raw.isnumeric() else 0 + + # FIXME: Provider does not provide size + size = 0 + + except (AttributeError, TypeError): + continue + + if not all([title, download_url]): + continue + + # Filter unseeded torrent + if seeders < self.minseed or leechers < self.minleech: + if mode is not 'RSS': + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue + + item = title, download_url, size, seeders, leechers + if mode is not 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) + + items[mode].append(item) + + except Exception: + logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.WARNING) + + # For each search mode sort all the items by seeders if available + items[mode].sort(key=lambda tup: tup[3], reverse=True) + + results += items[mode] + + return results + + + @staticmethod + def _processTitle(title): + + # Quality, if no literal is defined it's HDTV + if 'calidad' not in title: + title += ' HDTV x264' + + title = title.replace('(calidad baja)', 'HDTV x264') + title = title.replace('(Buena calidad)', '720p HDTV x264') + title = title.replace('(Alta calidad)', '720p HDTV x264') + title = title.replace('(calidad regular)', 'DVDrip x264') + title = title.replace('(calidad media)', 'DVDrip x264') + + #Language, all results from this provider have spanish audio, we append it to title (avoid to download undesired torrents) + title += ' SPANISH AUDIO' + title += '-ELITETORRENT' + + return title.strip() + + +class elitetorrentCache(tvcache.TVCache): + def __init__(self, provider_obj): + + tvcache.TVCache.__init__(self, provider_obj) + + self.minTime = 20 + + def _getRSSData(self): + search_params = {'RSS': ['']} + return {'entries': self.provider._doSearch(search_params)} + + +provider = elitetorrentProvider() diff --git a/sickbeard/providers/extratorrent.py b/sickbeard/providers/extratorrent.py index d16cccb75180c752bf8fccc6c906c176f44fdcde..eda874561dfa78266ab8f8fe4bd592cd64100af4 100644 --- a/sickbeard/providers/extratorrent.py +++ b/sickbeard/providers/extratorrent.py @@ -58,7 +58,7 @@ class ExtraTorrentProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) try: @@ -99,12 +99,12 @@ class ExtraTorrentProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/fnt.py b/sickbeard/providers/fnt.py index 52f83e8a32985ffb9ca1129eb9efc874ea2f8fb7..78bc2d1ea74e333a8bdb062758d06ed2e91a7de2 100644 --- a/sickbeard/providers/fnt.py +++ b/sickbeard/providers/fnt.py @@ -90,7 +90,7 @@ class FNTProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) self.search_params['recherche'] = search_string @@ -135,12 +135,12 @@ class FNTProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/freshontv.py b/sickbeard/providers/freshontv.py index 3c5b7688d2263a848cda22853a42611976029326..d0cc75351f1bbf564358aa889cd56d40f9ff5c04 100644 --- a/sickbeard/providers/freshontv.py +++ b/sickbeard/providers/freshontv.py @@ -117,7 +117,7 @@ class FreshOnTVProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (freeleech, search_string) @@ -190,7 +190,7 @@ class FreshOnTVProvider(generic.TorrentProvider): for individual_torrent in torrent_rows: # skip if torrent has been nuked due to poor quality - if individual_torrent.find('img', alt='Nuked') != None: + if individual_torrent.find('img', alt='Nuked') is not None: continue try: @@ -215,12 +215,12 @@ class FreshOnTVProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/gftracker.py b/sickbeard/providers/gftracker.py index 826e42ccf49dbda3a63661c9f5cf626d89f2e161..14d6656cdda3a1050548120b441c249d78675049 100644 --- a/sickbeard/providers/gftracker.py +++ b/sickbeard/providers/gftracker.py @@ -94,7 +94,7 @@ class GFTrackerProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (self.categories, search_string) @@ -146,12 +146,12 @@ class GFTrackerProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/hdspace.py b/sickbeard/providers/hdspace.py index 48eec48a7180d1cb79130ceb3e37c5efd1a39178..5a61844d13a233194b2cd0faa0800669f8b24e55 100644 --- a/sickbeard/providers/hdspace.py +++ b/sickbeard/providers/hdspace.py @@ -93,13 +93,13 @@ class HDSpaceProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': searchURL = self.urls['search'] % (urllib.quote_plus(search_string.replace('.', ' ')),) else: searchURL = self.urls['search'] % '' logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s" % search_string, logger.DEBUG) data = self.getURL(searchURL) @@ -145,12 +145,12 @@ class HDSpaceProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/hdtorrents.py b/sickbeard/providers/hdtorrents.py index e2708f3cdd8db40a231cc133930e7aba15b8bfa4..4183f702686d7d68f975bb8e59e5e86fff1798b0 100644 --- a/sickbeard/providers/hdtorrents.py +++ b/sickbeard/providers/hdtorrents.py @@ -92,13 +92,13 @@ class HDTorrentsProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': searchURL = self.urls['search'] % (urllib.quote_plus(search_string), self.categories) else: searchURL = self.urls['rss'] % self.categories logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s" % search_string, logger.DEBUG) data = self.getURL(searchURL) @@ -177,12 +177,12 @@ class HDTorrentsProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/hounddawgs.py b/sickbeard/providers/hounddawgs.py index 3736bfcb4d4bde0066dc9b69c71e5e8dfcdec5f5..704320e3e57ffd5b3cbd2374716d5e7b29ff7d3f 100644 --- a/sickbeard/providers/hounddawgs.py +++ b/sickbeard/providers/hounddawgs.py @@ -95,7 +95,7 @@ class HoundDawgsProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) self.search_params['searchstr'] = search_string @@ -157,12 +157,12 @@ class HoundDawgsProvider(generic.TorrentProvider): # Filter unseeded torrent # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/iptorrents.py b/sickbeard/providers/iptorrents.py index 000c9b03e42f0f19a84169d051d85f44bdcc5ac4..12c29eb991a7c1cfecb980ba437a8d78bc60f239 100644 --- a/sickbeard/providers/iptorrents.py +++ b/sickbeard/providers/iptorrents.py @@ -90,12 +90,12 @@ class IPTorrentsProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) # URL with 50 tv-show results, or max 150 if adjusted in IPTorrents profile searchURL = self.urls['search'] % (self.categories, freeleech, search_string) - searchURL += ';o=seeders' if mode is not 'RSS' else '' + searchURL += ';o=seeders' if mode != 'RSS' else '' logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) data = self.getURL(searchURL) @@ -136,12 +136,12 @@ class IPTorrentsProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/kat.py b/sickbeard/providers/kat.py index 7b097de4bec31582ae8e048a1c974b236db9fc41..ec62eafa106a4d15b7d5c7f77a5c5a40d80dc51f 100755 --- a/sickbeard/providers/kat.py +++ b/sickbeard/providers/kat.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Author: Mr_Orange <mr_orange@hotmail.it> -# URL: http://code.google.com/p/sickbeard/ +# Author: Dustyn Gibson <miigotu@gmail.com> +# URL: http://sickrage.github.io # # This file is part of SickRage. # @@ -18,18 +18,17 @@ # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +import posixpath # Must use posixpath import traceback - from urllib import urlencode - -import xmltodict +from bs4 import BeautifulSoup import sickbeard from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT from sickbeard.providers import generic -from xml.parsers.expat import ExpatError +from sickbeard.helpers import tryInt class KATProvider(generic.TorrentProvider): def __init__(self): @@ -44,14 +43,14 @@ class KATProvider(generic.TorrentProvider): self.minseed = None self.minleech = None - self.cache = KATCache(self) - self.urls = { - 'base_url': 'https://kickass.unblocked.pe/', - 'search': 'https://kickass.unblocked.pe/%s/', + 'base_url': 'https://kat.cr/', + 'search': 'https://kat.cr/%s/', } self.url = self.urls['base_url'] + self.custom_url = None + self.headers.update({'User-Agent': USER_AGENT}) self.search_params = { @@ -62,6 +61,8 @@ class KATProvider(generic.TorrentProvider): 'category': 'tv' } + self.cache = KATCache(self) + def _doSearch(self, search_strings, search_mode='eponly', epcount=0, age=0, epObj=None): results = [] items = {'Season': [], 'Episode': [], 'RSS': []} @@ -74,79 +75,71 @@ class KATProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - self.search_params['q'] = search_string.encode('utf-8') if mode is not 'RSS' else '' - self.search_params['field'] = 'seeders' if mode is not 'RSS' else 'time_add' + self.search_params['q'] = search_string.encode('utf-8') if mode != 'RSS' else '' + self.search_params['field'] = 'seeders' if mode != 'RSS' else 'time_add' - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s" % search_string, logger.DEBUG) - url_fmt_string = 'usearch' if mode is not 'RSS' else search_string + url_fmt_string = 'usearch' if mode != 'RSS' else search_string try: searchURL = self.urls['search'] % url_fmt_string + '?' + urlencode(self.search_params) + if self.custom_url: + searchURL = posixpath.join(self.custom_url, searchURL.split(self.url)[1].lstrip('/')) # Must use posixpath + logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) data = self.getURL(searchURL) - # data = self.getURL(self.urls[('search', 'rss')[mode is 'RSS']], params=self.search_params) if not data: - logger.log(u"No data returned from provider", logger.DEBUG) + logger.log(u'URL did not return data, maybe try a custom url, or a different one', logger.DEBUG) continue if not data.startswith('<?xml'): logger.log(u'Expected xml but got something else, is your mirror failing?', logger.INFO) continue - try: - data = xmltodict.parse(data) - except ExpatError: - logger.log(u"Failed parsing provider. Traceback: %r\n%r" % (traceback.format_exc(), data), logger.ERROR) - continue - - if not all([data, 'rss' in data, 'channel' in data['rss'], 'item' in data['rss']['channel']]): - logger.log(u"Malformed rss returned, skipping", logger.DEBUG) - continue - - # https://github.com/martinblech/xmltodict/issues/111 - entries = data['rss']['channel']['item'] - entries = entries if isinstance(entries, list) else [entries] + data = BeautifulSoup(data, features=["html5lib", "permissive"]) + entries = data.findAll('item') for item in entries: try: - title = item['title'] + title = item.title.text assert isinstance(title, unicode) # Use the torcache link kat provides, # unless it is not torcache or we are not using blackhole # because we want to use magnets if connecting direct to client # so that proxies work. - download_url = item['enclosure']['@url'] + download_url = item.enclosure['url'] if sickbeard.TORRENT_METHOD != "blackhole" or 'torcache' not in download_url: - download_url = item['torrent:magnetURI'] + download_url = item.find('torrent:magneturi').next.replace('CDATA', '').strip('[]') + + if not (title and download_url): + continue - seeders = int(item['torrent:seeds']) - leechers = int(item['torrent:peers']) - verified = bool(int(item['torrent:verified']) or 0) - size = int(item['torrent:contentLength']) + seeders = tryInt(item.find('torrent:seeds').text, 0) + leechers = tryInt(item.find('torrent:peers').text, 0) + verified = bool(tryInt(item.find('torrent:verified').text, 0)) + size = tryInt(item.find('torrent:contentlength').text) - info_hash = item['torrent:infoHash'] + info_hash = item.find('torrent:infohash').text # link = item['link'] - except (AttributeError, TypeError, KeyError): + except (AttributeError, TypeError, KeyError, ValueError): continue - if not all([title, download_url]): - continue # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue if self.confirmed and not verified: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result " + title + " but that doesn't seem like a verified result so I'm ignoring it", logger.DEBUG) continue item = title, download_url, size, seeders, leechers, info_hash - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/libertalia.py b/sickbeard/providers/libertalia.py index dc2a9a5141570b2c3774eead9bd91bdf2a026c47..657f3be0e50398ff0d602e7d20d9beb6c4e3d386 100644 --- a/sickbeard/providers/libertalia.py +++ b/sickbeard/providers/libertalia.py @@ -87,7 +87,7 @@ class LibertaliaProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urlsearch % (urllib.quote(search_string), self.categories) @@ -121,12 +121,12 @@ class LibertaliaProvider(generic.TorrentProvider): # Filter unseeded torrent # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/morethantv.py b/sickbeard/providers/morethantv.py index c89b22540ed42b04e4b01db0c55ed7a881b97bd6..82ad0842a77235dde2fa4cd4252a2eaa931a2558 100644 --- a/sickbeard/providers/morethantv.py +++ b/sickbeard/providers/morethantv.py @@ -106,7 +106,7 @@ class MoreThanTVProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (search_string.replace('(', '').replace(')', '')) @@ -133,7 +133,7 @@ class MoreThanTVProvider(generic.TorrentProvider): link = cells[1].find('a', attrs={'title': 'Download'}) # skip if torrent has been nuked due to poor quality - if cells[1].find('img', alt='Nuked') != None: + if cells[1].find('img', alt='Nuked') is not None: continue torrent_id_long = link['href'].replace('torrents.php?action=download&id=', '') @@ -162,12 +162,12 @@ class MoreThanTVProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/newpct.py b/sickbeard/providers/newpct.py index e4ca108dd15520ffb452be980646de8de0cd3b6b..3bcd2caaffb668c71f5d06d7670bf2259f0e0861 100644 --- a/sickbeard/providers/newpct.py +++ b/sickbeard/providers/newpct.py @@ -92,7 +92,7 @@ class newpctProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) self.search_params.update({'q': search_string.strip()}) @@ -136,12 +136,12 @@ class newpctProvider(generic.TorrentProvider): # Filter unseeded torrent (Unsupported) # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) @@ -156,6 +156,19 @@ class newpctProvider(generic.TorrentProvider): return results + def getURL(self, url, post_data=None, params=None, timeout=30, json=False, needBytes=False): + """ + needBytes=True when trying access to torrent info (For calling torrent client). Previously we must parse + the URL to get torrent file + """ + if needBytes: + data = helpers.getURL(url, post_data=None, params=None, headers=self.headers, timeout=timeout, + session=self.session, json=json, needBytes=False) + url = re.search(r'http://tumejorserie.com/descargar/.+\.torrent', data, re.DOTALL).group() + + return helpers.getURL(url, post_data=post_data, params=params, headers=self.headers, timeout=timeout, + session=self.session, json=json, needBytes=needBytes) + def downloadResult(self, result): """ Save the result to disk. diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py index a15114d86f379536d7c649295f6ad26fe5ee7420..d8b26fe3f0b19992c9f79af6e12a68c1a75492b4 100644 --- a/sickbeard/providers/newznab.py +++ b/sickbeard/providers/newznab.py @@ -125,7 +125,7 @@ class NewznabProvider(generic.NZBProvider): return False, return_categories, error_string for category in data.caps.categories.findAll('category'): - if hasattr(category, 'attrs') and category.attrs['name'] == 'TV': + if hasattr(category, 'attrs') and 'TV' in category.attrs['name']: return_categories.append({'id': category.attrs['id'], 'name': category.attrs['name']}) for subcat in category.findAll('subcat'): return_categories.append({'id': subcat.attrs['id'], 'name': subcat.attrs['name']}) @@ -282,18 +282,21 @@ class NewznabProvider(generic.NZBProvider): except (AttributeError, TypeError): continue - if title and download_url: - size = seeders = leechers = None - for attr in item.findAll('newznab:attr') + item.findAll('torznab:attr'): - size = helpers.tryInt(attr['value'], -1) if attr['name'] == 'size' else size - seeders = helpers.tryInt(attr['value'], 1) if attr['name'] == 'seeders' else seeders - leechers = helpers.tryInt(attr['value'], 0) if attr['name'] == 'leechers' else leechers + if not (title and download_url): + continue + + seeders = leechers = None + size = helpers.tryInt(item.size, -1) + for attr in item.findAll('newznab:attr') + item.findAll('torznab:attr'): + size = helpers.tryInt(attr['value'], -1) if attr['name'] == 'size' else size + seeders = helpers.tryInt(attr['value'], 1) if attr['name'] == 'seeders' else seeders + leechers = helpers.tryInt(attr['value'], 0) if attr['name'] == 'peers' else leechers - if not size or (torznab and (seeders is None or leechers is None)): - continue + if not size or (torznab and (seeders is None or leechers is None)): + continue - result = {'title': title, 'link': download_url, 'size': size, 'seeders': seeders, 'leechers': leechers} - results.append(result) + result = {'title': title, 'link': download_url, 'size': size, 'seeders': seeders, 'leechers': leechers} + results.append(result) data.decompose() diff --git a/sickbeard/providers/nextgen.py b/sickbeard/providers/nextgen.py index 08e575da4364b18283be23c117d8dbf8ce1b67c2..5915ff6a5f999b815dc9bd88e35bb92caa5850b7 100644 --- a/sickbeard/providers/nextgen.py +++ b/sickbeard/providers/nextgen.py @@ -122,7 +122,7 @@ class NextGenProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) @@ -171,12 +171,12 @@ class NextGenProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/nyaatorrents.py b/sickbeard/providers/nyaatorrents.py index ee7ef56343002f1163f55d7743f39f6adff1d441..1b58081c1697783adab1a487496cec72168a57ac 100644 --- a/sickbeard/providers/nyaatorrents.py +++ b/sickbeard/providers/nyaatorrents.py @@ -55,7 +55,7 @@ class NyaaProvider(generic.TorrentProvider): for mode in search_strings.keys(): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s" % search_string, logger.DEBUG) params = { @@ -64,7 +64,7 @@ class NyaaProvider(generic.TorrentProvider): "sort": 2, # Sort Descending By Seeders "order": 1 } - if mode is not 'RSS': + if mode != 'RSS': params["term"] = search_string.encode('utf-8') searchURL = self.url + '?' + urllib.urlencode(params) @@ -85,16 +85,16 @@ class NyaaProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue - if self.confirmed and not verified and mode is not 'RSS': + if self.confirmed and not verified and mode != 'RSS': logger.log(u"Found result " + title + " but that doesn't seem like a verified result so I'm ignoring it", logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/pretome.py b/sickbeard/providers/pretome.py index f165135dd6b09a1ebf9ae419bf5743b6e7edc7e4..30dbac2d2efaa2d74f989ae7ab462f28c16315f0 100644 --- a/sickbeard/providers/pretome.py +++ b/sickbeard/providers/pretome.py @@ -91,7 +91,7 @@ class PretomeProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) @@ -148,12 +148,12 @@ class PretomeProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/rarbg.py b/sickbeard/providers/rarbg.py index 38921fb59daeefb9109ed48785645f571476ecdb..f2b2bb362f26604ff8559eacfa3625d7b8456ee9 100644 --- a/sickbeard/providers/rarbg.py +++ b/sickbeard/providers/rarbg.py @@ -108,7 +108,7 @@ class RarbgProvider(generic.TorrentProvider): if not self._doLogin(): return results - if epObj != None: + if epObj is not None: ep_indexerid = epObj.show.indexerid ep_indexer = epObj.show.indexer else: @@ -119,7 +119,7 @@ class RarbgProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) if mode is 'RSS': @@ -226,7 +226,7 @@ class RarbgProvider(generic.TorrentProvider): continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/scc.py b/sickbeard/providers/scc.py index 67159e3e5867a320d5379ecffbbd55e513348120..c02ac02d5a63bd6fb2dc7b404fbf5d7525129953 100644 --- a/sickbeard/providers/scc.py +++ b/sickbeard/providers/scc.py @@ -89,10 +89,10 @@ class SCCProvider(generic.TorrentProvider): items = {'Season': [], 'Episode': [], 'RSS': []} for mode in search_strings.keys(): - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (urllib.quote(search_string), self.categories[search_mode]) @@ -140,12 +140,12 @@ class SCCProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/scenetime.py b/sickbeard/providers/scenetime.py index 92dabe2a0ce0a4df2f9aa4577ff8557b626a943f..c4136a07c4fc8a385fce958b5a2b1dc36e35ceda 100644 --- a/sickbeard/providers/scenetime.py +++ b/sickbeard/providers/scenetime.py @@ -81,7 +81,7 @@ class SceneTimeProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (urllib.quote(search_string), self.categories) @@ -132,12 +132,12 @@ class SceneTimeProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/speedcd.py b/sickbeard/providers/speedcd.py index a3c5377a8a474674e1f596e97e7706df4a813924..b5fe4feab1f0d11e383934e3c1e9f06172bb4cbb 100644 --- a/sickbeard/providers/speedcd.py +++ b/sickbeard/providers/speedcd.py @@ -80,7 +80,7 @@ class SpeedCDProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) search_string = '+'.join(search_string.split()) @@ -114,12 +114,12 @@ class SpeedCDProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/strike.py b/sickbeard/providers/strike.py index e64a6915130f529723e3b2c020262b833b7af1ed..a761eb5b893df139042b0afce2c2c95a6f9e05f1 100644 --- a/sickbeard/providers/strike.py +++ b/sickbeard/providers/strike.py @@ -1,5 +1,6 @@ -# Author: matigonkas -# URL: https://github.com/SickRage/sickrage +# coding=utf-8 +# Author: Gonçalo (aka duramato) <matigonkas@outlook.com> +# URL: https://github.com/SickRage/SickRage # # This file is part of SickRage. # @@ -41,7 +42,7 @@ class STRIKEProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: " + search_string.strip(), logger.DEBUG) searchURL = self.url + "api/v2/torrents/search/?category=TV&phrase=" + search_string @@ -65,11 +66,11 @@ class STRIKEProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) item = title, download_url, size, seeders, leechers @@ -91,12 +92,14 @@ class StrikeCache(tvcache.TVCache): def __init__(self, provider_obj): tvcache.TVCache.__init__(self, provider_obj) - - # set this 0 to suppress log line, since we aren't updating it anyways - self.minTime = 0 + + # Cache results for 10 min + self.minTime = 10 def _getRSSData(self): - # no rss for getstrike.net afaik, also can't search with empty string - return {'entries': {}} + + # Use this hacky way for RSS search since most results will use this codec + search_params = {'RSS': ['x264']} + return {'entries': self.provider._doSearch(search_params)} provider = STRIKEProvider() diff --git a/sickbeard/providers/t411.py b/sickbeard/providers/t411.py index 4a1d4d2f9aff776ad6ff46cee3d7720a1a6dc29f..55375b41a8fff87e4a3c5844b70ed59a12af272b 100644 --- a/sickbeard/providers/t411.py +++ b/sickbeard/providers/t411.py @@ -90,7 +90,7 @@ class T411Provider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURLS = ([self.urls['search'] % (search_string, u) for u in self.subcategories], [self.urls['rss']])[mode is 'RSS'] @@ -101,11 +101,11 @@ class T411Provider(generic.TorrentProvider): continue try: - if 'torrents' not in data and mode is not 'RSS': + if 'torrents' not in data and mode != 'RSS': logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) continue - torrents = data['torrents'] if mode is not 'RSS' else data + torrents = data['torrents'] if mode != 'RSS' else data if not torrents: logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) @@ -129,16 +129,16 @@ class T411Provider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue - if self.confirmed and not verified and mode is not 'RSS': + if self.confirmed and not verified and mode != 'RSS': logger.log(u"Found result " + title + " but that doesn't seem like a verified result so I'm ignoring it", logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/thepiratebay.py b/sickbeard/providers/thepiratebay.py index aba4c0b2ed1997f8b8c497597a9a9766d8f2e16a..159a375cef615d116630c7d9a535c4035b50e98d 100644 --- a/sickbeard/providers/thepiratebay.py +++ b/sickbeard/providers/thepiratebay.py @@ -18,8 +18,8 @@ import re +import posixpath # Must use posixpath from urllib import urlencode - from sickbeard import logger from sickbeard import tvcache from sickbeard.providers import generic @@ -42,12 +42,14 @@ class ThePirateBayProvider(generic.TorrentProvider): self.cache = ThePirateBayCache(self) self.urls = { - 'base_url': 'https://pirateproxy.pl/', - 'search': 'https://pirateproxy.pl/s/', - 'rss': 'https://pirateproxy.pl/tv/latest' + 'base_url': 'https://thepiratebay.gd/', + 'search': 'https://thepiratebay.gd/s/', + 'rss': 'https://thepiratebay.gd/tv/latest' } self.url = self.urls['base_url'] + self.custom_url = None + self.headers.update({'User-Agent': USER_AGENT}) """ @@ -75,14 +77,17 @@ class ThePirateBayProvider(generic.TorrentProvider): self.search_params.update({'q': search_string.strip()}) - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: " + search_string, logger.DEBUG) searchURL = self.urls[('search', 'rss')[mode is 'RSS']] + '?' + urlencode(self.search_params) + if self.custom_url: + searchURL = posixpath.join(self.custom_url, searchURL.split(self.url)[1].lstrip('/')) # Must use posixpath + logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) data = self.getURL(searchURL) - # data = self.getURL(self.urls[('search', 'rss')[mode is 'RSS']], params=self.search_params) if not data: + logger.log(u'URL did not return data, maybe try a custom url, or a different one', logger.DEBUG) continue matches = re.compile(self.re_title_url, re.DOTALL).finditer(data) @@ -99,18 +104,18 @@ class ThePirateBayProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue # Accept Torrent only from Good People for every Episode Search if self.confirmed and re.search(r'(VIP|Trusted|Helper|Moderator)', torrent.group(0)) is None: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result %s but that doesn't seem like a trusted result so I'm ignoring it" % title, logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/titansoftv.py b/sickbeard/providers/titansoftv.py index c94ba6ef53ec9044250e4b808cbdb45579c79295..cea172b8bd6cd41ed9a5afab296725cd490a8e14 100644 --- a/sickbeard/providers/titansoftv.py +++ b/sickbeard/providers/titansoftv.py @@ -51,6 +51,7 @@ class TitansOfTVProvider(generic.TorrentProvider): if 'error' in data: logger.log(u"Invalid api key. Check your settings", logger.WARNING) + return False return True @@ -95,7 +96,7 @@ class TitansOfTVProvider(generic.TorrentProvider): # Filter unseeded torrent # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue diff --git a/sickbeard/providers/tntvillage.py b/sickbeard/providers/tntvillage.py index 5b4fc5d4fb8036ee739ac052dc0d46535eb13c13..c00842148d894428c82c6f309e7afa44dc9d37f7 100644 --- a/sickbeard/providers/tntvillage.py +++ b/sickbeard/providers/tntvillage.py @@ -300,12 +300,12 @@ class TNTVillageProvider(generic.TorrentProvider): if last_page: break - if mode is not 'RSS': + if mode != 'RSS': searchURL = (self.urls['search_page'] + '&filter={2}').format(z, self.categories, search_string) else: searchURL = self.urls['search_page'].format(z, self.categories) - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) @@ -379,12 +379,12 @@ class TNTVillageProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/tokyotoshokan.py b/sickbeard/providers/tokyotoshokan.py index ed7acce2a9950d6992d484e8cee6acf92e6ee3fd..6a078faa8de2c8459c887366350a89d79b1d4992 100644 --- a/sickbeard/providers/tokyotoshokan.py +++ b/sickbeard/providers/tokyotoshokan.py @@ -95,7 +95,7 @@ class TokyoToshokanProvider(generic.TorrentProvider): # Filter unseeded torrent # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue diff --git a/sickbeard/providers/torrentbytes.py b/sickbeard/providers/torrentbytes.py index 30711eda2f6b62ddb9b4f378dc1e1e1987854f98..0a0d3a68a453a52169ef4ee46912320b052ea94d 100644 --- a/sickbeard/providers/torrentbytes.py +++ b/sickbeard/providers/torrentbytes.py @@ -84,7 +84,7 @@ class TorrentBytesProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) @@ -147,12 +147,12 @@ class TorrentBytesProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/torrentday.py b/sickbeard/providers/torrentday.py index 1d84445385dd6c11e7df7f754b65aed7db1b98dd..62550dab1691fff5d1d277d98ce36e2beb7ced46 100644 --- a/sickbeard/providers/torrentday.py +++ b/sickbeard/providers/torrentday.py @@ -102,7 +102,7 @@ class TorrentDayProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) search_string = '+'.join(search_string.split()) @@ -138,12 +138,12 @@ class TorrentDayProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/torrentleech.py b/sickbeard/providers/torrentleech.py index 2ab8987dcaa441a6788cb59c680f8442852d3d8f..7ccac63bb57895d8fd88f7fac4a85e164b373c66 100644 --- a/sickbeard/providers/torrentleech.py +++ b/sickbeard/providers/torrentleech.py @@ -125,12 +125,12 @@ class TorrentLeechProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/torrentproject.py b/sickbeard/providers/torrentproject.py index b9c52266d88910d4ea7ab80137ec22b5279499cc..2f84b81c8309da3ba1b787babf7bfc507026e772 100644 --- a/sickbeard/providers/torrentproject.py +++ b/sickbeard/providers/torrentproject.py @@ -48,7 +48,7 @@ class TORRENTPROJECTProvider(generic.TorrentProvider): for mode in search_strings.keys(): # Mode = RSS, Season, Episode logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) @@ -68,7 +68,7 @@ class TORRENTPROJECTProvider(generic.TorrentProvider): seeders = helpers.tryInt(torrents[i]["seeds"], 1) leechers = helpers.tryInt(torrents[i]["leechs"], 0) if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Torrent doesn't meet minimum seeds & leechers not selecting : %s" % title, logger.DEBUG) continue @@ -77,7 +77,7 @@ class TORRENTPROJECTProvider(generic.TorrentProvider): try: assert seeders < 10 - assert mode is not 'RSS' + assert mode != 'RSS' logger.log(u"Torrent has less than 10 seeds getting dyn trackers: " + title, logger.DEBUG) trackerUrl = self.urls['api'] + "" + t_hash + "/trackers_json" jdata = self.getURL(trackerUrl, json=True) @@ -91,7 +91,7 @@ class TORRENTPROJECTProvider(generic.TorrentProvider): item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s" % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/torrentz.py b/sickbeard/providers/torrentz.py index f1dd3e8ffaba848dfced0340fdbe8c4e770df8c5..576a8ca114fea8a6aef3a229e2cb20761e4d8e2a 100644 --- a/sickbeard/providers/torrentz.py +++ b/sickbeard/providers/torrentz.py @@ -63,7 +63,7 @@ class TORRENTZProvider(generic.TorrentProvider): for mode in search_strings: for search_string in search_strings[mode]: search_url = self.urls['verified'] if self.confirmed else self.urls['feed'] - if mode is not 'RSS': + if mode != 'RSS': search_url += '?q=' + urllib.parse.quote_plus(search_string) logger.log(search_url) @@ -112,7 +112,7 @@ class TORRENTZProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue diff --git a/sickbeard/providers/transmitthenet.py b/sickbeard/providers/transmitthenet.py index e4e5b30e79f0ef4931a0115c74dedd36880a1d24..2049b638ddb4b600d2425a1e32268f8db2d1c2c4 100644 --- a/sickbeard/providers/transmitthenet.py +++ b/sickbeard/providers/transmitthenet.py @@ -90,7 +90,7 @@ class TransmitTheNetProvider(generic.TorrentProvider): for mode in search_strings.keys(): for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) data = self.getURL(self.urls['index'], params=self.search_params) @@ -133,12 +133,12 @@ class TransmitTheNetProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/tvchaosuk.py b/sickbeard/providers/tvchaosuk.py index 49aab00075b0503218f75e6772d567116bda63eb..dee2176c41791fff58c385a2e5429c28f4b228b4 100644 --- a/sickbeard/providers/tvchaosuk.py +++ b/sickbeard/providers/tvchaosuk.py @@ -131,7 +131,7 @@ class TVChaosUKProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_strings[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) self.search_params['keywords'] = search_string.strip() @@ -156,7 +156,7 @@ class TVChaosUKProvider(generic.TorrentProvider): # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) continue @@ -176,7 +176,7 @@ class TVChaosUKProvider(generic.TorrentProvider): size = -1 item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/providers/xthor.py b/sickbeard/providers/xthor.py index 71cf973b5fb0310756133e72110c6c825f0b0370..2805fbe5b6270d4f4b9cf6ea7511fab4600510a4 100644 --- a/sickbeard/providers/xthor.py +++ b/sickbeard/providers/xthor.py @@ -80,7 +80,7 @@ class XthorProvider(generic.TorrentProvider): logger.log(u"Search Mode: %s" % mode, logger.DEBUG) for search_string in search_params[mode]: - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) searchURL = self.urlsearch % (urllib.quote(search_string), self.categories) @@ -109,12 +109,12 @@ class XthorProvider(generic.TorrentProvider): # Filter unseeded torrent # if seeders < self.minseed or leechers < self.minleech: - # if mode is not 'RSS': + # if mode != 'RSS': # logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) # continue item = title, download_url, size, seeders, leechers - if mode is not 'RSS': + if mode != 'RSS': logger.log(u"Found result: %s " % title, logger.DEBUG) items[mode].append(item) diff --git a/sickbeard/sab.py b/sickbeard/sab.py index b22c1b7f470fc01c3f4f31ebb7f62a492016b3e7..4c1cc0e7d8952ff68d86f03c4fc8b99c6191ce9a 100644 --- a/sickbeard/sab.py +++ b/sickbeard/sab.py @@ -44,11 +44,11 @@ def sendNZB(nzb): # set up a dict with the URL params in it params = {} - if sickbeard.SAB_USERNAME != None: + if sickbeard.SAB_USERNAME is not None: params['ma_username'] = sickbeard.SAB_USERNAME - if sickbeard.SAB_PASSWORD != None: + if sickbeard.SAB_PASSWORD is not None: params['ma_password'] = sickbeard.SAB_PASSWORD - if sickbeard.SAB_APIKEY != None: + if sickbeard.SAB_APIKEY is not None: params['apikey'] = sickbeard.SAB_APIKEY category = sickbeard.SAB_CATEGORY if nzb.show.is_anime: @@ -61,7 +61,7 @@ def sendNZB(nzb): if nzb.show.is_anime: category = sickbeard.SAB_CATEGORY_ANIME_BACKLOG - if category != None: + if category is not None: params['cat'] = category # use high priority if specified (recently aired episode) @@ -120,7 +120,7 @@ def sendNZB(nzb): return False # this means we couldn't open the connection or something just as bad - if f == None: + if f is None: logger.log(u"No data returned from SABnzbd, NZB not sent", logger.ERROR) return False @@ -202,7 +202,7 @@ def _sabURLOpenSimple(url): except httplib.InvalidURL, e: logger.log(u"Invalid SAB host, check your config: " + ex(e), logger.ERROR) return False, "Invalid SAB host" - if f == None: + if f is None: logger.log(u"No data returned from SABnzbd", logger.ERROR) return False, "No data returned from SABnzbd" else: diff --git a/sickbeard/search.py b/sickbeard/search.py index 57f22ea032da0afc316f1f82b575c091344fd600..293016c7039b546878ffa48e3bb7593154954670 100644 --- a/sickbeard/search.py +++ b/sickbeard/search.py @@ -52,7 +52,7 @@ def _downloadResult(result): """ resProvider = result.provider - if resProvider == None: + if resProvider is None: logger.log(u"Invalid provider name - this is a coding error, report it please", logger.ERROR) return False @@ -79,7 +79,7 @@ def _downloadResult(result): except EnvironmentError, e: logger.log(u"Error trying to save NZB to black hole: " + ex(e), logger.ERROR) newResult = False - elif resProvider.providerType == "torrent": + elif result.resultType == "torrent": newResult = resProvider.downloadResult(result) else: logger.log(u"Invalid provider type - this is a coding error, report it please", logger.ERROR) @@ -106,7 +106,7 @@ def snatchEpisode(result, endStatus=SNATCHED): for curEp in result.episodes: if datetime.date.today() - curEp.airdate <= datetime.timedelta(days=7): result.priority = 1 - if re.search(r'(^|[\. _-])(proper|repack)([\. _-]|$)', result.name, re.I) != None: + if re.search(r'(^|[\. _-])(proper|repack)([\. _-]|$)', result.name, re.I) is not None: endStatus = SNATCHED_PROPER if result.url.startswith('magnet') or result.url.endswith('torrent'): @@ -480,7 +480,7 @@ def searchProviders(show, episodes, manualSearch=False, downCurQuality=False): search_mode = curProvider.search_mode # Always search for episode when manually searching when in sponly - if search_mode == 'sponly' and manualSearch == True: + if search_mode == 'sponly' and manualSearch is True: search_mode = 'eponly' while True: diff --git a/sickbeard/searchBacklog.py b/sickbeard/searchBacklog.py index b25ca0801b5400bafb6a130b44064adad3e3f0c3..5f9f8d9a48ce8d3fffd7ffe7c90cf4a483615267 100644 --- a/sickbeard/searchBacklog.py +++ b/sickbeard/searchBacklog.py @@ -103,8 +103,9 @@ class BacklogSearcher: backlog_queue_item = search_queue.BacklogQueueItem(curShow, segment) sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item) # @UndefinedVariable - else: - logger.log(u"Nothing needs to be downloaded for {show_name}, skipping".format(show_name=curShow.name),logger.DEBUG) + + if not segments: + logger.log(u"Nothing needs to be downloaded for %s, skipping" % curShow.name, logger.DEBUG) # don't consider this an actual backlog search if we only did recent eps # or if we only did certain shows @@ -123,7 +124,7 @@ class BacklogSearcher: if len(sqlResults) == 0: lastBacklog = 1 - elif sqlResults[0]["last_backlog"] == None or sqlResults[0]["last_backlog"] == "": + elif sqlResults[0]["last_backlog"] is None or sqlResults[0]["last_backlog"] == "": lastBacklog = 1 else: lastBacklog = int(sqlResults[0]["last_backlog"]) @@ -143,7 +144,7 @@ class BacklogSearcher: logger.log(u"Seeing if we need anything from {show_name}".format(show_name=show.name), logger.DEBUG) myDB = db.DBConnection() - sqlResults = myDB.select("SELECT status, season, episode FROM tv_episodes WHERE season > 0 AND airdate > ? AND showid = ?", + sqlResults = myDB.select("SELECT status, season, episode FROM tv_episodes WHERE airdate > ? AND showid = ?", [fromDate.toordinal(), show.indexerid]) # check through the list of statuses to see if we want any diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py index c195832099dcb91a095eff98712106131be65b32..9cec79e7257cbdbd4844e319cfb7ffba7faf8995 100644 --- a/sickbeard/show_queue.py +++ b/sickbeard/show_queue.py @@ -48,7 +48,7 @@ class ShowQueue(generic_queue.GenericQueue): return show.indexerid in [x.show.indexerid if x.show else 0 for x in self.queue if x.action_id in actions] def _isBeingSomethinged(self, show, actions): - return self.currentItem != None and show == self.currentItem.show and \ + return self.currentItem is not None and show == self.currentItem.show and \ self.currentItem.action_id in actions def isInUpdateQueue(self, show): @@ -79,7 +79,7 @@ class ShowQueue(generic_queue.GenericQueue): return self._isBeingSomethinged(show, (ShowQueueActions.SUBTITLE,)) 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 is not None and x.isLoading] loadingShowList = property(_getLoadingShowList) @@ -256,7 +256,7 @@ class QueueItemAdd(ShowQueueItem): Returns the show name if there is a show object created, if not returns the dir that the show is being added to. """ - if self.show == None: + if self.show is None: return self.showDir return self.show.name @@ -267,7 +267,7 @@ class QueueItemAdd(ShowQueueItem): Returns True if we've gotten far enough to have a show object, or False if we still only know the folder name. """ - if self.show == None: + if self.show is None: return True return False @@ -352,13 +352,13 @@ class QueueItemAdd(ShowQueueItem): # set up initial values self.show.location = self.showDir - self.show.subtitles = self.subtitles if self.subtitles != None else sickbeard.SUBTITLES_DEFAULT + self.show.subtitles = self.subtitles if self.subtitles is not None else sickbeard.SUBTITLES_DEFAULT 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.anime = self.anime if self.anime != None else sickbeard.ANIME_DEFAULT - self.show.scene = self.scene if self.scene != None else sickbeard.SCENE_DEFAULT - self.show.archive_firstmatch = self.archive if self.archive != None else sickbeard.ARCHIVE_DEFAULT - self.show.paused = self.paused if self.paused != None else False + self.show.flatten_folders = self.flatten_folders if self.flatten_folders is not None else sickbeard.FLATTEN_FOLDERS_DEFAULT + self.show.anime = self.anime if self.anime is not None else sickbeard.ANIME_DEFAULT + self.show.scene = self.scene if self.scene is not None else sickbeard.SCENE_DEFAULT + self.show.archive_firstmatch = self.archive if self.archive is not None else sickbeard.ARCHIVE_DEFAULT + self.show.paused = self.paused if self.paused is not None else False # set up default new/missing episode status logger.log(u"Setting all episodes to the specified default status: " + str(self.show.default_ep_status)) @@ -479,7 +479,7 @@ class QueueItemAdd(ShowQueueItem): self.finish() def _finishEarly(self): - if self.show != None: + if self.show is not None: sickbeard.showQueueScheduler.action.removeShow(self.show) self.finish() diff --git a/sickbeard/tv.py b/sickbeard/tv.py index 6999fb4ef6527ecc6927084fe00b245d45650141..93ef682dbc8324b46d46c3af1cba6da17157c016 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -115,7 +115,7 @@ class TVShow(object): self.release_groups = None otherShow = helpers.findCertainShow(sickbeard.showList, self.indexerid) - if otherShow != None: + if otherShow is not None: raise MultipleShowObjectsException("Can't create a show if it already exists") self.loadFromDB() @@ -283,7 +283,7 @@ class TVShow(object): else: ep = TVEpisode(self, season, episode) - if ep != None: + if ep is not None: self.episodes[season][episode] = ep return self.episodes[season][episode] @@ -447,7 +447,7 @@ class TVShow(object): curEpisode.release_name = ep_file_name # store the reference in the show - if curEpisode != None: + if curEpisode is not None: if self.subtitles: try: curEpisode.refreshSubtitles() @@ -644,7 +644,7 @@ class TVShow(object): return None # for now lets assume that any episode in the show dir belongs to that show - season = parse_result.season_number if parse_result.season_number != None else 1 + season = parse_result.season_number if parse_result.season_number is not None else 1 episodes = parse_result.episode_numbers rootEp = None @@ -974,7 +974,7 @@ class TVShow(object): "SELECT airdate, season, episode FROM tv_episodes WHERE showid = ? AND airdate >= ? AND status IN (?,?) ORDER BY airdate ASC LIMIT 1", [self.indexerid, datetime.date.today().toordinal(), UNAIRED, WANTED]) - if sqlResults == None or len(sqlResults) == 0: + if sqlResults is None or len(sqlResults) == 0: logger.log(str(self.indexerid) + u": No episode found... need to implement a show status", logger.DEBUG) self.nextaired = "" else: @@ -1298,37 +1298,23 @@ class TVShow(object): return Overview.SKIPPED elif epStatus in Quality.ARCHIVED: return Overview.GOOD - elif epStatus in Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.FAILED + Quality.SNATCHED_BEST: - - _, bestQualities = Quality.splitQuality(self.quality) # @UnusedVariable - if bestQualities: - maxBestQuality = max(bestQualities) - minBestQuality = min(bestQualities) - else: - maxBestQuality = None - minBestQuality = None - + elif epStatus in Quality.FAILED: + return Overview.WANTED + elif epStatus in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.SNATCHED_BEST: + return Overview.SNATCHED + elif epStatus in Quality.DOWNLOADED: + anyQualities, bestQualities = Quality.splitQuality(self.quality) # @UnusedVariable epStatus, curQuality = Quality.splitCompositeStatus(epStatus) - if epStatus == FAILED: - return Overview.WANTED - if epStatus == DOWNLOADED and curQuality == Quality.UNKNOWN: - return Overview.QUAL - elif epStatus in (SNATCHED, SNATCHED_PROPER, SNATCHED_BEST): - return Overview.SNATCHED - # if they don't want re-downloads then we call it good if they have anything - elif maxBestQuality == None: - return Overview.GOOD - # if the want only first match and already have one call it good - elif self.archive_firstmatch and curQuality in bestQualities: - return Overview.GOOD - # if they want only first match and current quality is higher than minimal best quality call it good - elif self.archive_firstmatch and minBestQuality != None and curQuality > minBestQuality: + if curQuality not in anyQualities + bestQualities: + if curQuality != Quality.UNKNOWN and curQuality > max(anyQualities): + return Overview.GOOD + else: + return Overview.QUAL + elif self.archive_firstmatch: return Overview.GOOD - # if they have one but it's not the best they want then mark it as qual - elif curQuality < maxBestQuality: + elif bestQualities and curQuality not in bestQualities: return Overview.QUAL - # if it's >= maxBestQuality then it's good else: return Overview.GOOD @@ -2047,7 +2033,7 @@ class TVEpisode(object): singleName = False break - if curGoodName == None: + if curGoodName is None: curGoodName = match.group(1) elif curGoodName != match.group(1): singleName = False @@ -2087,7 +2073,7 @@ class TVEpisode(object): if name: name = helpers.remove_non_release_groups(helpers.remove_extension(name)) else: - return "" + return '' try: np = NameParser(name, showObj=show, naming_pattern=True) @@ -2098,7 +2084,7 @@ class TVEpisode(object): if not parse_result.release_group: return '' - return parse_result.release_group + return parse_result.release_group.strip('.- []{}') _, epQual = Quality.splitCompositeStatus(self.status) # @UnusedVariable @@ -2115,7 +2101,7 @@ class TVEpisode(object): if not rel_grp['location']: del rel_grp['location'] if hasattr(self, '_release_group'): # from the release group field in db - rel_grp['database'] = self._release_group + rel_grp['database'] = self._release_group.strip('.- []{}') if not rel_grp['database']: del rel_grp['database'] if hasattr(self, 'release_name'): # from the release name field in db @@ -2195,14 +2181,14 @@ class TVEpisode(object): Manipulates an episode naming pattern and then fills the template in """ - if pattern == None: + if pattern is None: pattern = sickbeard.NAMING_PATTERN - if multi == None: + if multi is None: multi = sickbeard.NAMING_MULTI_EP if sickbeard.NAMING_CUSTOM_ANIME: - if anime_type == None: + if anime_type is None: anime_type = sickbeard.NAMING_ANIME else: anime_type = 3 @@ -2406,7 +2392,7 @@ class TVEpisode(object): Just the filename of the episode, formatted based on the naming settings """ - if pattern == None: + if pattern is None: # we only use ABD if it's enabled, this is an ABD show, AND this is not a multi-ep if self.show.air_by_date and sickbeard.NAMING_CUSTOM_ABD and not self.relatedEps: pattern = sickbeard.NAMING_ABD_PATTERN diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py index 6c6ce5677c29117b532d7f9dd70be46af44da64c..5a9229bffa4c9fd47f5f07c2a828592891489ac3 100644 --- a/sickbeard/tvcache.py +++ b/sickbeard/tvcache.py @@ -298,7 +298,7 @@ class TVCache(object): myDB = self._getDB() sql = "SELECT * FROM [" + self.providerID + "] WHERE name LIKE '%.PROPER.%' OR name LIKE '%.REPACK.%'" - if date != None: + if date is not None: sql += " AND time >= " + str(int(time.mktime(date.timetuple()))) propers_results = myDB.select(sql) diff --git a/sickbeard/ui.py b/sickbeard/ui.py index 3d7c559bf934d59c1d188a2b4c6d22ca90b50dc2..7b62dac3f416e2d5f430a96dd3768cb0179464bb 100644 --- a/sickbeard/ui.py +++ b/sickbeard/ui.py @@ -128,7 +128,7 @@ class ProgressIndicators(): # if any of the progress indicators are done take them off the list for curPI in ProgressIndicators._pi[name]: - if curPI != None and curPI.percentComplete() == 100: + if curPI is not None and curPI.percentComplete() == 100: ProgressIndicators._pi[name].remove(curPI) # return the list of progress indicators associated with this name diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index b3dbf50cd0bf4cac5c6032f00151a6a9bdd7e88a..64a05f2f2a52d9e27698220848c3af85f8645ed5 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -71,7 +71,7 @@ class CheckVersion(object): if sickbeard.AUTO_UPDATE: logger.log(u"New update found for SickRage, starting auto-updater ...") ui.notifications.message('New update found for SickRage, starting auto-updater') - if self.run_backup_if_safe() is True: + if self.run_backup_if_safe(): if sickbeard.versionCheckScheduler.action.update(): logger.log(u"Update was successful!") ui.notifications.message('Update was successful') @@ -96,7 +96,7 @@ class CheckVersion(object): if not os.path.isdir(backupDir): os.mkdir(backupDir) - if self._keeplatestbackup(backupDir) == True and self._backup(backupDir) == True: + if self._keeplatestbackup(backupDir) and self._backup(backupDir): logger.log(u"Config backup successful, updating...") ui.notifications.message('Backup', 'Config backup successful, updating...') return True @@ -155,22 +155,26 @@ class CheckVersion(object): def safe_to_update(self): def db_safe(self): + message = { + 'equal': { + 'type': logger.DEBUG, + 'text': u"We can proceed with the update. New update has same DB version"}, + 'upgrade': { + 'type': logger.WARNING, + 'text': u"We can't proceed with the update. New update has a new DB version. Please manually update"}, + 'downgrade': { + 'type': logger.ERROR, + 'text': u"We can't proceed with the update. New update has a old DB version. It's not possible to downgrade"}, + } try: result = self.getDBcompare() - if result == 'equal': - logger.log(u"We can proceed with the update. New update has same DB version", logger.DEBUG) - return True - elif result == 'upgrade': - logger.log(u"We can't proceed with the update. New update has a new DB version. Please manually update", logger.WARNING) - return False - elif result == 'downgrade': - logger.log(u"We can't proceed with the update. New update has a old DB version. It's not possible to downgrade", logger.ERROR) - return False + if result in message: + logger.log(message[result]['text'], message[result]['type']) # unpack the result message into a log entry else: logger.log(u"We can't proceed with the update. Unable to check remote DB version. Error: %s" % result, logger.ERROR) - return False - except Exception as e: - logger.log(u"We can't proceed with the update. Unable to compare DB version. Error: %s" % repr(e), logger.ERROR) + return result in ['equal'] # add future True results to the list + except Exception as error: + logger.log(u"We can't proceed with the update. Unable to compare DB version. Error: %s" % repr(error), logger.ERROR) return False def postprocessor_safe(): @@ -193,7 +197,7 @@ class CheckVersion(object): postprocessor_safe = postprocessor_safe() showupdate_safe = showupdate_safe() - if db_safe == True and postprocessor_safe == True and showupdate_safe == True: + if db_safe and postprocessor_safe and showupdate_safe: logger.log(u"Proceeding with auto update", logger.DEBUG) return True else: diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py index 4a4a8bd980c7466cc8209ebffb3558696582a208..11fb7e6ae0b110f9fe738372169bebb8913b7334 100644 --- a/sickbeard/webapi.py +++ b/sickbeard/webapi.py @@ -1,3 +1,4 @@ +# coding=utf-8 # Author: Dennis Lutter <lad1337@gmail.com> # Author: Jonathon Saine <thezoggy@gmail.com> # URL: http://code.google.com/p/sickbeard/ @@ -16,7 +17,10 @@ # # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. -# pylint: disable=W0223,E0202 + +# TODO: break this up into separate files +# pylint: disable=C0301,C0302 +# pylint: disable=E1101,E0202,C0111,C0103 import io import os @@ -64,8 +68,10 @@ from sickbeard.common import statusStrings try: import json except ImportError: + # pylint: disable=F0401 import simplejson as json +# pylint: disable=F0401 from tornado.web import RequestHandler indexer_ids = ["indexerid", "tvdbid"] @@ -109,13 +115,13 @@ class ApiHandler(RequestHandler): # set the output callback # default json - outputCallbackDict = { + output_callback_dict = { 'default': self._out_as_json, 'image': self._out_as_image, } - accessMsg = u"API :: " + self.request.remote_ip + " - gave correct API KEY. ACCESS GRANTED" - logger.log(accessMsg, logger.DEBUG) + access_msg = u"API :: " + self.request.remote_ip + " - gave correct API KEY. ACCESS GRANTED" + logger.log(access_msg, logger.DEBUG) # set the original call_dispatcher as the local _call_dispatcher _call_dispatcher = self.call_dispatcher @@ -127,24 +133,24 @@ class ApiHandler(RequestHandler): del kwargs["profile"] try: - outDict = _call_dispatcher(args, kwargs) + out_dict = _call_dispatcher(args, kwargs) except Exception, e: # real internal error oohhh nooo :( logger.log(u"API :: " + ex(e), logger.ERROR) - errorData = { + error_data = { "error_msg": ex(e), "args": args, "kwargs": kwargs } - outDict = _responds(RESULT_FATAL, errorData, - "SickRage encountered an internal error! Please report to the Devs") + out_dict = _responds(RESULT_FATAL, error_data, + "SickRage encountered an internal error! Please report to the Devs") - if 'outputType' in outDict: - outputCallback = outputCallbackDict[outDict['outputType']] + if 'outputType' in out_dict: + output_callback = output_callback_dict[out_dict['outputType']] else: - outputCallback = outputCallbackDict['default'] + output_callback = output_callback_dict['default'] try: - self.finish(outputCallback(outDict)) + self.finish(output_callback(out_dict)) except Exception: pass @@ -157,7 +163,7 @@ class ApiHandler(RequestHandler): try: out = json.dumps(_dict, ensure_ascii=False, sort_keys=True) callback = self.get_query_argument('callback', None) or self.get_query_argument('jsonp', None) - if callback is not None: + if callback: out = callback + '(' + out + ');' # wrap with JSONP call if requested except Exception, e: # if we fail to generate the output fake an error logger.log(u"API :: " + traceback.format_exc(), logger.DEBUG) @@ -174,101 +180,94 @@ class ApiHandler(RequestHandler): logger.log(u"API :: all args: '" + str(args) + "'", logger.DEBUG) logger.log(u"API :: all kwargs: '" + str(kwargs) + "'", logger.DEBUG) - cmds = None + commands = None if args: - cmds = args[0] - args = args[1:] - - if "cmd" in kwargs: - cmds = kwargs["cmd"] - del kwargs["cmd"] - - outDict = {} - if cmds is not None: - cmds = cmds.split("|") - multiCmds = bool(len(cmds) > 1) - for cmd in cmds: - curArgs, curKwargs = self.filter_params(cmd, args, kwargs) - cmdIndex = None - if len(cmd.split("_")) > 1: # was a index used for this cmd ? - cmd, cmdIndex = cmd.split("_") # this gives us the clear cmd and the index - - logger.log(u"API :: " + cmd + ": curKwargs " + str(curKwargs), logger.DEBUG) - if not (multiCmds and cmd in ('show.getbanner', 'show.getfanart', 'show.getnetworklogo', 'show.getposter')): # skip these cmd while chaining + commands, args = args[0], args[1:] + commands = kwargs.pop("cmd", commands) + + out_dict = {} + if commands: + commands = commands.split("|") + multi_commands = len(commands) > 1 + for cmd in commands: + cur_args, cur_kwargs = self.filter_params(cmd, args, kwargs) + + cmd = cmd.split("_") # was a index used for this cmd ? + cmd, cmd_index = cmd[0], cmd[1:] # this gives us the clear cmd and the index + + logger.log(u"API :: " + cmd + ": cur_kwargs " + str(cur_kwargs), logger.DEBUG) + if not (cmd in ('show.getbanner', 'show.getfanart', 'show.getnetworklogo', 'show.getposter') and + multi_commands): # skip these cmd while chaining try: if cmd in function_mapper: - # map function - func = function_mapper.get(cmd) - - # add request handler to function - func.rh = self - - # call function and get response back - curOutDict = func(curArgs, curKwargs).run() + func = function_mapper.get(cmd) # map function + func.rh = self # add request handler to function + cur_out_dict = func(cur_args, cur_kwargs).run() # call function and get response elif _is_int(cmd): - curOutDict = TVDBShorthandWrapper(curArgs, curKwargs, cmd).run() + cur_out_dict = TVDBShorthandWrapper(cur_args, cur_kwargs, cmd).run() else: - curOutDict = _responds(RESULT_ERROR, "No such cmd: '" + cmd + "'") - except ApiError, e: # Api errors that we raised, they are harmless - curOutDict = _responds(RESULT_ERROR, msg=ex(e)) - else: # if someone chained one of the forbiden cmds they will get an error for this one cmd - curOutDict = _responds(RESULT_ERROR, msg="The cmd '" + cmd + "' is not supported while chaining") - - if multiCmds: - # note: if multiple same cmds are issued but one has not an index defined it will override all others - # or the other way around, this depends on the order of the cmds - # this is not a bug - if cmdIndex is None: # do we need a index dict for this cmd ? - outDict[cmd] = curOutDict + cur_out_dict = _responds(RESULT_ERROR, "No such cmd: '" + cmd + "'") + except ApiError as error: # Api errors that we raised, they are harmless + cur_out_dict = _responds(RESULT_ERROR, msg=ex(error)) + else: # if someone chained one of the forbidden commands they will get an error for this one cmd + cur_out_dict = _responds(RESULT_ERROR, msg="The cmd '" + cmd + "' is not supported while chaining") + + if multi_commands: + # note: if duplicate commands are issued and one has an index defined it will override + # all others or the other way around, depending on the command order + # THIS IS NOT A BUG! + if cmd_index: # do we need an index dict for this cmd ? + if cmd not in out_dict: + out_dict[cmd] = {} + out_dict[cmd][cmd_index] = cur_out_dict else: - if not cmd in outDict: - outDict[cmd] = {} - outDict[cmd][cmdIndex] = curOutDict + out_dict[cmd] = cur_out_dict else: - outDict = curOutDict + out_dict = cur_out_dict - if multiCmds: # if we had multiple cmds we have to wrap it in a response dict - outDict = _responds(RESULT_SUCCESS, outDict) + if multi_commands: # if we had multiple commands we have to wrap it in a response dict + out_dict = _responds(RESULT_SUCCESS, out_dict) else: # index / no cmd given - outDict = CMD_SickBeard(args, kwargs).run() + out_dict = CMD_SickBeard(args, kwargs).run() - return outDict + return out_dict def filter_params(self, cmd, args, kwargs): """ return only params kwargs that are for cmd and rename them to a clean version (remove "<cmd>_") - args are shared across all cmds + args are shared across all commands - all args and kwarks are lowerd + all args and kwargs are lowered cmd are separated by "|" e.g. &cmd=shows|future - kwargs are namespaced with "." e.g. show.indexerid=101501 - if a karg has no namespace asing it anyways (global) + kwargs are name-spaced with "." e.g. show.indexerid=101501 + if a kwarg has no namespace asking it anyways (global) full e.g. /api?apikey=1234&cmd=show.seasonlist_asd|show.seasonlist_2&show.seasonlist_asd.indexerid=101501&show.seasonlist_2.indexerid=79488&sort=asc two calls of show.seasonlist one has the index "asd" the other one "2" - the "indexerid" kwargs / params have the indexed cmd as a namspace + the "indexerid" kwargs / params have the indexed cmd as a namespace and the kwarg / param "sort" is a used as a global """ - curArgs = [] + cur_args = [] for arg in args: - curArgs.append(arg.lower()) - curArgs = tuple(curArgs) + cur_args.append(arg.lower()) + cur_args = tuple(cur_args) - curKwargs = {} + cur_kwargs = {} for kwarg in kwargs: if kwarg.find(cmd + ".") == 0: - cleanKey = kwarg.rpartition(".")[2] - curKwargs[cleanKey] = kwargs[kwarg].lower() - elif not "." in kwarg: # the kwarg was not namespaced therefore a "global" - curKwargs[kwarg] = kwargs[kwarg] - return curArgs, curKwargs + clean_key = kwarg.rpartition(".")[2] + cur_kwargs[clean_key] = kwargs[kwarg].lower() + elif "." not in kwarg: # the kwarg was not name-spaced therefore a "global" + cur_kwargs[kwarg] = kwargs[kwarg] + return cur_args, cur_kwargs class ApiCall(ApiHandler): + _help = {"desc": "This command is not documented. Please report this to the developers."} def __init__(self, args, kwargs): @@ -304,7 +303,7 @@ class ApiCall(ApiHandler): if paramType in self._help: for paramName in paramDict: - if not paramName in self._help[paramType]: + if paramName not in self._help[paramType]: self._help[paramType][paramName] = {} if paramDict[paramName]["allowedValues"]: self._help[paramType][paramName]["allowedValues"] = paramDict[paramName]["allowedValues"] @@ -331,7 +330,7 @@ class ApiCall(ApiHandler): msg = "The required parameters: '" + "','".join(self._missing) + "' where not set" return _responds(RESULT_ERROR, msg=msg) - def check_params(self, args, kwargs, key, default, required, arg_type, allowedValues): + def check_params(self, args, kwargs, key, default, required, arg_type, allowed_values): """ function to check passed params for the shorthand wrapper and to detect missing/required params @@ -345,10 +344,10 @@ class ApiCall(ApiHandler): self.indexer = indexer_ids.index(key) missing = True - orgDefault = default + org_default = default if arg_type == "bool": - allowedValues = [0, 1] + allowed_values = [0, 1] if args: default = args[0] @@ -363,28 +362,27 @@ class ApiCall(ApiHandler): self._requiredParams.append(key) except AttributeError: self._missing = [] - self._requiredParams = {key: {"allowedValues": allowedValues, - "defaultValue": orgDefault, + self._requiredParams = {key: {"allowed_values": allowed_values, + "defaultValue": org_default, "type": arg_type}} if missing and key not in self._missing: self._missing.append(key) else: try: - self._optionalParams[key] = {"allowedValues": allowedValues, - "defaultValue": orgDefault, + self._optionalParams[key] = {"allowed_values": allowed_values, + "defaultValue": org_default, "type": arg_type} except AttributeError: - self._optionalParams = {} - self._optionalParams[key] = {"allowedValues": allowedValues, - "defaultValue": orgDefault, - "type": arg_type} + self._optionalParams = {key: {"allowed_values": allowed_values, + "defaultValue": org_default, + "type": arg_type}} if default: default = self._check_param_type(default, key, arg_type) if arg_type == "bool": arg_type = [] - self._check_param_value(default, key, allowedValues) + self._check_param_value(default, key, allowed_values) return default, args @@ -430,25 +428,25 @@ class ApiCall(ApiHandler): return value - def _check_param_value(self, value, name, allowedValues): + def _check_param_value(self, value, name, allowed_values): """ will check if value (or all values in it ) are in allowed values will raise an exception if value is "out of range" - if bool(allowedValue) == False a check is not performed and all values are excepted + if bool(allowedValue) is False a check is not performed and all values are excepted """ - if allowedValues: + if allowed_values: error = False if isinstance(value, list): for item in value: - if not item in allowedValues: + if item not in allowed_values: error = True else: - if not value in allowedValues: + if value not in allowed_values: error = True if error: # this is kinda a ApiError but raising an error is the only way of quitting here raise ApiError(u"param: '" + str(name) + "' with given value: '" + str( - value) + "' is out of allowed range '" + str(allowedValues) + "'") + value) + "' is out of allowed range '" + str(allowed_values) + "'") class TVDBShorthandWrapper(ApiCall): @@ -489,10 +487,10 @@ def _is_int(data): return True -def _rename_element(dict_obj, oldKey, newKey): +def _rename_element(dict_obj, old_key, new_key): try: - dict_obj[newKey] = dict_obj[oldKey] - del dict_obj[oldKey] + dict_obj[new_key] = dict_obj[old_key] + del dict_obj[old_key] except (ValueError, TypeError, NameError): pass return dict_obj @@ -504,19 +502,17 @@ def _responds(result_type, data=None, msg=""): message is a human readable string, can be empty data is either a dict or a array, can be a empty dict or empty array """ - if data is None: - data = {} return {"result": result_type_map[result_type], "message": msg, - "data": data} + "data": {} if not data else data} -def _get_status_Strings(s): +def _get_status_strings(s): return statusStrings[s] -def _ordinal_to_dateTimeForm(ordinal): - # workaround for episodes with no airdate +def _ordinal_to_datetime_form(ordinal): + # workaround for episodes with no air date if int(ordinal) != 1: date = datetime.date.fromordinal(ordinal) else: @@ -524,7 +520,7 @@ def _ordinal_to_dateTimeForm(ordinal): return date.strftime(dateTimeFormat) -def _ordinal_to_dateForm(ordinal): +def _ordinal_to_date_form(ordinal): if int(ordinal) != 1: date = datetime.date.fromordinal(ordinal) else: @@ -532,28 +528,28 @@ def _ordinal_to_dateForm(ordinal): return date.strftime(dateFormat) -def _historyDate_to_dateTimeForm(timeString): - date = datetime.datetime.strptime(timeString, History.date_format) +def _history_date_to_datetime_form(time_string): + date = datetime.datetime.strptime(time_string, History.date_format) return date.strftime(dateTimeFormat) -def _mapQuality(showObj): - quality_map = _getQualityMap() +def _map_quality(show_obj): + quality_map = _get_quality_map() - anyQualities = [] - bestQualities = [] + any_qualities = [] + best_qualities = [] - iqualityID, aqualityID = Quality.splitQuality(int(showObj)) - if iqualityID: - for quality in iqualityID: - anyQualities.append(quality_map[quality]) - if aqualityID: - for quality in aqualityID: - bestQualities.append(quality_map[quality]) - return anyQualities, bestQualities + i_quality_id, a_quality_id = Quality.splitQuality(int(show_obj)) + if i_quality_id: + for quality in i_quality_id: + any_qualities.append(quality_map[quality]) + if a_quality_id: + for quality in a_quality_id: + best_qualities.append(quality_map[quality]) + return any_qualities, best_qualities -def _getQualityMap(): +def _get_quality_map(): return {Quality.SDTV: 'sdtv', Quality.SDDVD: 'sddvd', Quality.HDTV: 'hdtv', @@ -566,15 +562,15 @@ def _getQualityMap(): Quality.UNKNOWN: 'unknown'} -def _getRootDirs(): +def _get_root_dirs(): if sickbeard.ROOT_DIRS == "": return {} - rootDir = {} + root_dir = {} root_dirs = sickbeard.ROOT_DIRS.split('|') default_index = int(sickbeard.ROOT_DIRS.split('|')[0]) - rootDir["default_index"] = int(sickbeard.ROOT_DIRS.split('|')[0]) + root_dir["default_index"] = int(sickbeard.ROOT_DIRS.split('|')[0]) # remove default_index value from list (this fixes the offset) root_dirs.pop(0) @@ -597,11 +593,12 @@ def _getRootDirs(): if root_dir is default_dir: default = 1 - curDir = {} - curDir['valid'] = valid - curDir['location'] = root_dir - curDir['default'] = default - dir_list.append(curDir) + cur_dir = { + 'valid': valid, + 'location': root_dir, + 'default': default + } + dir_list.append(cur_dir) return dir_list @@ -614,7 +611,7 @@ class ApiError(Exception): class IntParseError(Exception): """ - A value could not be parsed into an int, but should be parsable to an int + A value could not be parsed into an int, but should be parse-able to an int """ @@ -722,44 +719,41 @@ class CMD_Episode(ApiCall): def run(self): """ Get detailed information about an episode """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") - myDB = db.DBConnection(row_type="dict") - sqlResults = myDB.select( + my_db = db.DBConnection(row_type="dict") + sql_results = my_db.select( "SELECT name, description, airdate, status, location, file_size, release_name, subtitles FROM tv_episodes WHERE showid = ? AND episode = ? AND season = ?", [self.indexerid, self.e, self.s]) - if not len(sqlResults) == 1: + if not len(sql_results) == 1: raise ApiError("Episode not found") - episode = sqlResults[0] + episode = sql_results[0] # handle path options # absolute vs relative vs broken - showPath = None + show_path = None try: - showPath = showObj.location + show_path = show_obj.location except ShowDirectoryNotFoundException: pass - if bool(self.fullPath) == True and showPath: - pass - elif bool(self.fullPath) == False and showPath: - # using the length because lstrip removes to much - showPathLength = len(showPath) + 1 # the / or \ yeah not that nice i know - episode["location"] = episode["location"][showPathLength:] - elif not showPath: # show dir is broken ... episode path will be empty + if not show_path: # show dir is broken ... episode path will be empty episode["location"] = "" + elif not self.fullPath: + # using the length because lstrip() removes to much + show_path_length = len(show_path) + 1 # the / or \ yeah not that nice i know + episode["location"] = episode["location"][show_path_length:] # convert stuff to human form - if helpers.tryInt(episode['airdate'], 1) > 693595: # 1900 + if helpers.tryInt(episode['airdate'], 1) > 693595: # 1900 episode['airdate'] = sbdatetime.sbdatetime.sbfdate(sbdatetime.sbdatetime.convert_to_setting( - network_timezones.parse_date_time(int(episode['airdate']), showObj.airs, showObj.network)), - d_preset=dateFormat) + network_timezones.parse_date_time(int(episode['airdate']), show_obj.airs, show_obj.network)), d_preset=dateFormat) else: episode['airdate'] = 'Never' status, quality = Quality.splitCompositeStatus(int(episode["status"])) - episode["status"] = _get_status_Strings(status) + episode["status"] = _get_status_strings(status) episode["quality"] = get_quality_string(quality) episode["file_size_human"] = helpers.pretty_filesize(episode["file_size"]) @@ -790,26 +784,26 @@ class CMD_EpisodeSearch(ApiCall): def run(self): """ Search for an episode """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") # retrieve the episode object and fail if we can't get one - epObj = showObj.getEpisode(int(self.s), int(self.e)) - if isinstance(epObj, str): + ep_obj = show_obj.getEpisode(int(self.s), int(self.e)) + if isinstance(ep_obj, str): return _responds(RESULT_FAILURE, msg="Episode not found") # make a queue item for it and put it on the queue - ep_queue_item = search_queue.ManualSearchQueueItem(showObj, epObj) + ep_queue_item = search_queue.ManualSearchQueueItem(show_obj, ep_obj) sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) # @UndefinedVariable # wait until the queue item tells us whether it worked or not - while ep_queue_item.success == None: # @UndefinedVariable + while ep_queue_item.success is None: # @UndefinedVariable time.sleep(1) # return the correct json value if ep_queue_item.success: - status, quality = Quality.splitCompositeStatus(epObj.status) # @UnusedVariable + status, quality = Quality.splitCompositeStatus(ep_obj.status) # @UnusedVariable # TODO: split quality and status? return _responds(RESULT_SUCCESS, {"quality": get_quality_string(quality)}, "Snatched (" + get_quality_string(quality) + ")") @@ -846,8 +840,8 @@ class CMD_EpisodeSetStatus(ApiCall): def run(self): """ Set the status of an episode or a season (when no episode is provided) """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") # convert the string status to a int @@ -855,22 +849,22 @@ class CMD_EpisodeSetStatus(ApiCall): if str(statusStrings[status]).lower() == str(self.status).lower(): self.status = status break - else: # if we dont break out of the for loop we got here. + else: # if we don't break out of the for loop we got here. # the allowed values has at least one item that could not be matched against the internal status strings raise ApiError("The status string could not be matched to a status. Report to Devs!") ep_list = [] if self.e: - epObj = showObj.getEpisode(self.s, self.e) - if epObj == None: + ep_obj = show_obj.getEpisode(self.s, self.e) + if not ep_obj: return _responds(RESULT_FAILURE, msg="Episode not found") - ep_list = [epObj] + ep_list = [ep_obj] else: - # get all episode numbers frome self,season - ep_list = showObj.getAllEpisodes(season=self.s) + # get all episode numbers from self, season + ep_list = show_obj.getAllEpisodes(season=self.s) - def _epResult(result_code, ep, msg=""): - return {'season': ep.season, 'episode': ep.episode, 'status': _get_status_Strings(ep.status), + def _ep_result(result_code, ep, msg=""): + return {'season': ep.season, 'episode': ep.episode, 'status': _get_status_strings(ep.status), 'result': result_type_map[result_code], 'message': msg} ep_results = [] @@ -879,52 +873,52 @@ class CMD_EpisodeSetStatus(ApiCall): segments = {} sql_l = [] - for epObj in ep_list: - with epObj.lock: + for ep_obj in ep_list: + with ep_obj.lock: if self.status == WANTED: # figure out what episodes are wanted so we can backlog them - if epObj.season in segments: - segments[epObj.season].append(epObj) + if ep_obj.season in segments: + segments[ep_obj.season].append(ep_obj) else: - segments[epObj.season] = [epObj] + segments[ep_obj.season] = [ep_obj] - # don't let them mess up UNAIRED episodes - if epObj.status == UNAIRED: - if self.e != None: # setting the status of a unaired is only considert a failure if we directly wanted this episode, but is ignored on a season request + # don't let them mess up UN-AIRED episodes + if ep_obj.status == UNAIRED: + if self.e is not None: # setting the status of an un-aired is only considered a failure if we directly wanted this episode, but is ignored on a season request ep_results.append( - _epResult(RESULT_FAILURE, epObj, "Refusing to change status because it is UNAIRED")) + _ep_result(RESULT_FAILURE, ep_obj, "Refusing to change status because it is UN-AIRED")) failure = True continue if self.status == FAILED and not sickbeard.USE_FAILED_DOWNLOADS: - ep_results.append(_epResult(RESULT_FAILURE, epObj, "Refusing to change status to FAILED because failed download handling is disabled")) + ep_results.append(_ep_result(RESULT_FAILURE, ep_obj, "Refusing to change status to FAILED because failed download handling is disabled")) failure = True continue # allow the user to force setting the status for an already downloaded episode - if epObj.status in Quality.DOWNLOADED + Quality.ARCHIVED and not self.force: - ep_results.append(_epResult(RESULT_FAILURE, epObj, "Refusing to change status because it is already marked as DOWNLOADED")) + if ep_obj.status in Quality.DOWNLOADED + Quality.ARCHIVED and not self.force: + ep_results.append(_ep_result(RESULT_FAILURE, ep_obj, "Refusing to change status because it is already marked as DOWNLOADED")) failure = True continue - epObj.status = self.status - sql_l.append(epObj.get_sql()) + ep_obj.status = self.status + sql_l.append(ep_obj.get_sql()) if self.status == WANTED: start_backlog = True - ep_results.append(_epResult(RESULT_SUCCESS, epObj)) + ep_results.append(_ep_result(RESULT_SUCCESS, ep_obj)) if len(sql_l) > 0: - myDB = db.DBConnection() - myDB.mass_action(sql_l) + my_db = db.DBConnection() + my_db.mass_action(sql_l) extra_msg = "" if start_backlog: for season, segment in segments.iteritems(): - cur_backlog_queue_item = search_queue.BacklogQueueItem(showObj, segment) + cur_backlog_queue_item = search_queue.BacklogQueueItem(show_obj, segment) sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item) # @UndefinedVariable - logger.log(u"API :: Starting backlog for " + showObj.name + " season " + str( + logger.log(u"API :: Starting backlog for " + show_obj.name + " season " + str( season) + " because some episodes were set to WANTED") extra_msg = " Backlog started" @@ -959,28 +953,28 @@ class CMD_SubtitleSearch(ApiCall): def run(self): """ Search for an episode subtitles """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") # retrieve the episode object and fail if we can't get one - epObj = showObj.getEpisode(int(self.s), int(self.e)) - if isinstance(epObj, str): + ep_obj = show_obj.getEpisode(int(self.s), int(self.e)) + if isinstance(ep_obj, str): return _responds(RESULT_FAILURE, msg="Episode not found") # try do download subtitles for that episode - previous_subtitles = epObj.subtitles + previous_subtitles = ep_obj.subtitles try: - subtitles = epObj.downloadSubtitles() + subtitles = ep_obj.downloadSubtitles() except Exception: return _responds(RESULT_FAILURE, msg='Unable to find subtitles') # return the correct json value - newSubtitles = frozenset(epObj.subtitles).difference(previous_subtitles) - if newSubtitles: - newLangs = [subtitles.fromietf(newSub) for newSub in newSubtitles] - status = 'New subtitles downloaded: %s' % ', '.join([newLang.name for newLang in newLangs]) + new_subtitles = frozenset(ep_obj.subtitles).difference(previous_subtitles) + if new_subtitles: + new_languages = [subtitles.fromietf(newSub) for newSub in new_subtitles] + status = 'New subtitles downloaded: %s' % ', '.join([new_language.name for new_language in new_languages]) response = _responds(RESULT_SUCCESS, msg='New subtitles found') else: status = 'No subtitles downloaded' @@ -1010,27 +1004,27 @@ class CMD_Exceptions(ApiCall): def run(self): """ Get the scene exceptions for all or a given show """ - myDB = db.DBConnection("cache.db", row_type="dict") + my_db = db.DBConnection("cache.db", row_type="dict") - if self.indexerid == None: - sqlResults = myDB.select("SELECT show_name, indexer_id AS 'indexerid' FROM scene_exceptions") + if self.indexerid is None: + sql_results = my_db.select("SELECT show_name, indexer_id AS 'indexerid' FROM scene_exceptions") scene_exceptions = {} - for row in sqlResults: + for row in sql_results: indexerid = row["indexerid"] - if not indexerid in scene_exceptions: + if indexerid not in scene_exceptions: scene_exceptions[indexerid] = [] scene_exceptions[indexerid].append(row["show_name"]) else: - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") - sqlResults = myDB.select( + sql_results = my_db.select( "SELECT show_name, indexer_id AS 'indexerid' FROM scene_exceptions WHERE indexer_id = ?", [self.indexerid]) scene_exceptions = [] - for row in sqlResults: + for row in sql_results: scene_exceptions.append(row["show_name"]) return _responds(RESULT_SUCCESS, scene_exceptions) @@ -1062,14 +1056,14 @@ class CMD_History(ApiCall): for row in data: status, quality = Quality.splitCompositeStatus(int(row["action"])) - status = _get_status_Strings(status) + status = _get_status_strings(status) if self.type and not status.lower() == self.type: continue row["status"] = status row["quality"] = get_quality_string(quality) - row["date"] = _historyDate_to_dateTimeForm(str(row["date"])) + row["date"] = _history_date_to_datetime_form(str(row["date"])) del row["action"] @@ -1134,15 +1128,15 @@ class CMD_Failed(ApiCall): def run(self): """ Get the failed downloads """ - myDB = db.DBConnection('failed.db', row_type="dict") + my_db = db.DBConnection('failed.db', row_type="dict") - ulimit = min(int(self.limit), 100) - if ulimit == 0: - sqlResults = myDB.select("SELECT * FROM failed") + u_limit = min(int(self.limit), 100) + if u_limit == 0: + sql_results = my_db.select("SELECT * FROM failed") else: - sqlResults = myDB.select("SELECT * FROM failed LIMIT ?", [ulimit]) + sql_results = my_db.select("SELECT * FROM failed LIMIT ?", [u_limit]) - return _responds(RESULT_SUCCESS, sqlResults) + return _responds(RESULT_SUCCESS, sql_results) class CMD_Backlog(ApiCall): @@ -1159,27 +1153,27 @@ class CMD_Backlog(ApiCall): shows = [] - myDB = db.DBConnection(row_type="dict") + my_db = db.DBConnection(row_type="dict") for curShow in sickbeard.showList: - showEps = [] + show_eps = [] - sqlResults = myDB.select( + sql_results = my_db.select( "SELECT tv_episodes.*, tv_shows.paused FROM tv_episodes INNER JOIN tv_shows ON tv_episodes.showid = tv_shows.indexer_id WHERE showid = ? and paused = 0 ORDER BY season DESC, episode DESC", [curShow.indexerid]) - for curResult in sqlResults: + for curResult in sql_results: - curEpCat = curShow.getOverview(int(curResult["status"] or -1)) - if curEpCat and curEpCat in (Overview.WANTED, Overview.QUAL): - showEps.append(curResult) + cur_ep_cat = curShow.getOverview(int(curResult["status"] or -1)) + if cur_ep_cat and cur_ep_cat in (Overview.WANTED, Overview.QUAL): + show_eps.append(curResult) - if showEps: + if show_eps: shows.append({ "indexerid": curShow.indexerid, "show_name": curShow.name, "status": curShow.status, - "episodes": showEps + "episodes": show_eps }) return _responds(RESULT_SUCCESS, shows) @@ -1208,7 +1202,7 @@ class CMD_Logs(ApiCall): def run(self): """ Get the logs """ # 10 = Debug / 20 = Info / 30 = Warning / 40 = Error - minLevel = logger.reverseNames[str(self.min_level).upper()] + min_level = logger.reverseNames[str(self.min_level).upper()] data = [] if os.path.isfile(logger.logFile): @@ -1217,11 +1211,11 @@ class CMD_Logs(ApiCall): regex = r"^(\d\d\d\d)\-(\d\d)\-(\d\d)\s*(\d\d)\:(\d\d):(\d\d)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$" - finalData = [] + final_data = [] - numLines = 0 - lastLine = False - numToShow = min(50, len(data)) + num_lines = 0 + last_line = False + num_to_show = min(50, len(data)) for x in reversed(data): @@ -1230,25 +1224,25 @@ class CMD_Logs(ApiCall): if match: level = match.group(7) if level not in logger.reverseNames: - lastLine = False + last_line = False continue - if logger.reverseNames[level] >= minLevel: - lastLine = True - finalData.append(x.rstrip("\n")) + if logger.reverseNames[level] >= min_level: + last_line = True + final_data.append(x.rstrip("\n")) else: - lastLine = False + last_line = False continue - elif lastLine: - finalData.append("AA" + x) + elif last_line: + final_data.append("AA" + x) - numLines += 1 + num_lines += 1 - if numLines >= numToShow: + if num_lines >= num_to_show: break - return _responds(RESULT_SUCCESS, finalData) + return _responds(RESULT_SUCCESS, final_data) class CMD_PostProcess(ApiCall): @@ -1296,7 +1290,7 @@ class CMD_PostProcess(ApiCall): if not self.return_data: data = "" - return _responds(RESULT_SUCCESS, data=data, msg="Started postprocess for %s" % self.path) + return _responds(RESULT_SUCCESS, data=data, msg="Started post-process for %s" % self.path) class CMD_SickBeard(ApiCall): @@ -1341,7 +1335,7 @@ class CMD_SickBeardAddRootDir(ApiCall): location_matched = 0 index = 0 - # dissallow adding/setting an invalid dir + # disallow adding/setting an invalid dir if not ek(os.path.isdir, self.location): return _responds(RESULT_FAILURE, msg="Location is invalid") @@ -1373,7 +1367,7 @@ class CMD_SickBeardAddRootDir(ApiCall): root_dirs_new = '|'.join(unicode(x) for x in root_dirs_new) sickbeard.ROOT_DIRS = root_dirs_new - return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directories updated") + return _responds(RESULT_SUCCESS, _get_root_dirs(), msg="Root directories updated") class CMD_SickBeardCheckVersion(ApiCall): @@ -1386,21 +1380,21 @@ class CMD_SickBeardCheckVersion(ApiCall): ApiCall.__init__(self, args, kwargs) def run(self): - checkversion = CheckVersion() - needs_update = checkversion.check_for_new_version() + check_version = CheckVersion() + needs_update = check_version.check_for_new_version() data = { "current_version": { - "branch": checkversion.get_branch(), - "commit": checkversion.updater.get_cur_commit_hash(), - "version": checkversion.updater.get_cur_version(), + "branch": check_version.get_branch(), + "commit": check_version.updater.get_cur_commit_hash(), + "version": check_version.updater.get_cur_version(), }, "latest_version": { - "branch": checkversion.get_branch(), - "commit": checkversion.updater.get_newest_commit_hash(), - "version": checkversion.updater.get_newest_version(), + "branch": check_version.get_branch(), + "commit": check_version.updater.get_newest_commit_hash(), + "version": check_version.updater.get_newest_version(), }, - "commits_offset": checkversion.updater.get_num_commits_behind(), + "commits_offset": check_version.updater.get_num_commits_behind(), "needs_update": needs_update, } @@ -1418,16 +1412,16 @@ class CMD_SickBeardCheckScheduler(ApiCall): def run(self): """ Get information about the scheduler """ - myDB = db.DBConnection() - sqlResults = myDB.select("SELECT last_backlog FROM info") + my_db = db.DBConnection() + sql_results = my_db.select("SELECT last_backlog FROM info") - backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused() # @UndefinedVariable - backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress() # @UndefinedVariable - nextBacklog = sickbeard.backlogSearchScheduler.nextRun().strftime(dateFormat).decode(sickbeard.SYS_ENCODING) + backlog_paused = sickbeard.searchQueueScheduler.action.is_backlog_paused() # @UndefinedVariable + backlog_running = sickbeard.searchQueueScheduler.action.is_backlog_in_progress() # @UndefinedVariable + next_backlog = sickbeard.backlogSearchScheduler.nextRun().strftime(dateFormat).decode(sickbeard.SYS_ENCODING) - data = {"backlog_is_paused": int(backlogPaused), "backlog_is_running": int(backlogRunning), - "last_backlog": _ordinal_to_dateForm(sqlResults[0]["last_backlog"]), - "next_backlog": nextBacklog} + data = {"backlog_is_paused": int(backlog_paused), "backlog_is_running": int(backlog_running), + "last_backlog": _ordinal_to_date_form(sql_results[0]["last_backlog"]), + "next_backlog": next_backlog} return _responds(RESULT_SUCCESS, data) @@ -1449,9 +1443,9 @@ class CMD_SickBeardDeleteRootDir(ApiCall): def run(self): """ Delete a root (parent) directory from SickRage """ if sickbeard.ROOT_DIRS == "": - return _responds(RESULT_FAILURE, _getRootDirs(), msg="No root directories detected") + return _responds(RESULT_FAILURE, _get_root_dirs(), msg="No root directories detected") - newIndex = 0 + new_index = 0 root_dirs_new = [] root_dirs = sickbeard.ROOT_DIRS.split('|') index = int(root_dirs[0]) @@ -1463,21 +1457,21 @@ class CMD_SickBeardDeleteRootDir(ApiCall): if not curRootDir == self.location: root_dirs_new.append(curRootDir) else: - newIndex = 0 + new_index = 0 for curIndex, curNewRootDir in enumerate(root_dirs_new): if curNewRootDir is old_root_dir: - newIndex = curIndex + new_index = curIndex break root_dirs_new = [urllib.unquote_plus(x) for x in root_dirs_new] if len(root_dirs_new) > 0: - root_dirs_new.insert(0, newIndex) + root_dirs_new.insert(0, new_index) root_dirs_new = "|".join(unicode(x) for x in root_dirs_new) sickbeard.ROOT_DIRS = root_dirs_new # what if the root dir was not found? - return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directory deleted") + return _responds(RESULT_SUCCESS, _get_root_dirs(), msg="Root directory deleted") class CMD_SickBeardGetDefaults(ApiCall): @@ -1492,11 +1486,11 @@ class CMD_SickBeardGetDefaults(ApiCall): def run(self): """ Get SickRage's user default configuration value """ - anyQualities, bestQualities = _mapQuality(sickbeard.QUALITY_DEFAULT) + any_qualities, best_qualities = _map_quality(sickbeard.QUALITY_DEFAULT) data = {"status": statusStrings[sickbeard.STATUS_DEFAULT].lower(), - "flatten_folders": int(sickbeard.FLATTEN_FOLDERS_DEFAULT), "initial": anyQualities, - "archive": bestQualities, "future_show_paused": int(sickbeard.COMING_EPS_DISPLAY_PAUSED)} + "flatten_folders": int(sickbeard.FLATTEN_FOLDERS_DEFAULT), "initial": any_qualities, + "archive": best_qualities, "future_show_paused": int(sickbeard.COMING_EPS_DISPLAY_PAUSED)} return _responds(RESULT_SUCCESS, data) @@ -1530,14 +1524,14 @@ class CMD_SickBeardGetRootDirs(ApiCall): def run(self): """ Get all root (parent) directories """ - return _responds(RESULT_SUCCESS, _getRootDirs()) + return _responds(RESULT_SUCCESS, _get_root_dirs()) class CMD_SickBeardPauseBacklog(ApiCall): _help = { - "desc": "Pause or unpause the backlog search", + "desc": "Pause or un-pause the backlog search", "optionalParameters": { - "pause ": {"desc": "True to pause the backlog search, False to unpause it"} + "pause ": {"desc": "True to pause the backlog search, False to un-pause it"} } } @@ -1549,13 +1543,13 @@ class CMD_SickBeardPauseBacklog(ApiCall): ApiCall.__init__(self, args, kwargs) def run(self): - """ Pause or unpause the backlog search """ + """ Pause or un-pause the backlog search """ if self.pause: sickbeard.searchQueueScheduler.action.pause_backlog() # @UndefinedVariable return _responds(RESULT_SUCCESS, msg="Backlog paused") else: sickbeard.searchQueueScheduler.action.unpause_backlog() # @UndefinedVariable - return _responds(RESULT_SUCCESS, msg="Backlog unpaused") + return _responds(RESULT_SUCCESS, msg="Backlog un-paused") class CMD_SickBeardPing(ApiCall): @@ -1623,23 +1617,23 @@ class CMD_SickBeardSearchIndexers(ApiCall): if self.name and not self.indexerid: # only name was given for _indexer in sickbeard.indexerApi().indexers if self.indexer == 0 else [int(self.indexer)]: - lINDEXER_API_PARMS = sickbeard.indexerApi(_indexer).api_params.copy() + indexer_api_params = sickbeard.indexerApi(_indexer).api_params.copy() if self.lang and not self.lang == sickbeard.INDEXER_DEFAULT_LANGUAGE: - lINDEXER_API_PARMS['language'] = self.lang + indexer_api_params['language'] = self.lang - lINDEXER_API_PARMS['actors'] = False - lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsListUI + indexer_api_params['actors'] = False + indexer_api_params['custom_ui'] = classes.AllShowsListUI - t = sickbeard.indexerApi(_indexer).indexer(**lINDEXER_API_PARMS) + t = sickbeard.indexerApi(_indexer).indexer(**indexer_api_params) try: - apiData = t[str(self.name).encode()] + api_data = t[str(self.name).encode()] except (sickbeard.indexer_shownotfound, sickbeard.indexer_showincomplete, sickbeard.indexer_error): logger.log(u"API :: Unable to find show with id " + str(self.indexerid), logger.WARNING) continue - for curSeries in apiData: + for curSeries in api_data: results.append({indexer_ids[_indexer]: int(curSeries['id']), "name": curSeries['seriesname'], "first_aired": curSeries['firstaired'], @@ -1649,31 +1643,31 @@ class CMD_SickBeardSearchIndexers(ApiCall): elif self.indexerid: for _indexer in sickbeard.indexerApi().indexers if self.indexer == 0 else [int(self.indexer)]: - lINDEXER_API_PARMS = sickbeard.indexerApi(_indexer).api_params.copy() + indexer_api_params = sickbeard.indexerApi(_indexer).api_params.copy() if self.lang and not self.lang == sickbeard.INDEXER_DEFAULT_LANGUAGE: - lINDEXER_API_PARMS['language'] = self.lang + indexer_api_params['language'] = self.lang - lINDEXER_API_PARMS['actors'] = False + indexer_api_params['actors'] = False - t = sickbeard.indexerApi(_indexer).indexer(**lINDEXER_API_PARMS) + t = sickbeard.indexerApi(_indexer).indexer(**indexer_api_params) try: - myShow = t[int(self.indexerid)] + my_show = t[int(self.indexerid)] except (sickbeard.indexer_shownotfound, sickbeard.indexer_showincomplete, sickbeard.indexer_error): logger.log(u"API :: Unable to find show with id " + str(self.indexerid), logger.WARNING) return _responds(RESULT_SUCCESS, {"results": [], "langid": lang_id}) - if not myShow.data['seriesname']: + if not my_show.data['seriesname']: logger.log( u"API :: Found show with indexerid: " + str( self.indexerid) + ", however it contained no show name", logger.DEBUG) return _responds(RESULT_FAILURE, msg="Show contains no name, invalid result") # found show - results = [{indexer_ids[_indexer]: int(myShow.data['id']), - "name": unicode(myShow.data['seriesname']), - "first_aired": myShow.data['firstaired'], + results = [{indexer_ids[_indexer]: int(my_show.data['id']), + "name": unicode(my_show.data['seriesname']), + "first_aired": my_show.data['firstaired'], "indexer": int(_indexer)}] break @@ -1763,18 +1757,18 @@ class CMD_SickBeardSetDefaults(ApiCall): 'fullhdbluray': Quality.FULLHDBLURAY, 'unknown': Quality.UNKNOWN} - iqualityID = [] - aqualityID = [] + i_quality_id = [] + a_quality_id = [] if self.initial: for quality in self.initial: - iqualityID.append(quality_map[quality]) + i_quality_id.append(quality_map[quality]) if self.archive: for quality in self.archive: - aqualityID.append(quality_map[quality]) + a_quality_id.append(quality_map[quality]) - if iqualityID or aqualityID: - sickbeard.QUALITY_DEFAULT = Quality.combineQualities(iqualityID, aqualityID) + if i_quality_id or a_quality_id: + sickbeard.QUALITY_DEFAULT = Quality.combineQualities(i_quality_id, a_quality_id) if self.status: # convert the string status to a int @@ -1782,18 +1776,18 @@ class CMD_SickBeardSetDefaults(ApiCall): if statusStrings[status].lower() == str(self.status).lower(): self.status = status break - # this should be obsolete bcause of the above - if not self.status in statusStrings: + # this should be obsolete because of the above + if self.status not in statusStrings: raise ApiError("Invalid Status") # only allow the status options we want if int(self.status) not in (3, 5, 6, 7): raise ApiError("Status Prohibited") sickbeard.STATUS_DEFAULT = self.status - if self.flatten_folders != None: + if self.flatten_folders is not None: sickbeard.FLATTEN_FOLDERS_DEFAULT = int(self.flatten_folders) - if self.future_show_paused != None: + if self.future_show_paused is not None: sickbeard.COMING_EPS_DISPLAY_PAUSED = int(self.future_show_paused) return _responds(RESULT_SUCCESS, msg="Saved defaults") @@ -1826,11 +1820,11 @@ class CMD_SickBeardUpdate(ApiCall): ApiCall.__init__(self, args, kwargs) def run(self): - checkversion = CheckVersion() + check_version = CheckVersion() - if checkversion.check_for_new_version(): - if checkversion.run_backup_if_safe(): - checkversion.update() + if check_version.check_for_new_version(): + if check_version.run_backup_if_safe(): + check_version.update() return _responds(RESULT_SUCCESS, msg="SickRage is updating ...") @@ -1859,76 +1853,76 @@ class CMD_Show(ApiCall): def run(self): """ Get detailed information about a show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") - showDict = { + show_dict = { "season_list": CMD_ShowSeasonList((), {"indexerid": self.indexerid}).run()["data"], "cache": CMD_ShowCache((), {"indexerid": self.indexerid}).run()["data"] } - genreList = [] - if showObj.genre: - genreListTmp = showObj.genre.split("|") - for genre in genreListTmp: + genre_list = [] + if show_obj.genre: + genre_list_tmp = show_obj.genre.split("|") + for genre in genre_list_tmp: if genre: - genreList.append(genre) + genre_list.append(genre) - showDict["genre"] = genreList - showDict["quality"] = get_quality_string(showObj.quality) + show_dict["genre"] = genre_list + show_dict["quality"] = get_quality_string(show_obj.quality) - anyQualities, bestQualities = _mapQuality(showObj.quality) - showDict["quality_details"] = {"initial": anyQualities, "archive": bestQualities} + any_qualities, best_qualities = _map_quality(show_obj.quality) + show_dict["quality_details"] = {"initial": any_qualities, "archive": best_qualities} try: - showDict["location"] = showObj.location + show_dict["location"] = show_obj.location except ShowDirectoryNotFoundException: - showDict["location"] = "" - - showDict["language"] = showObj.lang - showDict["show_name"] = showObj.name - showDict["paused"] = (0, 1)[showObj.paused] - showDict["subtitles"] = (0, 1)[showObj.subtitles] - showDict["air_by_date"] = (0, 1)[showObj.air_by_date] - showDict["flatten_folders"] = (0, 1)[showObj.flatten_folders] - showDict["sports"] = (0, 1)[showObj.sports] - showDict["anime"] = (0, 1)[showObj.anime] - showDict["airs"] = str(showObj.airs).replace('am', ' AM').replace('pm', ' PM').replace(' ', ' ') - showDict["dvdorder"] = (0, 1)[showObj.dvdorder] - - if showObj.rls_require_words: - showDict["rls_require_words"] = showObj.rls_require_words.split(", ") + show_dict["location"] = "" + + show_dict["language"] = show_obj.lang + show_dict["show_name"] = show_obj.name + show_dict["paused"] = (0, 1)[show_obj.paused] + show_dict["subtitles"] = (0, 1)[show_obj.subtitles] + show_dict["air_by_date"] = (0, 1)[show_obj.air_by_date] + show_dict["flatten_folders"] = (0, 1)[show_obj.flatten_folders] + show_dict["sports"] = (0, 1)[show_obj.sports] + show_dict["anime"] = (0, 1)[show_obj.anime] + show_dict["airs"] = str(show_obj.airs).replace('am', ' AM').replace('pm', ' PM').replace(' ', ' ') + show_dict["dvdorder"] = (0, 1)[show_obj.dvdorder] + + if show_obj.rls_require_words: + show_dict["rls_require_words"] = show_obj.rls_require_words.split(", ") else: - showDict["rls_require_words"] = [] + show_dict["rls_require_words"] = [] - if showObj.rls_ignore_words: - showDict["rls_ignore_words"] = showObj.rls_ignore_words.split(", ") + if show_obj.rls_ignore_words: + show_dict["rls_ignore_words"] = show_obj.rls_ignore_words.split(", ") else: - showDict["rls_ignore_words"] = [] + show_dict["rls_ignore_words"] = [] - showDict["scene"] = (0, 1)[showObj.scene] - showDict["archive_firstmatch"] = (0, 1)[showObj.archive_firstmatch] + show_dict["scene"] = (0, 1)[show_obj.scene] + show_dict["archive_firstmatch"] = (0, 1)[show_obj.archive_firstmatch] - showDict["indexerid"] = showObj.indexerid - showDict["tvdbid"] = helpers.mapIndexersToShow(showObj)[1] - showDict["imdbid"] = showObj.imdbid + show_dict["indexerid"] = show_obj.indexerid + show_dict["tvdbid"] = helpers.mapIndexersToShow(show_obj)[1] + show_dict["imdbid"] = show_obj.imdbid - showDict["network"] = showObj.network - if not showDict["network"]: - showDict["network"] = "" - showDict["status"] = showObj.status + show_dict["network"] = show_obj.network + if not show_dict["network"]: + show_dict["network"] = "" + show_dict["status"] = show_obj.status - if helpers.tryInt(showObj.nextaired, 1) > 693595: - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting( - network_timezones.parse_date_time(showObj.nextaired, showDict['airs'], showDict['network'])) - showDict['airs'] = sbdatetime.sbdatetime.sbftime(dtEpisodeAirs, t_preset=timeFormat).lstrip('0').replace( + if helpers.tryInt(show_obj.nextaired, 1) > 693595: + dt_episode_airs = sbdatetime.sbdatetime.convert_to_setting( + network_timezones.parse_date_time(show_obj.nextaired, show_dict['airs'], show_dict['network'])) + show_dict['airs'] = sbdatetime.sbdatetime.sbftime(dt_episode_airs, t_preset=timeFormat).lstrip('0').replace( ' 0', ' ') - showDict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) + show_dict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dt_episode_airs, d_preset=dateFormat) else: - showDict['next_ep_airdate'] = '' + show_dict['next_ep_airdate'] = '' - return _responds(RESULT_SUCCESS, showDict) + return _responds(RESULT_SUCCESS, show_dict) class CMD_ShowAddExisting(ApiCall): @@ -1967,27 +1961,27 @@ class CMD_ShowAddExisting(ApiCall): def run(self): """ Add an existing show in SickRage """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if show_obj: return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in the database") if not ek(os.path.isdir, self.location): return _responds(RESULT_FAILURE, msg='Not a valid location') - indexerName = None - indexerResult = CMD_SickBeardSearchIndexers([], {indexer_ids[self.indexer]: self.indexerid}).run() + indexer_name = None + indexer_result = CMD_SickBeardSearchIndexers([], {indexer_ids[self.indexer]: self.indexerid}).run() - if indexerResult['result'] == result_type_map[RESULT_SUCCESS]: - if not indexerResult['data']['results']: + if indexer_result['result'] == result_type_map[RESULT_SUCCESS]: + if not indexer_result['data']['results']: return _responds(RESULT_FAILURE, msg="Empty results returned, check indexerid and try again") - if len(indexerResult['data']['results']) == 1 and 'name' in indexerResult['data']['results'][0]: - indexerName = indexerResult['data']['results'][0]['name'] + if len(indexer_result['data']['results']) == 1 and 'name' in indexer_result['data']['results'][0]: + indexer_name = indexer_result['data']['results'][0]['name'] - if not indexerName: + if not indexer_name: return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer") # set indexer so we can pass it along when adding show to SR - indexer = indexerResult['data']['results'][0]['indexer'] + indexer = indexer_result['data']['results'][0]['indexer'] quality_map = {'sdtv': Quality.SDTV, 'sddvd': Quality.SDDVD, @@ -2000,28 +1994,28 @@ class CMD_ShowAddExisting(ApiCall): 'fullhdbluray': Quality.FULLHDBLURAY, 'unknown': Quality.UNKNOWN} - # use default quality as a failsafe - newQuality = int(sickbeard.QUALITY_DEFAULT) - iqualityID = [] - aqualityID = [] + # use default quality as a fail-safe + new_quality = int(sickbeard.QUALITY_DEFAULT) + i_quality_id = [] + a_quality_id = [] if self.initial: for quality in self.initial: - iqualityID.append(quality_map[quality]) + i_quality_id.append(quality_map[quality]) if self.archive: for quality in self.archive: - aqualityID.append(quality_map[quality]) + a_quality_id.append(quality_map[quality]) - if iqualityID or aqualityID: - newQuality = Quality.combineQualities(iqualityID, aqualityID) + if i_quality_id or a_quality_id: + new_quality = Quality.combineQualities(i_quality_id, a_quality_id) sickbeard.showQueueScheduler.action.addShow( int(indexer), int(self.indexerid), self.location, default_status=sickbeard.STATUS_DEFAULT, - quality=newQuality, flatten_folders=int(self.flatten_folders), subtitles=self.subtitles, + quality=new_quality, flatten_folders=int(self.flatten_folders), subtitles=self.subtitles, default_status_after=sickbeard.STATUS_DEFAULT_AFTER, archive=self.archive_firstmatch ) - return _responds(RESULT_SUCCESS, {"name": indexerName}, indexerName + " has been queued to be added") + return _responds(RESULT_SUCCESS, {"name": indexer_name}, indexer_name + " has been queued to be added") class CMD_ShowAddNew(ApiCall): @@ -2082,8 +2076,8 @@ class CMD_ShowAddNew(ApiCall): def run(self): """ Add a new show to SickRage """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if show_obj: return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in database") if not self.location: @@ -2109,23 +2103,23 @@ class CMD_ShowAddNew(ApiCall): 'fullhdbluray': Quality.FULLHDBLURAY, 'unknown': Quality.UNKNOWN} - # use default quality as a failsafe - newQuality = int(sickbeard.QUALITY_DEFAULT) - iqualityID = [] - aqualityID = [] + # use default quality as a fail-safe + new_quality = int(sickbeard.QUALITY_DEFAULT) + i_quality_id = [] + a_quality_id = [] if self.initial: for quality in self.initial: - iqualityID.append(quality_map[quality]) + i_quality_id.append(quality_map[quality]) if self.archive: for quality in self.archive: - aqualityID.append(quality_map[quality]) + a_quality_id.append(quality_map[quality]) - if iqualityID or aqualityID: - newQuality = Quality.combineQualities(iqualityID, aqualityID) + if i_quality_id or a_quality_id: + new_quality = Quality.combineQualities(i_quality_id, a_quality_id) - # use default status as a failsafe - newStatus = sickbeard.STATUS_DEFAULT + # use default status as a fail-safe + new_status = sickbeard.STATUS_DEFAULT if self.status: # convert the string status to a int for status in statusStrings: @@ -2139,9 +2133,9 @@ class CMD_ShowAddNew(ApiCall): # only allow the status options we want if int(self.status) not in (WANTED, SKIPPED, IGNORED): return _responds(RESULT_FAILURE, msg="Status prohibited") - newStatus = self.status + new_status = self.status - # use default status as a failsafe + # use default status as a fail-safe default_ep_status_after = sickbeard.STATUS_DEFAULT_AFTER if self.future_status: # convert the string status to a int @@ -2158,43 +2152,43 @@ class CMD_ShowAddNew(ApiCall): return _responds(RESULT_FAILURE, msg="Status prohibited") default_ep_status_after = self.future_status - indexerName = None - indexerResult = CMD_SickBeardSearchIndexers([], {indexer_ids[self.indexer]: self.indexerid}).run() + indexer_name = None + indexer_result = CMD_SickBeardSearchIndexers([], {indexer_ids[self.indexer]: self.indexerid}).run() - if indexerResult['result'] == result_type_map[RESULT_SUCCESS]: - if not indexerResult['data']['results']: + if indexer_result['result'] == result_type_map[RESULT_SUCCESS]: + if not indexer_result['data']['results']: return _responds(RESULT_FAILURE, msg="Empty results returned, check indexerid and try again") - if len(indexerResult['data']['results']) == 1 and 'name' in indexerResult['data']['results'][0]: - indexerName = indexerResult['data']['results'][0]['name'] + if len(indexer_result['data']['results']) == 1 and 'name' in indexer_result['data']['results'][0]: + indexer_name = indexer_result['data']['results'][0]['name'] - if not indexerName: + if not indexer_name: return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer") # set indexer for found show so we can pass it along - indexer = indexerResult['data']['results'][0]['indexer'] + indexer = indexer_result['data']['results'][0]['indexer'] # moved the logic check to the end in an attempt to eliminate empty directory being created from previous errors - showPath = ek(os.path.join, self.location, helpers.sanitizeFileName(indexerName)) + show_path = ek(os.path.join, self.location, helpers.sanitizeFileName(indexer_name)) # don't create show dir if config says not to if sickbeard.ADD_SHOWS_WO_DIR: - logger.log(u"Skipping initial creation of " + showPath + " due to config.ini setting") + logger.log(u"Skipping initial creation of " + show_path + " due to config.ini setting") else: - dir_exists = helpers.makeDir(showPath) + dir_exists = helpers.makeDir(show_path) if not dir_exists: - logger.log(u"API :: Unable to create the folder " + showPath + ", can't add the show", logger.ERROR) - return _responds(RESULT_FAILURE, {"path": showPath}, - "Unable to create the folder " + showPath + ", can't add the show") + logger.log(u"API :: Unable to create the folder " + show_path + ", can't add the show", logger.ERROR) + return _responds(RESULT_FAILURE, {"path": show_path}, + "Unable to create the folder " + show_path + ", can't add the show") else: - helpers.chmodAsParent(showPath) + helpers.chmodAsParent(show_path) sickbeard.showQueueScheduler.action.addShow( - int(indexer), int(self.indexerid), showPath, default_status=newStatus, quality=newQuality, + int(indexer), int(self.indexerid), show_path, default_status=new_status, quality=new_quality, flatten_folders=int(self.flatten_folders), lang=self.lang, subtitles=self.subtitles, anime=self.anime, scene=self.scene, default_status_after=default_ep_status_after, archive=self.archive_firstmatch ) - return _responds(RESULT_SUCCESS, {"name": indexerName}, indexerName + " has been queued to be added") + return _responds(RESULT_SUCCESS, {"name": indexer_name}, indexer_name + " has been queued to be added") class CMD_ShowCache(ApiCall): @@ -2217,8 +2211,8 @@ class CMD_ShowCache(ApiCall): def run(self): """ Check SickRage's cache to see if the images (poster, banner, fanart) for a show are valid """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") # TODO: catch if cache dir is missing/invalid.. so it doesn't break show/show.cache @@ -2229,9 +2223,9 @@ class CMD_ShowCache(ApiCall): has_poster = 0 has_banner = 0 - if ek(os.path.isfile, cache_obj.poster_path(showObj.indexerid)): + if ek(os.path.isfile, cache_obj.poster_path(show_obj.indexerid)): has_poster = 1 - if ek(os.path.isfile, cache_obj.banner_path(showObj.indexerid)): + if ek(os.path.isfile, cache_obj.banner_path(show_obj.indexerid)): has_banner = 1 return _responds(RESULT_SUCCESS, {"poster": has_poster, "banner": has_banner}) @@ -2263,7 +2257,7 @@ class CMD_ShowDelete(ApiCall): """ Delete a show in SickRage """ error, show = Show.delete(self.indexerid, self.removefiles) - if error is not None: + if error: return _responds(RESULT_FAILURE, msg=error) return _responds(RESULT_SUCCESS, msg='%s has been queued to be deleted' % show.name) @@ -2289,13 +2283,13 @@ class CMD_ShowGetQuality(ApiCall): def run(self): """ Get the quality setting of a show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") - anyQualities, bestQualities = _mapQuality(showObj.quality) + any_qualities, best_qualities = _map_quality(show_obj.quality) - return _responds(RESULT_SUCCESS, {"initial": anyQualities, "archive": bestQualities}) + return _responds(RESULT_SUCCESS, {"initial": any_qualities, "archive": best_qualities}) class CMD_ShowGetPoster(ApiCall): @@ -2406,7 +2400,7 @@ class CMD_ShowGetFanArt(ApiCall): class CMD_ShowPause(ApiCall): _help = { - "desc": "Pause or unpause a show", + "desc": "Pause or un-pause a show", "requiredParameters": { "indexerid": {"desc": "Unique ID of a show"}, }, @@ -2425,10 +2419,10 @@ class CMD_ShowPause(ApiCall): ApiCall.__init__(self, args, kwargs) def run(self): - """ Pause or unpause a show """ + """ Pause or un-pause a show """ error, show = Show.pause(self.indexerid, self.pause) - if error is not None: + if error: return _responds(RESULT_FAILURE, msg=error) return _responds(RESULT_SUCCESS, msg='%s has been %s' % (show.name, ('resumed', 'paused')[show.paused])) @@ -2456,7 +2450,7 @@ class CMD_ShowRefresh(ApiCall): """ Refresh a show in SickRage """ error, show = Show.refresh(self.indexerid) - if error is not None: + if error: return _responds(RESULT_FAILURE, msg=error) return _responds(RESULT_SUCCESS, msg='%s has queued to be refreshed' % show.name) @@ -2484,22 +2478,22 @@ class CMD_ShowSeasonList(ApiCall): def run(self): """ Get the list of seasons of a show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") - myDB = db.DBConnection(row_type="dict") + my_db = db.DBConnection(row_type="dict") if self.sort == "asc": - sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season ASC", - [self.indexerid]) + sql_results = my_db.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season ASC", + [self.indexerid]) else: - sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC", - [self.indexerid]) - seasonList = [] # a list with all season numbers - for row in sqlResults: - seasonList.append(int(row["season"])) + sql_results = my_db.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC", + [self.indexerid]) + season_list = [] # a list with all season numbers + for row in sql_results: + season_list.append(int(row["season"])) - return _responds(RESULT_SUCCESS, seasonList) + return _responds(RESULT_SUCCESS, season_list) class CMD_ShowSeasons(ApiCall): @@ -2524,57 +2518,57 @@ class CMD_ShowSeasons(ApiCall): def run(self): """ Get the list of episodes for one or all seasons of a show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + sho_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not sho_obj: return _responds(RESULT_FAILURE, msg="Show not found") - myDB = db.DBConnection(row_type="dict") + my_db = db.DBConnection(row_type="dict") - if self.season == None: - sqlResults = myDB.select( + if self.season is None: + sql_results = my_db.select( "SELECT name, episode, airdate, status, release_name, season, location, file_size, subtitles FROM tv_episodes WHERE showid = ?", [self.indexerid]) seasons = {} - for row in sqlResults: + for row in sql_results: status, quality = Quality.splitCompositeStatus(int(row["status"])) - row["status"] = _get_status_Strings(status) + row["status"] = _get_status_strings(status) row["quality"] = get_quality_string(quality) if helpers.tryInt(row['airdate'], 1) > 693595: # 1900 - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting( - network_timezones.parse_date_time(row['airdate'], showObj.airs, showObj.network)) - row['airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) + dt_episode_airs = sbdatetime.sbdatetime.convert_to_setting( + network_timezones.parse_date_time(row['airdate'], sho_obj.airs, sho_obj.network)) + row['airdate'] = sbdatetime.sbdatetime.sbfdate(dt_episode_airs, d_preset=dateFormat) else: row['airdate'] = 'Never' - curSeason = int(row["season"]) - curEpisode = int(row["episode"]) + cur_season = int(row["season"]) + cur_episode = int(row["episode"]) del row["season"] del row["episode"] - if not curSeason in seasons: - seasons[curSeason] = {} - seasons[curSeason][curEpisode] = row + if cur_season not in seasons: + seasons[cur_season] = {} + seasons[cur_season][cur_episode] = row else: - sqlResults = myDB.select( + sql_results = my_db.select( "SELECT name, episode, airdate, status, location, file_size, release_name, subtitles FROM tv_episodes WHERE showid = ? AND season = ?", [self.indexerid, self.season]) - if len(sqlResults) is 0: + if len(sql_results) == 0: return _responds(RESULT_FAILURE, msg="Season not found") seasons = {} - for row in sqlResults: - curEpisode = int(row["episode"]) + for row in sql_results: + cur_episode = int(row["episode"]) del row["episode"] status, quality = Quality.splitCompositeStatus(int(row["status"])) - row["status"] = _get_status_Strings(status) + row["status"] = _get_status_strings(status) row["quality"] = get_quality_string(quality) if helpers.tryInt(row['airdate'], 1) > 693595: # 1900 - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting( - network_timezones.parse_date_time(row['airdate'], showObj.airs, showObj.network)) - row['airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) + dt_episode_airs = sbdatetime.sbdatetime.convert_to_setting( + network_timezones.parse_date_time(row['airdate'], sho_obj.airs, sho_obj.network)) + row['airdate'] = sbdatetime.sbdatetime.sbfdate(dt_episode_airs, d_preset=dateFormat) else: row['airdate'] = 'Never' - if not curEpisode in seasons: - seasons[curEpisode] = {} - seasons[curEpisode] = row + if cur_episode not in seasons: + seasons[cur_episode] = {} + seasons[cur_episode] = row return _responds(RESULT_SUCCESS, seasons) @@ -2597,7 +2591,7 @@ class CMD_ShowSetQuality(ApiCall): self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # 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.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", _get_quality_map().values()[1:]) self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) @@ -2610,8 +2604,8 @@ class CMD_ShowSetQuality(ApiCall): def run(self): """ Set the quality setting of a show. If no quality is provided, the default user setting is used. """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") quality_map = {'sdtv': Quality.SDTV, @@ -2625,24 +2619,24 @@ class CMD_ShowSetQuality(ApiCall): 'fullhdbluray': Quality.FULLHDBLURAY, 'unknown': Quality.UNKNOWN} - # use default quality as a failsafe - newQuality = int(sickbeard.QUALITY_DEFAULT) - iqualityID = [] - aqualityID = [] + # use default quality as a fail-safe + new_quality = int(sickbeard.QUALITY_DEFAULT) + i_quality_id = [] + a_quality_id = [] if self.initial: for quality in self.initial: - iqualityID.append(quality_map[quality]) + i_quality_id.append(quality_map[quality]) if self.archive: for quality in self.archive: - aqualityID.append(quality_map[quality]) + a_quality_id.append(quality_map[quality]) - if iqualityID or aqualityID: - newQuality = Quality.combineQualities(iqualityID, aqualityID) - showObj.quality = newQuality + if i_quality_id or a_quality_id: + new_quality = Quality.combineQualities(i_quality_id, a_quality_id) + show_obj.quality = new_quality return _responds(RESULT_SUCCESS, - msg=showObj.name + " quality has been changed to " + get_quality_string(showObj.quality)) + msg=show_obj.name + " quality has been changed to " + get_quality_string(show_obj.quality)) class CMD_ShowStats(ApiCall): @@ -2665,21 +2659,19 @@ class CMD_ShowStats(ApiCall): def run(self): """ Get episode statistics for a given show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") # show stats - episode_status_counts_total = {} - episode_status_counts_total["total"] = 0 + episode_status_counts_total = {"total": 0} for status in statusStrings: if status in [UNKNOWN, DOWNLOADED, SNATCHED, SNATCHED_PROPER, ARCHIVED]: continue episode_status_counts_total[status] = 0 # add all the downloaded qualities - episode_qualities_counts_download = {} - episode_qualities_counts_download["total"] = 0 + episode_qualities_counts_download = {"total": 0} for statusCode in Quality.DOWNLOADED + Quality.ARCHIVED: status, quality = Quality.splitCompositeStatus(statusCode) if quality in [Quality.NONE]: @@ -2687,19 +2679,18 @@ class CMD_ShowStats(ApiCall): episode_qualities_counts_download[statusCode] = 0 # add all snatched qualities - episode_qualities_counts_snatch = {} - episode_qualities_counts_snatch["total"] = 0 + episode_qualities_counts_snatch = {"total": 0} for statusCode in Quality.SNATCHED + Quality.SNATCHED_PROPER: status, quality = Quality.splitCompositeStatus(statusCode) if quality in [Quality.NONE]: continue episode_qualities_counts_snatch[statusCode] = 0 - myDB = db.DBConnection(row_type="dict") - sqlResults = myDB.select("SELECT status, season FROM tv_episodes WHERE season != 0 AND showid = ?", - [self.indexerid]) + my_db = db.DBConnection(row_type="dict") + sql_results = my_db.select("SELECT status, season FROM tv_episodes WHERE season != 0 AND showid = ?", + [self.indexerid]) # the main loop that goes through all episodes - for row in sqlResults: + for row in sql_results: status, quality = Quality.splitCompositeStatus(int(row["status"])) episode_status_counts_total["total"] += 1 @@ -2710,36 +2701,35 @@ class CMD_ShowStats(ApiCall): elif status in Quality.SNATCHED + Quality.SNATCHED_PROPER: episode_qualities_counts_snatch["total"] += 1 episode_qualities_counts_snatch[int(row["status"])] += 1 - elif status == 0: # we dont count NONE = 0 = N/A + elif status == 0: # we don't count NONE = 0 = N/A pass else: episode_status_counts_total[status] += 1 # the outgoing container - episodes_stats = {} - episodes_stats["downloaded"] = {} + episodes_stats = {"downloaded": {}} # turning codes into strings for statusCode in episode_qualities_counts_download: if statusCode == "total": episodes_stats["downloaded"]["total"] = episode_qualities_counts_download[statusCode] continue status, quality = Quality.splitCompositeStatus(int(statusCode)) - statusString = Quality.qualityStrings[quality].lower().replace(" ", "_").replace("(", "").replace(")", "") - episodes_stats["downloaded"][statusString] = episode_qualities_counts_download[statusCode] + status_string = Quality.qualityStrings[quality].lower().replace(" ", "_").replace("(", "").replace(")", "") + episodes_stats["downloaded"][status_string] = episode_qualities_counts_download[statusCode] episodes_stats["snatched"] = {} - # truning codes into strings + # turning codes into strings # and combining proper and normal for statusCode in episode_qualities_counts_snatch: if statusCode == "total": episodes_stats["snatched"]["total"] = episode_qualities_counts_snatch[statusCode] continue status, quality = Quality.splitCompositeStatus(int(statusCode)) - statusString = Quality.qualityStrings[quality].lower().replace(" ", "_").replace("(", "").replace(")", "") + status_string = Quality.qualityStrings[quality].lower().replace(" ", "_").replace("(", "").replace(")", "") if Quality.qualityStrings[quality] in episodes_stats["snatched"]: - episodes_stats["snatched"][statusString] += episode_qualities_counts_snatch[statusCode] + episodes_stats["snatched"][status_string] += episode_qualities_counts_snatch[statusCode] else: - episodes_stats["snatched"][statusString] = episode_qualities_counts_snatch[statusCode] + episodes_stats["snatched"][status_string] = episode_qualities_counts_snatch[statusCode] # episodes_stats["total"] = {} for statusCode in episode_status_counts_total: @@ -2747,9 +2737,9 @@ class CMD_ShowStats(ApiCall): episodes_stats["total"] = episode_status_counts_total[statusCode] continue status, quality = Quality.splitCompositeStatus(int(statusCode)) - statusString = statusStrings[statusCode].lower().replace(" ", "_").replace("(", "").replace( + status_string = statusStrings[statusCode].lower().replace(" ", "_").replace("(", "").replace( ")", "") - episodes_stats[statusString] = episode_status_counts_total[statusCode] + episodes_stats[status_string] = episode_status_counts_total[statusCode] return _responds(RESULT_SUCCESS, episodes_stats) @@ -2774,16 +2764,16 @@ class CMD_ShowUpdate(ApiCall): def run(self): """ Update a show in SickRage """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) - if not showObj: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") try: - sickbeard.showQueueScheduler.action.updateShow(showObj, True) # @UndefinedVariable - return _responds(RESULT_SUCCESS, msg=str(showObj.name) + " has queued to be updated") + sickbeard.showQueueScheduler.action.updateShow(show_obj, True) # @UndefinedVariable + return _responds(RESULT_SUCCESS, msg=str(show_obj.name) + " has queued to be updated") except CantUpdateShowException as e: logger.log(u"API::Unable to update show: {0}".format(str(e)), logger.DEBUG) - return _responds(RESULT_FAILURE, msg="Unable to update " + str(showObj.name)) + return _responds(RESULT_FAILURE, msg="Unable to update " + str(show_obj.name)) class CMD_Shows(ApiCall): @@ -2808,12 +2798,12 @@ class CMD_Shows(ApiCall): shows = {} for curShow in sickbeard.showList: - if self.paused is not None and bool(self.paused) != bool(curShow.paused): + if not self.paused and not curShow.paused: continue - indexerShow = helpers.mapIndexersToShow(curShow) + indexer_show = helpers.mapIndexersToShow(curShow) - showDict = { + show_dict = { "paused": (0, 1)[curShow.paused], "quality": get_quality_string(curShow.quality), "language": curShow.lang, @@ -2821,7 +2811,7 @@ class CMD_Shows(ApiCall): "sports": (0, 1)[curShow.sports], "anime": (0, 1)[curShow.anime], "indexerid": curShow.indexerid, - "tvdbid": indexerShow[1], + "tvdbid": indexer_show[1], "network": curShow.network, "show_name": curShow.name, "status": curShow.status, @@ -2829,19 +2819,19 @@ class CMD_Shows(ApiCall): } if helpers.tryInt(curShow.nextaired, 1) > 693595: # 1900 - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting( - network_timezones.parse_date_time(curShow.nextaired, curShow.airs, showDict['network'])) - showDict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) + dt_episode_airs = sbdatetime.sbdatetime.convert_to_setting( + network_timezones.parse_date_time(curShow.nextaired, curShow.airs, show_dict['network'])) + show_dict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dt_episode_airs, d_preset=dateFormat) else: - showDict['next_ep_airdate'] = '' + show_dict['next_ep_airdate'] = '' - showDict["cache"] = CMD_ShowCache((), {"indexerid": curShow.indexerid}).run()["data"] - if not showDict["network"]: - showDict["network"] = "" + show_dict["cache"] = CMD_ShowCache((), {"indexerid": curShow.indexerid}).run()["data"] + if not show_dict["network"]: + show_dict["network"] = "" if self.sort == "name": - shows[curShow.name] = showDict + shows[curShow.name] = show_dict else: - shows[curShow.indexerid] = showDict + shows[curShow.indexerid] = show_dict return _responds(RESULT_SUCCESS, shows) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index b5ee0d8a09aaac682580f824844f63e588b6a432..baa9380f1c216e3f0bb78a744afdea2a91a37ccf 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -24,6 +24,7 @@ import time import urllib import datetime import traceback +import ast import sickbeard from sickbeard import config, sab @@ -995,19 +996,48 @@ class Home(WebRoot): data = {} size = 0 for r in rows: - data[r['show_id']] = {'id': r['show_id'], 'name': r['show_name'], 'list': r['notify_list']} + NotifyList = {'emails':'', 'prowlAPIs':''} + if (r['notify_list'] and len(r['notify_list']) > 0): + # First, handle legacy format (emails only) + if not r['notify_list'][0] == '{': + NotifyList['emails'] = r['notify_list'] + else: + NotifyList = dict(ast.literal_eval(r['notify_list'])) + + data[r['show_id']] = {'id': r['show_id'],'name': r['show_name'], + 'list': NotifyList['emails'], + 'prowl_notify_list': NotifyList['prowlAPIs'] + } size += 1 data['_size'] = size return json.dumps(data) @staticmethod - def saveShowNotifyList(show=None, emails=None): + def saveShowNotifyList(show=None, emails=None, prowlAPIs=None): + entries = {'emails':'', 'prowlAPIs':''} myDB = db.DBConnection() - if myDB.action("UPDATE tv_shows SET notify_list = ? WHERE show_id = ?", [emails, show]): - return 'OK' - else: - return 'ERROR' + + # Get current data + for subs in myDB.select("SELECT notify_list FROM tv_shows WHERE show_id = ?", [show]): + if (subs['notify_list'] and len(subs['notify_list']) > 0): + # First, handle legacy format (emails only) + if not subs['notify_list'][0] == '{': + entries['emails'] = subs['notify_list'] + else: + entries = dict(ast.literal_eval(subs['notify_list'])) + + if (emails is not None): + entries['emails'] = emails + if not myDB.action("UPDATE tv_shows SET notify_list = ? WHERE show_id = ?", [str(entries), show]): + return 'ERROR' + + if (prowlAPIs is not None): + entries['prowlAPIs'] = prowlAPIs + if not myDB.action("UPDATE tv_shows SET notify_list = ? WHERE show_id = ?", [str(entries), show]): + return 'ERROR' + + return 'OK' @staticmethod def testEmail(host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None): @@ -1149,14 +1179,15 @@ class Home(WebRoot): return json.dumps({"status": "error", 'message': 'General exception'}) def displayShow(self, show=None): + # todo: add more comprehensive show validation + try: + show = int(show) # fails if show id ends in a period SickRage/sickrage-issues#65 + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, show) + except (ValueError, TypeError): + return self._genericMessage("Error", "Invalid show ID: %s" % str(show)) - if show is None: - return self._genericMessage("Error", "Invalid show ID") - else: - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return self._genericMessage("Error", "Show not in show list") + if showObj is None: + return self._genericMessage("Error", "Show not in show list") myDB = db.DBConnection() seasonResults = myDB.select( @@ -3020,13 +3051,13 @@ class Manage(Home, WebRoot): epCounts[Overview.SNATCHED] = 0 sqlResults = myDB.select( - "SELECT * FROM tv_episodes WHERE tv_episodes.showid in (SELECT tv_shows.indexer_id FROM tv_shows WHERE tv_shows.indexer_id = ? AND paused = 0) ORDER BY tv_episodes.season DESC, tv_episodes.episode DESC", + "SELECT status, season, episode, name, airdate FROM tv_episodes WHERE tv_episodes.showid in (SELECT tv_shows.indexer_id FROM tv_shows WHERE tv_shows.indexer_id = ? AND paused = 0) ORDER BY tv_episodes.season DESC, tv_episodes.episode DESC", [curShow.indexerid]) for curResult in sqlResults: curEpCat = curShow.getOverview(int(curResult["status"] or -1)) if curEpCat: - epCats[str(curResult["season"]) + "x" + str(curResult["episode"])] = curEpCat + epCats['S%02dE%02d' % (curResult['season'], curResult['episode'])] = curEpCat epCounts[curEpCat] += 1 showCounts[curShow.indexerid] = epCounts @@ -3462,6 +3493,7 @@ class ManageSearches(Manage): return t.render(backlogPaused=sickbeard.searchQueueScheduler.action.is_backlog_paused(), backlogRunning=sickbeard.searchQueueScheduler.action.is_backlog_in_progress(), dailySearchStatus=sickbeard.dailySearchScheduler.action.amActive, findPropersStatus=sickbeard.properFinderScheduler.action.amActive, queueLength=sickbeard.searchQueueScheduler.action.queue_length(), + subtitlesFinderStatus=sickbeard.subtitlesFinderScheduler.action.amActive, title='Manage Searches', header='Manage Searches', topmenu='manage', controller="manage", action="manageSearches") @@ -3493,6 +3525,15 @@ class ManageSearches(Manage): return self.redirect("/manage/manageSearches/") + def forceSubtitlesFinder(self): + # force it to run the next time it looks + result = sickbeard.subtitlesFinderScheduler.forceRun() + if result: + logger.log(u"Subtitle search forced") + ui.notifications.message('Subtitle search started') + + return self.redirect("/manage/manageSearches/") + def pauseBacklog(self, paused=None): if paused == "1": sickbeard.searchQueueScheduler.action.pause_backlog() @@ -3666,7 +3707,7 @@ class ConfigGeneral(Config): handle_reverse_proxy=None, sort_article=None, auto_update=None, notify_on_update=None, proxy_setting=None, proxy_indexers=None, anon_redirect=None, git_path=None, git_remote=None, calendar_unprotected=None, calendar_icons=None, debug=None, ssl_verify=None, no_restart=None, coming_eps_missed_range=None, - filter_row=None, fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None, + fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None, indexer_timeout=None, download_url=None, rootDir=None, theme_name=None, default_page=None, git_reset=None, git_username=None, git_password=None, git_autoissues=None, display_all_seasons=None): @@ -3722,7 +3763,6 @@ class ConfigGeneral(Config): sickbeard.WEB_USERNAME = web_username sickbeard.WEB_PASSWORD = web_password - sickbeard.FILTER_ROW = config.checkbox_to_value(filter_row) sickbeard.FUZZY_DATING = config.checkbox_to_value(fuzzy_dating) sickbeard.TRIM_ZERO = config.checkbox_to_value(trim_zero) @@ -4429,6 +4469,12 @@ class ConfigProviders(Config): for curTorrentProvider in [curProvider for curProvider in sickbeard.providers.sortedProviderList() if curProvider.providerType == sickbeard.GenericProvider.TORRENT]: + if hasattr(curTorrentProvider, 'custom_url'): + try: + curTorrentProvider.custom_url = str(kwargs[curTorrentProvider.getID() + '_custom_url']).strip() + except Exception: + curTorrentProvider.custom_url = None + if hasattr(curTorrentProvider, 'minseed'): try: curTorrentProvider.minseed = int(str(kwargs[curTorrentProvider.getID() + '_minseed']).strip()) @@ -4655,6 +4701,7 @@ class ConfigNotifications(Config): freemobile_notify_onsubtitledownload=None, freemobile_id=None, freemobile_apikey=None, use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_notify_onsubtitledownload=None, prowl_api=None, prowl_priority=0, + prowl_show_list=None, prowl_show=None, prowl_message_title=None, use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None, twitter_notify_onsubtitledownload=None, twitter_usedm=None, twitter_dmto=None, use_boxcar2=None, boxcar2_notify_onsnatch=None, boxcar2_notify_ondownload=None, @@ -4738,6 +4785,7 @@ class ConfigNotifications(Config): sickbeard.PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(prowl_notify_onsubtitledownload) sickbeard.PROWL_API = prowl_api sickbeard.PROWL_PRIORITY = prowl_priority + sickbeard.PROWL_MESSAGE_TITLE = prowl_message_title sickbeard.USE_TWITTER = config.checkbox_to_value(use_twitter) sickbeard.TWITTER_NOTIFY_ONSNATCH = config.checkbox_to_value(twitter_notify_onsnatch) diff --git a/sickrage/helper/common.py b/sickrage/helper/common.py index 2ac3bf6698b7a249751a80f6185c7d95038e1fa0..e33a796a17c1bef65842b5978e2e2897c5bd06d1 100644 --- a/sickrage/helper/common.py +++ b/sickrage/helper/common.py @@ -18,4 +18,9 @@ dateFormat = '%Y-%m-%d' dateTimeFormat = '%Y-%m-%d %H:%M:%S' +media_extensions = [ + '3gp', 'avi', 'divx', 'dvr-ms', 'f4v', 'flv', 'img', 'iso', 'm2ts', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', + 'ogm', 'ogv', 'rmvb', 'tp', 'ts', 'vob', 'webm', 'wmv', 'wtv', +] +subtitle_extensions = ['ass', 'idx', 'srt', 'ssa', 'sub'] timeFormat = '%A %I:%M %p' diff --git a/sickrage/show/ComingEpisodes.py b/sickrage/show/ComingEpisodes.py index 65b34022839127c5404f4418df8d7c7893fae493..55a5f6292445ed168c3598c3ae165a2d0b96c4ec 100644 --- a/sickrage/show/ComingEpisodes.py +++ b/sickrage/show/ComingEpisodes.py @@ -54,11 +54,8 @@ class ComingEpisodes: :return: The list of coming episodes """ - if not isinstance(categories, list): - categories = categories.split('|') - - if sort not in ComingEpisodes.sorts.keys(): - sort = 'date' + categories = ComingEpisodes._get_categories(categories) + sort = ComingEpisodes._get_sort(sort) today = date.today().toordinal() next_week = (date.today() + timedelta(days=7)).toordinal() @@ -124,7 +121,7 @@ class ComingEpisodes: if not group: return results - grouped_results = {category: [] for category in categories} + grouped_results = ComingEpisodes._get_categories_map(categories) for result in results: if result['paused'] and not paused: @@ -158,3 +155,29 @@ class ComingEpisodes: grouped_results[category].append(result) return grouped_results + + @staticmethod + def _get_categories(categories): + if not categories: + return [] + + if not isinstance(categories, list): + return categories.split('|') + + return categories + + @staticmethod + def _get_categories_map(categories): + if not categories: + return {} + + return {category: [] for category in categories} + + @staticmethod + def _get_sort(sort): + sort = sort.lower() if sort else '' + + if sort not in ComingEpisodes.sorts.keys(): + return 'date' + + return sort diff --git a/sickrage/show/History.py b/sickrage/show/History.py index 0b04e02aa78342a8ae52053e19a140d6a0dcf36c..c015da8a8d420fe4f134205b52b3a30a5a037b0f 100644 --- a/sickrage/show/History.py +++ b/sickrage/show/History.py @@ -46,15 +46,8 @@ class History: :return: The last ``limit`` elements of type ``action`` in the history """ - action = action.lower() if isinstance(action, str) else '' - limit = int(limit) - - if action == 'downloaded': - actions = Quality.DOWNLOADED - elif action == 'snatched': - actions = Quality.SNATCHED - else: - actions = [] + actions = History._get_actions(action) + limit = History._get_limit(limit) common_sql = 'SELECT action, date, episode, provider, h.quality, resource, season, show_name, showid ' \ 'FROM history h, tv_shows s ' \ @@ -100,3 +93,27 @@ class History: 'WHERE date < ?', [(datetime.today() - timedelta(days=30)).strftime(History.date_format)] ) + + @staticmethod + def _get_actions(action): + action = action.lower() if isinstance(action, str) else '' + + if action == 'downloaded': + return Quality.DOWNLOADED + + if action == 'snatched': + return Quality.SNATCHED + + return [] + + @staticmethod + def _get_limit(limit): + try: + limit = int(limit) + except (TypeError, ValueError): + return 0 + + if limit < 0: + return 0 + + return int(limit) diff --git a/sickrage/show/Show.py b/sickrage/show/Show.py index 2e3601bce85ec339e53b1568b3307e7071e28b6a..a6824fc05054be5fdad8517d8cc0a644465bf02b 100644 --- a/sickrage/show/Show.py +++ b/sickrage/show/Show.py @@ -152,7 +152,9 @@ class Show: - the show object corresponding to ``indexer_id`` if it exists, ``None`` otherwise """ - if indexer_id is None: + try: + indexer_id = int(indexer_id) + except (TypeError, ValueError): return 'Invalid show ID', None try: diff --git a/tests/all_tests.py b/tests/all_tests.py index 010946da9ea0ff772dff8a2268efc3a0c7be66a3..de388878e699e32834af523c5ecf68227e96410d 100755 --- a/tests/all_tests.py +++ b/tests/all_tests.py @@ -18,35 +18,60 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. -import sys, os.path +import fnmatch +import os +import sys +import unittest -tests_dir=os.path.abspath(__file__)[:-len(os.path.basename(__file__))] +tests_dir = os.path.abspath(__file__)[:-len(os.path.basename(__file__))] sys.path.insert(1, os.path.join(tests_dir, '../lib')) sys.path.insert(1, os.path.join(tests_dir, '..')) -import glob -import unittest class AllTests(unittest.TestCase): - #Block issue_submitter_tests to avoid issue tracker spam on every build + # Block issue_submitter_tests to avoid issue tracker spam on every build blacklist = [tests_dir + 'all_tests.py', tests_dir + 'issue_submitter_tests.py', tests_dir + 'search_tests.py'] + def setUp(self): - self.test_file_strings = [ x for x in glob.glob(tests_dir + '*_tests.py') if not x in self.blacklist ] - self.module_strings = [file_string[len(tests_dir):len(file_string) - 3] for file_string in self.test_file_strings] - self.suites = [unittest.defaultTestLoader.loadTestsFromName(file_string) for file_string in self.module_strings] + self.test_file_strings = self._get_test_files() + self.module_strings = self._get_module_strings() + self.suites = self._get_test_suites() self.testSuite = unittest.TestSuite(self.suites) def testAll(self): - print "==================" + print "====================" print "STARTING - ALL TESTS" - print "==================" - for includedfiles in self.test_file_strings: - print "- " + includedfiles[len(tests_dir):-3] + print "====================" + + for included_files in self.test_file_strings: + print "- " + included_files[len(tests_dir):-3] text_runner = unittest.TextTestRunner().run(self.testSuite) if not text_runner.wasSuccessful(): sys.exit(-1) + def _get_module_strings(self): + modules = [] + for file_string in self.test_file_strings: + modules.append(file_string[len(tests_dir):len(file_string) - 3].replace(os.sep, '.')) + + return modules + + def _get_test_files(self): + matches = [] + for root, _, file_names in os.walk(tests_dir): + for filename in fnmatch.filter(file_names, '*_tests.py'): + filename_with_path = os.path.join(root, filename) + + if filename_with_path not in self.blacklist: + matches.append(filename_with_path) + + return matches + + def _get_test_suites(self): + return [unittest.defaultTestLoader.loadTestsFromName(file_string) for file_string in self.module_strings] + + if __name__ == "__main__": unittest.main() diff --git a/tests/common_tests.py b/tests/common_tests.py index 1ba62d02d5e958dccf085fc0b2065aed7782fc1b..b0e6a1df03d3cb955f6a2afb0d9fe0b20d2dedbb 100644 --- a/tests/common_tests.py +++ b/tests/common_tests.py @@ -236,7 +236,7 @@ class StatusStringsTest(unittest.TestCase): for i in unused: if i is None: with self.assertRaises(TypeError): - status_strings[str(i)] = 1 # 'None' != None + status_strings[str(i)] = 1 # 'None' is not None status_strings[i] = 1 # ...but None can still be used as a key else: status_strings[str(i)] = 1 diff --git a/tests/db_tests.py b/tests/db_tests.py index 74383f8f5d5d28639636684053212b256fb72e0c..fca31fec0458a010af0ebd25556163befae4c912 100644 --- a/tests/db_tests.py +++ b/tests/db_tests.py @@ -17,42 +17,82 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. -import sys, os.path -sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) -sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +""" +Test show database functionality. + +Tests: + DBBasicTests + DBMultiTests +""" +import sys +import os.path import unittest -import test_lib as test import threading +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +import tests.test_lib as test + + class DBBasicTests(test.SickbeardTestDBCase): + """ + Perform basic database tests. + + Tests: + test_select + """ + def setUp(self): + """ + Set up test. + """ super(DBBasicTests, self).setUp() - self.db = test.db.DBConnection() + self.sr_db = test.db.DBConnection() def test_select(self): - self.db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000]) + """ + Test selecting from the database + """ + self.sr_db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000]) + class DBMultiTests(test.SickbeardTestDBCase): + """ + Perform multi-threaded test of the database + + Tests: + test_threaded + """ def setUp(self): + """ + Set up test. + """ super(DBMultiTests, self).setUp() - self.db = test.db.DBConnection() + self.sr_db = test.db.DBConnection() def select(self): - self.db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000]) + """ + Select from the database. + """ + self.sr_db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000]) def test_threaded(self): - for i in xrange(4): - t = threading.Thread(target=self.select) - t.start() + """ + Test multi-threaded selection from the database + """ + for _ in xrange(4): + thread = threading.Thread(target=self.select) + thread.start() if __name__ == '__main__': print "==================" print "STARTING - DB TESTS" print "==================" print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(DBBasicTests) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(DBBasicTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) - #suite = unittest.TestLoader().loadTestsFromTestCase(DBMultiTests) - #unittest.TextTestRunner(verbosity=2).run(suite) + # suite = unittest.TestLoader().loadTestsFromTestCase(DBMultiTests) + # unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/notifier_tests.py b/tests/notifier_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..04701f64d6349eda7ce52a4cb834f72a5277408f --- /dev/null +++ b/tests/notifier_tests.py @@ -0,0 +1,214 @@ +# coding=UTF-8 +# URL: https://github.com/SickRage/SickRage +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################## + +### +# As a test case, there are instances in which it is necessary to call protected members of +# classes in order to test those classes. Therefore: +# pylint: disable=W0212 +### + +import sys, os.path +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +import unittest + +import tests.test_lib as test + +from sickbeard import db +from sickbeard.tv import TVEpisode, TVShow +from sickbeard.webserve import Home +from sickbeard.notifiers.emailnotify import EmailNotifier +from sickbeard.notifiers.prowl import ProwlNotifier + +from sickrage.helper.encoding import ss + +class NotifierTests(test.SickbeardTestDBCase): + + @classmethod + def setUpClass(cls): + num_legacy_shows = 3 + num_shows = 3 + num_episodes_per_show = 5 + cls.mydb = db.DBConnection() + cls.legacy_shows = [] + cls.shows = [] + + # Per-show-notifications were originally added for email notifications only. To add + # this feature to other notifiers, it was necessary to alter the way text is stored in + # one of the DB columns. Therefore, to test properly, we must create some shows that + # store emails in the old method (legacy method) and then other shows that will use + # the new method. + for show_counter in range(100, 100+num_legacy_shows): + show = TVShow(1, show_counter) + show.name = "Show "+str(show_counter) + show.episodes = [] + for episode_counter in range(0, num_episodes_per_show): + episode = TVEpisode(show, test.SEASON, episode_counter) + episode.name = "Episode "+str(episode_counter+1) + episode.quality = "SDTV" + show.episodes.append(episode) + show.saveToDB() + cls.legacy_shows.append(show) + + for show_counter in range(200, 200+num_shows): + show = TVShow(1, show_counter) + show.name = "Show "+str(show_counter) + show.episodes = [] + for episode_counter in range(0, num_episodes_per_show): + episode = TVEpisode(show, test.SEASON, episode_counter) + episode.name = "Episode "+str(episode_counter+1) + episode.quality = "SDTV" + show.episodes.append(episode) + show.saveToDB() + cls.shows.append(show) + + def setUp(self): + self._debug_spew("\n\r") + + #def test_boxcar(self): + # pass + + def test_email(self): + email_notifier = EmailNotifier() + + # Per-show-email notifications were added early on and utilized a different format than the other notifiers. + # Therefore, to test properly (and ensure backwards compatibility), this routine will test shows that use + # both the old and the new storage methodology + legacy_test_emails = "email-1@address.com,email2@address.org,email_3@address.tv" + test_emails = "email-4@address.com,email5@address.org,email_6@address.tv" + + for show in self.legacy_shows: + showid = self._get_showid_by_showname(show.name) + self.mydb.action("UPDATE tv_shows SET notify_list = ? WHERE show_id = ?", [legacy_test_emails, showid]) + + for show in self.shows: + showid = self._get_showid_by_showname(show.name) + Home.saveShowNotifyList(show=showid, emails=test_emails) + + + # Now, iterate through all shows using the email list generation routines that are used in the notifier proper + shows = self.legacy_shows+self.shows + for show in shows: + for episode in show.episodes: + ep_name = ss(episode._format_pattern('%SN - %Sx%0E - %EN - ')+episode.quality) + show_name = email_notifier._parseEp(ep_name) + recipients = email_notifier._generate_recipients(show_name) + self._debug_spew("- Email Notifications for "+show.name+" (episode: "+episode.name+") will be sent to:") + for email in recipients: + self._debug_spew("-- "+email.strip()) + self._debug_spew("\n\r") + + return True + + #def test_emby(self): + # pass + + #def test_freemobile(self): + # pass + + #def test_growl(self): + # pass + + #def test_kodi(self): + # pass + + #def test_libnotify(self): + # pass + + #def test_nma(self): + # pass + + #def test_nmj(self): + # pass + + #def test_nmjv2(self): + # pass + + #def test_plex(self): + # pass + + def test_prowl(self): + prowl_notifier = ProwlNotifier() + + # Prowl per-show-notifications only utilize the new methodology for storage; therefore, the list of legacy_shows + # will not be altered (to preserve backwards compatibility testing) + test_prowl_apis = "11111111111111111111,22222222222222222222" + + for show in self.shows: + showid = self._get_showid_by_showname(show.name) + Home.saveShowNotifyList(show=showid, prowlAPIs=test_prowl_apis) + + # Now, iterate through all shows using the Prowl API generation routines that are used in the notifier proper + for show in self.shows: + for episode in show.episodes: + ep_name = ss(episode._format_pattern('%SN - %Sx%0E - %EN - ')+episode.quality) + show_name = prowl_notifier._parse_episode(ep_name) + recipients = prowl_notifier._generate_recipients(show_name) + self._debug_spew("- Prowl Notifications for "+show.name+" (episode: "+episode.name+") will be sent to:") + for api in recipients: + self._debug_spew("-- "+api.strip()) + self._debug_spew("\n\r") + + return True + + #def test_pushalot(self): + # pass + + #def test_pushbullet(self): + # pass + + #def test_pushover(self): + # pass + + #def test_pytivo(self): + # pass + + #def test_synoindex(self): + # pass + + #def test_synologynotifier(self): + # pass + + #def test_trakt(self): + # pass + + #def test_tweet(self): + # pass + + @staticmethod + def _debug_spew(text): + if __name__ == '__main__' and text is not None: + print text + + def _get_showid_by_showname(self, showname): + if showname is not None: + rows = self.mydb.select("SELECT show_id FROM tv_shows WHERE show_name = ?", [showname]) + if len(rows) == 1: + return rows[0]['show_id'] + return -1 + +if __name__ == '__main__': + print "==================" + print "STARTING - NOTIFIER TESTS" + print "==================" + print "######################################################################" + SUITE = unittest.TestLoader().loadTestsFromTestCase(NotifierTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/pp_tests.py b/tests/pp_tests.py index 29d13985c58de8b6fc844a47681c15d8c4520b5e..c11b90338b1c8746d0b99d51bfeea538a75e016a 100644 --- a/tests/pp_tests.py +++ b/tests/pp_tests.py @@ -35,20 +35,20 @@ from sickbeard.name_cache import addNameToCache class PPInitTests(unittest.TestCase): def setUp(self): - self.pp = PostProcessor(test.FILEPATH) + self.pp = PostProcessor(test.FILE_PATH) def test_init_file_name(self): self.assertEqual(self.pp.file_name, test.FILENAME) def test_init_folder_name(self): - self.assertEqual(self.pp.folder_name, test.SHOWNAME) + self.assertEqual(self.pp.folder_name, test.SHOW_NAME) class PPBasicTests(test.SickbeardTestDBCase): def test_process(self): show = TVShow(1,3) - show.name = test.SHOWNAME - show.location = test.SHOWDIR + show.name = test.SHOW_NAME + show.location = test.SHOW_DIR show.saveToDB() sickbeard.showList = [show] @@ -59,7 +59,7 @@ class PPBasicTests(test.SickbeardTestDBCase): addNameToCache('show name', 3) sickbeard.PROCESS_METHOD = 'move' - pp = PostProcessor(test.FILEPATH) + pp = PostProcessor(test.FILE_PATH) self.assertTrue(pp.process()) diff --git a/tests/search_tests.py b/tests/search_tests.py old mode 100755 new mode 100644 diff --git a/tests/sickrage_tests/__init__.py b/tests/sickrage_tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bc77c49432b6bb0bf10b6649c7b7c87e3bbbdb09 --- /dev/null +++ b/tests/sickrage_tests/__init__.py @@ -0,0 +1,41 @@ +# This file is part of SickRage. +# +# URL: https://sickrage.github.io +# Git: https://github.com/SickRage/SickRage.git +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +from helper.quality_tests import QualityTests +from show.coming_episodes_tests import ComingEpisodesTests +from show.history_tests import HistoryTests +from show.show_tests import ShowTests +from system.restart_tests import RestartTests +from system.shutdown_tests import ShutdownTests +from unittest import TestLoader, TextTestRunner + +if __name__ == '__main__': + print('=====> Running all test in "sickrage_tests" <=====') + + test_classes = [ + ComingEpisodesTests, + HistoryTests, + QualityTests, + RestartTests, + ShowTests, + ShutdownTests, + ] + + for test_class in test_classes: + suite = TestLoader().loadTestsFromTestCase(test_class) + TextTestRunner(verbosity=2).run(suite) diff --git a/tests/sickrage_tests/helper/__init__.py b/tests/sickrage_tests/helper/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/sickrage_tests/helper/quality_tests.py b/tests/sickrage_tests/helper/quality_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..fcb7ff3ca209e478862c4679607df12dd54e0f19 --- /dev/null +++ b/tests/sickrage_tests/helper/quality_tests.py @@ -0,0 +1,60 @@ +# This file is part of SickRage. +# +# URL: https://sickrage.github.io +# Git: https://github.com/SickRage/SickRage.git +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from sickbeard.common import ANY, HD, HD1080p, HD720p, Quality, SD +from sickrage.helper.quality import get_quality_string +from unittest import TestCase, TestLoader, TextTestRunner + + +class QualityTests(TestCase): + def test_get_quality_string(self): + tests = { + ANY: 'Any', + HD: 'HD', + HD720p: 'HD720p', + HD1080p: 'HD1080p', + Quality.FULLHDBLURAY: '1080p BluRay', + Quality.FULLHDTV: '1080p HDTV', + Quality.FULLHDWEBDL: '1080p WEB-DL', + Quality.HDBLURAY: '720p BluRay', + Quality.HDTV: '720p HDTV', + Quality.HDWEBDL: '720p WEB-DL', + Quality.NONE: 'N/A', + Quality.RAWHDTV: 'RawHD', + Quality.SDDVD: 'SD DVD', + Quality.SDTV: 'SDTV', + Quality.UNKNOWN: 'Unknown', + SD: 'SD', + 1000000: 'Custom', # An invalid quality number to test the default case + } + + for (quality, result) in tests.iteritems(): + self.assertEqual(get_quality_string(quality), result) + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + suite = TestLoader().loadTestsFromTestCase(QualityTests) + TextTestRunner(verbosity=2).run(suite) diff --git a/tests/sickrage_tests/show/__init__.py b/tests/sickrage_tests/show/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/sickrage_tests/show/coming_episodes_tests.py b/tests/sickrage_tests/show/coming_episodes_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..0bcf26f7fd68710d8a7a33506a972eebbb87b1c9 --- /dev/null +++ b/tests/sickrage_tests/show/coming_episodes_tests.py @@ -0,0 +1,91 @@ +# This file is part of SickRage. +# +# URL: https://sickrage.github.io +# Git: https://github.com/SickRage/SickRage.git +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from sickrage.show.ComingEpisodes import ComingEpisodes +from unittest import TestCase, TestLoader, TextTestRunner + + +class ComingEpisodesTests(TestCase): + def test_get_categories(self): + categories_list = [ + None, [], ['A', 'B'], [u'A', u'B'], '', 'A|B', u'A|B', + ] + results_list = [ + [], [], ['A', 'B'], [u'A', u'B'], [], ['A', 'B'], ['A', 'B'] + ] + + self.assertEqual( + len(categories_list), len(results_list), + 'Number of parameters (%d) and results (%d) does not match' % (len(categories_list), len(results_list)) + ) + + for (index, categories) in enumerate(categories_list): + self.assertEqual(ComingEpisodes._get_categories(categories), results_list[index]) + + def test_get_categories_map(self): + categories_list = [ + None, [], ['A', 'B'], [u'A', u'B'] + ] + results_list = [ + {}, {}, {'A': [], 'B': []}, {u'A': [], u'B': []} + ] + + self.assertEqual( + len(categories_list), len(results_list), + 'Number of parameters (%d) and results (%d) does not match' % (len(categories_list), len(results_list)) + ) + + for (index, categories) in enumerate(categories_list): + self.assertEqual(ComingEpisodes._get_categories_map(categories), results_list[index]) + + def test_get_sort(self): + tests = { + None: 'date', + '': 'date', + u'': 'date', + 'wrong': 'date', + u'wrong': 'date', + 'date': 'date', + u'date': 'date', + 'Date': 'date', + u'Date': 'date', + 'network': 'network', + u'network': 'network', + 'NetWork': 'network', + u'NetWork': 'network', + 'show': 'show', + u'show': 'show', + 'Show': 'show', + u'Show': 'show', + } + + for (sort, result) in tests.iteritems(): + self.assertEqual(ComingEpisodes._get_sort(sort), result) + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + suite = TestLoader().loadTestsFromTestCase(ComingEpisodesTests) + TextTestRunner(verbosity=2).run(suite) diff --git a/tests/sickrage_tests/show/history_tests.py b/tests/sickrage_tests/show/history_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..0b8c28738f80477e7e267d98b153fe8a4a0e9045 --- /dev/null +++ b/tests/sickrage_tests/show/history_tests.py @@ -0,0 +1,80 @@ +# This file is part of SickRage. +# +# URL: https://sickrage.github.io +# Git: https://github.com/SickRage/SickRage.git +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from sickbeard.common import Quality +from sickrage.show.History import History +from unittest import TestCase, TestLoader, TextTestRunner + + +class HistoryTests(TestCase): + def test_get_actions(self): + tests = { + None: [], + '': [], + u'': [], + 'wrong': [], + u'wrong': [], + 'downloaded': Quality.DOWNLOADED, + u'downloaded': Quality.DOWNLOADED, + 'Downloaded': Quality.DOWNLOADED, + u'Downloaded': Quality.DOWNLOADED, + 'snatched': Quality.SNATCHED, + u'snatched': Quality.SNATCHED, + 'Snatched': Quality.SNATCHED, + u'Snatched': Quality.SNATCHED, + } + + for (action, result) in tests.iteritems(): + self.assertEqual(History._get_actions(action), result) + + def test_get_limit(self): + tests = { + None: 0, + '': 0, + u'': 0, + '0': 0, + u'0': 0, + '5': 5, + u'5': 5, + '-5': 0, + u'-5': 0, + '1.5': 0, + u'1.5': 0, + '-1.5': 0, + u'-1.5': 0, + 5: 5, + -5: 0, + 1.5: 1, + -1.5: 0, + } + + for (action, result) in tests.iteritems(): + self.assertEqual(History._get_limit(action), result) + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + suite = TestLoader().loadTestsFromTestCase(HistoryTests) + TextTestRunner(verbosity=2).run(suite) diff --git a/tests/sickrage_tests/show/show_tests.py b/tests/sickrage_tests/show/show_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..1e6044a915e752e2582808fcf7477ed84cbbf6bb --- /dev/null +++ b/tests/sickrage_tests/show/show_tests.py @@ -0,0 +1,83 @@ +# This file is part of SickRage. +# +# URL: https://sickrage.github.io +# Git: https://github.com/SickRage/SickRage.git +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +import sickbeard + +from sickbeard.common import Quality +from sickbeard.tv import TVShow +from sickrage.show.Show import Show +from unittest import TestCase, TestLoader, TextTestRunner + + +class ShowTests(TestCase): + def test_validate_indexer_id(self): + sickbeard.QUALITY_DEFAULT = Quality.FULLHDTV + + show123 = TestTVShow(0, 123) + show456 = TestTVShow(0, 456) + show789 = TestTVShow(0, 789) + sickbeard.showList = [ + show123, + show456, + show789, + ] + + invalid_show_id = ('Invalid show ID', None) + + indexer_id_list = [ + None, '', u'', '123', u'123', '456', u'456', '789', u'789', 123, 456, 789, ['123', '456'], [u'123', u'456'], + [123, 456] + ] + results_list = [ + invalid_show_id, invalid_show_id, invalid_show_id, (None, show123), (None, show123), (None, show456), + (None, show456), (None, show789), (None, show789), (None, show123), (None, show456), (None, show789), + invalid_show_id, invalid_show_id, invalid_show_id + ] + + self.assertEqual( + len(indexer_id_list), len(results_list), + 'Number of parameters (%d) and results (%d) does not match' % (len(indexer_id_list), len(results_list)) + ) + + for (index, indexer_id) in enumerate(indexer_id_list): + self.assertEqual(Show._validate_indexer_id(indexer_id), results_list[index]) + + +class TestTVShow(TVShow): + """ + A test ``TVShow`` object that do not need DB access. + """ + + def __init__(self, indexer, indexer_id): + super(TestTVShow, self).__init__(indexer, indexer_id) + + def loadFromDB(self, skip_nfo=False): + pass + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + suite = TestLoader().loadTestsFromTestCase(ShowTests) + TextTestRunner(verbosity=2).run(suite) diff --git a/tests/sickrage_tests/system/__init__.py b/tests/sickrage_tests/system/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/sickrage_tests/system/restart_tests.py b/tests/sickrage_tests/system/restart_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..70da92d250b03c43a9b5a2c96bd716704656fcf0 --- /dev/null +++ b/tests/sickrage_tests/system/restart_tests.py @@ -0,0 +1,57 @@ +# This file is part of SickRage. +# +# URL: https://sickrage.github.io +# Git: https://github.com/SickRage/SickRage.git +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +import sickbeard + +from sickbeard.event_queue import Events +from sickrage.system.Restart import Restart +from unittest import TestCase, TestLoader, TextTestRunner + + +class RestartTests(TestCase): + def test_restart(self): + sickbeard.PID = 123456 + sickbeard.events = Events(None) + + tests = { + 0: False, + '0': False, + u'0': False, + 123: False, + '123': False, + u'123': False, + 123456: True, + '123456': True, + u'123456': True, + } + + for (pid, result) in tests.iteritems(): + self.assertEqual(Restart.restart(pid), result) + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + suite = TestLoader().loadTestsFromTestCase(RestartTests) + TextTestRunner(verbosity=2).run(suite) diff --git a/tests/sickrage_tests/system/shutdown_tests.py b/tests/sickrage_tests/system/shutdown_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..6c84b64cc17bff0b0b800406ee2c27c20e4d9002 --- /dev/null +++ b/tests/sickrage_tests/system/shutdown_tests.py @@ -0,0 +1,57 @@ +# This file is part of SickRage. +# +# URL: https://sickrage.github.io +# Git: https://github.com/SickRage/SickRage.git +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +import sickbeard + +from sickbeard.event_queue import Events +from sickrage.system.Shutdown import Shutdown +from unittest import TestCase, TestLoader, TextTestRunner + + +class ShutdownTests(TestCase): + def test_shutdown(self): + sickbeard.PID = 123456 + sickbeard.events = Events(None) + + tests = { + 0: False, + '0': False, + u'0': False, + 123: False, + '123': False, + u'123': False, + 123456: True, + '123456': True, + u'123456': True, + } + + for (pid, result) in tests.iteritems(): + self.assertEqual(Shutdown.stop(pid), result) + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + suite = TestLoader().loadTestsFromTestCase(ShutdownTests) + TextTestRunner(verbosity=2).run(suite) diff --git a/tests/test_lib.py b/tests/test_lib.py index dd28942dcd379a2b6764f430f53b0e7b0df80845..86a12da9d5751125cad468a801835afdb8f801c7 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -1,5 +1,6 @@ # coding=UTF-8 # Author: Dennis Lutter <lad1337@gmail.com> + # URL: http://code.google.com/p/sickbeard/ # # This file is part of SickRage. @@ -17,58 +18,93 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +# pylint: disable=C0301 +# Line too long + +""" +Create a test database for testing. + +Methods: + create_test_log_folder + create_test_cache_folder + setup_test_db + teardown_test_db + setup_test_episode_file + teardown_test_episode_file + setup_test_show_dir + teardown_test_show_dir + +Classes: + SickbeardTestDBCase + TestDBConnection + TestCacheDBConnection +""" + +import sys +import os.path +import unittest +import shutil -import sys, os.path sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -import unittest +# pylint: disable=F0401 +# Unable to import +import shutil_custom -from configobj import ConfigObj import sickbeard - from sickbeard import db, providers from sickbeard.databases import mainDB from sickbeard.databases import cache_db, failed_db from sickbeard.tv import TVEpisode -import shutil -import shutil_custom +# pylint: disable=F0401 +# Unable to import +from configobj import ConfigObj + shutil.copyfile = shutil_custom.copyfile_custom -#================= -# test globals -#================= -TESTDIR = os.path.abspath(os.path.dirname(__file__)) -TESTDBNAME = "sickbeard.db" -TESTCACHEDBNAME = "cache.db" -TESTFAILEDDBNAME = "failed.db" +# ================= +# test globals +# ================= +TEST_DIR = os.path.abspath(os.path.dirname(__file__)) +TEST_DB_NAME = "sickbeard.db" +TEST_CACHE_DB_NAME = "cache.db" +TEST_FAILED_DB_NAME = "failed.db" -SHOWNAME = u"show name" +SHOW_NAME = u"show name" SEASON = 4 EPISODE = 2 FILENAME = u"show name - s0" + str(SEASON) + "e0" + str(EPISODE) + ".mkv" -FILEDIR = os.path.join(TESTDIR, SHOWNAME) -FILEPATH = os.path.join(FILEDIR, FILENAME) -SHOWDIR = os.path.join(TESTDIR, SHOWNAME + " final") - -#================= -# prepare env functions -#================= -def createTestLogFolder(): +FILE_DIR = os.path.join(TEST_DIR, SHOW_NAME) +FILE_PATH = os.path.join(FILE_DIR, FILENAME) +SHOW_DIR = os.path.join(TEST_DIR, SHOW_NAME + " final") + + +# ================= +# prepare env functions +# ================= +def create_test_log_folder(): + """ + Create a log folder for test logs. + """ if not os.path.isdir(sickbeard.LOG_DIR): os.mkdir(sickbeard.LOG_DIR) -def createTestCacheFolder(): + +def create_test_cache_folder(): + """ + Create a cache folder for caching tests. + """ if not os.path.isdir(sickbeard.CACHE_DIR): os.mkdir(sickbeard.CACHE_DIR) -# call env functions at appropriate time during sickbeard var setup +# call env functions at appropriate time during SickBeard var setup -#================= -# sickbeard globals -#================= +# ================= +# SickBeard globals +# ================= sickbeard.SYS_ENCODING = 'UTF-8' sickbeard.showList = [] @@ -85,168 +121,226 @@ sickbeard.PROVIDER_ORDER = ["sick_beard_index"] sickbeard.newznabProviderList = providers.getNewznabProviderList("'Sick Beard Index|http://lolo.sickbeard.com/|0|5030,5040|0|eponly|0|0|0!!!NZBs.org|https://nzbs.org/||5030,5040,5060,5070,5090|0|eponly|0|0|0!!!Usenet-Crawler|https://www.usenet-crawler.com/||5030,5040,5060|0|eponly|0|0|0'") sickbeard.providerList = providers.makeProviderList() -sickbeard.PROG_DIR = os.path.abspath(os.path.join(TESTDIR, '..')) -sickbeard.DATA_DIR = TESTDIR +sickbeard.PROG_DIR = os.path.abspath(os.path.join(TEST_DIR, '..')) +sickbeard.DATA_DIR = TEST_DIR sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, "config.ini") sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) -sickbeard.BRANCG = sickbeard.config.check_setting_str(sickbeard.CFG, 'General', 'branch', '') +sickbeard.BRANCH = sickbeard.config.check_setting_str(sickbeard.CFG, 'General', 'branch', '') sickbeard.CUR_COMMIT_HASH = sickbeard.config.check_setting_str(sickbeard.CFG, 'General', 'cur_commit_hash', '') sickbeard.GIT_USERNAME = sickbeard.config.check_setting_str(sickbeard.CFG, 'General', 'git_username', '') sickbeard.GIT_PASSWORD = sickbeard.config.check_setting_str(sickbeard.CFG, 'General', 'git_password', '', censor_log=True) -sickbeard.LOG_DIR = os.path.join(TESTDIR, 'Logs') +sickbeard.LOG_DIR = os.path.join(TEST_DIR, 'Logs') sickbeard.logger.logFile = os.path.join(sickbeard.LOG_DIR, 'test_sickbeard.log') -createTestLogFolder() +create_test_log_folder() -sickbeard.CACHE_DIR = os.path.join(TESTDIR, 'cache') -createTestCacheFolder() +sickbeard.CACHE_DIR = os.path.join(TEST_DIR, 'cache') +create_test_cache_folder() +# pylint: disable=E1103 +# Has no member sickbeard.logger.initLogging(False, True) -#================= -# dummy functions -#================= -def _dummy_saveConfig(): + +# ================= +# dummy functions +# ================= +def _dummy_save_config(): + """ + Override the SickBeard save_config which gets called during a db upgrade. + + :return: True + """ return True -# this overrides the sickbeard save_config which gets called during a db upgrade +# this overrides the SickBeard save_config which gets called during a db upgrade # this might be considered a hack -mainDB.sickbeard.save_config = _dummy_saveConfig +mainDB.sickbeard.save_config = _dummy_save_config -# the real one tries to contact tvdb just stop it from getting more info on the ep -def _fake_specifyEP(self, season, episode): +# pylint: disable=W0613 +# Unused arguments +def _fake_specify_ep(self, season, episode): + """ + Override contact to TVDB indexer. + + :param self: + :param season: Season to search for ...not used + :param episode: Episode to search for ...not used + """ pass -TVEpisode.specifyEpisode = _fake_specifyEP +# the real one tries to contact TVDB just stop it from getting more info on the ep +TVEpisode.specifyEpisode = _fake_specify_ep -#================= -# test classes -#================= + +# ================= +# test classes +# ================= class SickbeardTestDBCase(unittest.TestCase): + """ + Superclass for testing the database. + + Methods: + setUp + tearDown + """ def setUp(self): sickbeard.showList = [] - setUp_test_db() - setUp_test_episode_file() - setUp_test_show_dir() + setup_test_db() + setup_test_episode_file() + setup_test_show_dir() def tearDown(self): sickbeard.showList = [] - tearDown_test_db() - tearDown_test_episode_file() - tearDown_test_show_dir() + teardown_test_db() + teardown_test_episode_file() + teardown_test_show_dir() + class TestDBConnection(db.DBConnection, object): + """ + Test connecting to the database. + """ + def __init__(self, db_file_name=TEST_DB_NAME): + db_file_name = os.path.join(TEST_DIR, db_file_name) + super(TestDBConnection, self).__init__(db_file_name) - def __init__(self, dbFileName=TESTDBNAME): - dbFileName = os.path.join(TESTDIR, dbFileName) - super(TestDBConnection, self).__init__(dbFileName) class TestCacheDBConnection(TestDBConnection, object): - def __init__(self, providerName): - db.DBConnection.__init__(self, os.path.join(TESTDIR, TESTCACHEDBNAME)) + """ + Test connecting to the cache database. + """ + def __init__(self, provider_name): + # pylint: disable=W0233 + # Init method from non-direct base class + db.DBConnection.__init__(self, os.path.join(TEST_DIR, TEST_CACHE_DB_NAME)) # Create the table if it's not already there try: - if not self.hasTable(providerName): - sql = "CREATE TABLE [" + providerName + "] (name TEXT, season NUMERIC, episodes TEXT, indexerid NUMERIC, url TEXT, time NUMERIC, quality TEXT, release_group TEXT)" + if not self.hasTable(provider_name): + sql = "CREATE TABLE [" + provider_name + "] (name TEXT, season NUMERIC, episodes TEXT, indexerid NUMERIC, url TEXT, time NUMERIC, quality TEXT, release_group TEXT)" self.connection.execute(sql) self.connection.commit() - except Exception, e: - if str(e) != "table [" + providerName + "] already exists": + # pylint: disable=W0703 + # Catching too general exception + except Exception as error: + if str(error) != "table [" + provider_name + "] already exists": raise # add version column to table if missing - if not self.hasColumn(providerName, 'version'): - self.addColumn(providerName, 'version', "NUMERIC", "-1") + if not self.hasColumn(provider_name, 'version'): + self.addColumn(provider_name, 'version', "NUMERIC", "-1") # Create the table if it's not already there try: sql = "CREATE TABLE lastUpdate (provider TEXT, time NUMERIC);" self.connection.execute(sql) self.connection.commit() - except Exception, e: - if str(e) != "table lastUpdate already exists": + # pylint: disable=W0703 + # Catching too general exception + except Exception as error: + if str(error) != "table lastUpdate already exists": raise # this will override the normal db connection sickbeard.db.DBConnection = TestDBConnection sickbeard.tvcache.CacheDBConnection = TestCacheDBConnection -#================= -# test functions -#================= -def setUp_test_db(): - """upgrades the db to the latest version + +# ================= +# test functions +# ================= +def setup_test_db(): + """ + Set up the test databases. """ + # Upgrade the db to the latest version. # upgrading the db db.upgradeDatabase(db.DBConnection(), mainDB.InitialSchema) # fix up any db problems db.sanityCheckDatabase(db.DBConnection(), mainDB.MainSanityCheck) - # and for cachedb too + # and for cache.db too db.upgradeDatabase(db.DBConnection("cache.db"), cache_db.InitialSchema) - # and for faileddb too + # and for failed.db too db.upgradeDatabase(db.DBConnection("failed.db"), failed_db.InitialSchema) -def tearDown_test_db(): +def teardown_test_db(): + """ + Tear down the test database. + """ from sickbeard.db import db_cons for connection in db_cons: db_cons[connection].commit() -# db_cons[connection].close() - -# for current_db in [ TESTDBNAME, TESTCACHEDBNAME, TESTFAILEDDBNAME ]: -# file_name = os.path.join(TESTDIR, current_db) -# if os.path.exists(file_name): -# try: -# os.remove(file_name) -# except Exception as e: -# print 'ERROR: Failed to remove ' + file_name -# print exception(e) - - -def setUp_test_episode_file(): - if not os.path.exists(FILEDIR): - os.makedirs(FILEDIR) + # db_cons[connection].close() + # + # for current_db in [ TEST_DB_NAME, TEST_CACHE_DB_NAME, TEST_FAILED_DB_NAME ]: + # file_name = os.path.join(TEST_DIR, current_db) + # if os.path.exists(file_name): + # try: + # os.remove(file_name) + # except Exception as e: + # print 'ERROR: Failed to remove ' + file_name + # print exception(e) + + +def setup_test_episode_file(): + """ + Create a test episode directory with a test episode in it. + """ + if not os.path.exists(FILE_DIR): + os.makedirs(FILE_DIR) try: - with open(FILEPATH, 'wb') as f: - f.write("foo bar") - f.flush() + with open(FILE_PATH, 'wb') as ep_file: + ep_file.write("foo bar") + ep_file.flush() + # pylint: disable=W0703 + # Catching too general exception except Exception: print "Unable to set up test episode" raise -def tearDown_test_episode_file(): - if os.path.exists(FILEDIR): - shutil.rmtree(FILEDIR) +def teardown_test_episode_file(): + """ + Remove the test episode. + """ + if os.path.exists(FILE_DIR): + shutil.rmtree(FILE_DIR) -def setUp_test_show_dir(): - if not os.path.exists(SHOWDIR): - os.makedirs(SHOWDIR) +def setup_test_show_dir(): + """ + Create a test show directory. + """ + if not os.path.exists(SHOW_DIR): + os.makedirs(SHOW_DIR) -def tearDown_test_show_dir(): - if os.path.exists(SHOWDIR): - shutil.rmtree(SHOWDIR) +def teardown_test_show_dir(): + """ + Remove the test show. + """ + if os.path.exists(SHOW_DIR): + shutil.rmtree(SHOW_DIR) if __name__ == '__main__': print "==================" - print "Dont call this directly" + print "Don't call this directly" print "==================" print "you might want to call" - dirList = os.listdir(TESTDIR) - for fname in dirList: - if (fname.find("_test") > 0) and (fname.find("pyc") < 0): - print "- " + fname + DIR_LIST = os.listdir(TEST_DIR) + for filename in DIR_LIST: + if (filename.find("_test") > 0) and (filename.find("pyc") < 0): + print "- " + filename print "==================" print "or just call all_tests.py"