diff --git a/.build/Gruntfile.js b/.build/Gruntfile.js index 8cb9dbda06574ae45961239741bc1daa60b2731e..ec48f3d4ceee0ad6bf425aeabd20ae85d2c4aea6 100644 --- a/.build/Gruntfile.js +++ b/.build/Gruntfile.js @@ -1,9 +1,22 @@ module.exports = function(grunt) { + require('load-grunt-tasks')(grunt); + grunt.initConfig({ - bower_concat: { + clean: { + dist: './dist/', + bower_components: './bower_components' // jshint ignore:line + }, + bower: { + install: { + options: { + copy: false + } + } + }, + bower_concat: { // jshint ignore:line all: { - dest: './_bower.js', - cssDest: './_bower.css', + dest: './dist/bower.js', + cssDest: './dist/bower.css', exclude: [ ], dependencies: { @@ -32,22 +45,40 @@ module.exports = function(grunt) { } }, uglify: { - my_target: { + bower: { + files: { + '../gui/slick/js/vender.min.js': ['./dist/bower.js'] + } + }, + core: { files: { - '../gui/slick/js/vender.min.js': ['./_bower.js'], '../gui/slick/js/core.min.js': ['../gui/slick/js/core.js'] } } }, + sass: { + options: { + sourceMap: true + }, + core: { + files: { + './dist/core.css': ['../gui/slick/scss/core.scss'] + } + } + }, cssmin: { options: { shorthandCompacting: false, roundingPrecision: -1 }, - target: { + bower: { + files: { + '../gui/slick/css/vender.min.css': ['./dist/bower.css'] + } + }, + core: { files: { - '../gui/slick/css/vender.min.css': ['./_bower.css'], - // '../gui/slick/css/core.min.css': ['./gui/slick/css/core.css'] + '../gui/slick/css/core.min.css': ['./dist/core.css'] } } }, @@ -61,20 +92,37 @@ module.exports = function(grunt) { '!../gui/slick/js/ajaxNotifications.js', '!../gui/slick/js/**/*.min.js', // We use this because ignores doesn't seem to work :( ] + }, + mocha: { + all: { + src: ['tests/testrunner.html'], + }, + options: { + run: true + } } }); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-bower-task'); grunt.loadNpmTasks('grunt-bower-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-mocha'); grunt.registerTask('default', [ + 'clean', + 'bower', 'bower_concat', 'uglify', - 'cssmin'] - ); + 'sass', + 'cssmin', + 'jshint', + 'mocha' + ]); grunt.registerTask('travis', [ - 'jshint' + 'jshint', + 'mocha' ]); }; diff --git a/.build/package.json b/.build/package.json index 49d0816d9d36d12b27ea676cde6a9e567af51101..502f6995823ce8ab760914ea2cfe868ae1061b54 100644 --- a/.build/package.json +++ b/.build/package.json @@ -11,15 +11,24 @@ }, "homepage": "https://github.com/SickRage/SickRage#readme", "scripts": { - "test": "snyk test" + "test": "grunt travis", + "security": "snyk test" }, - "dependencies": { - "snyk": "^1.3.1", + "devDependencies": { + "chai": "^3.4.1", "grunt": "^0.4.5", - "grunt-bower-concat": "^0.5.0", + "grunt-bower-concat": "^0.6.0", + "grunt-bower-task": "^0.4.0", "grunt-cli": "^0.1.13", + "grunt-contrib-clean": "^0.7.0", "grunt-contrib-cssmin": "^0.14.0", "grunt-contrib-jshint": "^0.11.3", - "grunt-contrib-uglify": "^0.9.2" + "grunt-contrib-sass": "^0.9.2", + "grunt-contrib-uglify": "^0.11.0", + "grunt-mocha": "^0.4.15", + "grunt-sass": "^1.1.0", + "load-grunt-tasks": "^3.3.0", + "mocha": "^2.3.4", + "snyk": "^1.3.1" } } diff --git a/.build/test/testrunner.html b/.build/test/testrunner.html new file mode 100644 index 0000000000000000000000000000000000000000..008398e6db10f4d2db4aff6063e5cc0166b70fd8 --- /dev/null +++ b/.build/test/testrunner.html @@ -0,0 +1,34 @@ +<html> + <head> + <meta charset="utf-8"> + <title>Mocha Tests</title> + <link rel="stylesheet" href="../node_modules/mocha/mocha.css" /> + <meta data-var="FAKE_TRUE_BOOLEAN" data-content="TRUE"> + <meta data-var="FAKE_TRUE_NUMBER" data-content="1"> + <meta data-var="FAKE_FALSE_BOOLEAN" data-content="FALSE"> + <meta data-var="FAKE_FALSE_NUMBER" data-content="0"> + <meta data-var="FAKE_CONTENT" data-content="RANDOM_CONTENT"> + </head> + <body> + <div id="mocha"></div> + <script src="../node_modules/mocha/mocha.js"></script> + <script src="../node_modules/chai/chai.js"></script> + <script src="../../gui/slick/js/vender.min.js"></script> + <script src="../../gui/slick/js/core.js"></script> + + <script> + mocha.setup('bdd'); + mocha.reporter('html'); + </script> + + <!-- Tests --> + <script src="tests.js"></script> + + <script> + // Only tests run in real browser, injected script run if options.run == true + if (navigator.userAgent.indexOf('PhantomJS') < 0) { + mocha.run(); + } + </script> + </body> +</html> diff --git a/.build/test/tests.js b/.build/test/tests.js new file mode 100644 index 0000000000000000000000000000000000000000..f2348856946edf92b88de9c3b8f103dcc9eb9e99 --- /dev/null +++ b/.build/test/tests.js @@ -0,0 +1,26 @@ +describe('metaToBool', function(){ + it('should return true', function(){ + chai.assert.equal(true, metaToBool('FAKE_TRUE_BOOLEAN')); + chai.assert.equal(true, metaToBool('FAKE_TRUE_NUMBER')); + }); + it('should return false', function(){ + chai.assert.equal(false, metaToBool('FAKE_FALSE_BOOLEAN')); + chai.assert.equal(false, metaToBool('FAKE_FALSE_NUMBER')); + }); + it('should return undefined', function(){ + chai.assert.equal(undefined, metaToBool('FAKE_UNDEFINED')); + }); +}); +describe('getMeta', function(){ + it('should return meta variable', function(){ + chai.assert.equal('RANDOM_CONTENT', getMeta('FAKE_CONTENT')); + }); +}); +describe('isMeta', function(){ + it('should return true if any string in array is matched', function(){ + chai.assert.equal(true, isMeta('FAKE_CONTENT', ['RANDOM_CONTENT'])); + }); + it('should return false if no strings in the array match', function(){ + chai.assert.equal(false, isMeta('FAKE_CONTENT', ['a', 'b', 'c'])); + }); +}); diff --git a/.gitignore b/.gitignore index 77f444fa1137a7e4d2e8f273a4bb16be48468dfd..e92638ab65dc7010c470037c90fe0d103bf19123 100644 --- a/.gitignore +++ b/.gitignore @@ -60,8 +60,8 @@ Thumbs.db ###################### lib/unrar2/UnRAR.exe -# Bower # +# Grunt # ###################### .build/bower_components .build/node_modules -.build/_bower* +.build/dist diff --git a/.jshintrc b/.jshintrc index 1c554638c5d225598b91c26d17b6ef6ef205f8e3..189830acc77e451853115739a34afeef0b15636a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -12,6 +12,7 @@ "latedef": true, "maxerr": 100, "maxlen": 1000, + "mocha": true, "noarg": true, "noempty": true, "nonbsp": true, @@ -19,6 +20,8 @@ "unused": true, "node": true, "predef": { + "chai": true, + "mocha": true, "srRoot": true, "themeSpinner": true, "metaToBool": true, diff --git a/SickBeard.py b/SickBeard.py index 99816b58514eb500ac86e6e5e3ebb27ae934dace..21898a2eb23c4d7dbf15123b013f5f8d524a6c2d 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -70,6 +70,8 @@ from sickbeard.webserveInit import SRWebServer from sickbeard.event_queue import Events from configobj import ConfigObj +from sickrage.helper.encoding import ek + # http://bugs.python.org/issue7980#msg221094 throwaway = datetime.datetime.strptime('20110101', '%Y%m%d') @@ -138,9 +140,9 @@ class SickRage(object): # Too many statements def start(self): # do some preliminary stuff - sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__)) - sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME) - sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME) + sickbeard.MY_FULLNAME = ek(os.path.normpath, ek(os.path.abspath, __file__)) + sickbeard.MY_NAME = ek(os.path.basename, sickbeard.MY_FULLNAME) + sickbeard.PROG_DIR = ek(os.path.dirname, sickbeard.MY_FULLNAME) sickbeard.DATA_DIR = sickbeard.PROG_DIR sickbeard.MY_ARGS = sys.argv[1:] @@ -219,16 +221,16 @@ class SickRage(object): self.PIDFILE = str(a) # If the pidfile already exists, sickbeard may still be running, so exit - if os.path.exists(self.PIDFILE): + if ek(os.path.exists, self.PIDFILE): sys.exit("PID file: " + self.PIDFILE + " already exists. Exiting.") # Specify folder to load the config file from if o in ('--config',): - sickbeard.CONFIG_FILE = os.path.abspath(a) + sickbeard.CONFIG_FILE = ek(os.path.abspath, a) # Specify folder to use as the data dir if o in ('--datadir',): - sickbeard.DATA_DIR = os.path.abspath(a) + sickbeard.DATA_DIR = ek(os.path.abspath, a) # Prevent resizing of the banner/posters even if PIL is installed if o in ('--noresize',): @@ -237,10 +239,10 @@ class SickRage(object): # The pidfile is only useful in daemon mode, make sure we can write the file properly if self.CREATEPID: if self.runAsDaemon: - pid_dir = os.path.dirname(self.PIDFILE) - if not os.access(pid_dir, os.F_OK): + pid_dir = ek(os.path.dirname, self.PIDFILE) + if not ek(os.access, pid_dir, os.F_OK): sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.") - if not os.access(pid_dir, os.W_OK): + if not ek(os.access, pid_dir, os.W_OK): sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.") else: @@ -251,38 +253,38 @@ class SickRage(object): # If they don't specify a config file then put it in the data dir if not sickbeard.CONFIG_FILE: - sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, "config.ini") + sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR, "config.ini") # Make sure that we can create the data dir - if not os.access(sickbeard.DATA_DIR, os.F_OK): + if not ek(os.access, sickbeard.DATA_DIR, os.F_OK): try: - os.makedirs(sickbeard.DATA_DIR, 0744) + ek(os.makedirs, sickbeard.DATA_DIR, 0744) except os.error: raise SystemExit("Unable to create datadir '" + sickbeard.DATA_DIR + "'") # Make sure we can write to the data dir - if not os.access(sickbeard.DATA_DIR, os.W_OK): + if not ek(os.access, sickbeard.DATA_DIR, os.W_OK): raise SystemExit("Datadir must be writeable '" + sickbeard.DATA_DIR + "'") # Make sure we can write to the config file - if not os.access(sickbeard.CONFIG_FILE, os.W_OK): - if os.path.isfile(sickbeard.CONFIG_FILE): + if not ek(os.access, sickbeard.CONFIG_FILE, os.W_OK): + if ek(os.path.isfile, sickbeard.CONFIG_FILE): raise SystemExit("Config file '" + sickbeard.CONFIG_FILE + "' must be writeable.") - elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK): + elif not ek(os.access, ek(os.path.dirname, sickbeard.CONFIG_FILE), os.W_OK): raise SystemExit( - "Config file root dir '" + os.path.dirname(sickbeard.CONFIG_FILE) + "' must be writeable.") + "Config file root dir '" + ek(os.path.dirname, sickbeard.CONFIG_FILE) + "' must be writeable.") - os.chdir(sickbeard.DATA_DIR) + ek(os.chdir, sickbeard.DATA_DIR) # Check if we need to perform a restore first - restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore') - if os.path.exists(restoreDir): + restoreDir = ek(os.path.join, sickbeard.DATA_DIR, 'restore') + if ek(os.path.exists, restoreDir): success = self.restoreDB(restoreDir, sickbeard.DATA_DIR) if self.consoleLogging: sys.stdout.write(u"Restore: restoring DB and config.ini %s!\n" % ("FAILED", "SUCCESSFUL")[success]) # Load the config and publish it to the sickbeard package - if self.consoleLogging and not os.path.isfile(sickbeard.CONFIG_FILE): + if self.consoleLogging and not ek(os.path.isfile, sickbeard.CONFIG_FILE): sys.stdout.write(u"Unable to find '" + sickbeard.CONFIG_FILE + "' , all settings will be default!" + "\n") sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) @@ -324,15 +326,15 @@ class SickRage(object): self.web_options = { 'port': int(self.startPort), 'host': self.webhost, - 'data_root': os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), + 'data_root': ek(os.path.join, sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), 'web_root': sickbeard.WEB_ROOT, 'log_dir': self.log_dir, 'username': sickbeard.WEB_USERNAME, 'password': sickbeard.WEB_PASSWORD, 'enable_https': sickbeard.ENABLE_HTTPS, 'handle_reverse_proxy': sickbeard.HANDLE_REVERSE_PROXY, - 'https_cert': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT), - 'https_key': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY), + 'https_cert': ek(os.path.join, sickbeard.PROG_DIR, sickbeard.HTTPS_CERT), + 'https_key': ek(os.path.join, sickbeard.PROG_DIR, sickbeard.HTTPS_KEY), } # start web server @@ -344,12 +346,12 @@ class SickRage(object): # Clean up after update if sickbeard.GIT_NEWVER: - toclean = os.path.join(sickbeard.CACHE_DIR, 'mako') - for root, dirs, files in os.walk(toclean, topdown=False): + toclean = ek(os.path.join, sickbeard.CACHE_DIR, 'mako') + for root, dirs, files in ek(os.walk, toclean, topdown=False): for name in files: - os.remove(os.path.join(root, name)) + ek(os.remove, ek(os.path.join, root, name)) for name in dirs: - os.rmdir(os.path.join(root, name)) + ek(os.rmdir, ek(os.path.join, root, name)) sickbeard.GIT_NEWVER = False # Fire up all our threads @@ -438,8 +440,8 @@ class SickRage(object): @staticmethod def remove_pid_file(PIDFILE): try: - if os.path.exists(PIDFILE): - os.remove(PIDFILE) + if ek(os.path.exists, PIDFILE): + ek(os.remove, PIDFILE) except (IOError, OSError): return False @@ -473,10 +475,10 @@ class SickRage(object): filesList = ['sickbeard.db', 'config.ini', 'failed.db', 'cache.db'] for filename in filesList: - srcFile = os.path.join(srcDir, filename) - dstFile = os.path.join(dstDir, filename) - bakFile = os.path.join(dstDir, '{0}.bak-{1}'.format(filename, datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))) - if os.path.isfile(dstFile): + srcFile = ek(os.path.join, srcDir, filename) + dstFile = ek(os.path.join, dstDir, filename) + bakFile = ek(os.path.join, dstDir, '{0}.bak-{1}'.format(filename, datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))) + if ek(os.path.isfile, dstFile): shutil.move(dstFile, bakFile) shutil.move(srcFile, dstFile) return True diff --git a/gui/slick/css/core.min.css b/gui/slick/css/core.min.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index 2310d85bb4c384222613e33fc38b7484648cc4e2..c4964a0678e270ccc1546c9ba739e19043002702 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -265,13 +265,13 @@ home.mako color: #fff; } +.loading-spinner { + background-image: url("../images/loading32-dark.gif"); +} + .show-container { - margin: 12px; - width: 188px; - height: 352px; background-color: #333; - border: 1px solid #111; - border-radius: 6px; + border-color: #333; } .show-title:after { diff --git a/gui/slick/css/lib/jquery-ui-1.10.4.custom.min.css b/gui/slick/css/lib/jquery-ui-1.10.4.custom.min.css index b84acdccdb760465bcea11ba2666f20bd9fd79cd..6b7af1c07a4c736489c232aca3575ff12d09619a 100755 Binary files a/gui/slick/css/lib/jquery-ui-1.10.4.custom.min.css and b/gui/slick/css/lib/jquery-ui-1.10.4.custom.min.css differ diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index dd7d31b8f57060db1a9899d235fe0527e1ced587..733539b7f3403e08db5d85f0e926aab97eabd756 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -623,25 +623,43 @@ home.mako border-radius: 3px; } +span.show-option { + white-space: nowrap; + margin-left: 10px; + line-height: 40px; +} + +.show-grid { + display: none; +} + #container, #container-anime { margin: 0 auto; } +.loading-spinner { + background-image: url("../images/loading32.gif"); + width: 32px; + height: 32px; + display: inline-block; +} + .show-container { - margin: 12px; - width: 188px; - height: 352px; - background-color: #DFDACF; - border: 1px solid #111; - border-radius: 6px; + margin: 4px; + background-color: #F3F3F3; + border: 5px solid #F3F3F3; + overflow: hidden; + box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.31); +} + +.show-details { + height: 32px; + overflow: hidden; } .show-image { overflow: hidden; - height: 273px; - width: 186px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; + max-width: 100%; } .show-container .ui-progressbar { @@ -701,11 +719,11 @@ home.mako height: 100%; top: 0; right: 0; - background-image: -webkit-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: -moz-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: -ms-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: -o-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: linear-gradient(to left, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); + background-image: -webkit-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: -moz-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: -ms-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: -o-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: linear-gradient(to left, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); } .show-date { @@ -714,6 +732,7 @@ home.mako white-space: nowrap; font-size: 11px; margin: 0px 4px 4px 4px; + color: #949494; } .show-date:after { @@ -724,11 +743,11 @@ home.mako height: 100%; top: 0; right: 0; - background-image: -webkit-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: -moz-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: -ms-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: -o-linear-gradient(right, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); - background-image: linear-gradient(to left, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)); + background-image: -webkit-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: -moz-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: -ms-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: -o-linear-gradient(right, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); + background-image: linear-gradient(to left, rgba(243, 243, 243, 1), rgba(243, 243, 243, 0)); } .show-table { @@ -3265,3 +3284,12 @@ IMDB Popular .popularShow p{ margin-bottom:0px; } + +table.home-header { + margin-top: 20px; + margin-bottom: 10px; +} + +input.tablesorter-filter.disabled { + display: none; +} diff --git a/gui/slick/images/providers/bitsnoop.png b/gui/slick/images/providers/bitsnoop.png new file mode 100644 index 0000000000000000000000000000000000000000..3feed89dc113d5399d304962afbe8ff81d870724 Binary files /dev/null and b/gui/slick/images/providers/bitsnoop.png differ diff --git a/gui/slick/images/subtitles/flags/und.png b/gui/slick/images/subtitles/flags/und.png index af9249bc317b724a8923e1447c60977dc9a91827..be4d9f67e8f10134cc9fa330914b024c1b36dd92 100644 Binary files a/gui/slick/images/subtitles/flags/und.png and b/gui/slick/images/subtitles/flags/und.png differ diff --git a/gui/slick/js/addExistingShow.js b/gui/slick/js/addExistingShow.js deleted file mode 100644 index 150b5a158f41a7c0b9f15bd32e5b4f805dc0aa6b..0000000000000000000000000000000000000000 --- a/gui/slick/js/addExistingShow.js +++ /dev/null @@ -1,72 +0,0 @@ -$(document).ready(function() { - $('#tableDiv').on('click', '#checkAll', function() { - var seasCheck = this; - $('.dirCheck').each(function() { - this.checked = seasCheck.checked; - }); - }); - - $('#submitShowDirs').click(function() { - var dirArr = []; - $('.dirCheck').each(function() { - if (this.checked === true) { - var show = $(this).attr('id'); - var indexer = $(this).closest('tr').find('select').val(); - dirArr.push(encodeURIComponent(indexer + '|' + show)); - } - }); - - if (dirArr.length === 0) { - return false; - } - - window.location.href = srRoot + '/home/addShows/addExistingShows?promptForSettings=' + ($('#promptForSettings').prop('checked') ? 'on' : 'off') + '&shows_to_add=' + dirArr.join('&shows_to_add='); - }); - - - function loadContent() { - var url = ''; - $('.dir_check').each(function(i,w) { - if ($(w).is(':checked')) { - if (url.length) { - url += '&'; - } - url += 'rootDir=' + encodeURIComponent($(w).attr('id')); - } - }); - - $('#tableDiv').html('<img id="searchingAnim" src="' + srRoot + '/images/loading32.gif" height="32" width="32" /> loading folders...'); - $.get(srRoot + '/home/addShows/massAddTable/', url, function(data) { - $('#tableDiv').html(data); - $("#addRootDirTable").tablesorter({ - //sortList: [[1,0]], - widgets: ['zebra'], - headers: { - 0: { sorter: false } - } - }); - }); - } - - var lastTxt = ''; - $('#rootDirText').change(function() { - if (lastTxt === $('#rootDirText').val()) { - return false; - } else { - lastTxt = $('#rootDirText').val(); - } - $('#rootDirStaticList').html(''); - $('#rootDirs option').each(function(i, w) { - $('#rootDirStaticList').append('<li class="ui-state-default ui-corner-all"><input type="checkbox" class="cb dir_check" id="' + $(w).val() + '" checked=checked> <label for="' + $(w).val() + '"><b>' + $(w).val() + '</b></label></li>'); - }); - loadContent(); - }); - - $('#rootDirStaticList').on('click', '.dir_check', loadContent); - - $('#tableDiv').on('click', '.showManage', function(event) { - event.preventDefault(); - $("#tabs").tabs('option', 'active', 0); - $('html,body').animate({scrollTop:0}, 1000); - }); -}); diff --git a/gui/slick/js/addTrendingShow.js b/gui/slick/js/addTrendingShow.js deleted file mode 100644 index ef5abbc17df8265c3d1b73b58d8eda01503a7024..0000000000000000000000000000000000000000 --- a/gui/slick/js/addTrendingShow.js +++ /dev/null @@ -1,14 +0,0 @@ -$.fn.loadContent = function(path, loadingTxt, errorTxt) { - $(this).html('<img id="searchingAnim" src="' + srRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> ' + loadingTxt); - $(this).load(srRoot + path + ' #container', function(response, status) { - if (status === "error") { $(this).empty().html(errorTxt); } - }); -}; - -$(document).ready(function() { - $('#trendingShows').loadContent('/home/addShows/getTrendingShows/', 'Loading trending shows...', 'Trakt timed out, refresh page to try again'); - $('#container').isotope({ - itemSelector: '.trakt_show', - layoutMode: 'fitRows' - }); -}); diff --git a/gui/slick/js/browser.js b/gui/slick/js/browser.js index 51f6f4849dc6d84d70570af1b4c3deedb9947821..b3a447d8159202e16541e6f0e109bd9a1ddeb43e 100644 --- a/gui/slick/js/browser.js +++ b/gui/slick/js/browser.js @@ -90,7 +90,11 @@ autoOpen: false }); } - + else { + // The title may change, even if fileBrowserDialog already exists + fileBrowserDialog.dialog('option', 'title', options.title); + } + fileBrowserDialog.dialog('option', 'buttons', [ { text: 'Ok', diff --git a/gui/slick/js/core.js b/gui/slick/js/core.js index 496c0cc2f8e822e0d653bc55d824d08706a5ba37..ca1c245579f54bddc152564e7b58c2d32cb36d23 100644 --- a/gui/slick/js/core.js +++ b/gui/slick/js/core.js @@ -22,6 +22,26 @@ var configSuccess = function(){ $('#prowl_show').trigger('notify'); }; +function metaToBool(pyVar){ + var meta = $('meta[data-var="' + pyVar + '"]').data('content'); + if(meta === undefined){ + console.log(pyVar + ' is empty, did you forget to add this to main.mako?'); + return meta; + } else { + meta = (isNaN(meta) ? meta.toLowerCase() : meta.toString()); + return !(meta === 'false' || meta === 'none' || meta === '0'); + } +} + +function getMeta(pyVar){ + return $('meta[data-var="' + pyVar + '"]').data('content'); +} + +function isMeta(pyVar, result){ + var reg = new RegExp(result.length > 1 ? result.join('|') : result); + return (reg).test($('meta[data-var="' + pyVar + '"]').data('content')); +} + var SICKRAGE = { common: { init: function() { @@ -133,6 +153,12 @@ var SICKRAGE = { }; $("[datetime]").timeago(); } + + $(document.body).on('click', 'a[data-no-redirect]', function(e){ + e.preventDefault(); + $.get($(this).attr('href')); + return false; + }); } }, config: { @@ -868,14 +894,21 @@ var SICKRAGE = { // we have to call this function on dom ready to create the devices select getPushbulletDevices(); - // @TODO Find out what notify_data actually does since it doesn't seem to be a real function $('#email_show').on('change', function() { var key = parseInt($('#email_show').val(), 10); - $('#email_show_list').val(key >= 0 ? notify_data[key.toString()].list : ''); // jshint ignore:line + $.getJSON(srRoot + "/home/loadShowNotifyLists", function(notifyData) { + if (notifyData._size > 0) { + $('#email_show_list').val(key >= 0 ? notifyData[key.toString()].list : ''); + } + }); }); $('#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 + $.getJSON(srRoot + "/home/loadShowNotifyLists", function(notifyData) { + if (notifyData._size > 0) { + $('#prowl_show_list').val(key >= 0 ? notifyData[key.toString()].prowl_notify_list : ''); // jshint ignore:line + } + }); }); // Update the internal data struct anytime settings are saved to the server @@ -887,10 +920,8 @@ var SICKRAGE = { }); function loadShowNotifyLists() { - $.get(srRoot + "/home/loadShowNotifyLists", function(data) { - var list, html, s; - list = $.parseJSON(data); // @TODO The line below this is the same as the $('#email_show') function above - notify_data = list; // jshint ignore:line + $.getJSON(srRoot + "/home/loadShowNotifyLists", function(list) { + var html, s; if (list._size === 0) { return; } // Convert the 'list' object to a js array of objects so that we can sort it @@ -1754,7 +1785,7 @@ var SICKRAGE = { // Handle filtering in the poster layout $('#filterShowName').on('input', _.debounce(function (e) { - $('#container, #container-anime').isotope({ + $('.show-grid').isotope({ filter: function () { var name = $('div.show-title', this).text(); return (name.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1); @@ -1762,6 +1793,63 @@ var SICKRAGE = { }); }, 500)); + function resizePosters(newSize) { + var fontSize, logoWidth, borderRadius, borderWidth; + if (newSize < 125) { // small + borderRadius = 3; + borderWidth = 4; + } else if (newSize < 175) { // medium + fontSize = 9; + logoWidth = 40; + borderRadius = 4; + borderWidth = 5; + } else { // large + fontSize = 11; + logoWidth = 50; + borderRadius = 6; + borderWidth = 6; + } + + // If there's a poster popup, remove it before resizing + $('#posterPopup').remove(); + + if (fontSize === undefined) { + $('.show-details').hide(); + } else { + $('.show-details').show(); + $('.show-dlstats, .show-quality').css('fontSize', fontSize); + $('.show-network-image').css('width', logoWidth); + } + + $('.show-container').css({ + width: newSize, + borderWidth: borderWidth, + borderRadius: borderRadius + }); + } + + var posterSize; + if (typeof(Storage) !== 'undefined') { + posterSize = parseInt(localStorage.getItem('posterSize')); + } + if (typeof(posterSize) !== 'number' || isNaN(posterSize)) { + posterSize = 188; + } + resizePosters(posterSize); + + $('#posterSizeSlider').slider({ + min: 75, + max: 250, + value: posterSize, + change: function (e, ui) { + if (typeof(Storage) !== 'undefined') { + localStorage.setItem('posterSize', ui.value); + } + resizePosters(ui.value); + $('.show-grid').isotope('layout'); + } + }); + // This needs to be refined to work a little faster. $('.progressbar').each(function(){ var percentage = $(this).data('progress-percentage'); @@ -1786,7 +1874,9 @@ var SICKRAGE = { 3: function(node) { return $(node).find("span").prop("title").toLowerCase(); }, 4: function(node) { return $(node).find("span").text().toLowerCase(); }, 5: function(node) { return $(node).find("span:first").text(); }, - 6: function(node) { return $(node).find("img").attr("alt"); } + 6: function(node) { return $(node).data('show-size'); }, + 7: function(node) { return $(node).find("img").attr("alt"); } + }, widgets: ['saveSort', 'zebra', 'stickyHeaders', 'filter', 'columnSelector'], headers: { @@ -1795,7 +1885,8 @@ var SICKRAGE = { 2: { sorter: 'loadingNames' }, 4: { sorter: 'quality' }, 5: { sorter: 'eps' }, - 6: { filter: 'parsed' } + 6: { sorter: 'digit' }, + 7: { filter: 'parsed' } }, widgetOptions: { filter_columnFilters: true, // jshint ignore:line @@ -1862,43 +1953,98 @@ var SICKRAGE = { sortAppend: [[2,0]] }); - // @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({ + $('.show-grid').imagesLoaded(function () { + $('.loading-spinner').hide(); + $('.show-grid').show().isotope({ itemSelector: '.show-container', sortBy : getMeta('sickbeard.POSTER_SORTBY'), sortAscending: getMeta('sickbeard.POSTER_SORTDIR'), layoutMode: 'masonry', masonry: { - columnWidth: 13, isFitWidth: true }, getSortData: { - name: function(itemElem){ + name: function (itemElem) { var name = $(itemElem).attr('data-name'); return (metaToBool('sickbeard.SORT_ARTICLE') ? (name || '') : (name || '').replace(/^(The|A|An)\s/i,'')); }, network: '[data-network]', - date: function(itemElem){ + date: function (itemElem) { var date = $(itemElem).attr('data-date'); return date.length && parseInt(date, 10) || Number.POSITIVE_INFINITY; }, - progress: function(itemElem){ + progress: function (itemElem) { var progress = $(itemElem).attr('data-progress'); return progress.length && parseInt(progress, 10) || Number.NEGATIVE_INFINITY; } } }); + + // When posters are small enough to not display the .show-details + // table, display a larger poster when hovering. + var posterHoverTimer = null; + $('.show-container').on('mouseenter', function () { + var poster = $(this); + if (poster.find('.show-details').css('display') !== 'none') { + return; + } + posterHoverTimer = setTimeout(function () { + posterHoverTimer = null; + $('#posterPopup').remove(); + var popup = poster.clone().attr({ + id: 'posterPopup' + }); + var origLeft = poster.offset().left; + var origTop = poster.offset().top; + popup.css({ + position: 'absolute', + margin: 0, + top: origTop, + left: origLeft + }); + popup.find('.show-details').show(); + popup.on('mouseleave', function () { + $(this).remove(); + }); + popup.zIndex(9999); + popup.appendTo('body'); + + var height = 438, width = 250; + var newTop = (origTop+poster.height()/2)-(height/2); + var newLeft = (origLeft+poster.width()/2)-(width/2); + + // Make sure the popup isn't outside the viewport + var margin = 5; + var scrollTop = $(window).scrollTop(); + var scrollLeft = $(window).scrollLeft(); + var scrollBottom = scrollTop + $(window).innerHeight(); + var scrollRight = scrollLeft + $(window).innerWidth(); + if (newTop < scrollTop+margin) { newTop = scrollTop+margin; } + if (newLeft < scrollLeft+margin) { newLeft = scrollLeft+margin; } + if (newTop+height+margin > scrollBottom) { newTop = scrollBottom-height-margin; } + if (newLeft+width+margin > scrollRight) { newLeft = scrollRight-width-margin; } + + popup.animate({ + top: newTop, + left: newLeft, + width: 250, + height: 438 + }); + }, 300); + }).on('mouseleave', function () { + if (posterHoverTimer !== null) { + clearTimeout(posterHoverTimer); + } + }); }); $('#postersort').on('change', function(){ - $('#container, #container-anime').isotope({sortBy: $(this).val()}); + $('.show-grid').isotope({sortBy: $(this).val()}); $.get($(this).find('option[value=' + $(this).val() +']').attr('data-sort')); }); $('#postersortdirection').on('change', function(){ - $('#container, #container-anime').isotope({sortAscending: ($(this).val() === 'true')}); + $('.show-grid').isotope({sortAscending: ($(this).val() === 'true')}); $.get($(this).find('option[value=' + $(this).val() +']').attr('data-sort')); }); @@ -2262,9 +2408,9 @@ var SICKRAGE = { 8: function(node) { return $(node).find("img").attr("alt"); }, 9: function(node) { return $(node).find("img").attr("alt"); }, }, - widgets: ['zebra'], + widgets: ['zebra', 'filter'], headers: { - 0: { sorter: false}, + 0: { sorter: false, filter: false}, 1: { sorter: 'showNames'}, 2: { sorter: 'quality'}, 3: { sorter: 'sports'}, @@ -2601,6 +2747,403 @@ var SICKRAGE = { $.tablesorter.columnSelector.attachTo( $('#showListTable'), '#popover-target'); }); } + }, + addShows: { + init: function() { + $('#tabs').tabs({ + collapsible: true, + selected: (metaToBool('sickbeard.SORT_ARTICLE') ? -1 : 0) + }); + + $.initRemoteShowGrid = function(){ + // Set defaults on page load + $('#showsort').val('original'); + $('#showsortdirection').val('asc'); + + $('#showsort').on('change', function() { + var sortCriteria; + switch (this.value) { + case 'original': + sortCriteria = 'original-order'; + break; + case 'rating': + /* randomise, else the rating_votes can already + * have sorted leaving this with nothing to do. + */ + $('#container').isotope({sortBy: 'random'}); + sortCriteria = 'rating'; + break; + case 'rating_votes': + sortCriteria = ['rating', 'votes']; + break; + case 'votes': + sortCriteria = 'votes'; + break; + default: + sortCriteria = 'name'; + break; + } + $('#container').isotope({ + sortBy: sortCriteria + }); + }); + + $('#showsortdirection').on('change', function() { + $('#container').isotope({ + sortAscending: ('asc' === this.value) + }); + }); + + $('#container').imagesLoaded(function() { + $('#container').isotope({ + sortBy: 'original-order', + layoutMode: 'fitRows', + getSortData: { + name: function(itemElem) { + var name = $(itemElem).attr('data-name') || ''; + return (metaToBool('sickbeard.SORT_ARTICLE') ? name : name.replace(/^(The|A|An)\s/i, '')).toLowerCase(); + }, + rating: '[data-rating] parseInt', + votes: '[data-votes] parseInt', + } + }); + }); + }; + + $.fn.loadRemoteShows = function(path, loadingTxt, errorTxt) { + $(this).html('<img id="searchingAnim" src="' + srRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> ' + loadingTxt); + $(this).load(srRoot + path + ' #container', function(response, status) { + if (status === "error") { + $(this).empty().html(errorTxt); + } else { + $.initRemoteShowGrid(); + } + }); + }; + }, + index: function() { + + }, + newShow: function() { + var searchRequestXhr = null; + + function searchIndexers() { + if (!$('#nameToSearch').val().length) { return; } + + if (searchRequestXhr) { searchRequestXhr.abort(); } + + var searchingFor = $('#nameToSearch').val().trim() + ' on ' + $('#providedIndexer option:selected').text() + ' in ' + $('#indexerLangSelect').val(); + $('#searchResults').empty().html('<img id="searchingAnim" src="' + srRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> searching ' + searchingFor + '...'); + + searchRequestXhr = $.ajax({ + url: srRoot + '/addShows/searchIndexersForShowName', + data: { + 'search_term': $('#nameToSearch').val().trim(), + 'lang': $('#indexerLangSelect').val(), + 'indexer': $('#providedIndexer').val()}, + timeout: parseInt($('#indexer_timeout').val(), 10) * 1000, + dataType: 'json', + error: function () { + $('#searchResults').empty().html('search timed out, try again or try another indexer'); + }, + success: function (data) { + var firstResult = true; + var resultStr = '<fieldset>\n<legend class="legendStep">Search Results:</legend>\n'; + var checked = ''; + + if (data.results.length === 0) { + resultStr += '<b>No results found, try a different search.</b>'; + } else { + $.each(data.results, function(index, obj) { + if (firstResult) { + checked = ' checked'; + firstResult = false; + } else { + checked = ''; + } + + var whichSeries = obj.join('|'); + + resultStr += '<input type="radio" id="whichSeries" name="whichSeries" value="' + whichSeries.replace(/"/g, '') + '"' + checked + ' /> '; + if (data.langid && data.langid !== '') { + resultStr += '<a href="' + anonURL + obj[2] + obj[3] + '&lid=' + data.langid + '" onclick=\"window.open(this.href, \'_blank\'); return false;\" ><b>' + obj[4] + '</b></a>'; + } else { + resultStr += '<a href="' + anonURL + obj[2] + obj[3] + '" onclick=\"window.open(this.href, \'_blank\'); return false;\" ><b>' + obj[4] + '</b></a>'; + } + + if (obj[5] !== null) { + var startDate = new Date(obj[5]); + var today = new Date(); + if (startDate > today) { + resultStr += ' (will debut on ' + obj[5] + ')'; + } else { + resultStr += ' (started on ' + obj[5] + ')'; + } + } + + if (obj[0] !== null) { + resultStr += ' [' + obj[0] + ']'; + } + + resultStr += '<br>'; + }); + resultStr += '</ul>'; + } + resultStr += '</fieldset>'; + $('#searchResults').html(resultStr); + updateSampleText(); + myform.loadsection(0); + } + }); + } + + $('#searchName').click(function () { searchIndexers(); }); + + if ($('#nameToSearch').length && $('#nameToSearch').val().length) { + $('#searchName').click(); + } + + $('#addShowButton').click(function () { + // if they haven't picked a show don't let them submit + if (!$('input:radio[name="whichSeries"]:checked').val() && !$('input:hidden[name="whichSeries"]').val().length) { + alert('You must choose a show to continue'); + return false; + } + generateBlackWhiteList(); + $('#addShowForm').submit(); + }); + + $('#skipShowButton').click(function () { + $('#skipShow').val('1'); + $('#addShowForm').submit(); + }); + + $('#qualityPreset').change(function () { + myform.loadsection(2); + }); + + /*********************************************** + * jQuery Form to Form Wizard- (c) Dynamic Drive (www.dynamicdrive.com) + * This notice MUST stay intact for legal use + * Visit http://www.dynamicdrive.com/ for this script and 100s more. + ***********************************************/ + + // @TODO we need to move to real forms instead of this + + var myform = new formtowizard({ // jshint ignore:line + formid: 'addShowForm', + revealfx: ['slide', 500], + oninit: function () { + updateSampleText(); + if ($('input:hidden[name=whichSeries]').length && $('#fullShowPath').length) { + goToStep(3); + } + } + }); + + function goToStep(num) { + $('.step').each(function () { + if ($.data(this, 'section') + 1 === num) { + $(this).click(); + } + }); + } + + $('#nameToSearch').focus(); + + function updateSampleText() { + // if something's selected then we have some behavior to figure out + + var showName, sepChar; + // if they've picked a radio button then use that + if ($('input:radio[name=whichSeries]:checked').length) { + showName = $('input:radio[name=whichSeries]:checked').val().split('|')[4]; + } else if ($('input:hidden[name=whichSeries]').length && $('input:hidden[name=whichSeries]').val().length) { // if we provided a show in the hidden field, use that + showName = $('#providedName').val(); + } else { + showName = ''; + } + updateBlackWhiteList(showName); + var sampleText = 'Adding show <b>' + showName + '</b> into <b>'; + + // if we have a root dir selected, figure out the path + if ($('#rootDirs option:selected').length) { + var rootDirectoryText = $('#rootDirs option:selected').val(); + if (rootDirectoryText.indexOf('/') >= 0) { + sepChar = '/'; + } else if (rootDirectoryText.indexOf('\\') >= 0) { + sepChar = '\\'; + } else { + sepChar = ''; + } + + if (rootDirectoryText.substr(sampleText.length - 1) !== sepChar) { + rootDirectoryText += sepChar; + } + rootDirectoryText += '<i>||</i>' + sepChar; + + sampleText += rootDirectoryText; + } else if ($('#fullShowPath').length && $('#fullShowPath').val().length) { + sampleText += $('#fullShowPath').val(); + } else { + sampleText += 'unknown dir.'; + } + + sampleText += '</b>'; + + // if we have a show name then sanitize and use it for the dir name + if (showName.length) { + $.get(srRoot + '/addShows/sanitizeFileName', {name: showName}, function (data) { + $('#displayText').html(sampleText.replace('||', data)); + }); + // if not then it's unknown + } else { + $('#displayText').html(sampleText.replace('||', '??')); + } + + // also toggle the add show button + if (($("#rootDirs option:selected").length || ($('#fullShowPath').length && $('#fullShowPath').val().length)) && + ($('input:radio[name=whichSeries]:checked').length) || ($('input:hidden[name=whichSeries]').length && $('input:hidden[name=whichSeries]').val().length)) { + $('#addShowButton').attr('disabled', false); + } else { + $('#addShowButton').attr('disabled', true); + } + } + + $('#rootDirText').change(updateSampleText); + $('#searchResults').on('change', '#whichSeries', updateSampleText); + + $('#nameToSearch').keyup(function(event) { + if (event.keyCode === 13) { + $('#searchName').click(); + } + }); + + $('#anime').change (function() { + updateSampleText(); + myform.loadsection(2); + }); + + function updateBlackWhiteList(showName) { + $('#white').children().remove(); + $('#black').children().remove(); + $('#pool').children().remove(); + + if ($('#anime').prop('checked')) { + $('#blackwhitelist').show(); + if (showName) { + $.getJSON(srRoot + '/home/fetch_releasegroups', { + 'show_name': showName + }, function (data) { + if (data.result === 'success') { + $.each(data.groups, function(i, group) { + var option = $("<option>"); + option.attr("value", group.name); + option.html(group.name + ' | ' + group.rating + ' | ' + group.range); + option.appendTo('#pool'); + }); + } + }); + } + } else { + $('#blackwhitelist').hide(); + } + } + }, + addExistingShow: function(){ + $('#tableDiv').on('click', '#checkAll', function() { + var seasCheck = this; + $('.dirCheck').each(function() { + this.checked = seasCheck.checked; + }); + }); + + $('#submitShowDirs').on('click', function() { + var dirArr = []; + $('.dirCheck').each(function() { + if (this.checked === true) { + var show = $(this).attr('id'); + var indexer = $(this).closest('tr').find('select').val(); + dirArr.push(encodeURIComponent(indexer + '|' + show)); + } + }); + + if (dirArr.length === 0) { + return false; + } + + window.location.href = srRoot + '/addShows/addExistingShows?promptForSettings=' + ($('#promptForSettings').prop('checked') ? 'on' : 'off') + '&shows_to_add=' + dirArr.join('&shows_to_add='); + }); + + function loadContent() { + var url = ''; + $('.dir_check').each(function(i,w) { + if ($(w).is(':checked')) { + if (url.length) { + url += '&'; + } + url += 'rootDir=' + encodeURIComponent($(w).attr('id')); + } + }); + + $('#tableDiv').html('<img id="searchingAnim" src="' + srRoot + '/images/loading32.gif" height="32" width="32" /> loading folders...'); + $.get(srRoot + '/addShows/massAddTable/', url, function(data) { + $('#tableDiv').html(data); + $("#addRootDirTable").tablesorter({ + //sortList: [[1,0]], + widgets: ['zebra'], + headers: { + 0: { sorter: false } + } + }); + }); + } + + var lastTxt = ''; + // @TODO this needs a real name, for now this fixes the issue of the page not loading at all, + // before I added this I couldn't get the directories to show in the table + var a = function() { + if (lastTxt === $('#rootDirText').val()) { + return false; + } else { + lastTxt = $('#rootDirText').val(); + } + $('#rootDirStaticList').html(''); + $('#rootDirs option').each(function(i, w) { + $('#rootDirStaticList').append('<li class="ui-state-default ui-corner-all"><input type="checkbox" class="cb dir_check" id="' + $(w).val() + '" checked=checked> <label for="' + $(w).val() + '"><b>' + $(w).val() + '</b></label></li>'); + }); + loadContent(); + }; + + a(); + + $('#rootDirText').on('change', a); + + $('#rootDirStaticList').on('click', '.dir_check', loadContent); + + $('#tableDiv').on('click', '.showManage', function(event) { + event.preventDefault(); + $("#tabs").tabs('option', 'active', 0); + $('html,body').animate({scrollTop:0}, 1000); + }); + }, + recommendedShows: function(){ + $('#recommendedShows').loadRemoteShows( + '/addShows/getRecommendedShows/', + 'Loading recommended shows...', + 'Trakt timed out, refresh page to try again' + ); + }, + trendingShows: function(){ + $('#trendingShows').loadRemoteShows( + '/addShows/getTrendingShows/', + 'Loading trending shows...', + 'Trakt timed out, refresh page to try again' + ); + }, + popularShows: function(){ + $.initRemoteShowGrid(); + } } }; @@ -2623,5 +3166,6 @@ var UTIL = { UTIL.exec(controller, action); } }; - -$(document).ready(UTIL.init); +if (navigator.userAgent.indexOf('PhantomJS') === -1) { + $(document).ready(UTIL.init); +} diff --git a/gui/slick/js/core.min.js b/gui/slick/js/core.min.js index ec512de8b35ca25d9fc76f1969820616877f9338..01fceaea955eaee788560d372e33a918b79d996f 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/home_addExistingShow.js b/gui/slick/js/home_addExistingShow.js deleted file mode 100644 index 3e3e8dc37f7dfb8c94b377dbae7a7cf56e473d71..0000000000000000000000000000000000000000 --- a/gui/slick/js/home_addExistingShow.js +++ /dev/null @@ -1,6 +0,0 @@ -$(document).ready(function(){ - $( "#tabs" ).tabs({ - collapsible: true, - selected: (metaToBool('sickbeard.SORT_ARTICLE') ? -1 : 0) - }); -}); diff --git a/gui/slick/js/home_recommendedShows.js b/gui/slick/js/home_recommendedShows.js deleted file mode 100644 index fa82ab85cd0eb6f55b0099621a8f6769ace9978e..0000000000000000000000000000000000000000 --- a/gui/slick/js/home_recommendedShows.js +++ /dev/null @@ -1,57 +0,0 @@ -$(document).ready(function(){ - $( "#tabs" ).tabs({ - collapsible: true, - selected: (metaToBool('sickbeard.SORT_ARTICLE') ? -1 : 0) - }); - - // initialise combos for dirty page refreshes - $('#showsort').val('original'); - $('#showsortdirection').val('asc'); - - var $container = [$('#container')]; - $.each($container, function() { - this.isotope({ - itemSelector: '.trakt_show', - sortBy: 'original-order', - layoutMode: 'fitRows', - getSortData: { - name: function( itemElem ) { - var name = $( itemElem ).attr('data-name'); - return (metaToBool('sickbeard.SORT_ARTICLE') ? (name || '') : (name || '').replace(/^(The|A|An)\s/i,'')); - }, - rating: '[data-rating] parseInt', - votes: '[data-votes] parseInt', - } - }); - }); - - $('#showsort').on( 'change', function() { - var sortCriteria; - switch (this.value) { - case 'original': - sortCriteria = 'original-order'; - break; - case 'rating': - /* randomise, else the rating_votes can already - * have sorted leaving this with nothing to do. - */ - $('#container').isotope({sortBy: 'random'}); - sortCriteria = 'rating'; - break; - case 'rating_votes': - sortCriteria = ['rating', 'votes']; - break; - case 'votes': - sortCriteria = 'votes'; - break; - default: - sortCriteria = 'name'; - break; - } - $('#container').isotope({sortBy: sortCriteria}); - }); - - $('#showsortdirection').on( 'change', function() { - $('#container').isotope({sortAscending: ('asc' === this.value)}); - }); -}); diff --git a/gui/slick/js/home_trendingShows.js b/gui/slick/js/home_trendingShows.js deleted file mode 100644 index 3f21b696a5f69b6e58891a7def52a1262fb2fa8e..0000000000000000000000000000000000000000 --- a/gui/slick/js/home_trendingShows.js +++ /dev/null @@ -1,54 +0,0 @@ -$(document).ready(function(){ - $("#tabs").tabs({ - collapsible: true, - // selected: ${('0', '-1')[bool(sickbeard.ROOT_DIRS)]} - }); - - // initialise combos for dirty page refreshes - $('#showsort').val('original'); - $('#showsortdirection').val('asc'); - - $('#container').isotope({ - itemSelector: '.trakt_show', - sortBy: 'original-order', - layoutMode: 'fitRows', - getSortData: { - name: function( itemElem ) { - var name = $(itemElem).attr('data-name') || ''; - return (metaToBool('sickbeard.SORT_ARTICLE') ? name : name.replace(/^(The|A|An)\s/i, '')).toLowerCase(); - }, - rating: '[data-rating] parseInt', - votes: '[data-votes] parseInt', - } - }); - - $('#showsort').on( 'change', function() { - var sortCriteria; - switch (this.value) { - case 'original': - sortCriteria = 'original-order'; - break; - case 'rating': - /* randomise, else the rating_votes can already - * have sorted leaving this with nothing to do. - */ - $('#container').isotope({sortBy: 'random'}); - sortCriteria = 'rating'; - break; - case 'rating_votes': - sortCriteria = ['rating', 'votes']; - break; - case 'votes': - sortCriteria = 'votes'; - break; - default: - sortCriteria = 'name'; - break; - } - $('#container').isotope({sortBy: sortCriteria}); - }); - - $('#showsortdirection').on( 'change', function() { - $('#container').isotope({sortAscending: ('asc' === this.value)}); - }); -}); diff --git a/gui/slick/js/meta.js b/gui/slick/js/meta.js deleted file mode 100644 index 89e1ed36cb69f6fce012d834866d9e7d65bbdaf9..0000000000000000000000000000000000000000 --- a/gui/slick/js/meta.js +++ /dev/null @@ -1,19 +0,0 @@ -function metaToBool(pyVar){ // jshint ignore:line - var meta = $('meta[data-var="' + pyVar + '"]').data('content'); - if(meta === undefined){ - console.log(pyVar + ' is empty, did you forget to add this to main.mako?'); - return meta; - } else { - meta = (isNaN(meta) ? meta.toLowerCase() : meta.toString()); - return !(meta === 'false' || meta === 'none' || meta === '0'); - } -} - -function getMeta(pyVar){ // jshint ignore:line - return $('meta[data-var="' + pyVar + '"]').data('content'); -} - -function isMeta(pyVar, result){ // jshint ignore:line - var reg = new RegExp(result.length > 1 ? result.join('|') : result); - return (reg).test($('meta[data-var="' + pyVar + '"]').data('content')); -} diff --git a/gui/slick/js/newShow.js b/gui/slick/js/newShow.js deleted file mode 100644 index df8b4cce36e53207cf499f5531fd0ddb448fa1ee..0000000000000000000000000000000000000000 --- a/gui/slick/js/newShow.js +++ /dev/null @@ -1,223 +0,0 @@ -$(document).ready(function () { - - var searchRequestXhr = null; - - function searchIndexers() { - if (!$('#nameToSearch').val().length) { return; } - - if (searchRequestXhr) { searchRequestXhr.abort(); } - - var searchingFor = $('#nameToSearch').val().trim() + ' on ' + $('#providedIndexer option:selected').text() + ' in ' + $('#indexerLangSelect').val(); - $('#searchResults').empty().html('<img id="searchingAnim" src="' + srRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> searching ' + searchingFor + '...'); - - searchRequestXhr = $.ajax({ - url: srRoot + '/home/addShows/searchIndexersForShowName', - data: {'search_term': $('#nameToSearch').val().trim(), 'lang': $('#indexerLangSelect').val(), 'indexer': $('#providedIndexer').val()}, - timeout: parseInt($('#indexer_timeout').val(), 10) * 1000, - dataType: 'json', - error: function () { - $('#searchResults').empty().html('search timed out, try again or try another indexer'); - }, - success: function (data) { - var firstResult = true; - var resultStr = '<fieldset>\n<legend class="legendStep">Search Results:</legend>\n'; - var checked = ''; - - if (data.results.length === 0) { - resultStr += '<b>No results found, try a different search.</b>'; - } else { - $.each(data.results, function (index, obj) { - if (firstResult) { - checked = ' checked'; - firstResult = false; - } else { - checked = ''; - } - - var whichSeries = obj.join('|'); - - - resultStr += '<input type="radio" id="whichSeries" name="whichSeries" value="' + whichSeries.replace(/"/g, '') + '"' + checked + ' /> '; - if (data.langid && data.langid !== '') { - resultStr += '<a href="' + anonURL + obj[2] + obj[3] + '&lid=' + data.langid + '" onclick=\"window.open(this.href, \'_blank\'); return false;\" ><b>' + obj[4] + '</b></a>'; - } else { - resultStr += '<a href="' + anonURL + obj[2] + obj[3] + '" onclick=\"window.open(this.href, \'_blank\'); return false;\" ><b>' + obj[4] + '</b></a>'; - } - - if (obj[5] !== null) { - var startDate = new Date(obj[5]); - var today = new Date(); - if (startDate > today) { - resultStr += ' (will debut on ' + obj[5] + ')'; - } else { - resultStr += ' (started on ' + obj[5] + ')'; - } - } - - if (obj[0] !== null) { - resultStr += ' [' + obj[0] + ']'; - } - - resultStr += '<br>'; - }); - resultStr += '</ul>'; - } - resultStr += '</fieldset>'; - $('#searchResults').html(resultStr); - updateSampleText(); - myform.loadsection(0); - } - }); - } - - $('#searchName').click(function () { searchIndexers(); }); - - if ($('#nameToSearch').length && $('#nameToSearch').val().length) { - $('#searchName').click(); - } - - $('#addShowButton').click(function () { - // if they haven't picked a show don't let them submit - if (!$('input:radio[name="whichSeries"]:checked').val() && !$('input:hidden[name="whichSeries"]').val().length) { - alert('You must choose a show to continue'); - return false; - } - generateBlackWhiteList(); - $('#addShowForm').submit(); - }); - - $('#skipShowButton').click(function () { - $('#skipShow').val('1'); - $('#addShowForm').submit(); - }); - - $('#qualityPreset').change(function () { - myform.loadsection(2); - }); - - /*********************************************** - * jQuery Form to Form Wizard- (c) Dynamic Drive (www.dynamicdrive.com) - * This notice MUST stay intact for legal use - * Visit http://www.dynamicdrive.com/ for this script and 100s more. - ***********************************************/ - - var myform = new formtowizard({ // jshint ignore:line - formid: 'addShowForm', - revealfx: ['slide', 500], - oninit: function () { - updateSampleText(); - if ($('input:hidden[name=whichSeries]').length && $('#fullShowPath').length) { - goToStep(3); - } - } - }); - - function goToStep(num) { - $('.step').each(function () { - if ($.data(this, 'section') + 1 === num) { - $(this).click(); - } - }); - } - - $('#nameToSearch').focus(); - - function updateSampleText() { - // if something's selected then we have some behavior to figure out - - var showName, sepChar; - // if they've picked a radio button then use that - if ($('input:radio[name=whichSeries]:checked').length) { - showName = $('input:radio[name=whichSeries]:checked').val().split('|')[4]; - } else if ($('input:hidden[name=whichSeries]').length && $('input:hidden[name=whichSeries]').val().length) { // if we provided a show in the hidden field, use that - showName = $('#providedName').val(); - } else { - showName = ''; - } - updateBlackWhiteList(showName); - var sampleText = 'Adding show <b>' + showName + '</b> into <b>'; - - // if we have a root dir selected, figure out the path - if ($('#rootDirs option:selected').length) { - var rootDirectoryText = $('#rootDirs option:selected').val(); - if (rootDirectoryText.indexOf('/') >= 0) { - sepChar = '/'; - } else if (rootDirectoryText.indexOf('\\') >= 0) { - sepChar = '\\'; - } else { - sepChar = ''; - } - - if (rootDirectoryText.substr(sampleText.length - 1) !== sepChar) { - rootDirectoryText += sepChar; - } - rootDirectoryText += '<i>||</i>' + sepChar; - - sampleText += rootDirectoryText; - } else if ($('#fullShowPath').length && $('#fullShowPath').val().length) { - sampleText += $('#fullShowPath').val(); - } else { - sampleText += 'unknown dir.'; - } - - sampleText += '</b>'; - - // if we have a show name then sanitize and use it for the dir name - if (showName.length) { - $.get(srRoot + '/home/addShows/sanitizeFileName', {name: showName}, function (data) { - $('#displayText').html(sampleText.replace('||', data)); - }); - // if not then it's unknown - } else { - $('#displayText').html(sampleText.replace('||', '??')); - } - - // also toggle the add show button - if (($("#rootDirs option:selected").length || ($('#fullShowPath').length && $('#fullShowPath').val().length)) && - ($('input:radio[name=whichSeries]:checked').length) || ($('input:hidden[name=whichSeries]').length && $('input:hidden[name=whichSeries]').val().length)) { - $('#addShowButton').attr('disabled', false); - } else { - $('#addShowButton').attr('disabled', true); - } - } - - $('#rootDirText').change(updateSampleText); - $('#searchResults').on('change', '#whichSeries', updateSampleText); - - $('#nameToSearch').keyup(function(event) { - if (event.keyCode === 13) { - $('#searchName').click(); - } - }); - - $('#anime').change (function() { - updateSampleText(); - myform.loadsection(2); - }); - - function updateBlackWhiteList(showName) { - $('#white').children().remove(); - $('#black').children().remove(); - $('#pool').children().remove(); - - if ($('#anime').prop('checked')) { - $('#blackwhitelist').show(); - if (showName) { - $.getJSON(srRoot + '/home/fetch_releasegroups', { - 'show_name': showName - }, function (data) { - if (data.result === 'success') { - $.each(data.groups, function(i, group) { - var option = $("<option>"); - option.attr("value", group.name); - option.html(group.name + ' | ' + group.rating + ' | ' + group.range); - option.appendTo('#pool'); - }); - } - }); - } - } else { - $('#blackwhitelist').hide(); - } - } -}); diff --git a/gui/slick/js/qualityChooser.js b/gui/slick/js/qualityChooser.js index 42827f4485f856e6d1683992fa7f2eb19a7e4562..81439f94f2828c25985541e82fa9171b415f1885 100644 --- a/gui/slick/js/qualityChooser.js +++ b/gui/slick/js/qualityChooser.js @@ -28,7 +28,7 @@ $(document).ready(function() { return; } - $('#qualityPreset').change(function() { + $('#qualityPreset').on('change', function() { setFromPresets($('#qualityPreset :selected').val()); }); diff --git a/gui/slick/js/recommendedShows.js b/gui/slick/js/recommendedShows.js deleted file mode 100644 index c64be8e6fefc1b65861f506622479ea82be846c6..0000000000000000000000000000000000000000 --- a/gui/slick/js/recommendedShows.js +++ /dev/null @@ -1,14 +0,0 @@ -$.fn.loadContent = function(path, loadingTxt, errorTxt) { - $(this).html('<img id="searchingAnim" src="' + srRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> ' + loadingTxt); - $(this).load(srRoot + path + ' #container', function(response, status) { - if (status === "error") { $(this).empty().html(errorTxt); } - }); -}; - -$(document).ready(function() { - $('#trendingShows').loadContent('/home/addShows/getRecommendedShows/', 'Loading recommended shows...', 'Trakt timed out, refresh page to try again'); - $('#container').isotope({ - itemSelector: '.trakt_show', - layoutMode: 'fitRows' - }); -}); diff --git a/gui/slick/js/rootDirs.js b/gui/slick/js/rootDirs.js index 0d1d516cf015afc54d0a3a5d78659f08fa9e5cc9..716022dbee67f0e05ed3c22061e8fc9020c009ba 100644 --- a/gui/slick/js/rootDirs.js +++ b/gui/slick/js/rootDirs.js @@ -63,17 +63,23 @@ $(document).ready(function() { } refreshRootDirs(); - $.get(srRoot+'/config/general/saveRootDirs', {rootDirString: $('#rootDirText').val()}); + $.get(srRoot+'/config/general/saveRootDirs', { + rootDirString: $('#rootDirText').val() + }); } - $('#addRootDir').click(function(){$(this).nFileBrowser(addRootDir);}); - $('#editRootDir').click(function(){$(this).nFileBrowser(editRootDir, {initialDir: $("#rootDirs option:selected").val()});}); + $('#addRootDir').on('click', function(){ + $(this).nFileBrowser(addRootDir); + }); + $('#editRootDir').on('click', function(){ + $(this).nFileBrowser(editRootDir, { + 'initialDir': $("#rootDirs option:selected").val() + }); + }); - $('#deleteRootDir').click(function() { + $('#deleteRootDir').on('click', function() { if ($("#rootDirs option:selected").length) { - var toDelete = $("#rootDirs option:selected"); - var newDefault = (toDelete.attr('id') === $("#whichDefaultRootDir").val()); var deletedNum = $("#rootDirs option:selected").attr('id').substr(3); @@ -81,7 +87,6 @@ $(document).ready(function() { syncOptionIDs(); if (newDefault) { - console.log('new default when deleting'); // we deleted the default so this isn't valid anymore @@ -101,15 +106,19 @@ $(document).ready(function() { } refreshRootDirs(); - $.get(srRoot+'/config/general/saveRootDirs', {rootDirString: $('#rootDirText').val()}); + $.get(srRoot+'/config/general/saveRootDirs', { + 'rootDirString': $('#rootDirText').val() + }); }); - $('#defaultRootDir').click(function(){ + $('#defaultRootDir').on('click', function(){ if ($("#rootDirs option:selected").length) { setDefault($("#rootDirs option:selected").attr('id')); } refreshRootDirs(); - $.get(srRoot+'/config/general/saveRootDirs', {rootDirString: $('#rootDirText').val()}); + $.get(srRoot + '/config/general/saveRootDirs', { + rootDirString: $('#rootDirText').val() + }); }); function setDefault(which, force){ diff --git a/gui/slick/js/vender.min.js b/gui/slick/js/vender.min.js index 9167a17446d294abb22ed0d82b51ddf6341d7d2a..eaccd1e2eca7ca8bbe64db35dfe67708146d1e6b 100644 Binary files a/gui/slick/js/vender.min.js and b/gui/slick/js/vender.min.js differ diff --git a/gui/slick/scss/core.scss b/gui/slick/scss/core.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/gui/slick/views/home_addShows.mako b/gui/slick/views/addShows.mako similarity index 84% rename from gui/slick/views/home_addShows.mako rename to gui/slick/views/addShows.mako index a42f588421e63b2d443f5d2360babae47b8f3141..e664999eda481f35ce805f85062265f30a47c8cf 100644 --- a/gui/slick/views/home_addShows.mako +++ b/gui/slick/views/addShows.mako @@ -12,7 +12,7 @@ % endif <div id="addShowPortal"> - <a href="${srRoot}/home/addShows/newShow/" id="btnNewShow" class="btn btn-large"> + <a href="${srRoot}/addShows/newShow/" id="btnNewShow" class="btn btn-large"> <div class="button"><div class="icon-addnewshow"></div></div> <div class="buttontext"> <h3>Add New Show</h3> @@ -22,7 +22,7 @@ <br><br> % if sickbeard.USE_TRAKT is True: - <a href="${srRoot}/home/addShows/trendingShows/" id="btnNewShow" class="btn btn-large"> + <a href="${srRoot}/addShows/trendingShows/" id="btnNewShow" class="btn btn-large"> <div class="button"><div class="icon-addtrendingshow"></div></div> <div class="buttontext"> <h3>Add Trending Show</h3> @@ -32,7 +32,7 @@ <br><br> - <a href="${srRoot}/home/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large"> + <a href="${srRoot}/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large"> <div class="button"><div class="icon-addrecommendedshow"></div></div> <div class="buttontext"> <h3>Add Recommended Shows</h3> @@ -43,7 +43,7 @@ <br><br> % endif - <a href="${srRoot}/home/addShows/popularShows/" id="btnNewShow" class="btn btn-large"> + <a href="${srRoot}/addShows/popularShows/" id="btnNewShow" class="btn btn-large"> <div class="button"><div class="icon-addtrendingshow"></div></div> <div class="buttontext"> <h3>View Popular Shows</h3> @@ -53,7 +53,7 @@ <br><br> - <a href="${srRoot}/home/addShows/existingShows/" id="btnExistingShow" class="btn btn-large"> + <a href="${srRoot}/addShows/existingShows/" id="btnExistingShow" class="btn btn-large"> <div class="button"><div class="icon-addexistingshow"></div></div> <div class="buttontext"> <h3>Add Existing Shows</h3> diff --git a/gui/slick/views/home_addExistingShow.mako b/gui/slick/views/addShows_addExistingShow.mako similarity index 83% rename from gui/slick/views/home_addExistingShow.mako rename to gui/slick/views/addShows_addExistingShow.mako index 902429e6ba7945de409a708d7ef7fad849d41a70..ad6d318e312d0d7b04186d0a44a570d794ebf988 100644 --- a/gui/slick/views/home_addExistingShow.mako +++ b/gui/slick/views/addShows_addExistingShow.mako @@ -4,10 +4,7 @@ %> <%block name="scripts"> <script type="text/javascript" src="${srRoot}/js/qualityChooser.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/addExistingShow.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/rootDirs.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/addShowOptions.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/home_addExistingShow.js"></script> </%block> <%block name="content"> % if not header is UNDEFINED: @@ -22,7 +19,7 @@ <div id="core-component-group1" class="tab-pane active component-group"> - <form id="addShowForm" method="post" action="${srRoot}/home/addShows/addNewShow" accept-charset="utf-8"> + <form id="addShowForm" method="post" action="${srRoot}/addShows/addNewShow" accept-charset="utf-8"> <div id="tabs"> <ul> diff --git a/gui/slick/views/addShows_newShow.mako b/gui/slick/views/addShows_newShow.mako new file mode 100644 index 0000000000000000000000000000000000000000..2abd76d9589d81fce8454173b8ea32ff2f2aff54 --- /dev/null +++ b/gui/slick/views/addShows_newShow.mako @@ -0,0 +1,101 @@ +<%inherit file="/layouts/main.mako"/> +<%! + import sickbeard + from sickbeard.helpers import anon_url +%> +<%block name="scripts"> +<script type="text/javascript" src="${srRoot}/js/qualityChooser.js?${sbPID}"></script> +<script type="text/javascript" src="${srRoot}/js/addShowOptions.js?${sbPID}"></script> +<script type="text/javascript" src="${srRoot}/js/rootDirs.js?${sbPID}"></script> +<script type="text/javascript" src="${srRoot}/js/blackwhite.js?${sbPID}"></script> +</%block> +<%block name="content"> +% if not header is UNDEFINED: + <h1 class="header">${header}</h1> +% else: + <h1 class="title">${title}</h1> +% endif + +<div id="newShowPortal"> + <div id="config-components"> + <ul> + <li><a href="#core-component-group1">Add New Show</a></li> + </ul> + + <div id="core-component-group1" class="tab-pane active component-group"> + <div id="displayText"></div> + <br> + <form id="addShowForm" method="post" action="${srRoot}/addShows/addNewShow" accept-charset="utf-8"> + <fieldset class="sectionwrap"> + <legend class="legendStep">Find a show on theTVDB</legend> + + <div class="stepDiv"> + <input type="hidden" id="indexer_timeout" value="${sickbeard.INDEXER_TIMEOUT}" /> + + % if use_provided_info: + Show retrieved from existing metadata: <a href="${anon_url(sickbeard.indexerApi(provided_indexer).config['show_url'], provided_indexer_id)}">${provided_indexer_name}</a> + <input type="hidden" id="indexerLang" name="indexerLang" value="en" /> + <input type="hidden" id="whichSeries" name="whichSeries" value="${provided_indexer_id}" /> + <input type="hidden" id="providedIndexer" name="providedIndexer" value="${provided_indexer}" /> + <input type="hidden" id="providedName" value="${provided_indexer_name}" /> + % else: + <input type="text" id="nameToSearch" value="${default_show_name}" class="form-control form-control-inline input-sm input350" autocapitalize="off" /> + + <select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm bfh-languages" data-language="${sickbeard.INDEXER_DEFAULT_LANGUAGE}" data-available="${','.join(sickbeard.indexerApi().config['valid_languages'])}"> + </select><b>*</b> + + <select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm"> + <option value="0" ${('', 'selected="selected"')[provided_indexer == 0]}>All Indexers</option> + % for indexer in indexers: + <option value="${indexer}" ${('', 'selected="selected"')[provided_indexer == indexer]}> + ${indexers[indexer]} + </option> + % endfor + </select> + + <input class="btn btn-inline" type="button" id="searchName" value="Search" /> + + <br><br> + <b>*</b> This will only affect the language of the retrieved metadata file contents and episode filenames.<br> + This <b>DOES NOT</b> allow SickRage to download non-english TV episodes!<br><br> + <div id="searchResults" style="height: 100%;"><br></div> + % endif + </div> + </fieldset> + + <fieldset class="sectionwrap"> + <legend class="legendStep">Pick the parent folder</legend> + <div class="stepDiv"> + % if provided_show_dir: + Pre-chosen Destination Folder: <b>${provided_show_dir}</b> <br> + <input type="hidden" id="fullShowPath" name="fullShowPath" value="${provided_show_dir}" /><br> + % else: + <%include file="/inc_rootDirs.mako"/> + % endif + </div> + </fieldset> + + <fieldset class="sectionwrap"> + <legend class="legendStep">Customize options</legend> + <div class="stepDiv"> + <%include file="/inc_addShowOptions.mako"/> + </div> + </fieldset> + + % for curNextDir in other_shows: + <input type="hidden" name="other_shows" value="${curNextDir}" /> + % endfor + <input type="hidden" name="skipShow" id="skipShow" value="" /> + </form> + <br> + + <div style="width: 100%; text-align: center;"> + <input class="btn" type="button" id="addShowButton" value="Add Show" disabled="disabled" /> + % if provided_show_dir: + <input class="btn" type="button" id="skipShowButton" value="Skip Show" /> + % endif + </div> + </div> + </div> +</div> +</%block> diff --git a/gui/slick/views/addShows_popularShows.mako b/gui/slick/views/addShows_popularShows.mako new file mode 100644 index 0000000000000000000000000000000000000000..635862674599f372469562f7bdb19e4eaa5f5bd6 --- /dev/null +++ b/gui/slick/views/addShows_popularShows.mako @@ -0,0 +1,82 @@ +<%inherit file="/layouts/main.mako"/> +<%! + from sickbeard.helpers import anon_url + import sickbeard +%> +<%block name="metas"> +<meta data-var="sickbeard.SORT_ARTICLE" data-content="${sickbeard.SORT_ARTICLE}"> +</%block> +<%block name="content"> +% if not header is UNDEFINED: + <h1 class="header">${header}</h1> +% else: + <h1 class="title">${title}</h1> +% endif + +<div id="tabs"> + <span>Sort By:</span> + <select id="showsort" class="form-control form-control-inline input-sm"> + <option value="name">Name</option> + <option value="original" selected="selected">Original</option> + <option value="votes">Votes</option> + <option value="rating">% Rating</option> + <option value="rating_votes">% Rating > Votes</option> + </select> + + <span style="margin-left:12px">Sort Order:</span> + <select id="showsortdirection" class="form-control form-control-inline input-sm"> + <option value="asc" selected="selected">Asc</option> + <option value="desc">Desc</option> + </select> +</div> + +<% imdb_tt = [show.imdbid for show in sickbeard.showList if show.imdbid] %> + +<br> +<div id="popularShows"> + <div id="container"> + % if not popular_shows: + <div class="trakt_show" style="width:100%; margin-top:20px"> + <p class="red-text">Fetching of IMDB Data failed. Are you online? + <strong>Exception:</strong> + <p>${imdb_exception}</p> + </div> + % else: + % for cur_result in popular_shows: + % if cur_result['imdb_tt'] in imdb_tt: + <% continue %> + % endif + + % if 'rating' in cur_result and cur_result['rating']: + <% cur_rating = cur_result['rating'] %> + <% cur_votes = cur_result['votes'] %> + % else: + <% cur_rating = '0' %> + <% cur_votes = '0' %> + % endif + + <div class="trakt_show" data-name="${cur_result['name']}" data-rating="${cur_rating}" data-votes="${cur_votes}"> + <div class="traktContainer"> + <div class="trakt-image"> + <a class="trakt-image" href="${anon_url(cur_result['imdb_url'])}" target="_blank"><img alt="" class="trakt-image" src="${srRoot}/cache/${cur_result['image_path']}" /></a> + </div> + + <div class="show-title"> + ${(cur_result['name'], '<span> </span>')['' == cur_result['name']]} + </div> + + <div class="clearfix"> + <p>${int(float(cur_rating)*10)}% <img src="${srRoot}/images/heart.png"></p> + <i>${cur_votes} votes</i> + <div class="traktShowTitleIcons"> + <a href="${srRoot}/addShows/addShowByID?indexer_id=${cur_result['imdb_tt']}&showName=${cur_result['name']}&indexer=IMDB" class="btn btn-xs" data-no-redirect>Add Show</a> + </div> + </div> + </div> + </div> + % endfor + % endif + </div> +</div> +<br> +</%block> diff --git a/gui/slick/views/home_recommendedShows.mako b/gui/slick/views/addShows_recommendedShows.mako similarity index 87% rename from gui/slick/views/home_recommendedShows.mako rename to gui/slick/views/addShows_recommendedShows.mako index 080ecfad10833bf3d8ca1151ae32faef1c38d510..97c530e7b65a12d884ef4fe8654319ee27f6989e 100644 --- a/gui/slick/views/home_recommendedShows.mako +++ b/gui/slick/views/addShows_recommendedShows.mako @@ -3,10 +3,8 @@ import sickbeard %> <%block name="scripts"> -<script type="text/javascript" src="${srRoot}/js/recommendedShows.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/rootDirs.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/plotTooltip.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/home_recommendedShows.js"></script> </%block> <%block name="content"> % if not header is UNDEFINED: @@ -45,6 +43,6 @@ </div> <br> -<div id="trendingShows"></div> +<div id="recommendedShows"></div> <br> </%block> diff --git a/gui/slick/views/home_trendingShows.mako b/gui/slick/views/addShows_trendingShows.mako similarity index 91% rename from gui/slick/views/home_trendingShows.mako rename to gui/slick/views/addShows_trendingShows.mako index 666528478907f07bcac96e8593ff17fecdb98487..6027ffbba3e18122e5c81bff2c4bdca9263db8ac 100644 --- a/gui/slick/views/home_trendingShows.mako +++ b/gui/slick/views/addShows_trendingShows.mako @@ -9,10 +9,8 @@ from sickbeard.helpers import anon_url %> <%block name="scripts"> -<script type="text/javascript" src="${srRoot}/js/addTrendingShow.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/rootDirs.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/plotTooltip.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/home_trendingShows.js"></script> </%block> <%block name="content"> % if not header is UNDEFINED: diff --git a/gui/slick/views/apiBuilder.mako b/gui/slick/views/apiBuilder.mako index 7077ccdb6ef5071746bc8a096030ed110399e5b7..9b8ae20b036bf8784aca317c137279de4c232285 100644 --- a/gui/slick/views/apiBuilder.mako +++ b/gui/slick/views/apiBuilder.mako @@ -185,7 +185,6 @@ var commands = ${sorted(commands)}; var episodes = ${episodes}; </script> <script type="text/javascript" src="${srRoot}/js/vender.min.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/meta.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/core.min.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/apibuilder.js?${sbPID}"></script> </body> diff --git a/gui/slick/views/config_anime.mako b/gui/slick/views/config_anime.mako index d006a14999b465671f1074ce49a58253d13848f2..d1aa10189055911d7738293cab6d30397e16fc7e 100644 --- a/gui/slick/views/config_anime.mako +++ b/gui/slick/views/config_anime.mako @@ -38,7 +38,7 @@ <div class="field-pair"> <label class="nocheck"> <span class="component-title">AniDB Username</span> - <input type="text" name="anidb_username" id="anidb_username" value="${sickbeard.ANIDB_USERNAME}" class="form-control input-sm input350" /> + <input type="text" name="anidb_username" id="anidb_username" value="${sickbeard.ANIDB_USERNAME}" class="form-control input-sm input350" autocapitalize="off" autocomplete="no" /> </label> <label class="nocheck"> <span class="component-title"> </span> @@ -49,7 +49,7 @@ <div class="field-pair"> <label class="nocheck"> <span class="component-title">AniDB Password</span> - <input type="password" name="anidb_password" id="anidb_password" value="${sickbeard.ANIDB_PASSWORD}" class="form-control input-sm input350" /> + <input type="password" name="anidb_password" id="anidb_password" value="${sickbeard.ANIDB_PASSWORD}" class="form-control input-sm input350" autocomplete="no" autocapitalize="off" /> </label> <label class="nocheck"> <span class="component-title"> </span> diff --git a/gui/slick/views/config_backuprestore.mako b/gui/slick/views/config_backuprestore.mako index a93bcc664cadbbb9f38bfb3dd0bd9e8b4f101a8a..c4b9569e8d576dd067bb63f63c782d0ea1ff95ee 100644 --- a/gui/slick/views/config_backuprestore.mako +++ b/gui/slick/views/config_backuprestore.mako @@ -43,7 +43,7 @@ <br><br> - <input type="text" name="backupDir" id="backupDir" class="form-control input-sm input350" /> + <input type="text" name="backupDir" id="backupDir" class="form-control input-sm input350" autocapitalize="off" /> <input class="btn btn-inline" type="button" value="Backup" id="Backup" /> <br> @@ -66,7 +66,7 @@ <br><br> - <input type="text" name="backupFile" id="backupFile" class="form-control input-sm input350" /> + <input type="text" name="backupFile" id="backupFile" class="form-control input-sm input350" autocapitalize="off" /> <input class="btn btn-inline" type="button" value="Restore" id="Restore" /> <br> diff --git a/gui/slick/views/config_general.mako b/gui/slick/views/config_general.mako index b011958682c4d4035c50f5c5a7537d3bb6c83eae..f369fcce3a37c313daf47f2ada7de6494f0d47df 100644 --- a/gui/slick/views/config_general.mako +++ b/gui/slick/views/config_general.mako @@ -86,7 +86,7 @@ <label for="showupdate_hour"> <span class="component-title">When to update shows</span> <span class="component-desc"> - <input type="text" name="showupdate_hour" id="showupdate_hour" value="${sickbeard.SHOWUPDATE_HOUR}" class="form-control input-sm input75" /> + <input type="text" name="showupdate_hour" id="showupdate_hour" value="${sickbeard.SHOWUPDATE_HOUR}" class="form-control input-sm input75" autocapitalize="off" /> <p>with information such as next air dates, show ended, etc. Use 15 for 3pm, 4 for 4am etc. Anything over 23 or under 0 will be set to 0 (12am)</p> </span> </label> @@ -111,7 +111,7 @@ <label for="log_dir"> <span class="component-title">Log file folder location</span> <span class="component-desc"> - <input type="text" name="log_dir" id="log_dir" value="${sickbeard.ACTUAL_LOG_DIR}" class="form-control input-sm input350" /> + <input type="text" name="log_dir" id="log_dir" value="${sickbeard.ACTUAL_LOG_DIR}" class="form-control input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -120,7 +120,7 @@ <label for="log_nr"> <span class="component-title">Number of Log files saved</span> <span class="component-desc"> - <input type="text" name="log_nr" id="log_nr" value="${sickbeard.LOG_NR}" class="form-control input-sm input75" /> + <input type="text" name="log_nr" id="log_nr" value="${sickbeard.LOG_NR}" class="form-control input-sm input75" autocapitalize="off" /> <p>number of log files saved when rotating logs (default: 5) (REQUIRES RESTART)</p> </span> </label> @@ -130,7 +130,7 @@ <label for="log_size"> <span class="component-title">Size of Log files saved</span> <span class="component-desc"> - <input type="text" name="log_size" id="log_size" value="${sickbeard.LOG_SIZE}" class="form-control input-sm input75" /> + <input type="text" name="log_size" id="log_size" value="${sickbeard.LOG_SIZE}" class="form-control input-sm input75" autocapitalize="off" /> <p>maximum size of a log file saved (default: 1048576 (1MB)) (REQUIRES RESTART)</p> </span> </label> @@ -155,7 +155,7 @@ <label for="indexer_timeout"> <span class="component-title">Timeout show indexer at</span> <span class="component-desc"> - <input type="text" name="indexer_timeout" id="indexer_timeout" value="${sickbeard.INDEXER_TIMEOUT}" class="form-control input-sm input75" /> + <input type="text" name="indexer_timeout" id="indexer_timeout" value="${sickbeard.INDEXER_TIMEOUT}" class="form-control input-sm input75" autocapitalize="off" /> <p>seconds of inactivity when finding new shows (default:10)</p> </span> </label> @@ -208,7 +208,7 @@ <label> <span class="component-title">Check the server every*</span> <span class="component-desc"> - <input type="text" name="update_frequency" id="update_frequency" value="${sickbeard.UPDATE_FREQUENCY}" class="form-control input-sm input75" /> + <input type="text" name="update_frequency" id="update_frequency" value="${sickbeard.UPDATE_FREQUENCY}" class="form-control input-sm input75" autocapitalize="off" /> <p>hours for software updates (default:12)</p> </span> </label> @@ -353,7 +353,7 @@ <div class="field-pair"> <label for="download_url"> <span class="component-title">Download url</span> - <input type="text" name="download_url" id="download_url" value="${sickbeard.DOWNLOAD_URL}" size="35" /> + <input type="text" name="download_url" id="download_url" value="${sickbeard.DOWNLOAD_URL}" size="35" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -382,7 +382,7 @@ <label for="api_key"> <span class="component-title">API key</span> <span class="component-desc"> - <input type="text" name="api_key" id="api_key" value="${sickbeard.API_KEY}" class="form-control input-sm input300" readonly="readonly" /> + <input type="text" name="api_key" id="api_key" value="${sickbeard.API_KEY}" class="form-control input-sm input300" readonly="readonly" autocapitalize="off" /> <input class="btn btn-inline" type="button" id="generate_new_apikey" value="Generate"> <div class="clear-left"> <p>used to give 3rd party programs limited access to SickRage</p> @@ -406,7 +406,7 @@ <label for="web_username"> <span class="component-title">HTTP username</span> <span class="component-desc"> - <input type="text" name="web_username" id="web_username" value="${sickbeard.WEB_USERNAME}" class="form-control input-sm input300" /> + <input type="text" name="web_username" id="web_username" value="${sickbeard.WEB_USERNAME}" class="form-control input-sm input300" autocapitalize="off" autocomplete="no" /> <p>set blank for no login</p> </span> </label> @@ -416,7 +416,7 @@ <label for="web_password"> <span class="component-title">HTTP password</span> <span class="component-desc"> - <input type="password" name="web_password" id="web_password" value="${sickbeard.WEB_PASSWORD}" class="form-control input-sm input300" /> + <input type="password" name="web_password" id="web_password" value="${sickbeard.WEB_PASSWORD}" class="form-control input-sm input300" autocomplete="no" autocapitalize="off" /> <p>blank = no authentication</span> </label> </div> @@ -425,7 +425,7 @@ <label for="web_port"> <span class="component-title">HTTP port</span> <span class="component-desc"> - <input type="text" name="web_port" id="web_port" value="${sickbeard.WEB_PORT}" class="form-control input-sm input100" /> + <input type="text" name="web_port" id="web_port" value="${sickbeard.WEB_PORT}" class="form-control input-sm input100" autocapitalize="off" /> <p>web port to browse and access SickRage (default:8081)</p> </span> </label> @@ -455,7 +455,7 @@ <label for="https_cert"> <span class="component-title">HTTPS certificate</span> <span class="component-desc"> - <input type="text" name="https_cert" id="https_cert" value="${sickbeard.HTTPS_CERT}" class="form-control input-sm input300" /> + <input type="text" name="https_cert" id="https_cert" value="${sickbeard.HTTPS_CERT}" class="form-control input-sm input300" autocapitalize="off" /> <div class="clear-left"><p>file name or path to HTTPS certificate</p></div> </span> </label> @@ -464,7 +464,7 @@ <label for="https_key"> <span class="component-title">HTTPS key</span> <span class="component-desc"> - <input type="text" name="https_key" id="https_key" value="${sickbeard.HTTPS_KEY}" class="form-control input-sm input300" /> + <input type="text" name="https_key" id="https_key" value="${sickbeard.HTTPS_KEY}" class="form-control input-sm input300" autocapitalize="off" /> <div class="clear-left"><p>file name or path to HTTPS key</p></div> </span> </label> @@ -517,7 +517,7 @@ <label> <span class="component-title">Anonymous redirect</span> <span class="component-desc"> - <input type="text" name="anon_redirect" value="${sickbeard.ANON_REDIRECT}" class="form-control input-sm input300" /> + <input type="text" name="anon_redirect" value="${sickbeard.ANON_REDIRECT}" class="form-control input-sm input300" autocapitalize="off" /> <div class="clear-left"><p>backlink protection via anonymizer service, must end in "?"</p></div> </span> </label> @@ -593,7 +593,7 @@ <label> <span class="component-title">Proxy host</span> <span class="component-desc"> - <input type="text" name="proxy_setting" value="${sickbeard.PROXY_SETTING}" class="form-control input-sm input300" /> + <input type="text" name="proxy_setting" value="${sickbeard.PROXY_SETTING}" class="form-control input-sm input300" autocapitalize="off" /> <div class="clear-left"><p>blank to disable or proxy to use when connecting to providers</p></div> </label> </div> @@ -695,7 +695,7 @@ <label for="git_username"> <span class="component-title">GitHub username</span> <span class="component-desc"> - <input type="text" name="git_username" id="git_username" value="${sickbeard.GIT_USERNAME}" class="form-control input-sm input300" /> + <input type="text" name="git_username" id="git_username" value="${sickbeard.GIT_USERNAME}" class="form-control input-sm input300" autocapitalize="off" autocomplete="no" /> <div class="clear-left"><p>*** (REQUIRED FOR SUBMITTING ISSUES) ***</p></div> </span> </label> @@ -705,7 +705,7 @@ <label for="git_password"> <span class="component-title">GitHub password</span> <span class="component-desc"> - <input type="password" name="git_password" id="git_password" value="${sickbeard.GIT_PASSWORD}" class="form-control input-sm input300" /> + <input type="password" name="git_password" id="git_password" value="${sickbeard.GIT_PASSWORD}" class="form-control input-sm input300" autocomplete="no" autocapitalize="off" /> <div class="clear-left"><p>*** (REQUIRED FOR SUBMITTING ISSUES) ***</p></div> </span> </label> @@ -715,7 +715,7 @@ <label for="git_remote"> <span class="component-title">GitHub remote for branch</span> <span class="component-desc"> - <input type="text" name="git_remote" id="git_remote" value="${sickbeard.GIT_REMOTE}" class="form-control input-sm input300" /> + <input type="text" name="git_remote" id="git_remote" value="${sickbeard.GIT_REMOTE}" class="form-control input-sm input300" autocapitalize="off" /> <div class="clear-left"><p>default:origin. Access repo configured remotes (save then refresh browser)</p></div> </span> </label> @@ -725,7 +725,7 @@ <label> <span class="component-title">Git executable path</span> <span class="component-desc"> - <input type="text" name="git_path" value="${sickbeard.GIT_PATH}" class="form-control input-sm input300" /> + <input type="text" name="git_path" value="${sickbeard.GIT_PATH}" class="form-control input-sm input300" autocapitalize="off" /> <div class="clear-left"><p>only needed if OS is unable to locate git from env</p></div> </span> </label> diff --git a/gui/slick/views/config_notifications.mako b/gui/slick/views/config_notifications.mako index 69f41f94d3ea1f41285f0afa1ca61469978525ea..8f5f3feb8cac847ba78605ebc105dcf60df43ca2 100644 --- a/gui/slick/views/config_notifications.mako +++ b/gui/slick/views/config_notifications.mako @@ -109,7 +109,7 @@ <div class="field-pair"> <label for="kodi_host"> <span class="component-title">KODI IP:Port</span> - <input type="text" name="kodi_host" id="kodi_host" value="${sickbeard.KODI_HOST}" class="form-control input-sm input350" /> + <input type="text" name="kodi_host" id="kodi_host" value="${sickbeard.KODI_HOST}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -123,7 +123,7 @@ <div class="field-pair"> <label for="kodi_username"> <span class="component-title">KODI username</span> - <input type="text" name="kodi_username" id="kodi_username" value="${sickbeard.KODI_USERNAME}" class="form-control input-sm input250" /> + <input type="text" name="kodi_username" id="kodi_username" value="${sickbeard.KODI_USERNAME}" class="form-control input-sm input250" autocapitalize="off" autocomplete="no" /> </label> <label> <span class="component-title"> </span> @@ -133,7 +133,7 @@ <div class="field-pair"> <label for="kodi_password"> <span class="component-title">KODI password</span> - <input type="password" name="kodi_password" id="kodi_password" value="${sickbeard.KODI_PASSWORD}" class="form-control input-sm input250" /> + <input type="password" name="kodi_password" id="kodi_password" value="${sickbeard.KODI_PASSWORD}" class="form-control input-sm input250" autocomplete="no" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -172,7 +172,7 @@ <div class="field-pair"> <label for="plex_server_token"> <span class="component-title">Plex Media Server Auth Token</span> - <input type="text" name="plex_server_token" id="plex_server_token" value="${sickbeard.PLEX_SERVER_TOKEN}" class="form-control input-sm input250" /> + <input type="text" name="plex_server_token" id="plex_server_token" value="${sickbeard.PLEX_SERVER_TOKEN}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -188,7 +188,7 @@ <label for="plex_username"> <span class="component-title">Server Username</span> <span class="component-desc"> - <input type="text" name="plex_username" id="plex_username" value="${sickbeard.PLEX_USERNAME}" class="form-control input-sm input250" /> + <input type="text" name="plex_username" id="plex_username" value="${sickbeard.PLEX_USERNAME}" class="form-control input-sm input250" autocapitalize="off" autocomplete="no" /> <p>blank = no authentication</p> </span> </label> @@ -197,7 +197,7 @@ <label for="plex_password"> <span class="component-title">Server/client password</span> <span class="component-desc"> - <input type="password" name="plex_password" id="plex_password" value="${'*' * len(sickbeard.PLEX_PASSWORD)}" class="form-control input-sm input250" /> + <input type="password" name="plex_password" id="plex_password" value="${'*' * len(sickbeard.PLEX_PASSWORD)}" class="form-control input-sm input250" autocomplete="no" autocapitalize="off" /> <p>blank = no authentication</p> </span> </label> @@ -219,7 +219,7 @@ <label for="plex_server_host"> <span class="component-title">Plex Media Server IP:Port</span> <span class="component-desc"> - <input type="text" name="plex_server_host" id="plex_server_host" value="${re.sub(r'\b,\b', ', ', sickbeard.PLEX_SERVER_HOST)}" class="form-control input-sm input350" /> + <input type="text" name="plex_server_host" id="plex_server_host" value="${re.sub(r'\b,\b', ', ', sickbeard.PLEX_SERVER_HOST)}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"> <p>one or more hosts running Plex Media Server<br>(eg. 192.168.1.1:32400, 192.168.1.2:32400)</p> </div> @@ -287,7 +287,7 @@ <label for="plex_host"> <span class="component-title">Plex Client IP:Port</span> <span class="component-desc"> - <input type="text" name="plex_host" id="plex_host" value="${sickbeard.PLEX_HOST}" class="form-control input-sm input350" /> + <input type="text" name="plex_host" id="plex_host" value="${sickbeard.PLEX_HOST}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"> <p>one or more hosts running Plex client<br>(eg. 192.168.1.100:3000, 192.168.1.101:3000)</p> </div> @@ -299,7 +299,7 @@ <label for="plex_username"> <span class="component-title">Server Username</span> <span class="component-desc"> - <input type="text" name="plex_client_username" id="plex_client_username" value="${sickbeard.PLEX_CLIENT_USERNAME}" class="form-control input-sm input250" /> + <input type="text" name="plex_client_username" id="plex_client_username" value="${sickbeard.PLEX_CLIENT_USERNAME}" class="form-control input-sm input250" autocapitalize="off" autocomplete="no" /> <p>blank = no authentication</p> </span> </label> @@ -308,7 +308,7 @@ <label for="plex_client_password"> <span class="component-title">Client Password</span> <span class="component-desc"> - <input type="password" name="plex_client_password" id="plex_client_password" value="${'*' * len(sickbeard.PLEX_CLIENT_PASSWORD)}" class="form-control input-sm input250" /> + <input type="password" name="plex_client_password" id="plex_client_password" value="${'*' * len(sickbeard.PLEX_CLIENT_PASSWORD)}" class="form-control input-sm input250" autocomplete="no" autocapitalize="off" /> <p>blank = no authentication</p> </span> </label> @@ -346,7 +346,7 @@ <div class="field-pair"> <label for="emby_host"> <span class="component-title">Emby IP:Port</span> - <input type="text" name="emby_host" id="emby_host" value="${sickbeard.EMBY_HOST}" class="form-control input-sm input250" /> + <input type="text" name="emby_host" id="emby_host" value="${sickbeard.EMBY_HOST}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -356,7 +356,7 @@ <div class="field-pair"> <label for="emby_apikey"> <span class="component-title">Emby API Key</span> - <input type="text" name="emby_apikey" id="emby_apikey" value="${sickbeard.EMBY_APIKEY}" class="form-control input-sm input250" /> + <input type="text" name="emby_apikey" id="emby_apikey" value="${sickbeard.EMBY_APIKEY}" class="form-control input-sm input250" autocapitalize="off" /> </label> </div> <div class="testNotification" id="testEMBY-result">Click below to test.</div> @@ -389,7 +389,7 @@ <div class="field-pair"> <label for="nmj_host"> <span class="component-title">Popcorn IP address</span> - <input type="text" name="nmj_host" id="nmj_host" value="${sickbeard.NMJ_HOST}" class="form-control input-sm input250" /> + <input type="text" name="nmj_host" id="nmj_host" value="${sickbeard.NMJ_HOST}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -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 is 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]} autocapitalize="off" /> </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 is 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]} autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -455,7 +455,7 @@ <div class="field-pair"> <label for="nmjv2_host"> <span class="component-title">Popcorn IP address</span> - <input type="text" name="nmjv2_host" id="nmjv2_host" value="${sickbeard.NMJv2_HOST}" class="form-control input-sm input250" /> + <input type="text" name="nmjv2_host" id="nmjv2_host" value="${sickbeard.NMJv2_HOST}" class="form-control input-sm input250" autocapitalize="off" /> </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 is 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]} autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -633,7 +633,7 @@ <div class="field-pair"> <label for="pytivo_host"> <span class="component-title">pyTivo IP:Port</span> - <input type="text" name="pytivo_host" id="pytivo_host" value="${sickbeard.PYTIVO_HOST}" class="form-control input-sm input250" /> + <input type="text" name="pytivo_host" id="pytivo_host" value="${sickbeard.PYTIVO_HOST}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -643,7 +643,7 @@ <div class="field-pair"> <label for="pytivo_share_name"> <span class="component-title">pyTivo share name</span> - <input type="text" name="pytivo_share_name" id="pytivo_share_name" value="${sickbeard.PYTIVO_SHARE_NAME}" class="form-control input-sm input250" /> + <input type="text" name="pytivo_share_name" id="pytivo_share_name" value="${sickbeard.PYTIVO_SHARE_NAME}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -653,7 +653,7 @@ <div class="field-pair"> <label for="pytivo_tivo_name"> <span class="component-title">Tivo name</span> - <input type="text" name="pytivo_tivo_name" id="pytivo_tivo_name" value="${sickbeard.PYTIVO_TIVO_NAME}" class="form-control input-sm input250" /> + <input type="text" name="pytivo_tivo_name" id="pytivo_tivo_name" value="${sickbeard.PYTIVO_TIVO_NAME}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -718,7 +718,7 @@ <div class="field-pair"> <label for="growl_host"> <span class="component-title">Growl IP:Port</span> - <input type="text" name="growl_host" id="growl_host" value="${sickbeard.GROWL_HOST}" class="form-control input-sm input250" /> + <input type="text" name="growl_host" id="growl_host" value="${sickbeard.GROWL_HOST}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -728,7 +728,7 @@ <div class="field-pair"> <label for="growl_password"> <span class="component-title">Growl password</span> - <input type="password" name="growl_password" id="growl_password" value="${sickbeard.GROWL_PASSWORD}" class="form-control input-sm input250" /> + <input type="password" name="growl_password" id="growl_password" value="${sickbeard.GROWL_PASSWORD}" class="form-control input-sm input250" autocomplete="no" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -796,13 +796,13 @@ <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" /> + <input type="text" name="prowl_message_title" id="prowl_message_title" value="${sickbeard.PROWL_MESSAGE_TITLE}" class="form-control input-sm input250" autocapitalize="off" /> </label> </div> <div class="field-pair"> <label for="prowl_api"> <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" /> + <input type="text" name="prowl_api" id="prowl_api" value="${sickbeard.PROWL_API}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -822,7 +822,7 @@ </label> <label> <span class="component-title"> </span> - <input type="text" name="prowl_show_list" id="prowl_show_list" class="form-control input-sm input350" /> + <input type="text" name="prowl_show_list" id="prowl_show_list" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -962,7 +962,7 @@ <div class="field-pair"> <label for="pushover_userkey"> <span class="component-title">Pushover key</span> - <input type="text" name="pushover_userkey" id="pushover_userkey" value="${sickbeard.PUSHOVER_USERKEY}" class="form-control input-sm input250" /> + <input type="text" name="pushover_userkey" id="pushover_userkey" value="${sickbeard.PUSHOVER_USERKEY}" class="form-control input-sm input250" autocapitalize="off" autocomplete="no" /> </label> <label> <span class="component-title"> </span> @@ -972,7 +972,7 @@ <div class="field-pair"> <label for="pushover_apikey"> <span class="component-title">Pushover API key</span> - <input type="text" name="pushover_apikey" id="pushover_apikey" value="${sickbeard.PUSHOVER_APIKEY}" class="form-control input-sm input250" /> + <input type="text" name="pushover_apikey" id="pushover_apikey" value="${sickbeard.PUSHOVER_APIKEY}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -982,7 +982,7 @@ <div class="field-pair"> <label for="pushover_device"> <span class="component-title">Pushover devices</span> - <input type="text" name="pushover_device" id="pushover_device" value="${sickbeard.PUSHOVER_DEVICE}" class="form-control input-sm input250" /> + <input type="text" name="pushover_device" id="pushover_device" value="${sickbeard.PUSHOVER_DEVICE}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1079,7 +1079,7 @@ <div class="field-pair"> <label for="boxcar2_accesstoken"> <span class="component-title">Boxcar2 access token</span> - <input type="text" name="boxcar2_accesstoken" id="boxcar2_accesstoken" value="${sickbeard.BOXCAR2_ACCESSTOKEN}" class="form-control input-sm input250" /> + <input type="text" name="boxcar2_accesstoken" id="boxcar2_accesstoken" value="${sickbeard.BOXCAR2_ACCESSTOKEN}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1142,7 +1142,7 @@ <div class="field-pair"> <label for="nma_api"> <span class="component-title">NMA API key:</span> - <input type="text" name="nma_api" id="nma_api" value="${sickbeard.NMA_API}" class="form-control input-sm input350" /> + <input type="text" name="nma_api" id="nma_api" value="${sickbeard.NMA_API}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1221,7 +1221,7 @@ <div class="field-pair"> <label for="pushalot_authorizationtoken"> <span class="component-title">Pushalot authorization token</span> - <input type="text" name="pushalot_authorizationtoken" id="pushalot_authorizationtoken" value="${sickbeard.PUSHALOT_AUTHORIZATIONTOKEN}" class="form-control input-sm input350" /> + <input type="text" name="pushalot_authorizationtoken" id="pushalot_authorizationtoken" value="${sickbeard.PUSHALOT_AUTHORIZATIONTOKEN}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1284,7 +1284,7 @@ <div class="field-pair"> <label for="pushbullet_api"> <span class="component-title">Pushbullet API key</span> - <input type="text" name="pushbullet_api" id="pushbullet_api" value="${sickbeard.PUSHBULLET_API}" class="form-control input-sm input350" /> + <input type="text" name="pushbullet_api" id="pushbullet_api" value="${sickbeard.PUSHBULLET_API}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1358,7 +1358,7 @@ <div class="field-pair"> <label for="freemobile_id"> <span class="component-title">Free Mobile customer ID</span> - <input type="text" name="freemobile_id" id="freemobile_id" value="${sickbeard.FREEMOBILE_ID}" class="form-control input-sm input250" /> + <input type="text" name="freemobile_id" id="freemobile_id" value="${sickbeard.FREEMOBILE_ID}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1368,7 +1368,7 @@ <div class="field-pair"> <label for="freemobile_password"> <span class="component-title">Free Mobile API Key</span> - <input type="text" name="freemobile_apikey" id="freemobile_apikey" value="${sickbeard.FREEMOBILE_APIKEY}" class="form-control input-sm input250" /> + <input type="text" name="freemobile_apikey" id="freemobile_apikey" value="${sickbeard.FREEMOBILE_APIKEY}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1447,7 +1447,7 @@ <div class="field-pair"> <label for="twitter_dmto"> <span class="component-title">Send DM to</span> - <input type="text" name="twitter_dmto" id="twitter_dmto" value="${sickbeard.TWITTER_DMTO}" class="form-control input-sm input250" /> + <input type="text" name="twitter_dmto" id="twitter_dmto" value="${sickbeard.TWITTER_DMTO}" class="form-control input-sm input250" autocapitalize="off" /> </label> <p> <span class="component-desc">Twitter account to send Direct Messages to (must follow you)</span> @@ -1468,7 +1468,7 @@ </label> <label> <span style="font-size: 11px;">Enter the key Twitter gave you below, and click "Verify Key".<br><br></span> - <input type="text" id="twitter_key" value="" class="form-control input-sm input350" /> + <input type="text" id="twitter_key" value="" class="form-control input-sm input350" autocapitalize="off" /> <input class="btn btn-inline" type="button" value="Verify Key" id="twitterStep2" /> </label> </div> @@ -1509,7 +1509,7 @@ <div class="field-pair"> <label for="trakt_username"> <span class="component-title">Trakt username</span> - <input type="text" name="trakt_username" id="trakt_username" value="${sickbeard.TRAKT_USERNAME}" class="form-control input-sm input250" /> + <input type="text" name="trakt_username" id="trakt_username" value="${sickbeard.TRAKT_USERNAME}" class="form-control input-sm input250" autocapitalize="off" autocomplete="no" /> </label> <p> <span class="component-desc">username of your Trakt account.</span> @@ -1520,7 +1520,7 @@ <div class="field-pair"> <label for="trakt_pin"> <span class="component-title">Trakt PIN</span> - <input type="text" name="trakt_pin" id="trakt_pin" value="" class="form-control input-sm input250" /> + <input type="text" name="trakt_pin" id="trakt_pin" value="" class="form-control input-sm input250" autocapitalize="off" /> </label> <p> <span class="component-desc">PIN code to authorize SickRage to access Trakt on your behalf.</span> @@ -1530,7 +1530,7 @@ <div class="field-pair"> <label for="trakt_timeout"> <span class="component-title">API Timeout</span> - <input type="text" name="trakt_timeout" id="trakt_timeout" value="${sickbeard.TRAKT_TIMEOUT}" class="form-control input-sm input75" /> + <input type="text" name="trakt_timeout" id="trakt_timeout" value="${sickbeard.TRAKT_TIMEOUT}" class="form-control input-sm input75" autocapitalize="off" /> </label> <p> <span class="component-desc"> @@ -1635,7 +1635,7 @@ <div class="field-pair"> <label for="trakt_blacklist_name"> <span class="component-title">Trakt blackList name</span> - <input type="text" name="trakt_blacklist_name" id="trakt_blacklist_name" value="${sickbeard.TRAKT_BLACKLIST_NAME}" class="form-control input-sm input150" /> + <input type="text" name="trakt_blacklist_name" id="trakt_blacklist_name" value="${sickbeard.TRAKT_BLACKLIST_NAME}" class="form-control input-sm input150" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1697,7 +1697,7 @@ <div class="field-pair"> <label for="email_host"> <span class="component-title">SMTP host</span> - <input type="text" name="email_host" id="email_host" value="${sickbeard.EMAIL_HOST}" class="form-control input-sm input250" /> + <input type="text" name="email_host" id="email_host" value="${sickbeard.EMAIL_HOST}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1707,7 +1707,7 @@ <div class="field-pair"> <label for="email_port"> <span class="component-title">SMTP port</span> - <input type="text" name="email_port" id="email_port" value="${sickbeard.EMAIL_PORT}" class="form-control input-sm input75" /> + <input type="text" name="email_port" id="email_port" value="${sickbeard.EMAIL_PORT}" class="form-control input-sm input75" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1717,7 +1717,7 @@ <div class="field-pair"> <label for="email_from"> <span class="component-title">SMTP from</span> - <input type="text" name="email_from" id="email_from" value="${sickbeard.EMAIL_FROM}" class="form-control input-sm input250" /> + <input type="text" name="email_from" id="email_from" value="${sickbeard.EMAIL_FROM}" class="form-control input-sm input250" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1736,7 +1736,7 @@ <div class="field-pair"> <label for="email_user"> <span class="component-title">SMTP user</span> - <input type="text" name="email_user" id="email_user" value="${sickbeard.EMAIL_USER}" class="form-control input-sm input250" /> + <input type="text" name="email_user" id="email_user" value="${sickbeard.EMAIL_USER}" class="form-control input-sm input250" autocapitalize="off" autocomplete="no" /> </label> <label> <span class="component-title"> </span> @@ -1746,7 +1746,7 @@ <div class="field-pair"> <label for="email_password"> <span class="component-title">SMTP password</span> - <input type="password" name="email_password" id="email_password" value="${sickbeard.EMAIL_PASSWORD}" class="form-control input-sm input250" /> + <input type="password" name="email_password" id="email_password" value="${sickbeard.EMAIL_PASSWORD}" class="form-control input-sm input250" autocomplete="no" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1756,7 +1756,7 @@ <div class="field-pair"> <label for="email_list"> <span class="component-title">Global email list</span> - <input type="text" name="email_list" id="email_list" value="${sickbeard.EMAIL_LIST}" class="form-control input-sm input350" /> + <input type="text" name="email_list" id="email_list" value="${sickbeard.EMAIL_LIST}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -1773,7 +1773,7 @@ </label> <label> <span class="component-title"> </span> - <input type="text" name="email_show_list" id="email_show_list" class="form-control input-sm input350" /> + <input type="text" name="email_show_list" id="email_show_list" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> diff --git a/gui/slick/views/config_postProcessing.mako b/gui/slick/views/config_postProcessing.mako index 376dbe23c621601ddae41f4981c8671dc1627db7..7920b34bbbcc2c142f397dfb2a4b81d6bde3e74e 100644 --- a/gui/slick/views/config_postProcessing.mako +++ b/gui/slick/views/config_postProcessing.mako @@ -9,6 +9,7 @@ from sickbeard import metadata from sickbeard.metadata.generic import GenericMetadata from sickbeard import naming + from sickrage.helper.encoding import ek %> <%block name="content"> <div id="content960"> @@ -46,7 +47,7 @@ <div class="field-pair"> <label class="nocheck" for="tv_download_dir"> <span class="component-title">Post Processing Dir</span> - <input type="text" name="tv_download_dir" id="tv_download_dir" value="${sickbeard.TV_DOWNLOAD_DIR}" class="form-control input-sm input350" /> + <input type="text" name="tv_download_dir" id="tv_download_dir" value="${sickbeard.TV_DOWNLOAD_DIR}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label class="nocheck"> <span class="component-title"> </span> @@ -98,7 +99,7 @@ <div class="field-pair"> <label class="nocheck"> <span class="component-title">Sync File Extensions</span> - <input type="text" name="sync_files" id="sync_files" value="${sickbeard.SYNC_FILES}" class="form-control input-sm input350" /> + <input type="text" name="sync_files" id="sync_files" value="${sickbeard.SYNC_FILES}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label class="nocheck"> <span class="component-title"> </span> @@ -144,7 +145,7 @@ <div class="field-pair"> <label class="nocheck"> <span class="component-title">Allowed associated file extensions</span> - <input type="text" name="allowed_extensions" id="allowed_extensions" value="${sickbeard.ALLOWED_EXTENSIONS}" class="form-control input-sm input350" /> + <input type="text" name="allowed_extensions" id="allowed_extensions" value="${sickbeard.ALLOWED_EXTENSIONS}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label class="nocheck"> <span class="component-title"> </span> @@ -240,7 +241,7 @@ <div class="field-pair"> <label class="nocheck"> <span class="component-title">Extra Scripts</span> - <input type="text" name="extra_scripts" value="${'|'.join(sickbeard.EXTRA_SCRIPTS)}" class="form-control input-sm input350" /> + <input type="text" name="extra_scripts" value="${'|'.join(sickbeard.EXTRA_SCRIPTS)}" class="form-control input-sm input350" autocapitalize="off" /> </label> <label class="nocheck"> <span class="component-title"> </span> @@ -269,7 +270,7 @@ % if cur_preset == sickbeard.NAMING_PATTERN: <% is_custom = False %> % endif - <option id="${cur_preset}" ${('', 'selected="selected"')[sickbeard.NAMING_PATTERN == cur_preset]}>${os.path.join(tmp['dir'], tmp['name'])}</option> + <option id="${cur_preset}" ${('', 'selected="selected"')[sickbeard.NAMING_PATTERN == cur_preset]}>${ek(os.path.join, tmp['dir'], tmp['name'])}</option> % endfor <option id="${sickbeard.NAMING_PATTERN}" ${('', 'selected="selected"')[bool(is_custom)]}>Custom...</option> </select> @@ -284,7 +285,7 @@ </span> <span class="component-desc"> - <input type="text" name="naming_pattern" id="naming_pattern" value="${sickbeard.NAMING_PATTERN}" class="form-control input-sm input350" /> + <input type="text" name="naming_pattern" id="naming_pattern" value="${sickbeard.NAMING_PATTERN}" class="form-control input-sm input350" autocapitalize="off" /> <img src="${srRoot}/images/legend16.png" width="16" height="16" alt="[Toggle Key]" id="show_naming_key" title="Toggle Naming Legend" class="legend" class="legend" /> </span> </label> @@ -393,7 +394,7 @@ <td> </td> <td>%Y</td> <td>${datetime.date.today().year}</td> - </tr> + </tr> <tr> <td class="align-right"><b>Post-Processing Date:</b></td> <td>%CM</td> @@ -408,7 +409,7 @@ <td> </td> <td>%CY</td> <td>${datetime.date.today().year}</td> - </tr> + </tr> <tr> <td class="align-right"><b>Quality:</b></td> <td>%QN</td> @@ -521,7 +522,7 @@ % if cur_preset == sickbeard.NAMING_ABD_PATTERN: <% is_abd_custom = False %> % endif - <option id="${cur_preset}" ${('', 'selected="selected"')[sickbeard.NAMING_ABD_PATTERN == cur_preset]}>${os.path.join(tmp['dir'], tmp['name'])}</option> + <option id="${cur_preset}" ${('', 'selected="selected"')[sickbeard.NAMING_ABD_PATTERN == cur_preset]}>${ek(os.path.join, tmp['dir'], tmp['name'])}</option> % endfor <option id="${sickbeard.NAMING_ABD_PATTERN}" ${('', 'selected="selected"')[bool(is_abd_custom)]}>Custom...</option> </select> @@ -536,7 +537,7 @@ </span> <span class="component-desc"> - <input type="text" name="naming_abd_pattern" id="naming_abd_pattern" value="${sickbeard.NAMING_ABD_PATTERN}" class="form-control input-sm input350" /> + <input type="text" name="naming_abd_pattern" id="naming_abd_pattern" value="${sickbeard.NAMING_ABD_PATTERN}" class="form-control input-sm input350" autocapitalize="off" /> <img src="${srRoot}/images/legend16.png" width="16" height="16" alt="[Toggle Key]" id="show_naming_abd_key" title="Toggle ABD Naming Legend" class="legend" /> </span> </label> @@ -698,7 +699,7 @@ % if cur_preset == sickbeard.NAMING_SPORTS_PATTERN: <% is_sports_custom = False %> % endif - <option id="${cur_preset}" ${('', 'selected="selected"')[NAMING_SPORTS_PATTERN == cur_preset]}>${os.path.join(tmp['dir'], tmp['name'])}</option> + <option id="${cur_preset}" ${('', 'selected="selected"')[NAMING_SPORTS_PATTERN == cur_preset]}>${ek(os.path.join, tmp['dir'], tmp['name'])}</option> % endfor <option id="${sickbeard.NAMING_SPORTS_PATTERN}" ${('', 'selected="selected"')[bool(is_sports_custom)]}>Custom...</option> </select> @@ -713,7 +714,7 @@ </span> <span class="component-desc"> - <input type="text" name="naming_sports_pattern" id="naming_sports_pattern" value="${sickbeard.NAMING_SPORTS_PATTERN}" class="form-control input-sm input350" /> + <input type="text" name="naming_sports_pattern" id="naming_sports_pattern" value="${sickbeard.NAMING_SPORTS_PATTERN}" class="form-control input-sm input350" autocapitalize="off" /> <img src="${srRoot}/images/legend16.png" width="16" height="16" alt="[Toggle Key]" id="show_naming_sports_key" title="Toggle Sports Naming Legend" class="legend" /> </span> </label> @@ -876,7 +877,7 @@ % if cur_preset == sickbeard.NAMING_ANIME_PATTERN: <% is_anime_custom = False %> % endif - <option id="${cur_preset}" ${('', 'selected="selected"')[cur_preset == sickbeard.NAMING_ANIME_PATTERN]}>${os.path.join(tmp['dir'], tmp['name'])}</option> + <option id="${cur_preset}" ${('', 'selected="selected"')[cur_preset == sickbeard.NAMING_ANIME_PATTERN]}>${ek(os.path.join, tmp['dir'], tmp['name'])}</option> % endfor <option id="${sickbeard.NAMING_ANIME_PATTERN}" ${('', 'selected="selected"')[bool(is_anime_custom)]}>Custom...</option> </select> @@ -891,7 +892,7 @@ </span> <span class="component-desc"> - <input type="text" name="naming_anime_pattern" id="naming_anime_pattern" value="${sickbeard.NAMING_ANIME_PATTERN}" class="form-control input-sm input350" /> + <input type="text" name="naming_anime_pattern" id="naming_anime_pattern" value="${sickbeard.NAMING_ANIME_PATTERN}" class="form-control input-sm input350" autocapitalize="off" /> <img src="${srRoot}/images/legend16.png" width="16" height="16" alt="[Toggle Key]" id="show_naming_anime_key" title="Toggle Anime Naming Legend" class="legend" /> </span> </label> diff --git a/gui/slick/views/config_providers.mako b/gui/slick/views/config_providers.mako index 40808db9b0d223fc52864c26f4467b9b0cfc0a23..66bfc399cae32bd3f710fbd410f999083b01317d 100644 --- a/gui/slick/views/config_providers.mako +++ b/gui/slick/views/config_providers.mako @@ -140,7 +140,7 @@ $('#config-components').tabs(); <label for="${curNewznabProvider.getID()}_url"> <span class="component-title">URL:</span> <span class="component-desc"> - <input type="text" id="${curNewznabProvider.getID()}_url" value="${curNewznabProvider.url}" class="form-control input-sm input350" disabled/> + <input type="text" id="${curNewznabProvider.getID()}_url" value="${curNewznabProvider.url}" class="form-control input-sm input350" disabled autocapitalize="off" /> </span> </label> </div> @@ -148,7 +148,7 @@ $('#config-components').tabs(); <label for="${curNewznabProvider.getID()}_hash"> <span class="component-title">API key:</span> <span class="component-desc"> - <input type="text" id="${curNewznabProvider.getID()}_hash" value="${curNewznabProvider.key}" newznab_name="${curNewznabProvider.getID()}_hash" class="newznab_key form-control input-sm input350" /> + <input type="text" id="${curNewznabProvider.getID()}_hash" value="${curNewznabProvider.key}" newznab_name="${curNewznabProvider.getID()}_hash" class="newznab_key form-control input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -223,7 +223,7 @@ $('#config-components').tabs(); <label for="${curNzbProvider.getID()}_username"> <span class="component-title">Username:</span> <span class="component-desc"> - <input type="text" name="${curNzbProvider.getID()}_username" value="${curNzbProvider.username}" class="form-control input-sm input350" /> + <input type="text" name="${curNzbProvider.getID()}_username" value="${curNzbProvider.username}" class="form-control input-sm input350" autocapitalize="off" autocomplete="no" /> </span> </label> </div> @@ -234,7 +234,7 @@ $('#config-components').tabs(); <label for="${curNzbProvider.getID()}_api_key"> <span class="component-title">API key:</span> <span class="component-desc"> - <input type="text" name="${curNzbProvider.getID()}_api_key" value="${curNzbProvider.api_key}" class="form-control input-sm input350" /> + <input type="text" name="${curNzbProvider.getID()}_api_key" value="${curNzbProvider.api_key}" class="form-control input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -311,7 +311,7 @@ $('#config-components').tabs(); <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" /> + <input type="text" name="${curTorrentProvider.getID()}_custom_url" id="${curTorrentProvider.getID()}_custom_url" value="${curTorrentProvider.custom_url}" class="form-control input-sm input350" autocapitalize="off" /> </span> </label> <label> @@ -328,7 +328,7 @@ $('#config-components').tabs(); <label for="${curTorrentProvider.getID()}_api_key"> <span class="component-title">Api key:</span> <span class="component-desc"> - <input type="text" name="${curTorrentProvider.getID()}_api_key" id="${curTorrentProvider.getID()}_api_key" value="${curTorrentProvider.api_key}" class="form-control input-sm input350" /> + <input type="text" name="${curTorrentProvider.getID()}_api_key" id="${curTorrentProvider.getID()}_api_key" value="${curTorrentProvider.api_key}" class="form-control input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -339,7 +339,7 @@ $('#config-components').tabs(); <label for="${curTorrentProvider.getID()}_digest"> <span class="component-title">Digest:</span> <span class="component-desc"> - <input type="text" name="${curTorrentProvider.getID()}_digest" id="${curTorrentProvider.getID()}_digest" value="${curTorrentProvider.digest}" class="form-control input-sm input350" /> + <input type="text" name="${curTorrentProvider.getID()}_digest" id="${curTorrentProvider.getID()}_digest" value="${curTorrentProvider.digest}" class="form-control input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -350,7 +350,7 @@ $('#config-components').tabs(); <label for="${curTorrentProvider.getID()}_hash"> <span class="component-title">Hash:</span> <span class="component-desc"> - <input type="text" name="${curTorrentProvider.getID()}_hash" id="${curTorrentProvider.getID()}_hash" value="${curTorrentProvider.hash}" class="form-control input-sm input350" /> + <input type="text" name="${curTorrentProvider.getID()}_hash" id="${curTorrentProvider.getID()}_hash" value="${curTorrentProvider.hash}" class="form-control input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -361,7 +361,7 @@ $('#config-components').tabs(); <label for="${curTorrentProvider.getID()}_username"> <span class="component-title">Username:</span> <span class="component-desc"> - <input type="text" name="${curTorrentProvider.getID()}_username" id="${curTorrentProvider.getID()}_username" value="${curTorrentProvider.username}" class="form-control input-sm input350" /> + <input type="text" name="${curTorrentProvider.getID()}_username" id="${curTorrentProvider.getID()}_username" value="${curTorrentProvider.username}" class="form-control input-sm input350" autocapitalize="off" autocomplete="no" /> </span> </label> </div> @@ -372,7 +372,7 @@ $('#config-components').tabs(); <label for="${curTorrentProvider.getID()}_password"> <span class="component-title">Password:</span> <span class="component-desc"> - <input type="password" name="${curTorrentProvider.getID()}_password" id="${curTorrentProvider.getID()}_password" value="${curTorrentProvider.password}" class="form-control input-sm input350" /> + <input type="password" name="${curTorrentProvider.getID()}_password" id="${curTorrentProvider.getID()}_password" value="${curTorrentProvider.password}" class="form-control input-sm input350" autocomplete="no" autocapitalize="off" /> </span> </label> </div> @@ -383,7 +383,7 @@ $('#config-components').tabs(); <label for="${curTorrentProvider.getID()}_passkey"> <span class="component-title">Passkey:</span> <span class="component-desc"> - <input type="text" name="${curTorrentProvider.getID()}_passkey" id="${curTorrentProvider.getID()}_passkey" value="${curTorrentProvider.passkey}" class="form-control input-sm input350" /> + <input type="text" name="${curTorrentProvider.getID()}_passkey" id="${curTorrentProvider.getID()}_passkey" value="${curTorrentProvider.passkey}" class="form-control input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -394,7 +394,7 @@ $('#config-components').tabs(); <label for="${curTorrentProvider.getID()}_pin"> <span class="component-title">Pin:</span> <span class="component-desc"> - <input type="password" name="${curTorrentProvider.getID()}_pin" id="${curTorrentProvider.getID()}_pin" value="${curTorrentProvider.pin}" class="form-control input-sm input100" /> + <input type="password" name="${curTorrentProvider.getID()}_pin" id="${curTorrentProvider.getID()}_pin" value="${curTorrentProvider.pin}" class="form-control input-sm input100" autocomplete="no" autocapitalize="off" /> </span> </label> </div> @@ -636,19 +636,19 @@ $('#config-components').tabs(); <div class="field-pair"> <label for="newznab_name"> <span class="component-title">Provider name:</span> - <input type="text" id="newznab_name" class="form-control input-sm input200" /> + <input type="text" id="newznab_name" class="form-control input-sm input200" autocapitalize="off" /> </label> </div> <div class="field-pair"> <label for="newznab_url"> <span class="component-title">Site URL:</span> - <input type="text" id="newznab_url" class="form-control input-sm input350" /> + <input type="text" id="newznab_url" class="form-control input-sm input350" autocapitalize="off" /> </label> </div> <div class="field-pair"> <label for="newznab_key"> <span class="component-title">API key:</span> - <input type="text" id="newznab_key" class="form-control input-sm input350" /> + <input type="text" id="newznab_key" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -712,19 +712,19 @@ $('#config-components').tabs(); <div class="field-pair"> <label for="torrentrss_name"> <span class="component-title">Provider name:</span> - <input type="text" id="torrentrss_name" class="form-control input-sm input200" /> + <input type="text" id="torrentrss_name" class="form-control input-sm input200" autocapitalize="off" /> </label> </div> <div class="field-pair"> <label for="torrentrss_url"> <span class="component-title">RSS URL:</span> - <input type="text" id="torrentrss_url" class="form-control input-sm input350" /> + <input type="text" id="torrentrss_url" class="form-control input-sm input350" autocapitalize="off" /> </label> </div> <div class="field-pair"> <label for="torrentrss_cookies"> <span class="component-title">Cookies:</span> - <input type="text" id="torrentrss_cookies" class="form-control input-sm input350" /> + <input type="text" id="torrentrss_cookies" class="form-control input-sm input350" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> @@ -734,7 +734,7 @@ $('#config-components').tabs(); <div class="field-pair"> <label for="torrentrss_titleTAG"> <span class="component-title">Search element:</span> - <input type="text" id="torrentrss_titleTAG" class="form-control input-sm input200" value="title"/> + <input type="text" id="torrentrss_titleTAG" class="form-control input-sm input200" value="title" autocapitalize="off" /> </label> <label> <span class="component-title"> </span> diff --git a/gui/slick/views/config_search.mako b/gui/slick/views/config_search.mako index 61e3054dfd68e15001d3933a3efe8f28235f4c52..7df3b52c8eca25d423147d69b80fffa68ebe6610 100644 --- a/gui/slick/views/config_search.mako +++ b/gui/slick/views/config_search.mako @@ -65,7 +65,7 @@ <label> <span class="component-title">Backlog search day(s)</span> <span class="component-desc"> - <input type="text" name="backlog_days" value="${sickbeard.BACKLOG_DAYS}" class="form-control input-sm input75" /> + <input type="text" name="backlog_days" value="${sickbeard.BACKLOG_DAYS}" class="form-control input-sm input75" autocapitalize="off" /> <p>number of day(s) that the "Forced Backlog Search" will cover (e.g. 7 Days)</p> </span> </label> @@ -75,7 +75,7 @@ <label> <span class="component-title">Backlog search frequency</span> <span class="component-desc"> - <input type="text" name="backlog_frequency" value="${sickbeard.BACKLOG_FREQUENCY}" class="form-control input-sm input75" /> + <input type="text" name="backlog_frequency" value="${sickbeard.BACKLOG_FREQUENCY}" class="form-control input-sm input75" autocapitalize="off" /> <p>time in minutes between searches (min. ${sickbeard.MIN_BACKLOG_FREQUENCY})</p> </span> </label> @@ -85,7 +85,7 @@ <label> <span class="component-title">Daily search frequency</span> <span class="component-desc"> - <input type="text" name="dailysearch_frequency" value="${sickbeard.DAILYSEARCH_FREQUENCY}" class="form-control input-sm input75" /> + <input type="text" name="dailysearch_frequency" value="${sickbeard.DAILYSEARCH_FREQUENCY}" class="form-control input-sm input75" autocapitalize="off" /> <p>time in minutes between searches (min. ${sickbeard.MIN_DAILYSEARCH_FREQUENCY})</p> </span> </label> @@ -95,7 +95,7 @@ <label> <span class="component-title">Usenet retention</span> <span class="component-desc"> - <input type="text" name="usenet_retention" value="${sickbeard.USENET_RETENTION}" class="form-control input-sm input75" /> + <input type="text" name="usenet_retention" value="${sickbeard.USENET_RETENTION}" class="form-control input-sm input75" autocapitalize="off" /> <p>age limit in days for usenet articles to be used (e.g. 500)</p> </span> </label> @@ -105,7 +105,7 @@ <label> <span class="component-title">Ignore words</span> <span class="component-desc"> - <input type="text" name="ignore_words" value="${sickbeard.IGNORE_WORDS}" class="form-control input-sm input350" /> + <input type="text" name="ignore_words" value="${sickbeard.IGNORE_WORDS}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left">results with one or more word from this list will be ignored<br> separate words with a comma, e.g. "word1,word2,word3" </div> @@ -117,7 +117,7 @@ <label> <span class="component-title">Require words</span> <span class="component-desc"> - <input type="text" name="require_words" value="${sickbeard.REQUIRE_WORDS}" class="form-control input-sm input350" /> + <input type="text" name="require_words" value="${sickbeard.REQUIRE_WORDS}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left">results with no word from this list will be ignored<br> separate words with a comma, e.g. "word1,word2,word3" </div> @@ -129,7 +129,7 @@ <label> <span class="component-title">Trackers list</span> <span class="component-desc"> - <input type="text" name="trackers_list" value="${sickbeard.TRACKERS_LIST}" class="form-control input-sm input350" /> + <input type="text" name="trackers_list" value="${sickbeard.TRACKERS_LIST}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left">Trackers that will be added to magnets without trackers<br> separate trackers with a comma, e.g. "tracker1,tracker2,tracker3" </div> @@ -141,7 +141,7 @@ <label> <span class="component-title">Ignore language names in subbed results</span> <span class="component-desc"> - <input type="text" name="ignored_subs_list" value="${sickbeard.IGNORED_SUBS_LIST}" class="form-control input-sm input350" /> + <input type="text" name="ignored_subs_list" value="${sickbeard.IGNORED_SUBS_LIST}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left">Ignore subbed releases based on language names <br> Example: "dk" will ignore words: dksub, dksubs, dksubbed, dksubed <br> separate languages with a comma, e.g. "lang1,lang2,lang3 @@ -229,7 +229,7 @@ <label> <span class="component-title">Black hole folder location</span> <span class="component-desc"> - <input type="text" name="nzb_dir" id="nzb_dir" value="${sickbeard.NZB_DIR}" class="form-control input-sm input350" /> + <input type="text" name="nzb_dir" id="nzb_dir" value="${sickbeard.NZB_DIR}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"><p><b>.nzb</b> files are stored at this location for external software to find and use</p></div> </span> </label> @@ -241,7 +241,7 @@ <label> <span class="component-title">SABnzbd server URL</span> <span class="component-desc"> - <input type="text" id="sab_host" name="sab_host" value="${sickbeard.SAB_HOST}" class="form-control input-sm input350" /> + <input type="text" id="sab_host" name="sab_host" value="${sickbeard.SAB_HOST}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"><p>URL to your SABnzbd server (e.g. http://localhost:8080/)</p></div> </span> </label> @@ -251,7 +251,7 @@ <label> <span class="component-title">SABnzbd username</span> <span class="component-desc"> - <input type="text" name="sab_username" id="sab_username" value="${sickbeard.SAB_USERNAME}" class="form-control input-sm input200" /> + <input type="text" name="sab_username" id="sab_username" value="${sickbeard.SAB_USERNAME}" class="form-control input-sm input200" autocapitalize="off" autocomplete="no" /> <p>(blank for none)</p> </span> </label> @@ -261,7 +261,7 @@ <label> <span class="component-title">SABnzbd password</span> <span class="component-desc"> - <input type="password" name="sab_password" id="sab_password" value="${sickbeard.SAB_PASSWORD}" class="form-control input-sm input200" /> + <input type="password" name="sab_password" id="sab_password" value="${sickbeard.SAB_PASSWORD}" class="form-control input-sm input200" autocomplete="no" autocapitalize="off" /> <p>(blank for none)</p> </span> </label> @@ -271,7 +271,7 @@ <label> <span class="component-title">SABnzbd API key</span> <span class="component-desc"> - <input type="text" name="sab_apikey" id="sab_apikey" value="${sickbeard.SAB_APIKEY}" class="form-control input-sm input350" /> + <input type="text" name="sab_apikey" id="sab_apikey" value="${sickbeard.SAB_APIKEY}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"><p>locate at... SABnzbd Config -> General -> API Key</p></div> </span> </label> @@ -281,7 +281,7 @@ <label> <span class="component-title">Use SABnzbd category</span> <span class="component-desc"> - <input type="text" name="sab_category" id="sab_category" value="${sickbeard.SAB_CATEGORY}" class="form-control input-sm input200" /> + <input type="text" name="sab_category" id="sab_category" value="${sickbeard.SAB_CATEGORY}" class="form-control input-sm input200" autocapitalize="off" /> <p>add downloads to this category (e.g. TV)</p> </span> </label> @@ -291,7 +291,7 @@ <label> <span class="component-title">Use SABnzbd category (backlog episodes)</span> <span class="component-desc"> - <input type="text" name="sab_category_backlog" id="sab_category_backlog" value="${sickbeard.SAB_CATEGORY_BACKLOG}" class="form-control input-sm input200" /> + <input type="text" name="sab_category_backlog" id="sab_category_backlog" value="${sickbeard.SAB_CATEGORY_BACKLOG}" class="form-control input-sm input200" autocapitalize="off" /> <p>add downloads of old episodes to this category (e.g. TV)</p> </span> </label> @@ -301,7 +301,7 @@ <label> <span class="component-title">Use SABnzbd category for anime</span> <span class="component-desc"> - <input type="text" name="sab_category_anime" id="sab_category_anime" value="${sickbeard.SAB_CATEGORY_ANIME}" class="form-control input-sm input200" /> + <input type="text" name="sab_category_anime" id="sab_category_anime" value="${sickbeard.SAB_CATEGORY_ANIME}" class="form-control input-sm input200" autocapitalize="off" /> <p>add anime downloads to this category (e.g. anime)</p> </span> </label> @@ -312,7 +312,7 @@ <label> <span class="component-title">Use SABnzbd category for anime (backlog episodes)</span> <span class="component-desc"> - <input type="text" name="sab_category_anime_backlog" id="sab_category_anime_backlog" value="${sickbeard.SAB_CATEGORY_ANIME_BACKLOG}" class="form-control input-sm input200" /> + <input type="text" name="sab_category_anime_backlog" id="sab_category_anime_backlog" value="${sickbeard.SAB_CATEGORY_ANIME_BACKLOG}" class="form-control input-sm input200" autocapitalize="off" /> <p>add anime downloads of old episodes to this category (e.g. anime)</p> </span> </label> @@ -346,7 +346,7 @@ <label> <span class="component-title">NZBget host:port</span> <span class="component-desc"> - <input type="text" name="nzbget_host" id="nzbget_host" value="${sickbeard.NZBGET_HOST}" class="form-control input-sm input350" /> + <input type="text" name="nzbget_host" id="nzbget_host" value="${sickbeard.NZBGET_HOST}" class="form-control input-sm input350" autocapitalize="off" /> <p>(e.g. localhost:6789)</p> <div class="clear-left"><p>NZBget RPC host name and port number (not NZBgetweb!)</p></div> </span> @@ -357,7 +357,7 @@ <label> <span class="component-title">NZBget username</span> <span class="component-desc"> - <input type="text" name="nzbget_username" value="${sickbeard.NZBGET_USERNAME}" class="form-control input-sm input200" /> + <input type="text" name="nzbget_username" value="${sickbeard.NZBGET_USERNAME}" class="form-control input-sm input200" autocapitalize="off" autocomplete="no" /> <p>locate in nzbget.conf (default:nzbget)</p> </span> </label> @@ -367,7 +367,7 @@ <label> <span class="component-title">NZBget password</span> <span class="component-desc"> - <input type="password" name="nzbget_password" id="nzbget_password" value="${sickbeard.NZBGET_PASSWORD}" class="form-control input-sm input200" /> + <input type="password" name="nzbget_password" id="nzbget_password" value="${sickbeard.NZBGET_PASSWORD}" class="form-control input-sm input200" autocomplete="no" autocapitalize="off" /> <p>locate in nzbget.conf (default:tegbzn6789)</p> </span> </label> @@ -377,7 +377,7 @@ <label> <span class="component-title">Use NZBget category</span> <span class="component-desc"> - <input type="text" name="nzbget_category" id="nzbget_category" value="${sickbeard.NZBGET_CATEGORY}" class="form-control input-sm input200" /> + <input type="text" name="nzbget_category" id="nzbget_category" value="${sickbeard.NZBGET_CATEGORY}" class="form-control input-sm input200" autocapitalize="off" /> <p>send downloads marked this category (e.g. TV)</p> </span> </label> @@ -387,7 +387,7 @@ <label> <span class="component-title">Use NZBget category (backlog episodes)</span> <span class="component-desc"> - <input type="text" name="nzbget_category_backlog" id="nzbget_category_backlog" value="${sickbeard.NZBGET_CATEGORY_BACKLOG}" class="form-control input-sm input200" /> + <input type="text" name="nzbget_category_backlog" id="nzbget_category_backlog" value="${sickbeard.NZBGET_CATEGORY_BACKLOG}" class="form-control input-sm input200" autocapitalize="off" /> <p>send downloads of old episodes marked this category (e.g. TV)</p> </span> </label> @@ -397,7 +397,7 @@ <label> <span class="component-title">Use NZBget category for anime</span> <span class="component-desc"> - <input type="text" name="nzbget_category_anime" id="nzbget_category_anime" value="${sickbeard.NZBGET_CATEGORY_ANIME}" class="form-control input-sm input200" /> + <input type="text" name="nzbget_category_anime" id="nzbget_category_anime" value="${sickbeard.NZBGET_CATEGORY_ANIME}" class="form-control input-sm input200" autocapitalize="off" /> <p>send anime downloads marked this category (e.g. anime)</p> </span> </label> @@ -407,7 +407,7 @@ <label> <span class="component-title">Use NZBget category for anime (backlog episodes)</span> <span class="component-desc"> - <input type="text" name="nzbget_category_anime_backlog" id="nzbget_category_anime_backlog" value="${sickbeard.NZBGET_CATEGORY_ANIME_BACKLOG}" class="form-control input-sm input200" /> + <input type="text" name="nzbget_category_anime_backlog" id="nzbget_category_anime_backlog" value="${sickbeard.NZBGET_CATEGORY_ANIME_BACKLOG}" class="form-control input-sm input200" autocapitalize="off" /> <p>send anime downloads of old episodes marked this category (e.g. anime)</p> </span> </label> @@ -475,7 +475,7 @@ <label> <span class="component-title">Black hole folder location</span> <span class="component-desc"> - <input type="text" name="torrent_dir" id="torrent_dir" value="${sickbeard.TORRENT_DIR}" class="form-control input-sm input350" /> + <input type="text" name="torrent_dir" id="torrent_dir" value="${sickbeard.TORRENT_DIR}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"><p><b>.torrent</b> files are stored at this location for external software to find and use</p></div> </span> </label> @@ -491,7 +491,7 @@ <label> <span class="component-title" id="host_title">Torrent host:port</span> <span class="component-desc"> - <input type="text" name="torrent_host" id="torrent_host" value="${sickbeard.TORRENT_HOST}" class="form-control input-sm input350" /> + <input type="text" name="torrent_host" id="torrent_host" value="${sickbeard.TORRENT_HOST}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"> <p id="host_desc_torrent">URL to your torrent client (e.g. http://localhost:8000/)</p> </div> @@ -503,7 +503,7 @@ <label> <span class="component-title" id="rpcurl_title">Torrent RPC URL</span> <span class="component-desc"> - <input type="text" name="torrent_rpcurl" id="torrent_rpcurl" value="${sickbeard.TORRENT_RPCURL}" class="form-control input-sm input350"/> + <input type="text" name="torrent_rpcurl" id="torrent_rpcurl" value="${sickbeard.TORRENT_RPCURL}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"> <p id="rpcurl_desc_">The path without leading and trailing slashes (e.g. transmission)</p> </div> @@ -541,7 +541,7 @@ <label> <span class="component-title" id="username_title">Client username</span> <span class="component-desc"> - <input type="text" name="torrent_username" id="torrent_username" value="${sickbeard.TORRENT_USERNAME}" class="form-control input-sm input200" /> + <input type="text" name="torrent_username" id="torrent_username" value="${sickbeard.TORRENT_USERNAME}" class="form-control input-sm input200" autocapitalize="off" autocomplete="no" /> <p>(blank for none)</p> </span> </label> @@ -551,7 +551,7 @@ <label> <span class="component-title" id="password_title">Client password</span> <span class="component-desc"> - <input type="password" name="torrent_password" id="torrent_password" value="${sickbeard.TORRENT_PASSWORD}" class="form-control input-sm input200" /> + <input type="password" name="torrent_password" id="torrent_password" value="${sickbeard.TORRENT_PASSWORD}" class="form-control input-sm input200" autocomplete="no" autocapitalize="off" /> <p>(blank for none)</p> </span> </label> @@ -561,7 +561,7 @@ <label> <span class="component-title">Add label to torrent</span> <span class="component-desc"> - <input type="text" name="torrent_label" id="torrent_label" value="${sickbeard.TORRENT_LABEL}" class="form-control input-sm input200" /> + <input type="text" name="torrent_label" id="torrent_label" value="${sickbeard.TORRENT_LABEL}" class="form-control input-sm input200" autocapitalize="off" /> <span id="label_warning_deluge" style="display:none"><p>(blank spaces are not allowed)</p> <div class="clear-left"><p>note: label plugin must be enabled in Deluge clients</p></div> </span> @@ -573,7 +573,7 @@ <label> <span class="component-title">Add label to torrent for anime</span> <span class="component-desc"> - <input type="text" name="torrent_label_anime" id="torrent_label_anime" value="${sickbeard.TORRENT_LABEL_ANIME}" class="form-control input-sm input200" /> + <input type="text" name="torrent_label_anime" id="torrent_label_anime" value="${sickbeard.TORRENT_LABEL_ANIME}" class="form-control input-sm input200" autocapitalize="off" /> <span id="label_anime_warning_deluge" style="display:none"><p>(blank spaces are not allowed)</p> <div class="clear-left"><p>note: label plugin must be enabled in Deluge clients</p></div> </span> @@ -585,7 +585,7 @@ <label> <span class="component-title" id="directory_title">Downloaded files location</span> <span class="component-desc"> - <input type="text" name="torrent_path" id="torrent_path" value="${sickbeard.TORRENT_PATH}" class="form-control input-sm input350" /> + <input type="text" name="torrent_path" id="torrent_path" value="${sickbeard.TORRENT_PATH}" class="form-control input-sm input350" autocapitalize="off" /> <div class="clear-left"><p>where <span id="torrent_client">the torrent client</span> will save downloaded files (blank for client default) <span id="path_synology"> <b>note:</b> the destination has to be a shared folder for Synology DS</span></p> </div> diff --git a/gui/slick/views/config_subtitles.mako b/gui/slick/views/config_subtitles.mako index bf1f208908a51c7ade978639f69bbc2ef6608233..01ea0fd718fc8d9f731011428fccc58cb81b20ee 100644 --- a/gui/slick/views/config_subtitles.mako +++ b/gui/slick/views/config_subtitles.mako @@ -7,13 +7,13 @@ <%block name="scripts"> <script> $(document).ready(function() { - $("#subtitles_languages").tokenInput([${','.join("{\"id\": \"" + lang.opensubtitles + "\", name: \"" + lang.name + "\"}" for lang in subtitles.subtitleLanguageFilter())}], { + $("#subtitles_languages").tokenInput([${','.join("{\"id\": \"" + code + "\", name: \"" + subtitles.name_from_code(code) + "\"}" for code in subtitles.subtitle_code_filter())}], { method: "POST", hintText: "Write to search a language and select it", preventDuplicates: true, - prePopulate: [${','.join("{\"id\": \"" + subtitles.fromietf(lang).opensubtitles + "\", name: \"" + subtitles.fromietf(lang).name + "\"}" for lang in subtitles.wantedLanguages()) if subtitles.wantedLanguages() else ''}], - resultsFormatter: function(item){ return "<li><img src='${srRoot}/images/subtitles/flags/" + item.id + ".png' /> " + item.name + "</li>" }, - tokenFormatter: function(item) { return "<li><img src='${srRoot}/images/subtitles/flags/" + item.id + ".png' /> " + item.name + "</li>" }, + prePopulate: [${','.join("{\"id\": \"" + code + "\", name: \"" + subtitles.name_from_code(code) + "\"}" for code in subtitles.wanted_languages())}], + resultsFormatter: function(item){ return "<li><img src='${srRoot}/images/subtitles/flags/" + item.id + ".png' onError='this.onerror=null;this.src=\"${srRoot}/images/flags/unknown.png\";' style='vertical-align: middle !important;' /> " + item.name + "</li>" }, + tokenFormatter: function(item) { return "<li><img src='${srRoot}/images/subtitles/flags/" + item.id + ".png' onError='this.onerror=null;this.src=\"${srRoot}/images/flags/unknown.png\";' style='vertical-align: middle !important;' /> " + item.name + "</li>" }, }); }); $('#config-components').tabs(); @@ -59,7 +59,7 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } <div class="field-pair"> <label> <span class="component-title">Subtitle Languages</span> - <span class="component-desc"><input type="text" id="subtitles_languages" name="subtitles_languages" /></span> + <span class="component-desc"><input type="text" id="subtitles_languages" name="subtitles_languages" autocapitalize="off" /></span> </label> </div> <div class="field-pair"> @@ -134,7 +134,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" autocapitalize="off" /> </label> <label class="nocheck"> <span class="component-title"> </span> @@ -173,7 +173,7 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } <fieldset class="component-group-list" style="margin-left: 50px; margin-top:36px"> <ul id="service_order_list"> - % for curService in sickbeard.subtitles.sortedServiceList(): + % for curService in sickbeard.subtitles.sorted_service_list(): <li class="ui-state-default" id="${curService['name']}"> <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"> @@ -184,7 +184,7 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } </li> % endfor </ul> - <input type="hidden" name="service_order" id="service_order" value="<%" ".join(['%s:%d' % (x['name'], x['enabled']) for x in sickbeard.subtitles.sortedServiceList()])%>"/> + <input type="hidden" name="service_order" id="service_order" value="<%" ".join(['%s:%d' % (x['name'], x['enabled']) for x in sickbeard.subtitles.sorted_service_list()])%>"/> <br><input type="submit" class="btn config_submitter" value="Save Changes" /><br> </fieldset> @@ -202,7 +202,7 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } 'addic7ed': {'user': sickbeard.ADDIC7ED_USER, 'pass': sickbeard.ADDIC7ED_PASS}, 'opensubtitles': {'user': sickbeard.OPENSUBTITLES_USER, 'pass': sickbeard.OPENSUBTITLES_PASS}} %> - % for curService in sickbeard.subtitles.sortedServiceList(): + % for curService in sickbeard.subtitles.sorted_service_list(): % if curService['name'] not in providerLoginDict.keys(): <% continue %> % endif @@ -211,13 +211,13 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' } <label class="nocheck" for="${curService['name']}_user"> <span class="component-title">${curService['name'].capitalize()} User Name</span> <span class="component-desc"> - <input type="text" name="${curService['name']}_user" id="${curService['name']}_user" value="${providerLoginDict[curService['name']]['user']}" class="form-control input-sm input300" /> + <input type="text" name="${curService['name']}_user" id="${curService['name']}_user" value="${providerLoginDict[curService['name']]['user']}" class="form-control input-sm input300" autocapitalize="off" autocomplete="no" /> </span> </label> <label class="nocheck" for="${curService['name']}_pass"> <span class="component-title">${curService['name'].capitalize()} Password</span> <span class="component-desc"> - <input type="password" name="${curService['name']}_pass" id="${curService['name']}_pass" value="${providerLoginDict[curService['name']]['pass']}" class="form-control input-sm input300" /> + <input type="password" name="${curService['name']}_pass" id="${curService['name']}_pass" value="${providerLoginDict[curService['name']]['pass']}" class="form-control input-sm input300" autocomplete="no" autocapitalize="off" /> </span> </label> </div> diff --git a/gui/slick/views/displayShow.mako b/gui/slick/views/displayShow.mako index 3ac9d1361d68324415c10ad14af4862f602f95fd..fd52eb56b48bdacefe2b24b7f4aa5499e0e79b39 100644 --- a/gui/slick/views/displayShow.mako +++ b/gui/slick/views/displayShow.mako @@ -207,7 +207,7 @@ </table> <table style="width:180px; float: right; vertical-align: middle; height: 100%;"> - <% info_flag = subtitles.fromietf(show.lang).opensubtitles if show.lang else '' %> + <% info_flag = subtitles.code_from_code(show.lang) if show.lang else '' %> <tr><td class="showLegend">Info Language:</td><td><img src="${srRoot}/images/subtitles/flags/${info_flag}.png" width="16" height="11" alt="${show.lang}" title="${show.lang}" onError="this.onerror=null;this.src='${srRoot}/images/flags/unknown.png';"/></td></tr> % if sickbeard.USE_SUBTITLES: <tr><td class="showLegend">Subtitles: </td><td><img src="${srRoot}/images/${("no16.png", "yes16.png")[bool(show.subtitles)]}" alt="${("N", "Y")[bool(show.subtitles)]}" width="16" height="16" /></td></tr> @@ -445,7 +445,7 @@ % else: value="${str(scSeas)}x${str(scEpis)}" % endif - style="padding: 0; text-align: center; max-width: 60px;" /> + style="padding: 0; text-align: center; max-width: 60px;" autocapitalize="off" /> </td> <td align="center"> <input type="text" placeholder="${str(dfltAbsolute)}" size="6" maxlength="8" @@ -457,7 +457,7 @@ % else: value="${str(scAbsolute)}" % endif - style="padding: 0; text-align: center; max-width: 60px;" /> + style="padding: 0; text-align: center; max-width: 60px;" autocapitalize="off" /> </td> <td class="col-name"> % if epResult["description"] != "" and epResult["description"] is not None: @@ -499,12 +499,10 @@ % endif </td> <td class="col-subtitles" align="center"> - % for sub_lang in [subtitles.fromietf(x) for x in epResult["subtitles"].split(',') if epResult["subtitles"]]: - <% flag = sub_lang.opensubtitles %> - % if (not sickbeard.SUBTITLES_MULTI and len(subtitles.wantedLanguages()) is 1) and subtitles.wantedLanguages()[0] in sub_lang.opensubtitles: - <% flag = 'checkbox' %> + % for flag in (epResult["subtitles"] or '').split(','): + % if flag.strip(): + <img src="${srRoot}/images/subtitles/flags/${flag}.png" width="16" height="11" alt="${subtitles.name_from_code(flag)}" onError="this.onerror=null;this.src='${srRoot}/images/flags/unknown.png';" /> % endif - <img src="${srRoot}/images/subtitles/flags/${flag}.png" width="16" height="11" alt="${sub_lang.name}" onError="this.onerror=null;this.src='${srRoot}/images/flags/unknown.png';" /> % endfor </td> <% curStatus, curQuality = Quality.splitCompositeStatus(int(epResult["status"])) %> @@ -521,7 +519,7 @@ <a class="epSearch" id="${str(show.indexerid)}x${str(epResult["season"])}x${str(epResult["episode"])}" name="${str(show.indexerid)}x${str(epResult["season"])}x${str(epResult["episode"])}" href="searchEpisode?show=${show.indexerid}&season=${epResult["season"]}&episode=${epResult["episode"]}"><img src="${srRoot}/images/search16.png" width="16" height="16" alt="search" title="Manual Search" /></a> % endif % endif - % if sickbeard.USE_SUBTITLES and show.subtitles and epResult["location"] and frozenset(subtitles.wantedLanguages()).difference(epResult["subtitles"].split(',')): + % if sickbeard.USE_SUBTITLES and show.subtitles and epResult["location"] and subtitles.needs_subtitles(epResult['subtitles']): <a class="epSubtitlesSearch" href="searchEpisodeSubtitles?show=${show.indexerid}&season=${epResult["season"]}&episode=${epResult["episode"]}"><img src="${srRoot}/images/closed_captioning.png" height="16" alt="search subtitles" title="Search Subtitles" /></a> % endif </td> diff --git a/gui/slick/views/editShow.mako b/gui/slick/views/editShow.mako index 299168570c6e184e613eb6e108dfe120d375dc2f..9b3be1c6814317af095969c450b549e226a7df27 100644 --- a/gui/slick/views/editShow.mako +++ b/gui/slick/views/editShow.mako @@ -50,7 +50,7 @@ <span class="component-title">Show Location</span> <span class="component-desc"> <input type="hidden" name="show" value="${show.indexerid}" /> - <input type="text" name="location" id="location" value="${show._location}" class="form-control form-control-inline input-sm input350" /> + <input type="text" name="location" id="location" value="${show._location}" class="form-control form-control-inline input-sm input350" autocapitalize="off" /> </span> </label> </div> @@ -202,7 +202,7 @@ <label for="rls_ignore_words"> <span class="component-title">Ignored Words</span> <span class="component-desc"> - <input type="text" id="rls_ignore_words" name="rls_ignore_words" id="rls_ignore_words" value="${show.rls_ignore_words}" class="form-control form-control-inline input-sm input350" /><br> + <input type="text" id="rls_ignore_words" name="rls_ignore_words" id="rls_ignore_words" value="${show.rls_ignore_words}" class="form-control form-control-inline input-sm input350" autocapitalize="off" /><br> <div class="clear-left"> <p>comma-separated <i>e.g. "word1,word2,word3"</i></> <p>Search results with one or more words from this list will be ignored.</p> @@ -215,7 +215,7 @@ <label for="rls_require_words"> <span class="component-title">Required Words</span> <span class="component-desc"> - <input type="text" id="rls_require_words" name="rls_require_words" id="rls_require_words" value="${show.rls_require_words}" class="form-control form-control-inline input-sm input350" /><br> + <input type="text" id="rls_require_words" name="rls_require_words" id="rls_require_words" value="${show.rls_require_words}" class="form-control form-control-inline input-sm input350" autocapitalize="off" /><br> <div class="clear-left"> <p>comma-separated <i>e.g. "word1,word2,word3"</i></p> <p>Search results with no words from this list will be ignored.</p> @@ -228,7 +228,7 @@ <label for="SceneName"> <span class="component-title">Scene Exception</span> <span class="component-desc"> - <input type="text" id="SceneName" class="form-control form-control-inline input-sm input200" /><input class="btn btn-inline" type="button" value="Add" id="addSceneName" /><br><br> + <input type="text" id="SceneName" class="form-control form-control-inline input-sm input200" autocapitalize="off" /><input class="btn btn-inline" type="button" value="Add" id="addSceneName" /><br><br> <div class="pull-left"> <select id="exceptions_list" name="exceptions_list" multiple="multiple" style="min-width:200px;height:99px;"> % for cur_exception in show.exceptions: diff --git a/gui/slick/views/history.mako b/gui/slick/views/history.mako index 8895b5b044e84ddc132f883bc49daa0c6fe38929..385f707b1da2f7ecca9a41bd26f11774d04f574b 100644 --- a/gui/slick/views/history.mako +++ b/gui/slick/views/history.mako @@ -14,6 +14,7 @@ from sickbeard.common import Quality, statusStrings, Overview from sickrage.show.History import History + from sickrage.helper.encoding import ek %> <%block name="content"> <%namespace file="/inc_defs.mako" import="renderQualityPill"/> @@ -76,7 +77,7 @@ % if curStatus == SUBTITLED: <img width="16" height="11" style="vertical-align:middle;" src="${srRoot}/images/subtitles/flags/${hItem['resource']}.png" onError="this.onerror=null;this.src='${srRoot}/images/flags/unknown.png';"> % endif - <span style="cursor: help; vertical-align:middle;" title="${os.path.basename(hItem['resource'])}">${statusStrings[curStatus]}</span> + <span style="cursor: help; vertical-align:middle;" title="${ek(os.path.basename, hItem['resource'])}">${statusStrings[curStatus]}</span> </td> <td align="center"> % if curStatus in [DOWNLOADED, ARCHIVED]: @@ -143,7 +144,7 @@ % if curStatus in [SNATCHED, FAILED]: <% provider = providers.getProviderClass(generic.GenericProvider.makeID(action["provider"])) %> % 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"])}"/> + <img src="${srRoot}/images/providers/${provider.imageName()}" width="16" height="16" style="vertical-align:middle;" alt="${provider.name}" style="cursor: help;" title="${provider.name}: ${ek(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"/> % endif @@ -155,9 +156,9 @@ <% curStatus, curQuality = Quality.splitCompositeStatus(int(action["action"])) %> % if curStatus in [DOWNLOADED, ARCHIVED]: % if action["provider"] != "-1": - <span style="cursor: help;" title="${os.path.basename(action["resource"])}"><i>${action["provider"]}</i></span> + <span style="cursor: help;" title="${ek(os.path.basename, action["resource"])}"><i>${action["provider"]}</i></span> % else: - <span style="cursor: help;" title="${os.path.basename(action["resource"])}"></span> + <span style="cursor: help;" title="${ek(os.path.basename, action["resource"])}"></span> % endif % endif % endfor @@ -167,7 +168,7 @@ % for action in sorted(hItem["actions"]): <% curStatus, curQuality = Quality.splitCompositeStatus(int(action["action"])) %> % if curStatus == SUBTITLED: - <img src="${srRoot}/images/subtitles/${action['provider']}.png" width="16" height="16" style="vertical-align:middle;" alt="${action["provider"]}" title="${action["provider"].capitalize()}: ${os.path.basename(action["resource"])}"/> + <img src="${srRoot}/images/subtitles/${action['provider']}.png" width="16" height="16" style="vertical-align:middle;" alt="${action["provider"]}" title="${action["provider"].capitalize()}: ${ek(os.path.basename, action["resource"])}"/> <span style="vertical-align:middle;"> / </span> <img width="16" height="11" style="vertical-align:middle;" src="${srRoot}/images/subtitles/flags/${action['resource']}.png" onError="this.onerror=null;this.src='${srRoot}/images/flags/unknown.png';" style="vertical-align: middle !important;"> diff --git a/gui/slick/views/home.mako b/gui/slick/views/home.mako index df431a375c11b71f216b57c55e2505ee9f4555cd..efa1090e2b9c669870dfd19dee6b892ce586babb 100644 --- a/gui/slick/views/home.mako +++ b/gui/slick/views/home.mako @@ -12,65 +12,82 @@ </%block> <%block name="content"> <%namespace file="/inc_defs.mako" import="renderQualityPill"/> -% if not header is UNDEFINED: - <h1 class="header">${header}</h1> -% else: - <h1 class="title">${title}</h1> -% endif -<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> - </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> - <option value="date" data-sort="${srRoot}/setPosterSortBy/?sort=date" ${('', 'selected="selected"')[sickbeard.POSTER_SORTBY == 'date']}>Next Episode</option> - <option value="network" data-sort="${srRoot}/setPosterSortBy/?sort=network" ${('', 'selected="selected"')[sickbeard.POSTER_SORTBY == 'network']}>Network</option> - <option value="progress" data-sort="${srRoot}/setPosterSortBy/?sort=progress" ${('', 'selected="selected"')[sickbeard.POSTER_SORTBY == 'progress']}>Progress</option> - </select> - </span> - - <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]}>A ➜ Z</option> - <option value="false" data-sort="${srRoot}/setPosterSortDir/?direction=0" ${('', 'selected="selected"')[sickbeard.POSTER_SORTDIR == 0]}>Z ➜ A</option> - </select> - </span> - % endif +<table style="width: 100%;" class="home-header"> + <tr> + <td nowrap> + % if not header is UNDEFINED: + <h1 class="header" style="margin: 0;">${header}</h1> + % else: + <h1 class="title" style="margin: 0;">${title}</h1> + % endif + </td> - - <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> + <td align="right"> + <div> + % if sickbeard.HOME_LAYOUT != 'poster': + <span class="show-option"> + <button id="popover" type="button" class="btn btn-inline">Select Columns <b class="caret"></b></button> + </span> + + <span class="show-option"> + <button type="button" class="resetsorting btn btn-inline">Clear Filter(s)</button> + </span> + % endif + + % if sickbeard.HOME_LAYOUT == 'poster': + <span class="show-option"> Poster Size: + <div style="width: 100px; display: inline-block; margin-left: 7px;" id="posterSizeSlider"></div> + </span> + + <span class="show-option"> + <input id="filterShowName" class="form-control form-control-inline input-sm input200" type="search" placeholder="Filter Show Name"> + </span> + + <span class="show-option"> 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> + <option value="date" data-sort="${srRoot}/setPosterSortBy/?sort=date" ${('', 'selected="selected"')[sickbeard.POSTER_SORTBY == 'date']}>Next Episode</option> + <option value="network" data-sort="${srRoot}/setPosterSortBy/?sort=network" ${('', 'selected="selected"')[sickbeard.POSTER_SORTBY == 'network']}>Network</option> + <option value="progress" data-sort="${srRoot}/setPosterSortBy/?sort=progress" ${('', 'selected="selected"')[sickbeard.POSTER_SORTBY == 'progress']}>Progress</option> + </select> + </span> + + <span class="show-option"> 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]}>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 class="show-option"> 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> + </td> + </tr> +</table> +% if sickbeard.HOME_LAYOUT == 'poster': + <div class="loading-spinner"></div> +% endif % for curShowlist in showlists: <% curListType = curShowlist[0] %> <% myShowList = list(curShowlist[1]) %> % if curListType == "Anime": <h1 class="header">Anime List</h1> + % if sickbeard.HOME_LAYOUT == 'poster': + <div class="loading-spinner"></div> + % endif % endif % if sickbeard.HOME_LAYOUT == 'poster': -<div id="${('container', 'container-anime')[curListType == 'Anime' and sickbeard.HOME_LAYOUT == 'poster']}" class="clearfix"> +<div id="${('container', 'container-anime')[curListType == 'Anime' and sickbeard.HOME_LAYOUT == 'poster']}" class="show-grid clearfix"> <div class="posterview"> % for curLoadingShow in sickbeard.showQueueScheduler.action.loadingShowList: % if curLoadingShow.show is None: @@ -116,23 +133,22 @@ if not cur_total: cur_total = 0 - if cur_total != 0: - download_stat = str(cur_downloaded) - download_stat_tip = "Downloaded: " + str(cur_downloaded) - if cur_snatched > 0: - download_stat = download_stat - download_stat_tip = download_stat_tip + "
" + "Snatched: " + str(cur_snatched) + download_stat = str(cur_downloaded) + download_stat_tip = "Downloaded: " + str(cur_downloaded) - download_stat = download_stat + " / " + str(cur_total) - download_stat_tip = download_stat_tip + "
" + "Total: " + str(cur_total) - else: - download_stat = '?' - download_stat_tip = "no data" + if cur_snatched: + download_stat = download_stat + "+" + str(cur_snatched) + download_stat_tip = download_stat_tip + "
" + "Snatched: " + str(cur_snatched) + + download_stat = download_stat + " / " + str(cur_total) + download_stat_tip = download_stat_tip + "
" + "Total: " + str(cur_total) nom = cur_downloaded - den = cur_total - if den == 0: + if cur_total: + den = cur_total + else: den = 1 + download_stat_tip = "Unaired" progressbar_percent = nom * 100 / den @@ -183,29 +199,31 @@ % endif </div> - <table width="100%" cellspacing="1" border="0" cellpadding="0"> - <tr> - <td class="show-table"> - <span class="show-dlstats" title="${download_stat_tip}">${download_stat}</span> - </td> - - <td class="show-table"> - % if sickbeard.HOME_LAYOUT != 'simple': - % if curShow.network: - <span title="${curShow.network}"><img class="show-network-image" src="${srRoot}/showPoster/?show=${curShow.indexerid}&which=network" alt="${curShow.network}" title="${curShow.network}" /></span> + <div class="show-details"> + <table class="show-details" width="100%" cellspacing="1" border="0" cellpadding="0"> + <tr> + <td class="show-table"> + <span class="show-dlstats" title="${download_stat_tip}">${download_stat}</span> + </td> + + <td class="show-table"> + % if sickbeard.HOME_LAYOUT != 'simple': + % if curShow.network: + <span title="${curShow.network}"><img class="show-network-image" src="${srRoot}/showPoster/?show=${curShow.indexerid}&which=network" alt="${curShow.network}" title="${curShow.network}" /></span> + % else: + <span title="No Network"><img class="show-network-image" src="${srRoot}/images/network/nonetwork.png" alt="No Network" title="No Network" /></span> + % endif % else: - <span title="No Network"><img class="show-network-image" src="${srRoot}/images/network/nonetwork.png" alt="No Network" title="No Network" /></span> + <span title="${curShow.network}">${curShow.network}</span> % endif - % else: - <span title="${curShow.network}">${curShow.network}</span> - % endif - </td> + </td> - <td class="show-table"> - ${renderQualityPill(curShow.quality, showTitle=True, overrideClass="show-quality")} - </td> - </tr> - </table> + <td class="show-table"> + ${renderQualityPill(curShow.quality, showTitle=True, overrideClass="show-quality")} + </td> + </tr> + </table> + </div> </div> @@ -225,7 +243,7 @@ <th>Network</th> <th>Quality</th> <th>Downloads</th> - ## <th>Size</th> + <th>Size</th> <th>Active</th> <th>Status</th> </tr> @@ -233,13 +251,13 @@ <tfoot class="hidden-print"> <tr> - <th rowspan="1" colspan="1" align="center"><a href="${srRoot}/home/addShows/">Add ${('Show', 'Anime')[curListType == 'Anime']}</a></th> + <th rowspan="1" colspan="1" align="center"><a href="${srRoot}/addShows/">Add ${('Show', 'Anime')[curListType == 'Anime']}</a></th> + <th> </th> <th> </th> <th> </th> <th> </th> <th> </th> <th> </th> - ## <th> </th> // This is needed for size <th> </th> <th> </th> </tr> @@ -282,6 +300,7 @@ cur_snatched = 0 cur_downloaded = 0 cur_total = 0 + show_size = 0 download_stat_tip = '' if curShow.indexerid in show_stat: @@ -300,8 +319,11 @@ if not cur_total: cur_total = 0 + show_size = show_stat[curShow.indexerid]['show_size'] + download_stat = str(cur_downloaded) download_stat_tip = "Downloaded: " + str(cur_downloaded) + if cur_snatched: download_stat = download_stat + "+" + str(cur_snatched) download_stat_tip = download_stat_tip + "
" + "Snatched: " + str(cur_snatched) @@ -391,8 +413,7 @@ <span class="visible-print-inline">${download_stat}</span> </td> - ## <% show_size = sickbeard.helpers.get_size(curShow._location) %> - ## <td align="center" data-show-size="${show_size}">${pretty_file_size(show_size)}</td> + <td align="center" data-show-size="${show_size}">${pretty_file_size(show_size)}</td> <td align="center"> <% paused = int(curShow.paused) == 0 and curShow.status == 'Continuing' %> diff --git a/gui/slick/views/home_newShow.mako b/gui/slick/views/home_newShow.mako deleted file mode 100644 index 6905df264290f57e0007e556907b0bdcca2105b7..0000000000000000000000000000000000000000 --- a/gui/slick/views/home_newShow.mako +++ /dev/null @@ -1,106 +0,0 @@ -<%inherit file="/layouts/main.mako"/> -<%! - import sickbeard - from sickbeard.helpers import anon_url -%> -<%block name="scripts"> -<script type="text/javascript" src="${srRoot}/js/qualityChooser.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/newShow.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/addShowOptions.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/rootDirs.js?${sbPID}"></script> -<script type="text/javascript" src="${srRoot}/js/blackwhite.js?${sbPID}"></script> -</%block> -<%block name="content"> -% if not header is UNDEFINED: - <h1 class="header">${header}</h1> -% else: - <h1 class="title">${title}</h1> -% endif - -<div id="newShowPortal"> - <div id="config-components"> - <ul> - <li><a href="#core-component-group1">Add New Show</a></li> - </ul> - - <div id="core-component-group1" class="tab-pane active component-group"> - - <div id="displayText">aoeu</div> - <br> - - <form id="addShowForm" method="post" action="${srRoot}/home/addShows/addNewShow" accept-charset="utf-8"> - - <fieldset class="sectionwrap"> - <legend class="legendStep">Find a show on theTVDB</legend> - - <div class="stepDiv"> - <input type="hidden" id="indexer_timeout" value="${sickbeard.INDEXER_TIMEOUT}" /> - - % if use_provided_info: - Show retrieved from existing metadata: <a href="${anon_url(sickbeard.indexerApi(provided_indexer).config['show_url'], provided_indexer_id)}">${provided_indexer_name}</a> - <input type="hidden" id="indexerLang" name="indexerLang" value="en" /> - <input type="hidden" id="whichSeries" name="whichSeries" value="${provided_indexer_id}" /> - <input type="hidden" id="providedIndexer" name="providedIndexer" value="${provided_indexer}" /> - <input type="hidden" id="providedName" value="${provided_indexer_name}" /> - % else: - <input type="text" id="nameToSearch" value="${default_show_name}" class="form-control form-control-inline input-sm input350" /> - - <select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm bfh-languages" data-language="${sickbeard.INDEXER_DEFAULT_LANGUAGE}" data-available="${','.join(sickbeard.indexerApi().config['valid_languages'])}"> - </select><b>*</b> - - <select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm"> - <option value="0" ${('', 'selected="selected"')[provided_indexer == 0]}>All Indexers</option> - % for indexer in indexers: - <option value="${indexer}" ${('', 'selected="selected"')[provided_indexer == indexer]}> - ${indexers[indexer]} - </option> - % endfor - </select> - - <input class="btn btn-inline" type="button" id="searchName" value="Search" /> - - <br><br> - <b>*</b> This will only affect the language of the retrieved metadata file contents and episode filenames.<br> - This <b>DOES NOT</b> allow SickRage to download non-english TV episodes!<br><br> - <div id="searchResults" style="height: 100%;"><br></div> - % endif - - </div> - </fieldset> - - <fieldset class="sectionwrap"> - <legend class="legendStep">Pick the parent folder</legend> - - <div class="stepDiv"> - % if provided_show_dir: - Pre-chosen Destination Folder: <b>${provided_show_dir}</b> <br> - <input type="hidden" id="fullShowPath" name="fullShowPath" value="${provided_show_dir}" /><br> - % else: - <%include file="/inc_rootDirs.mako"/> - % endif - </div> - </fieldset> - - <fieldset class="sectionwrap"> - <legend class="legendStep">Customize options</legend> - <div class="stepDiv"> - <%include file="/inc_addShowOptions.mako"/> - </div> - </fieldset> - - % for curNextDir in other_shows: - <input type="hidden" name="other_shows" value="${curNextDir}" /> - % endfor - <input type="hidden" name="skipShow" id="skipShow" value="" /> - </form> - -<br> - -<div style="width: 100%; text-align: center;"> -<input class="btn" type="button" id="addShowButton" value="Add Show" disabled="disabled" /> -% if provided_show_dir: -<input class="btn" type="button" id="skipShowButton" value="Skip Show" /> -% endif -</div> -</div></div></div></div> -</%block> diff --git a/gui/slick/views/home_popularShows.mako b/gui/slick/views/home_popularShows.mako deleted file mode 100644 index 0da617745e709194ec03fd230d1ad13f01a7bfc6..0000000000000000000000000000000000000000 --- a/gui/slick/views/home_popularShows.mako +++ /dev/null @@ -1,45 +0,0 @@ -<%inherit file="/layouts/main.mako"/> -<%! - from sickbeard.helpers import anon_url - import sickbeard -%> -<%block name="content"> -<h2>Popular Shows</h2> -<br> - -<% imdb_tt = [show.imdbid for show in sickbeard.showList if show.imdbid] %> -% if not popular_shows: - <h3>Fetching of IMDB Data failed. Are you online?</h3> - - <strong>Exception:</strong> - <p>${imdb_exception}</p> - -% else: - % for cur_result in popular_shows: - <div class="popularShow"> - <div class="left"> - <img class="coverImage" src="${srRoot}/cache/${cur_result['image_path']}" /> - </div> - <div class="right"> - <h3>${cur_result['name']}</h3> - - % if 'rating' in cur_result and cur_result['rating']: - <span class="rating">${cur_result['rating']}/10 (${cur_result['votes']})</span> - % endif - - <p>${cur_result['outline']}<span class="year"> - Released ${cur_result['year']}<span></p> - <span class="imdb_url"><a href="${anon_url(cur_result['imdb_url'])}">View on IMDB</a></span> | - % if cur_result['imdb_tt'] not in imdb_tt: - <span class="imdb_sickrage_search"><a href="${srRoot}/home/addShows/newShow/?search_string=${cur_result['name']}"> - Add Show</a></span> - % else: - <span> Already added </span> - % endif - - </div> - <br style="clear:both" /> - - </div> - % endfor -% endif -</%block> diff --git a/gui/slick/views/home_postprocess.mako b/gui/slick/views/home_postprocess.mako index 8876ebd9c2ed11848b9bd1ff332edab78b8c3755..a66fe4c3aea54ce624e91ed678d83ae92848ead8 100644 --- a/gui/slick/views/home_postprocess.mako +++ b/gui/slick/views/home_postprocess.mako @@ -19,7 +19,7 @@ <b>Enter the folder containing the episode:</b> </td> <td> - <input type="text" name="dir" id="episodeDir" class="form-control form-control-inline input-sm input350" /> + <input type="text" name="dir" id="episodeDir" class="form-control form-control-inline input-sm input350" autocapitalize="off" /> </td> </tr> <tr> diff --git a/gui/slick/views/inc_blackwhitelist.mako b/gui/slick/views/inc_blackwhitelist.mako index 37cc9fb65d8669432e430faccc6c1b555ee52844..86793b5db79b2056537388f4de28605ea82f5208 100644 --- a/gui/slick/views/inc_blackwhitelist.mako +++ b/gui/slick/views/inc_blackwhitelist.mako @@ -50,7 +50,7 @@ </div> <br style="clear:both" /> <div class="blackwhitelist manual"> - <input type="text" id="addToPoolText" class="form-control form-control-inline input-sm input250" /> + <input type="text" id="addToPoolText" class="form-control form-control-inline input-sm input250" autocapitalize="off" /> <input class="btn btn-inline" type="button" value="Add to Whitelist" id="addToWhite"> <input class="btn btn-inline" type="button" value="Add to Blacklist" id="addToBlack"> </div> diff --git a/gui/slick/views/inc_rootDirs.mako b/gui/slick/views/inc_rootDirs.mako index ecdec42255be6864b5c5397236fde1cb664ad22a..11493a365b22fb0c585b9f5dc062e4a0f6799371 100644 --- a/gui/slick/views/inc_rootDirs.mako +++ b/gui/slick/views/inc_rootDirs.mako @@ -26,4 +26,4 @@ <input class="btn" type="button" id="deleteRootDir" value="Delete" /> <input class="btn" type="button" id="defaultRootDir" value="Set as Default *" /> </div> -<input type="text" style="display: none" id="rootDirText" /> +<input type="text" style="display: none" id="rootDirText" autocapitalize="off" /> diff --git a/gui/slick/views/layouts/main.mako b/gui/slick/views/layouts/main.mako index 3b850185cad01a60fd8ad8c7d9dacccadb4e611c..9da4508649d58ef1f425dbfef31bd897daf1fe8f 100644 --- a/gui/slick/views/layouts/main.mako +++ b/gui/slick/views/layouts/main.mako @@ -112,7 +112,7 @@ </a> <ul class="dropdown-menu"> <li><a href="${srRoot}/home/"><i class="menu-icon-home"></i> Show List</a></li> - <li><a href="${srRoot}/home/addShows/"><i class="menu-icon-addshow"></i> Add Shows</a></li> + <li><a href="${srRoot}/addShows/"><i class="menu-icon-addshow"></i> Add Shows</a></li> <li><a href="${srRoot}/home/postprocess/"><i class="menu-icon-postprocess"></i> Manual Post-Processing</a></li> % if sickbeard.SHOWS_RECENT: <li role="separator" class="divider"></li> @@ -317,7 +317,7 @@ <script type="text/javascript" src="${srRoot}/js/lib/jquery.selectboxes.min.js?${sbPID}"></script> <script type="text/javascript" src="${srRoot}/js/lib/formwizard.js?${sbPID}"></script><!-- Can't be added to bower --> <script type="text/javascript" src="${srRoot}/js/parsers.js?${sbPID}"></script> - <script type="text/javascript" src="${srRoot}/js/meta.js?${sbPID}"></script> + <script type="text/javascript" src="${srRoot}/js/rootDirs.js?${sbPID}"></script> % if sickbeard.DEVELOPER: <script type="text/javascript" src="${srRoot}/js/core.js?${sbPID}"></script> % else: diff --git a/gui/slick/views/login.mako b/gui/slick/views/login.mako index 57a20f0cc9a353810446bacf5db65fd484b7c9f1..a2388f883940e5cc2761398494e29b0ea918ff77 100644 --- a/gui/slick/views/login.mako +++ b/gui/slick/views/login.mako @@ -3,7 +3,7 @@ <div class="login"> <form action="" method="post"> <h1>SickRage</h1> - <div class="ctrlHolder"><input class="inlay" name="username" type="text" placeholder="Username" autocomplete="off" /></div> + <div class="ctrlHolder"><input class="inlay" name="username" type="text" placeholder="Username" autocomplete="off" autocomplete="no" /></div> <div class="ctrlHolder"><input class="inlay" name="password" type="password" placeholder="Password" autocomplete="off" /></div> <div class="ctrlHolder"> <label class="remember_me" title="for 30 days"><input class="inlay" id="remember_me" name="remember_me" type="checkbox" value="1"checked="checked" /> Remember me</label> diff --git a/gui/slick/views/manage.mako b/gui/slick/views/manage.mako index bcac1e4bd9a5cbad61c83e517c6d20c9e0be3aeb..f2891bf374bb6c424ca4761ea4fe1e78ead6d084 100644 --- a/gui/slick/views/manage.mako +++ b/gui/slick/views/manage.mako @@ -16,7 +16,7 @@ % endif <form name="massUpdateForm" method="post" action="massUpdate"> -<table id="massUpdateTable" class="sickbeardTable tablesorter" cellspacing="1" border="0" cellpadding="0"> +<table id="massUpdateTable" class="tablesorter" cellspacing="1" border="0" cellpadding="0"> <thead> <tr> <th class="col-checkbox">Edit<br><input type="checkbox" class="bulkCheck" id="editCheck" /></th> @@ -29,7 +29,7 @@ <th class="col-legend">Archive first match</th> <th class="col-legend">Paused</th> <th class="col-legend">Subtitle</th> - <th class="col-legend">Default Ep<br>Status</th> + <th class="col-legend">Default Ep Status</th> <th class="col-legend">Status</th> <th width="1%">Update<br><input type="checkbox" class="bulkCheck" id="updateCheck" /></th> <th width="1%">Rescan<br><input type="checkbox" class="bulkCheck" id="refreshCheck" /></th> diff --git a/gui/slick/views/manage_massEdit.mako b/gui/slick/views/manage_massEdit.mako index 0892016a97e75f1e78b853719456eb3d2fefc9cf..a1cbd763d5d9d11c87b066b5a8ff0f3eb052da0a 100644 --- a/gui/slick/views/manage_massEdit.mako +++ b/gui/slick/views/manage_massEdit.mako @@ -75,7 +75,7 @@ <a href="#" class="btn edit_root_dir" class="edit_root_dir" id="edit_root_dir_${cur_index}">Edit</a> <a href="#" class="btn delete_root_dir" class="delete_root_dir" id="delete_root_dir_${cur_index}">Delete</a> <input type="hidden" name="orig_root_dir_${cur_index}" value="${cur_dir}" /> - <input type="text" style="display: none" name="new_root_dir_${cur_index}" id="new_root_dir_${cur_index}" class="new_root_dir" value="${cur_dir}" /> + <input type="text" style="display: none" name="new_root_dir_${cur_index}" id="new_root_dir_${cur_index}" class="new_root_dir" value="${cur_dir}" autocapitalize="off" /> </td> </tr> % endfor diff --git a/gui/slick/views/manage_subtitleMissed.mako b/gui/slick/views/manage_subtitleMissed.mako index d488199aa6787ab8213e2a97c0c81c04ddfa9278..25d8442888f380f71b78b1f8afad353b544da957 100644 --- a/gui/slick/views/manage_subtitleMissed.mako +++ b/gui/slick/views/manage_subtitleMissed.mako @@ -6,56 +6,53 @@ from sickbeard import common %> <%block name="content"> -<div id="content960"> -% if not header is UNDEFINED: - <h1 class="header">${header}</h1> -% else: - <h1 class="title">${title}</h1> -% endif -% if whichSubs: -<% subsLanguage = subtitles.fromietf(whichSubs).name if not whichSubs == 'all' else 'All' %> -% endif -% if not whichSubs or (whichSubs and not ep_counts): + <div id="content960"> + % if not header is UNDEFINED: + <h1 class="header">${header}</h1> + % else: + <h1 class="title">${title}</h1> + % endif + % if whichSubs: + <% subsLanguage = subtitles.name_from_code(whichSubs) if not whichSubs == 'all' else 'All' %> + % endif + % if not whichSubs or (whichSubs and not ep_counts): + % if whichSubs: + <h2>All of your episodes have ${subsLanguage} subtitles.</h2> + <br> + % endif -% if whichSubs: -<h2>All of your episodes have ${subsLanguage} subtitles.</h2> -<br> -% endif + <form action="${srRoot}/manage/subtitleMissed" method="get"> + Manage episodes without <select name="whichSubs" class="form-control form-control-inline input-sm"> + <option value="all">All</option> + % for sub_code in subtitles.wanted_languages(): + <option value="${sub_code}">${subtitles.name_from_code(sub_code)}</option> + % endfor + </select> -<form action="${srRoot}/manage/subtitleMissed" method="get"> -Manage episodes without <select name="whichSubs" class="form-control form-control-inline input-sm"> -<option value="all">All</option> -<% sub_langs = [subtitles.fromietf(x) for x in subtitles.wantedLanguages()] %> -% for sub_lang in sub_langs: -<option value="${sub_lang.opensubtitles}">${sub_lang.name}</option> -% endfor -</select> -subtitles -<input class="btn" type="submit" value="Manage" /> -</form> + <input class="btn" type="submit" value="Manage" /> + </form> -% else: -<input type="hidden" id="selectSubLang" name="selectSubLang" value="${whichSubs}" /> - -<form action="${srRoot}/manage/downloadSubtitleMissed" method="post"> -<h2>Episodes without ${subsLanguage} subtitles.</h2> -<br> -Download missed subtitles for selected episodes <input class="btn btn-inline" type="submit" value="Go" /> -<div> - <button type="button" class="btn btn-xs selectAllShows">Select all</a></button> - <button type="button" class="btn btn-xs unselectAllShows">Clear all</a></button> -</div> -<br> -<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0"> -% for cur_indexer_id in sorted_show_ids: - <tr id="${cur_indexer_id}"> - <th><input type="checkbox" class="allCheck" id="allCheck-${cur_indexer_id}" name="${cur_indexer_id}-all"checked="checked" /></th> - <th colspan="3" style="width: 100%; text-align: left;"><a class="whitelink" href="${srRoot}/home/displayShow?show=${cur_indexer_id}">${show_names[cur_indexer_id]}</a> (${ep_counts[cur_indexer_id]}) <input type="button" class="pull-right get_more_eps btn" id="${cur_indexer_id}" value="Expand" /></th> - </tr> -% endfor -</table> -</form> - -% endif -</div> + % else: + ##Strange that this is used by js but is an input outside of any form? + <input type="hidden" id="selectSubLang" name="selectSubLang" value="${whichSubs}" /> + <form action="${srRoot}/manage/downloadSubtitleMissed" method="post"> + <h2>Episodes without ${subsLanguage} subtitles.</h2> + <br> + Download missed subtitles for selected episodes <input class="btn btn-inline" type="submit" value="Go" /> + <div> + <button type="button" class="btn btn-xs selectAllShows">Select all</a></button> + <button type="button" class="btn btn-xs unselectAllShows">Clear all</a></button> + </div> + <br> + <table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0"> + % for cur_indexer_id in sorted_show_ids: + <tr id="${cur_indexer_id}"> + <th><input type="checkbox" class="allCheck" id="allCheck-${cur_indexer_id}" name="${cur_indexer_id}-all"checked="checked" /></th> + <th colspan="3" style="width: 100%; text-align: left;"><a class="whitelink" href="${srRoot}/home/displayShow?show=${cur_indexer_id}">${show_names[cur_indexer_id]}</a> (${ep_counts[cur_indexer_id]}) <input type="button" class="pull-right get_more_eps btn" id="${cur_indexer_id}" value="Expand" /></th> + </tr> + % endfor + </table> + </form> + % endif + </div> </%block> diff --git a/gui/slick/views/schedule.mako b/gui/slick/views/schedule.mako index ff76a17c3b8ca220b86e46a71de968345f04a844..215d12dc495b1b49fc6c9d26792e940b5c05e4d0 100644 --- a/gui/slick/views/schedule.mako +++ b/gui/slick/views/schedule.mako @@ -288,7 +288,7 @@ <div class="tvshowDiv"> <table width="100%" border="0" cellpadding="0" cellspacing="0"> <tr> - <th ${('class="nobg"', 'rowspan="2"')[banner == layout]} valign="top"> + <th ${('class="nobg"', 'rowspan="2"')[layout == 'poster']} valign="top"> <a href="${srRoot}/home/displayShow?show=${cur_result['showid']}"> <img alt="" class="${('posterThumb', 'bannerThumb')[layout == 'banner']}" src="${srRoot}/showPoster/?show=${cur_result['showid']}&which=${(layout, 'poster_thumb')[layout == 'poster']}" /> </a> @@ -324,10 +324,10 @@ <span class="title">Quality:</span> ${renderQualityPill(cur_result['quality'], showTitle=True)} </div> - </td> - </tr> - <tr> - <td style="vertical-align: top;"> + ##</td> + ##</tr> + ##<tr> + ##<td style="vertical-align: top;"> <div> % if cur_result['description']: <span class="title" style="vertical-align:middle;">Plot:</span> diff --git a/gui/slick/views/trendingShows.mako b/gui/slick/views/trendingShows.mako index 0a8ab311793775dbadf734c5b8602db28402722e..ccd207d0cd06cfd5a6c63440b58d690a3103011d 100644 --- a/gui/slick/views/trendingShows.mako +++ b/gui/slick/views/trendingShows.mako @@ -38,9 +38,9 @@ <p>${int(cur_show['show']['rating']*10)}% <img src="${srRoot}/images/heart.png"></p> <i>${cur_show['show']['votes']} votes</i> <div class="traktShowTitleIcons"> - <a href="${srRoot}/home/addShows/addTraktShow?indexer_id=${cur_show['show']['ids']['tvdb']}&showName=${cur_show['show']['title']}" class="btn btn-xs">Add Show</a> + <a href="${srRoot}/addShows/addShowByID?indexer_id=${cur_show['show']['ids']['tvdb']}&showName=${cur_show['show']['title']}" class="btn btn-xs" data-no-redirect>Add Show</a> % if blacklist: - <a href="${srRoot}/home/addShows/addShowToBlacklist?indexer_id=${cur_show['show']['ids']['tvdb'] or cur_show['show']['ids']['tvrage']}" class="btn btn-xs">Remove Show</a> + <a href="${srRoot}/addShows/addShowToBlacklist?indexer_id=${cur_show['show']['ids']['tvdb'] or cur_show['show']['ids']['tvrage']}" class="btn btn-xs">Remove Show</a> % endif </div> </div> diff --git a/gui/slick/views/viewlogs.mako b/gui/slick/views/viewlogs.mako index 73e7200c149333126a653410ef38d3306ff1ac0e..3c54383446dab0abaf325a97417b5e9eb9322736 100644 --- a/gui/slick/views/viewlogs.mako +++ b/gui/slick/views/viewlogs.mako @@ -37,7 +37,7 @@ Filter log by: <select name="logFilter" id="logFilter" class="form-control form- % endfor </select> Search log by: -<input type="text" name="logSearch" placeholder="clear to reset" id="logSearch" value="${('', logSearch)[bool(logSearch)]}" class="form-control form-control-inline input-sm" /> +<input type="text" name="logSearch" placeholder="clear to reset" id="logSearch" value="${('', logSearch)[bool(logSearch)]}" class="form-control form-control-inline input-sm" autocapitalize="off" /> </div> <br> <div class="align-left"><pre> diff --git a/lib/github/CommitStatus.py b/lib/github/CommitStatus.py index dad911dd912e8baf87a3afc04833625ded8777d0..d6074ea8546e36f11cc8d7059e9577be904e5991 100644 --- a/lib/github/CommitStatus.py +++ b/lib/github/CommitStatus.py @@ -31,7 +31,7 @@ import github.NamedUser class CommitStatus(github.GithubObject.NonCompletableGithubObject): """ - This class represents CommitStatuss as returned for example by http://developer.github.com/v3/todo + This class represents CommitStatuss as returned for example by https://developer.github.com/v3/repos/statuses/ """ @property diff --git a/lib/github/PaginatedList.py b/lib/github/PaginatedList.py index a777654aff9d3036d6d279cd328d715445a28dc4..12e1acc7e15f89a4b9f92dc9136e6822331053c7 100644 --- a/lib/github/PaginatedList.py +++ b/lib/github/PaginatedList.py @@ -106,7 +106,7 @@ class PaginatedList(PaginatedListBase): some_other_repos = user.get_repos().get_page(3) """ - def __init__(self, contentClass, requester, firstUrl, firstParams): + def __init__(self, contentClass, requester, firstUrl, firstParams, headers=None): PaginatedListBase.__init__(self) self.__requester = requester self.__contentClass = contentClass @@ -114,6 +114,7 @@ class PaginatedList(PaginatedListBase): self.__firstParams = firstParams or () self.__nextUrl = firstUrl self.__nextParams = firstParams or {} + self.__headers = headers if self.__requester.per_page != 30: self.__nextParams["per_page"] = self.__requester.per_page self._reversed = False @@ -130,7 +131,8 @@ class PaginatedList(PaginatedListBase): headers, data = self.__requester.requestJsonAndCheck( "GET", self.__firstUrl, - parameters=self.__nextParams + parameters=self.__nextParams, + headers=self.__headers ) links = self.__parseLinkHeader(headers) lastUrl = links.get("last") @@ -155,7 +157,8 @@ class PaginatedList(PaginatedListBase): headers, data = self.__requester.requestJsonAndCheck( "GET", self.__nextUrl, - parameters=self.__nextParams + parameters=self.__nextParams, + headers=self.__headers ) data = data if data else [] @@ -201,7 +204,8 @@ class PaginatedList(PaginatedListBase): headers, data = self.__requester.requestJsonAndCheck( "GET", self.__firstUrl, - parameters=params + parameters=params, + headers=self.__headers ) if 'items' in data: diff --git a/lib/github/Repository.py b/lib/github/Repository.py index 640bb857879cae41f25f8b4f28eba45c7d8e6bae..64c96e3eabac67388688cdd70b1e2775565878ef 100644 --- a/lib/github/Repository.py +++ b/lib/github/Repository.py @@ -11,6 +11,7 @@ # Copyright 2013 Mark Roddy <markroddy@gmail.com> # # Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net> # # Copyright 2013 martinqt <m.ki2@laposte.net> # +# Copyright 2015 Jannis Gebauer <ja.geb@me.com> # # # # This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # # # @@ -67,6 +68,7 @@ import github.StatsCommitActivity import github.StatsCodeFrequency import github.StatsParticipation import github.StatsPunchCard +import github.Stargazer class Repository(github.GithubObject.CompletableGithubObject): @@ -1430,7 +1432,7 @@ class Repository(github.GithubObject.CompletableGithubObject): ) return github.Issue.Issue(self._requester, headers, data, completed=True) - def get_issues(self, milestone=github.GithubObject.NotSet, state=github.GithubObject.NotSet, assignee=github.GithubObject.NotSet, mentioned=github.GithubObject.NotSet, labels=github.GithubObject.NotSet, sort=github.GithubObject.NotSet, direction=github.GithubObject.NotSet, since=github.GithubObject.NotSet): + def get_issues(self, milestone=github.GithubObject.NotSet, state=github.GithubObject.NotSet, assignee=github.GithubObject.NotSet, mentioned=github.GithubObject.NotSet, labels=github.GithubObject.NotSet, sort=github.GithubObject.NotSet, direction=github.GithubObject.NotSet, since=github.GithubObject.NotSet, creator=github.GithubObject.NotSet): """ :calls: `GET /repos/:owner/:repo/issues <http://developer.github.com/v3/issues>`_ :param milestone: :class:`github.Milestone.Milestone` or "none" or "*" @@ -1441,6 +1443,7 @@ class Repository(github.GithubObject.CompletableGithubObject): :param sort: string :param direction: string :param since: datetime.datetime + :param creator: string or :class:`github.NamedUser.NamedUser` :rtype: :class:`github.PaginatedList.PaginatedList` of :class:`github.Issue.Issue` """ assert milestone is github.GithubObject.NotSet or milestone == "*" or milestone == "none" or isinstance(milestone, github.Milestone.Milestone), milestone @@ -1451,6 +1454,7 @@ class Repository(github.GithubObject.CompletableGithubObject): assert sort is github.GithubObject.NotSet or isinstance(sort, (str, unicode)), sort assert direction is github.GithubObject.NotSet or isinstance(direction, (str, unicode)), direction assert since is github.GithubObject.NotSet or isinstance(since, datetime.datetime), since + assert creator is github.GithubObject.NotSet or isinstance(creator, github.NamedUser.NamedUser) or isinstance(creator, (str, unicode)), creator url_parameters = dict() if milestone is not github.GithubObject.NotSet: if isinstance(milestone, str): @@ -1474,6 +1478,11 @@ class Repository(github.GithubObject.CompletableGithubObject): url_parameters["direction"] = direction if since is not github.GithubObject.NotSet: url_parameters["since"] = since.strftime("%Y-%m-%dT%H:%M:%SZ") + if creator is not github.GithubObject.NotSet: + if isinstance(creator, str): + url_parameters["creator"] = creator + else: + url_parameters["creator"] = creator._identity return github.PaginatedList.PaginatedList( github.Issue.Issue, self._requester, @@ -1655,19 +1664,28 @@ class Repository(github.GithubObject.CompletableGithubObject): ) return github.PullRequest.PullRequest(self._requester, headers, data, completed=True) - def get_pulls(self, state=github.GithubObject.NotSet, sort=github.GithubObject.NotSet): + def get_pulls(self, state=github.GithubObject.NotSet, sort=github.GithubObject.NotSet, direction=github.GithubObject.NotSet, base=github.GithubObject.NotSet): """ :calls: `GET /repos/:owner/:repo/pulls <http://developer.github.com/v3/pulls>`_ :param state: string + :param sort: string + :param direction: string + :param base: string :rtype: :class:`github.PaginatedList.PaginatedList` of :class:`github.PullRequest.PullRequest` """ assert state is github.GithubObject.NotSet or isinstance(state, (str, unicode)), state assert sort is github.GithubObject.NotSet or isinstance(sort, (str, unicode)), sort + assert direction is github.GithubObject.NotSet or isinstance(direction, (str, unicode)), direction + assert base is github.GithubObject.NotSet or isinstance(base, (str, unicode)), base url_parameters = dict() if state is not github.GithubObject.NotSet: url_parameters["state"] = state if sort is not github.GithubObject.NotSet: url_parameters["sort"] = sort + if direction is not github.GithubObject.NotSet: + url_parameters["direction"] = direction + if base is not github.GithubObject.NotSet: + url_parameters["base"] = base return github.PaginatedList.PaginatedList( github.PullRequest.PullRequest, self._requester, @@ -1739,6 +1757,19 @@ class Repository(github.GithubObject.CompletableGithubObject): None ) + def get_stargazers_with_dates(self): + """ + :calls: `GET /repos/:owner/:repo/stargazers <http://developer.github.com/v3/activity/starring>`_ + :rtype: :class:`github.PaginatedList.PaginatedList` of :class:`github.Stargazer.Stargazer` + """ + return github.PaginatedList.PaginatedList( + github.Stargazer.Stargazer, + self._requester, + self.url + "/stargazers", + None, + headers={'Accept': 'application/vnd.github.v3.star+json'} + ) + def get_stats_contributors(self): """ :calls: `GET /repos/:owner/:repo/stats/contributors <http://developer.github.com/v3/repos/statistics/#get-contributors-list-with-additions-deletions-and-commit-counts>`_ diff --git a/lib/github/Stargazer.py b/lib/github/Stargazer.py new file mode 100644 index 0000000000000000000000000000000000000000..4f261c64cd8538457c165bced148a495d62bd1a7 --- /dev/null +++ b/lib/github/Stargazer.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# ########################## Copyrights and license ############################ +# # +# Copyright 2012 Christopher Gilbert <christopher.john.gilbert@gmail.com> # +# Copyright 2012 Steve English <steve.english@navetas.com> # +# Copyright 2012 Vincent Jacques <vincent@vincent-jacques.net> # +# Copyright 2012 Zearin <zearin@gonk.net> # +# Copyright 2013 AKFish <akfish@gmail.com> # +# Copyright 2013 Adrian Petrescu <adrian.petrescu@maluuba.com> # +# Copyright 2013 Mark Roddy <markroddy@gmail.com> # +# Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net> # +# Copyright 2013 martinqt <m.ki2@laposte.net> # +# Copyright 2015 Dan Vanderkam <danvdk@gmail.com> # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub 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 Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see <http://www.gnu.org/licenses/>. # +# # +# ############################################################################## + +import github + + +class Stargazer(github.GithubObject.NonCompletableGithubObject): + """ + This class represents Stargazers with the date of starring as returned by + https://developer.github.com/v3/activity/starring/#alternative-response-with-star-creation-timestamps + """ + @property + def starred_at(self): + """ + :type: datetime.datetime + """ + return self._starred_at.value + + @property + def user(self): + """ + :type: :class:`github.NamedUser` + """ + return self._user.value + + def _initAttributes(self): + self._starred_at = github.GithubObject.NotSet + self._user = github.GithubObject.NotSet + self._url = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if 'starred_at' in attributes: + self._starred_at = self._makeDatetimeAttribute(attributes['starred_at']) + if 'user' in attributes: + self._user = self._makeClassAttribute(github.NamedUser.NamedUser, attributes['user']) diff --git a/lib/github/StatsCodeFrequency.py b/lib/github/StatsCodeFrequency.py old mode 100644 new mode 100755 diff --git a/lib/github/StatsCommitActivity.py b/lib/github/StatsCommitActivity.py old mode 100644 new mode 100755 diff --git a/lib/github/StatsContributor.py b/lib/github/StatsContributor.py old mode 100644 new mode 100755 diff --git a/lib/github/StatsParticipation.py b/lib/github/StatsParticipation.py old mode 100644 new mode 100755 diff --git a/lib/github/StatsPunchCard.py b/lib/github/StatsPunchCard.py old mode 100644 new mode 100755 diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 4079965bb78696d3e66592d570f0e4e89ccf99bd..61330544b7c66c5beed6ca74e4b1d109353e7e25 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -55,7 +55,10 @@ from sickbeard.common import SD from sickbeard.common import SKIPPED from sickbeard.common import WANTED from sickbeard.databases import mainDB, cache_db, failed_db + +from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex +from sickrage.show.Show import Show from sickrage.system.Shutdown import Shutdown from configobj import ConfigObj @@ -663,7 +666,7 @@ def initialize(consoleLogging=True): DEFAULT_PAGE = 'home' ACTUAL_LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', 'Logs') - LOG_DIR = os.path.normpath(os.path.join(DATA_DIR, ACTUAL_LOG_DIR)) + LOG_DIR = ek(os.path.normpath, ek(os.path.join, DATA_DIR, ACTUAL_LOG_DIR)) LOG_NR = check_setting_int(CFG, 'General', 'log_nr', 5) # Default to 5 backup file (sickrage.log.x) LOG_SIZE = check_setting_int(CFG, 'General', 'log_size', 1048576) # Default to max 1MB per logfile fileLogging = True @@ -682,7 +685,7 @@ def initialize(consoleLogging=True): gh = Github(login_or_token=GIT_USERNAME, password=GIT_PASSWORD, user_agent="SiCKRAGE").get_organization(GIT_ORG).get_repo(GIT_REPO) except Exception as e: gh = None - logger.log(u'Unable to setup GitHub properly. GitHub will not be available. Error: %s' % ex(e), logger.WARNING) + logger.log(u'Unable to setup GitHub properly. GitHub will not be available. Error: %s' % str(e), logger.WARNING) # git reset on update GIT_RESET = bool(check_setting_int(CFG, 'General', 'git_reset', 1)) @@ -711,8 +714,8 @@ def initialize(consoleLogging=True): ACTUAL_CACHE_DIR = 'cache' # unless they specify, put the cache dir inside the data dir - if not os.path.isabs(ACTUAL_CACHE_DIR): - CACHE_DIR = os.path.join(DATA_DIR, ACTUAL_CACHE_DIR) + if not ek(os.path.isabs, ACTUAL_CACHE_DIR): + CACHE_DIR = ek(os.path.join, DATA_DIR, ACTUAL_CACHE_DIR) else: CACHE_DIR = ACTUAL_CACHE_DIR @@ -722,36 +725,36 @@ def initialize(consoleLogging=True): # Check if we need to perform a restore of the cache folder try: - restoreDir = os.path.join(DATA_DIR, 'restore') - if os.path.exists(restoreDir) and os.path.exists(os.path.join(restoreDir, 'cache')): + restoreDir = ek(os.path.join, DATA_DIR, 'restore') + if ek(os.path.exists, restoreDir) and ek(os.path.exists, ek(os.path.join, restoreDir, 'cache')): def restoreCache(srcDir, dstDir): def path_leaf(path): - head, tail = os.path.split(path) - return tail or os.path.basename(head) + head, tail = ek(os.path.split, path) + return tail or ek(os.path.basename, head) try: - if os.path.isdir(dstDir): + if ek(os.path.isdir, dstDir): bakFilename = '{0}-{1}'.format(path_leaf(dstDir), datetime.datetime.strftime(datetime.datetime.now(), '%Y%m%d_%H%M%S')) - shutil.move(dstDir, os.path.join(os.path.dirname(dstDir), bakFilename)) + shutil.move(dstDir, ek(os.path.join, ek(os.path.dirname, dstDir), bakFilename)) shutil.move(srcDir, dstDir) logger.log(u"Restore: restoring cache successful", logger.INFO) except Exception as e: logger.log(u"Restore: restoring cache failed: {0}".format(str(e)), logger.ERROR) - restoreCache(os.path.join(restoreDir, 'cache'), CACHE_DIR) + restoreCache(ek(os.path.join, restoreDir, 'cache'), CACHE_DIR) except Exception as e: logger.log(u"Restore: restoring cache failed: {0}".format(ex(e)), logger.ERROR) finally: - if os.path.exists(os.path.join(DATA_DIR, 'restore')): + if ek(os.path.exists, ek(os.path.join, DATA_DIR, 'restore')): try: - shutil.rmtree(os.path.join(DATA_DIR, 'restore')) + shutil.rmtree(ek(os.path.join, DATA_DIR, 'restore')) except Exception as e: logger.log(u"Restore: Unable to remove the restore directory: {0}".format(ex(e)), logger.ERROR) for cleanupDir in ['mako', 'sessions', 'indexers']: try: - shutil.rmtree(os.path.join(CACHE_DIR, cleanupDir)) + shutil.rmtree(ek(os.path.join, CACHE_DIR, cleanupDir)) except Exception as e: logger.log(u"Restore: Unable to remove the cache/{0} directory: {1}".format(cleanupDir, ex(e)), logger.WARNING) @@ -1229,7 +1232,7 @@ def initialize(consoleLogging=True): 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) + 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) @@ -1338,7 +1341,7 @@ def initialize(consoleLogging=True): curNzbProvider.getID() + '_enable_backlog', curNzbProvider.supportsBacklog)) - if not os.path.isfile(CONFIG_FILE): + if not ek(os.path.isfile, CONFIG_FILE): logger.log(u"Unable to find '" + CONFIG_FILE + "', all settings will be default!", logger.DEBUG) save_config() @@ -2205,7 +2208,7 @@ def getEpList(epIDs, showid=None): epList = [] for curEp in sqlResults: - curShowObj = helpers.findCertainShow(showList, int(curEp["showid"])) + curShowObj = Show.find(showList, int(curEp["showid"])) curEpObj = curShowObj.getEpisode(int(curEp["season"]), int(curEp["episode"])) epList.append(curEpObj) diff --git a/sickbeard/browser.py b/sickbeard/browser.py index 9288465ba51d605edf1c7d20e6e07dac5bb61590..021adc81c76fa8e063d450141b1f737cae20afb9 100644 --- a/sickbeard/browser.py +++ b/sickbeard/browser.py @@ -79,12 +79,12 @@ def foldersAtPath(path, includeParent=False, includeFiles=False): """ # walk up the tree until we find a valid path - while path and not os.path.isdir(path): - if path == os.path.dirname(path): + while path and not ek(os.path.isdir, path): + if path == ek(os.path.dirname, path): path = '' break else: - path = os.path.dirname(path) + path = ek(os.path.dirname, path) if path == "": if os.name == 'nt': @@ -97,8 +97,8 @@ def foldersAtPath(path, includeParent=False, includeFiles=False): path = '/' # fix up the path and find the parent - path = os.path.abspath(os.path.normpath(path)) - parentPath = os.path.dirname(path) + path = ek(os.path.abspath, ek(os.path.normpath, path)) + parentPath = ek(os.path.dirname, path) # if we're at the root then the next step is the meta-node showing our drive letters if path == parentPath and os.name == 'nt': @@ -111,7 +111,7 @@ def foldersAtPath(path, includeParent=False, includeFiles=False): fileList = getFileList(parentPath, includeFiles) fileList = sorted(fileList, - lambda x, y: cmp(os.path.basename(x['name']).lower(), os.path.basename(y['path']).lower())) + lambda x, y: cmp(ek(os.path.basename, x['name']).lower(), ek(os.path.basename, y['path']).lower())) entries = [{'currentPath': path}] if includeParent and parentPath != path: diff --git a/sickbeard/clients/__init__.py b/sickbeard/clients/__init__.py index ddc11114af7adfac01cef01991ef12ae81616aa5..f1e67523c4484d193c3389757158f6dd91d8fd3f 100644 --- a/sickbeard/clients/__init__.py +++ b/sickbeard/clients/__init__.py @@ -28,82 +28,6 @@ __all__ = [ 'mlnet' ] -# Mapping error status codes to official W3C names -http_error_code = { - # todo: Handle error codes with duplicates (e.g. 451, 499) - 300: 'Multiple Choices', - 301: 'Moved Permanently', - 302: 'Found', - 303: 'See Other', - 304: 'Not Modified', - 305: 'Use Proxy', - 306: 'Switch Proxy', - 307: 'Temporary Redirect', - 308: 'Permanent Redirect', - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Request Entity Too Large', - 414: 'Request-URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Requested Range Not Satisfiable', - 417: 'Expectation Failed', - 418: 'Im a teapot', - 419: 'Authentication Timeout', - 420: 'Enhance Your Calm', - 422: 'Unprocessable Entity', - 423: 'Locked', - 424: 'Failed Dependency', - 426: 'Upgrade Required', - 428: 'Precondition Required', - 429: 'Too Many Requests', - 431: 'Request Header Fields Too Large', - 440: 'Login Timeout', - 444: 'No Response', - 449: 'Retry With', - 450: 'Blocked by Windows Parental Controls', - 451: 'Redirect', - 451: 'Unavailable For Legal Reasons', - 494: 'Request Header Too Large', - 495: 'Cert Error', - 496: 'No Cert', - 497: 'HTTP to HTTPS', - 498: 'Token expired/invalid', - 499: 'Client Closed Request', - 499: 'Token required', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported', - 506: 'Variant Also Negotiates', - 507: 'Insufficient Storage', - 508: 'Loop Detected', - 509: 'Bandwidth Limit Exceeded', - 510: 'Not Extended', - 511: 'Network Authentication Required', - 520: 'Cloudfare - Web server is returning an unknown error', - 521: 'Cloudfare - Web server is down', - 522: 'Cloudfare - Connection timed out', - 523: 'Cloudfare - Origin is unreachable', - 524: 'Cloudfare - A timeout occurred', - 525: 'Cloudfare - SSL handshake failed', - 526: 'Cloudfare - Invalid SSL certificate', - 598: 'Network read timeout error', - 599: 'Network connect timeout error ' -} - default_host = { 'utorrent': 'http://localhost:8000', 'transmission': 'http://localhost:9091', diff --git a/sickbeard/clients/generic.py b/sickbeard/clients/generic.py index 945554403af1025bdf6606f78c9b2f0a8946c00d..2109da630a568984ea4cb3c5c78d92541ee165b3 100644 --- a/sickbeard/clients/generic.py +++ b/sickbeard/clients/generic.py @@ -7,10 +7,10 @@ from base64 import b16encode, b32decode import sickbeard from sickbeard import logger -from sickbeard.clients import http_error_code from bencode import bencode, bdecode import requests from bencode.BTL import BTFailure +from sickrage.helper.common import http_code_description class GenericClient(object): @@ -66,8 +66,10 @@ class GenericClient(object): logger.log(self.name + u': Invalid Username or Password, check your config', logger.ERROR) return False - if self.response.status_code in http_error_code.keys(): - logger.log(self.name + u': ' + http_error_code[self.response.status_code], logger.DEBUG) + code_description = http_code_description(self.response.status_code) + + if code_description is not None: + logger.log(self.name + u': ' + code_description, logger.DEBUG) return False logger.log(self.name + u': Response to ' + method.upper() + ' request is ' + self.response.text, logger.DEBUG) diff --git a/sickbeard/common.py b/sickbeard/common.py index bbc240b237cd36023f2339ab3e76a6bdd09700f6..731aaa84896b266d875e8d8e09217072db5e7e74 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -1,3 +1,4 @@ +# coding=utf-8 # Author: Nic Wolfe <nic@wolfeden.ca> # URL: https://sickrage.github.io/ # Git: https://github.com/SickRage/SickRage.git @@ -17,34 +18,42 @@ # 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.path +""" +Common interface for Quality and Status +""" + +# pylint: disable=line-too-long + + import operator +from os import path import platform +from random import shuffle import re import uuid -from sickbeard.numdict import NumDict - -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 hachoir_parser import createParser # pylint: disable=import-error +from hachoir_metadata import extractMetadata # pylint: disable=import-error +from hachoir_core.log import log # pylint: disable=import-error -from random import shuffle +from sickbeard.numdict import NumDict +from sickrage.helper.encoding import ek SPOOF_USER_AGENT = False # If some provider has an issue with functionality of SR, other than user agents, it's best to come talk to us rather than block. # It is no different than us going to a provider if we have questions or issues. Be a team player here. # This is disabled, was only added for testing, and has no config.ini or web ui setting. To enable, set SPOOF_USER_AGENT = True -user_agents = ['Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36' - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36' - 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0' - 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0' - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A' - 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25' - 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' - 'Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko' - ] +user_agents = [ + 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36' + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36' + 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0' + 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0' + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A' + 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25' + 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' + 'Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko' +] INSTANCE_ID = str(uuid.uuid1()) USER_AGENT = ('SickRage/(' + platform.system() + '; ' + platform.release() + '; ' + INSTANCE_ID + ')') @@ -53,10 +62,11 @@ if SPOOF_USER_AGENT: shuffle(user_agents) USER_AGENT = user_agents[0] -cpu_presets = {'HIGH': 5, - 'NORMAL': 2, - 'LOW': 1 - } +cpu_presets = { + 'HIGH': 5, + 'NORMAL': 2, + 'LOW': 1 +} # Other constants MULTI_EP_RESULT = -1 @@ -69,12 +79,13 @@ NOTIFY_SUBTITLE_DOWNLOAD = 3 NOTIFY_GIT_UPDATE = 4 NOTIFY_GIT_UPDATE_TEXT = 5 -notifyStrings = {} -notifyStrings[NOTIFY_SNATCH] = "Started Download" -notifyStrings[NOTIFY_DOWNLOAD] = "Download Finished" -notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD] = "Subtitle Download Finished" -notifyStrings[NOTIFY_GIT_UPDATE] = "SickRage Updated" -notifyStrings[NOTIFY_GIT_UPDATE_TEXT] = "SickRage Updated To Commit#: " +notifyStrings = NumDict({ + NOTIFY_SNATCH: "Started Download", + NOTIFY_DOWNLOAD: "Download Finished", + NOTIFY_SUBTITLE_DOWNLOAD: "Subtitle Download Finished", + NOTIFY_GIT_UPDATE: "SickRage Updated", + NOTIFY_GIT_UPDATE_TEXT: "SickRage Updated To Commit#: " +}) # Episode statuses UNKNOWN = -1 # should never happen @@ -88,7 +99,7 @@ IGNORED = 7 # episodes that you don't want included in your download stats SNATCHED_PROPER = 9 # qualified with quality SUBTITLED = 10 # qualified with quality FAILED = 11 # episode downloaded or snatched we don't want -SNATCHED_BEST = 12 # episode redownloaded using best quality +SNATCHED_BEST = 12 # episode re-downloaded using best quality NAMING_REPEAT = 1 NAMING_EXTEND = 2 @@ -97,16 +108,20 @@ NAMING_LIMITED_EXTEND = 8 NAMING_SEPARATED_REPEAT = 16 NAMING_LIMITED_EXTEND_E_PREFIXED = 32 -multiEpStrings = {} -multiEpStrings[NAMING_REPEAT] = "Repeat" -multiEpStrings[NAMING_SEPARATED_REPEAT] = "Repeat (Separated)" -multiEpStrings[NAMING_DUPLICATE] = "Duplicate" -multiEpStrings[NAMING_EXTEND] = "Extend" -multiEpStrings[NAMING_LIMITED_EXTEND] = "Extend (Limited)" -multiEpStrings[NAMING_LIMITED_EXTEND_E_PREFIXED] = "Extend (Limited, E-prefixed)" +MULTI_EP_STRINGS = NumDict({ + NAMING_REPEAT: "Repeat", + NAMING_SEPARATED_REPEAT: "Repeat (Separated)", + NAMING_DUPLICATE: "Duplicate", + NAMING_EXTEND: "Extend", + NAMING_LIMITED_EXTEND: "Extend (Limited)", + NAMING_LIMITED_EXTEND_E_PREFIXED: "Extend (Limited, E-prefixed)" +}) + -# pylint: disable=W0232 class Quality(object): + """ + Determine quality and set status codes + """ NONE = 0 # 0 SDTV = 1 # 1 SDDVD = 1 << 1 # 2 @@ -124,55 +139,66 @@ class Quality(object): # put these bits at the other end of the spectrum, far enough out that they shouldn't interfere UNKNOWN = 1 << 15 # 32768 - qualityStrings = {NONE: "N/A", - UNKNOWN: "Unknown", - SDTV: "SDTV", - SDDVD: "SD DVD", - HDTV: "720p HDTV", - RAWHDTV: "RawHD", - FULLHDTV: "1080p HDTV", - HDWEBDL: "720p WEB-DL", - FULLHDWEBDL: "1080p WEB-DL", - HDBLURAY: "720p BluRay", - FULLHDBLURAY: "1080p BluRay"} - - sceneQualityStrings = {NONE: "N/A", - UNKNOWN: "Unknown", - SDTV: "HDTV", - SDDVD: "", - HDTV: "720p HDTV", - RAWHDTV: "1080i HDTV", - FULLHDTV: "1080p HDTV", - HDWEBDL: "720p WEB-DL", - FULLHDWEBDL: "1080p WEB-DL", - HDBLURAY: "720p BluRay", - FULLHDBLURAY: "1080p BluRay"} - - combinedQualityStrings = {ANYHDTV: "HDTV", - ANYWEBDL: "WEB-DL", - ANYBLURAY: "BluRay"} - - cssClassStrings = {NONE: "N/A", - UNKNOWN: "Unknown", - SDTV: "SDTV", - SDDVD: "SDDVD", - HDTV: "HD720p", - RAWHDTV: "RawHD", - FULLHDTV: "HD1080p", - HDWEBDL: "HD720p", - FULLHDWEBDL: "HD1080p", - HDBLURAY: "HD720p", - FULLHDBLURAY: "HD1080p", - ANYHDTV: "any-hd", - ANYWEBDL: "any-hd", - ANYBLURAY: "any-hd"} - - statusPrefixes = {DOWNLOADED: "Downloaded", - SNATCHED: "Snatched", - SNATCHED_PROPER: "Snatched (Proper)", - FAILED: "Failed", - SNATCHED_BEST: "Snatched (Best)", - ARCHIVED: "Archived"} + qualityStrings = NumDict({ + NONE: "N/A", + UNKNOWN: "Unknown", + SDTV: "SDTV", + SDDVD: "SD DVD", + HDTV: "720p HDTV", + RAWHDTV: "RawHD", + FULLHDTV: "1080p HDTV", + HDWEBDL: "720p WEB-DL", + FULLHDWEBDL: "1080p WEB-DL", + HDBLURAY: "720p BluRay", + FULLHDBLURAY: "1080p BluRay" + }) + + sceneQualityStrings = NumDict({ + NONE: "N/A", + UNKNOWN: "Unknown", + SDTV: "HDTV", + SDDVD: "", + HDTV: "720p HDTV", + RAWHDTV: "1080i HDTV", + FULLHDTV: "1080p HDTV", + HDWEBDL: "720p WEB-DL", + FULLHDWEBDL: "1080p WEB-DL", + HDBLURAY: "720p BluRay", + FULLHDBLURAY: "1080p BluRay" + }) + + combinedQualityStrings = NumDict({ + ANYHDTV: "HDTV", + ANYWEBDL: "WEB-DL", + ANYBLURAY: "BluRay" + }) + + cssClassStrings = NumDict({ + NONE: "N/A", + UNKNOWN: "Unknown", + SDTV: "SDTV", + SDDVD: "SDDVD", + HDTV: "HD720p", + RAWHDTV: "RawHD", + FULLHDTV: "HD1080p", + HDWEBDL: "HD720p", + FULLHDWEBDL: "HD1080p", + HDBLURAY: "HD720p", + FULLHDBLURAY: "HD1080p", + ANYHDTV: "any-hd", + ANYWEBDL: "any-hd", + ANYBLURAY: "any-hd" + }) + + statusPrefixes = NumDict({ + DOWNLOADED: "Downloaded", + SNATCHED: "Snatched", + SNATCHED_PROPER: "Snatched (Proper)", + FAILED: "Failed", + SNATCHED_BEST: "Snatched (Best)", + ARCHIVED: "Archived" + }) + @staticmethod def _getStatusStrings(status): """ @@ -181,33 +207,33 @@ class Quality(object): :param status: Status prefix to resolve :return: Human readable status value """ - toReturn = {} - for q in Quality.qualityStrings.keys(): - toReturn[Quality.compositeStatus(status, q)] = Quality.statusPrefixes[status] + " (" + \ - Quality.qualityStrings[q] + ")" - return toReturn + to_return = {} + for quality in Quality.qualityStrings: + to_return[Quality.compositeStatus(status, quality)] = Quality.statusPrefixes[status] + " (" + \ + Quality.qualityStrings[quality] + ")" + return to_return @staticmethod - def combineQualities(anyQualities, bestQualities): - anyQuality = 0 - bestQuality = 0 - if anyQualities: - anyQuality = reduce(operator.or_, anyQualities) - if bestQualities: - bestQuality = reduce(operator.or_, bestQualities) - return anyQuality | (bestQuality << 16) + def combineQualities(any_qualities, best_qualities): + any_quality = 0 + best_quality = 0 + if any_qualities: + any_quality = reduce(operator.or_, any_qualities) + if best_qualities: + best_quality = reduce(operator.or_, best_qualities) + return any_quality | (best_quality << 16) @staticmethod def splitQuality(quality): - anyQualities = [] - bestQualities = [] - for curQual in Quality.qualityStrings.keys(): - if curQual & quality: - anyQualities.append(curQual) - if curQual << 16 & quality: - bestQualities.append(curQual) + any_qualities = [] + best_qualities = [] + for cur_qual in Quality.qualityStrings: + if cur_qual & quality: + any_qualities.append(cur_qual) + if cur_qual << 16 & quality: + best_qualities.append(cur_qual) - return (sorted(anyQualities), sorted(bestQualities)) + return sorted(any_qualities), sorted(best_qualities) @staticmethod def nameQuality(name, anime=False): @@ -215,6 +241,7 @@ class Quality(object): Return The quality from an episode File renamed by SickRage If no quality is achieved it will try sceneQuality regex + :param name: to parse :param anime: Boolean to indicate if the show we're resolving is Anime :return: Quality prefix """ @@ -230,9 +257,8 @@ class Quality(object): return Quality.UNKNOWN - @staticmethod - def sceneQuality(name, anime=False): + def sceneQuality(name, anime=False): # pylint: disable=too-many-branches """ Return The quality from the scene episode File @@ -241,60 +267,55 @@ class Quality(object): :return: Quality prefix """ - # pylint: disable=R0912 - ret = Quality.UNKNOWN if not name: return ret - name = os.path.basename(name) + name = ek(path.basename, name) - checkName = lambda list, func: func([re.search(x, name, re.I) for x in list]) + check_name = lambda regex_list, func: func([re.search(regex, name, re.I) for regex in regex_list]) if anime: - dvdOptions = checkName([r"dvd", r"dvdrip"], any) - blueRayOptions = checkName([r"BD", r"blue?-?ray"], any) - sdOptions = checkName([r"360p", r"480p", r"848x480", r"XviD"], any) - hdOptions = checkName([r"720p", r"1280x720", r"960x720"], any) - fullHD = checkName([r"1080p", r"1920x1080"], any) + dvd_options = check_name([r"dvd", r"dvdrip"], any) + bluray_options = check_name([r"BD", r"blue?-?ray"], any) + sd_options = check_name([r"360p", r"480p", r"848x480", r"XviD"], any) + hd_options = check_name([r"720p", r"1280x720", r"960x720"], any) + full_hd = check_name([r"1080p", r"1920x1080"], any) - if sdOptions and not blueRayOptions and not dvdOptions: + if sd_options and not bluray_options and not dvd_options: ret = Quality.SDTV - elif dvdOptions: + elif dvd_options: ret = Quality.SDDVD - elif hdOptions and not blueRayOptions and not fullHD: + elif hd_options and not bluray_options and not full_hd: ret = Quality.HDTV - elif fullHD and not blueRayOptions and not hdOptions: + elif full_hd and not bluray_options and not hd_options: ret = Quality.FULLHDTV - elif hdOptions and not blueRayOptions and not fullHD: + elif hd_options and not bluray_options and not full_hd: ret = Quality.HDWEBDL - elif blueRayOptions and hdOptions and not fullHD: + elif bluray_options and hd_options and not full_hd: ret = Quality.HDBLURAY - elif blueRayOptions and fullHD and not hdOptions: + elif bluray_options and full_hd and not hd_options: ret = Quality.FULLHDBLURAY return ret - if (checkName([r"480p|web.?dl|web(rip|mux|hd)|[sph]d.?tv|dsr|tv(rip|mux)|satrip", r"xvid|divx|[xh].?26[45]"], all) - and not checkName([r"(720|1080)[pi]"], all) and not checkName([r"hr.ws.pdtv.[xh].?26[45]"], any)): + if check_name([r"480p|web.?dl|web(rip|mux|hd)|[sph]d.?tv|dsr|tv(rip|mux)|satrip", r"xvid|divx|[xh].?26[45]"], all) and not check_name([r"(720|1080)[pi]"], all) and not check_name([r"hr.ws.pdtv.[xh].?26[45]"], any): ret = Quality.SDTV - elif (checkName([r"dvd(rip|mux)|b[rd](rip|mux)|blue?-?ray", r"xvid|divx|[xh].?26[45]"], all) - and not checkName([r"(720|1080)[pi]"], all) and not checkName([r"hr.ws.pdtv.[xh].?26[45]"], any)): + elif check_name([r"dvd(rip|mux)|b[rd](rip|mux)|blue?-?ray", r"xvid|divx|[xh].?26[45]"], all) and not check_name([r"(720|1080)[pi]"], all) and not check_name([r"hr.ws.pdtv.[xh].?26[45]"], any): ret = Quality.SDDVD - elif (checkName([r"720p", r"hd.?tv", r"[xh].?26[45]"], all) or checkName([r"hr.ws.pdtv.[xh].?26[45]"], any) - and not checkName([r"1080[pi]"], all)): + elif check_name([r"720p", r"hd.?tv", r"[xh].?26[45]"], all) or check_name([r"hr.ws.pdtv.[xh].?26[45]"], any) and not check_name([r"1080[pi]"], all): ret = Quality.HDTV - elif checkName([r"720p|1080i", r"hd.?tv", r"mpeg-?2"], all) or checkName([r"1080[pi].hdtv", r"h.?26[45]"], all): + elif check_name([r"720p|1080i", r"hd.?tv", r"mpeg-?2"], all) or check_name([r"1080[pi].hdtv", r"h.?26[45]"], all): ret = Quality.RAWHDTV - elif checkName([r"1080p", r"hd.?tv", r"[xh].?26[45]"], all): + elif check_name([r"1080p", r"hd.?tv", r"[xh].?26[45]"], all): ret = Quality.FULLHDTV - elif checkName([r"720p", r"web.?dl|web(rip|mux|hd)"], all) or checkName([r"720p", r"itunes", r"[xh].?26[45]"], all): + elif check_name([r"720p", r"web.?dl|web(rip|mux|hd)"], all) or check_name([r"720p", r"itunes", r"[xh].?26[45]"], all): ret = Quality.HDWEBDL - elif checkName([r"1080p", r"web.?dl|web(rip|mux|hd)"], all) or checkName([r"1080p", r"itunes", r"[xh].?26[45]"], all): + elif check_name([r"1080p", r"web.?dl|web(rip|mux|hd)"], all) or check_name([r"1080p", r"itunes", r"[xh].?26[45]"], all): ret = Quality.FULLHDWEBDL - elif checkName([r"720p", r"blue?-?ray|hddvd|b[rd](rip|mux)", r"[xh].?26[45]"], all): + elif check_name([r"720p", r"blue?-?ray|hddvd|b[rd](rip|mux)", r"[xh].?26[45]"], all): ret = Quality.HDBLURAY - elif checkName([r"1080p", r"blue?-?ray|hddvd|b[rd](rip|mux)", r"[xh].?26[45]"], all): + elif check_name([r"1080p", r"blue?-?ray|hddvd|b[rd](rip|mux)", r"[xh].?26[45]"], all): ret = Quality.FULLHDBLURAY return ret @@ -317,7 +338,7 @@ class Quality(object): return Quality.UNKNOWN @staticmethod - def qualityFromFileMeta(filename): + def qualityFromFileMeta(filename): # pylint: disable=too-many-branches """ Get quality file file metadata @@ -325,17 +346,11 @@ class Quality(object): :return: Quality prefix """ - # pylint: disable=R0912 - - from hachoir_parser import createParser - from hachoir_metadata import extractMetadata - from hachoir_core.log import log log.use_print = False try: parser = createParser(filename) - # pylint: disable=W0703 - except Exception: + except Exception: # pylint: disable=broad-except parser = None if not parser: @@ -343,15 +358,12 @@ class Quality(object): try: metadata = extractMetadata(parser) - # pylint: disable=W0703 - except Exception: + except Exception: # pylint: disable=broad-except metadata = None try: - # pylint: disable=W0212 - parser.stream._input.close() - # pylint: disable=W0703 - except Exception: + parser.stream._input.close() # pylint: disable=protected-access + except Exception: # pylint: disable=broad-except pass if not metadata: @@ -370,14 +382,14 @@ class Quality(object): if not height: return Quality.UNKNOWN - base_filename = os.path.basename(filename) + base_filename = ek(path.basename, filename) bluray = re.search(r"blue?-?ray|hddvd|b[rd](rip|mux)", base_filename, re.I) is not None webdl = re.search(r"web.?dl|web(rip|mux|hd)", base_filename, re.I) is not None ret = Quality.UNKNOWN if height > 1000: ret = ((Quality.FULLHDTV, Quality.FULLHDBLURAY)[bluray], Quality.FULLHDWEBDL)[webdl] - elif height > 680 and height < 800: + elif 680 < height < 800: ret = ((Quality.HDTV, Quality.HDBLURAY)[bluray], Quality.HDWEBDL)[webdl] elif height < 680: ret = (Quality.SDTV, Quality.SDDVD)[re.search(r'dvd|b[rd]rip|blue?-?ray', base_filename, re.I) is not None] @@ -394,18 +406,23 @@ class Quality(object): @staticmethod def splitCompositeStatus(status): - """Returns a tuple containing (status, quality)""" + """ + Split a composite status code into a status and quality. + + :param status: to split + :returns: a tuple containing (status, quality) + """ if status == UNKNOWN: - return (UNKNOWN, Quality.UNKNOWN) + return UNKNOWN, Quality.UNKNOWN for q in sorted(Quality.qualityStrings.keys(), reverse=True): if status > q * 100: - return (status - q * 100, q) + return status - q * 100, q - return (status, Quality.NONE) + return status, Quality.NONE @staticmethod - def sceneQualityFromName(name, quality): # pylint: disable=R0912 + def sceneQualityFromName(name, quality): # pylint: disable=too-many-branches """ Get scene naming parameters from filename and quality @@ -413,17 +430,17 @@ class Quality(object): :param quality: int of quality to make sure we get the right rip type :return: encoder type for scene quality naming """ - codecList = ['xvid', 'divx'] - x264List = ['x264', 'x 264', 'x.264'] - h264List = ['h264', 'h 264', 'h.264', 'avc'] - x265List = ['x265', 'x 265', 'x.265'] - h265List = ['h265', 'h 265', 'h.265', 'hevc'] - codecList.extend(x264List + h264List + x265List + h265List) + codec_list = ['xvid', 'divx'] + x264_list = ['x264', 'x 264', 'x.264'] + h264_list = ['h264', 'h 264', 'h.264', 'avc'] + x265_list = ['x265', 'x 265', 'x.265'] + h265_list = ['h265', 'h 265', 'h.265', 'hevc'] + codec_list += x264_list + h264_list + x265_list + h265_list found_codecs = {} found_codec = None - for codec in codecList: + for codec in codec_list: if codec in name.lower(): found_codecs[name.lower().rfind(codec)] = codec @@ -441,18 +458,18 @@ class Quality(object): rip_type = "" if found_codec: - if codecList[0] in found_codec: + if codec_list[0] in found_codec: found_codec = 'XviD' - elif codecList[1] in found_codec: + elif codec_list[1] in found_codec: found_codec = 'DivX' - elif found_codec in x264List: - found_codec = x264List[0] - elif found_codec in h264List: - found_codec = h264List[0] - elif found_codec in x265List: - found_codec = x265List[0] - elif found_codec in h265List: - found_codec = h265List[0] + elif found_codec in x264_list: + found_codec = x264_list[0] + elif found_codec in h264_list: + found_codec = h264_list[0] + elif found_codec in x265_list: + found_codec = x265_list[0] + elif found_codec in h265_list: + found_codec = h265_list[0] if quality == 2: return rip_type + " " + found_codec @@ -485,12 +502,12 @@ class Quality(object): SNATCHED_BEST = None ARCHIVED = None -Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings.keys()] -Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()] -Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()] -Quality.FAILED = [Quality.compositeStatus(FAILED, x) for x in Quality.qualityStrings.keys()] -Quality.SNATCHED_BEST = [Quality.compositeStatus(SNATCHED_BEST, x) for x in Quality.qualityStrings.keys()] -Quality.ARCHIVED = [Quality.compositeStatus(ARCHIVED, x) for x in Quality.qualityStrings.keys()] +Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings] +Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings] +Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings] +Quality.FAILED = [Quality.compositeStatus(FAILED, x) for x in Quality.qualityStrings] +Quality.SNATCHED_BEST = [Quality.compositeStatus(SNATCHED_BEST, x) for x in Quality.qualityStrings] +Quality.ARCHIVED = [Quality.compositeStatus(ARCHIVED, x) for x in Quality.qualityStrings] HD720p = Quality.combineQualities([Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY], []) HD1080p = Quality.combineQualities([Quality.FULLHDTV, Quality.FULLHDWEBDL, Quality.FULLHDBLURAY], []) @@ -503,28 +520,23 @@ ANY = Quality.combineQualities([SD, HD], []) BEST = Quality.combineQualities([Quality.SDTV, Quality.HDTV, Quality.HDWEBDL], [Quality.HDTV]) qualityPresets = (SD, HD, HD720p, HD1080p, ANY) -qualityPresetStrings = {SD: "SD", - HD: "HD", - HD720p: "HD720p", - HD1080p: "HD1080p", - ANY: "Any"} +qualityPresetStrings = NumDict({ + SD: "SD", + HD: "HD", + HD720p: "HD720p", + HD1080p: "HD1080p", + ANY: "Any" +}) class StatusStrings(NumDict): """ Dictionary containing strings for status codes """ - # todo: Deprecate StatusStrings().statusStrings and use StatusStrings() directly - # todo: Deprecate .has_key and switch to 'x in y' # todo: Make views return Qualities too - # todo: qualities = Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.SNATCHED_BEST + Quality.ARCHIVED + Quality.FAILED - @property - def statusStrings(self): # for backwards compatibility - return self.data - def __missing__(self, key): """ If the key is not found try to determine a status from Quality @@ -563,30 +575,30 @@ statusStrings = StatusStrings({ }) -# pylint: disable=R0903 -class Overview(object): +class Overview(object): # pylint: disable=too-few-public-methods UNAIRED = UNAIRED # 1 - SNATCHED = SNATCHED # 2 + SNATCHED = SNATCHED # 2 WANTED = WANTED # 3 - GOOD = DOWNLOADED # 4 + GOOD = DOWNLOADED # 4 SKIPPED = SKIPPED # 5 SNATCHED_PROPER = SNATCHED_PROPER # 9 - SNATCHED_BEST = SNATCHED_BEST # 12 + SNATCHED_BEST = SNATCHED_BEST # 12 # Should suffice! QUAL = 50 - overviewStrings = { + overviewStrings = NumDict({ SKIPPED: "skipped", WANTED: "wanted", QUAL: "qual", GOOD: "good", UNAIRED: "unaired", SNATCHED: "snatched", - # we can give these a different class later, otherwise breaks checkboxes in displayshow for showing different statuses + # we can give these a different class later, otherwise + # breaks checkboxes in displayShow for showing different statuses SNATCHED_BEST: "snatched", SNATCHED_PROPER: "snatched" - } + }) countryList = { 'Australia': 'AU', diff --git a/sickbeard/config.py b/sickbeard/config.py index 670835e10682f47eb762dc66d286d3941e2769b6..920bf2dceba6e1c38b1e82573f8a6cba21b96136 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -28,6 +28,9 @@ from sickbeard import logger from sickbeard import naming from sickbeard import db +from sickrage.helper.common import try_int +from sickrage.helper.encoding import ek + # Address poor support for scgi over unix domain sockets # this is not nicely handled by python currently # http://bugs.python.org/issue23636 @@ -65,9 +68,9 @@ def change_HTTPS_CERT(https_cert): sickbeard.HTTPS_CERT = '' return True - if os.path.normpath(sickbeard.HTTPS_CERT) != os.path.normpath(https_cert): - if helpers.makeDir(os.path.dirname(os.path.abspath(https_cert))): - sickbeard.HTTPS_CERT = os.path.normpath(https_cert) + if ek(os.path.normpath, sickbeard.HTTPS_CERT) != ek(os.path.normpath, https_cert): + if helpers.makeDir(ek(os.path.dirname, ek(os.path.abspath, https_cert))): + sickbeard.HTTPS_CERT = ek(os.path.normpath, https_cert) logger.log(u"Changed https cert path to " + https_cert) else: return False @@ -86,9 +89,9 @@ def change_HTTPS_KEY(https_key): sickbeard.HTTPS_KEY = '' return True - if os.path.normpath(sickbeard.HTTPS_KEY) != os.path.normpath(https_key): - if helpers.makeDir(os.path.dirname(os.path.abspath(https_key))): - sickbeard.HTTPS_KEY = os.path.normpath(https_key) + if ek(os.path.normpath, sickbeard.HTTPS_KEY) != ek(os.path.normpath, https_key): + if helpers.makeDir(ek(os.path.dirname, ek(os.path.abspath, https_key))): + sickbeard.HTTPS_KEY = ek(os.path.normpath, https_key) logger.log(u"Changed https key path to " + https_key) else: return False @@ -105,12 +108,12 @@ def change_LOG_DIR(log_dir, web_log): :return: True on success, False on failure """ log_dir_changed = False - abs_log_dir = os.path.normpath(os.path.join(sickbeard.DATA_DIR, log_dir)) + abs_log_dir = ek(os.path.normpath, ek(os.path.join, sickbeard.DATA_DIR, log_dir)) web_log_value = checkbox_to_value(web_log) - if os.path.normpath(sickbeard.LOG_DIR) != abs_log_dir: + if ek(os.path.normpath, sickbeard.LOG_DIR) != abs_log_dir: if helpers.makeDir(abs_log_dir): - sickbeard.ACTUAL_LOG_DIR = os.path.normpath(log_dir) + sickbeard.ACTUAL_LOG_DIR = ek(os.path.normpath, log_dir) sickbeard.LOG_DIR = abs_log_dir logger.initLogging() @@ -137,9 +140,9 @@ def change_NZB_DIR(nzb_dir): sickbeard.NZB_DIR = '' return True - if os.path.normpath(sickbeard.NZB_DIR) != os.path.normpath(nzb_dir): + if ek(os.path.normpath, sickbeard.NZB_DIR) != ek(os.path.normpath, nzb_dir): if helpers.makeDir(nzb_dir): - sickbeard.NZB_DIR = os.path.normpath(nzb_dir) + sickbeard.NZB_DIR = ek(os.path.normpath, nzb_dir) logger.log(u"Changed NZB folder to " + nzb_dir) else: return False @@ -158,9 +161,9 @@ def change_TORRENT_DIR(torrent_dir): sickbeard.TORRENT_DIR = '' return True - if os.path.normpath(sickbeard.TORRENT_DIR) != os.path.normpath(torrent_dir): + if ek(os.path.normpath, sickbeard.TORRENT_DIR) != ek(os.path.normpath, torrent_dir): if helpers.makeDir(torrent_dir): - sickbeard.TORRENT_DIR = os.path.normpath(torrent_dir) + sickbeard.TORRENT_DIR = ek(os.path.normpath, torrent_dir) logger.log(u"Changed torrent folder to " + torrent_dir) else: return False @@ -179,9 +182,9 @@ def change_TV_DOWNLOAD_DIR(tv_download_dir): sickbeard.TV_DOWNLOAD_DIR = '' return True - if os.path.normpath(sickbeard.TV_DOWNLOAD_DIR) != os.path.normpath(tv_download_dir): + if ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR) != ek(os.path.normpath, tv_download_dir): if helpers.makeDir(tv_download_dir): - sickbeard.TV_DOWNLOAD_DIR = os.path.normpath(tv_download_dir) + sickbeard.TV_DOWNLOAD_DIR = ek(os.path.normpath, tv_download_dir) logger.log(u"Changed TV download folder to " + tv_download_dir) else: return False @@ -196,7 +199,7 @@ def change_AUTOPOSTPROCESSER_FREQUENCY(freq): :param freq: New frequency """ - sickbeard.AUTOPOSTPROCESSER_FREQUENCY = to_int(freq, default=sickbeard.DEFAULT_AUTOPOSTPROCESSER_FREQUENCY) + sickbeard.AUTOPOSTPROCESSER_FREQUENCY = try_int(freq, sickbeard.DEFAULT_AUTOPOSTPROCESSER_FREQUENCY) if sickbeard.AUTOPOSTPROCESSER_FREQUENCY < sickbeard.MIN_AUTOPOSTPROCESSER_FREQUENCY: sickbeard.AUTOPOSTPROCESSER_FREQUENCY = sickbeard.MIN_AUTOPOSTPROCESSER_FREQUENCY @@ -210,7 +213,7 @@ def change_DAILYSEARCH_FREQUENCY(freq): :param freq: New frequency """ - sickbeard.DAILYSEARCH_FREQUENCY = to_int(freq, default=sickbeard.DEFAULT_DAILYSEARCH_FREQUENCY) + sickbeard.DAILYSEARCH_FREQUENCY = try_int(freq, sickbeard.DEFAULT_DAILYSEARCH_FREQUENCY) if sickbeard.DAILYSEARCH_FREQUENCY < sickbeard.MIN_DAILYSEARCH_FREQUENCY: sickbeard.DAILYSEARCH_FREQUENCY = sickbeard.MIN_DAILYSEARCH_FREQUENCY @@ -224,7 +227,7 @@ def change_BACKLOG_FREQUENCY(freq): :param freq: New frequency """ - sickbeard.BACKLOG_FREQUENCY = to_int(freq, default=sickbeard.DEFAULT_BACKLOG_FREQUENCY) + sickbeard.BACKLOG_FREQUENCY = try_int(freq, sickbeard.DEFAULT_BACKLOG_FREQUENCY) sickbeard.MIN_BACKLOG_FREQUENCY = sickbeard.get_backlog_cycle_time() if sickbeard.BACKLOG_FREQUENCY < sickbeard.MIN_BACKLOG_FREQUENCY: @@ -239,7 +242,7 @@ def change_UPDATE_FREQUENCY(freq): :param freq: New frequency """ - sickbeard.UPDATE_FREQUENCY = to_int(freq, default=sickbeard.DEFAULT_UPDATE_FREQUENCY) + sickbeard.UPDATE_FREQUENCY = try_int(freq, sickbeard.DEFAULT_UPDATE_FREQUENCY) if sickbeard.UPDATE_FREQUENCY < sickbeard.MIN_UPDATE_FREQUENCY: sickbeard.UPDATE_FREQUENCY = sickbeard.MIN_UPDATE_FREQUENCY @@ -253,7 +256,7 @@ def change_SHOWUPDATE_HOUR(freq): :param freq: New frequency """ - sickbeard.SHOWUPDATE_HOUR = to_int(freq, default=sickbeard.DEFAULT_SHOWUPDATE_HOUR) + sickbeard.SHOWUPDATE_HOUR = try_int(freq, sickbeard.DEFAULT_SHOWUPDATE_HOUR) if sickbeard.SHOWUPDATE_HOUR > 23: sickbeard.SHOWUPDATE_HOUR = 0 @@ -272,7 +275,7 @@ def change_SUBTITLES_FINDER_FREQUENCY(subtitles_finder_frequency): if subtitles_finder_frequency == '' or subtitles_finder_frequency is None: subtitles_finder_frequency = 1 - sickbeard.SUBTITLES_FINDER_FREQUENCY = to_int(subtitles_finder_frequency, 1) + sickbeard.SUBTITLES_FINDER_FREQUENCY = try_int(subtitles_finder_frequency, 1) def change_VERSION_NOTIFY(version_notify): @@ -504,24 +507,13 @@ def clean_url(url): return cleaned_url -def to_int(val, default=0): - """ Return int value of val or default on error """ - - try: - val = int(val) - except Exception: - val = default - - return val - - ################################################################################ # Check_setting_int # ################################################################################ def minimax(val, default, low, high): """ Return value forced within range """ - val = to_int(val, default=default) + val = try_int(val, default) if val < low: return low diff --git a/sickbeard/dailysearcher.py b/sickbeard/dailysearcher.py index 89abc9ca1695d15409959450414420f082026496..fd6f8443b22b50fe3663b4aff0d2fbcb89e8d5b8 100644 --- a/sickbeard/dailysearcher.py +++ b/sickbeard/dailysearcher.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. - import datetime import threading @@ -25,8 +24,8 @@ import sickbeard from sickbeard import logger from sickbeard import db from sickbeard import common -from sickbeard import helpers from sickbeard import network_timezones +from sickrage.show.Show import Show from sickrage.helper.exceptions import MultipleShowObjectsException @@ -68,7 +67,7 @@ class DailySearcher(): for sqlEp in sqlResults: try: if not show or int(sqlEp["showid"]) != show.indexerid: - show = helpers.findCertainShow(sickbeard.showList, int(sqlEp["showid"])) + show = Show.find(sickbeard.showList, int(sqlEp["showid"])) # for when there is orphaned series in the database but not loaded into our showlist if not show or show.paused: diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py index 131406cef8fbef13dc99d894d90ac7eb5e77797f..4707b685a4758bee94a1b2ab9d5ff0e968606a52 100644 --- a/sickbeard/databases/mainDB.py +++ b/sickbeard/databases/mainDB.py @@ -29,7 +29,7 @@ from sickbeard.name_parser.parser import NameParser, InvalidNameException, Inval from sickrage.helper.common import dateTimeFormat from sickrage.helper.encoding import ek -from babelfish import language_converters, Language +from sickbeard import subtitles MIN_DB_VERSION = 9 # oldest db version we support migrating from MAX_DB_VERSION = 42 @@ -268,8 +268,6 @@ class MainSanityCheck(db.DBSanityCheck): [datetime.datetime(2015, 7, 15, 17, 20, 44, 326380).strftime(dateTimeFormat)] ) - validLanguages = [Language.fromopensubtitles(language).opensubtitles for language in language_converters['opensubtitles'].codes if len(language) == 3] - if not sqlResults: return @@ -280,7 +278,7 @@ class MainSanityCheck(db.DBSanityCheck): (sqlResult['episode_id'], sqlResult['subtitles']), logger.DEBUG) for subcode in sqlResult['subtitles'].split(','): - if not len(subcode) is 3 or subcode not in validLanguages: + if not len(subcode) is 3 or subcode not in subtitles.subtitle_code_filter(): logger.log(u"Fixing subtitle codes for episode_id: %s, invalid code: %s" % (sqlResult['episode_id'], subcode), logger.DEBUG) continue @@ -442,7 +440,7 @@ class AddSizeAndSceneNameFields(InitialSchema): for cur_result in empty_results: ep_file_name = ek(os.path.basename, cur_result["location"]) - ep_file_name = os.path.splitext(ep_file_name)[0] + ep_file_name = ek(os.path.splitext, ep_file_name)[0] # only want to find real scene names here so anything with a space in it is out if ' ' in ep_file_name: diff --git a/sickbeard/failed_history.py b/sickbeard/failed_history.py index 177fed1c3e69d4d248920b95f1cc5428319ce908..a3608b153ecb39c6c33ffea8285b7e2873792d07 100644 --- a/sickbeard/failed_history.py +++ b/sickbeard/failed_history.py @@ -132,8 +132,8 @@ def revertEpisode(epObj): logger.log(u"Found in history") epObj.status = history_eps[epObj.episode]['old_status'] else: - logger.log(u"WARNING: Episode not found in history. Setting it back to WANTED", - logger.WARNING) + logger.log(u"Episode don't have a previous snatched status to revert. Setting it back to WANTED", + logger.DEBUG) epObj.status = WANTED epObj.saveToDB() @@ -246,4 +246,4 @@ def findRelease(epObj): # Release was not found logger.log(u"No releases found for season (%s) of (%s)" % (epObj.season, epObj.show.indexerid), logger.DEBUG) - return (release, provider) \ No newline at end of file + return (release, provider) diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index 9e78f997a4578437779ac68dd97fede5d962ecfc..4c21d00bd49bf44ebc20b523e2a3ba66a242669f 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -52,17 +52,19 @@ from sickbeard import logger, classes from sickbeard.common import USER_AGENT from sickbeard import db from sickbeard.notifiers.synoindex import notifier as synoindex_notifier -from sickbeard import clients -from sickrage.helper.common import media_extensions, pretty_file_size, subtitle_extensions +from sickrage.helper.common import http_code_description, media_extensions, pretty_file_size, subtitle_extensions from sickrage.helper.encoding import ek -from sickrage.helper.exceptions import ex, MultipleShowObjectsException +from sickrage.helper.exceptions import ex +from sickrage.show.Show import Show from cachecontrol import CacheControl, caches -from babelfish import Language from itertools import izip, cycle import shutil import shutil_custom +import xml.etree.ElementTree as ET +import json + shutil.copyfile = shutil_custom.copyfile_custom # pylint: disable=W0212 @@ -70,14 +72,6 @@ shutil.copyfile = shutil_custom.copyfile_custom urllib._urlopener = classes.SickBeardURLopener() -def isValidLanguage(language): - try: - Language.fromopensubtitles(language) - except Exception: - return False - return True - - def fixGlob(path): path = re.sub(r'\[', '[[]', path) return re.sub(r'(?<!\[)\]', '[]]', path) @@ -101,6 +95,7 @@ def indentXML(elem, level=0): if level and (not elem.tail or not elem.tail.strip()): elem.tail = i + def remove_non_release_groups(name): """ Remove non release groups from name @@ -237,32 +232,6 @@ def remove_file_failed(failed_file): pass -def findCertainShow(showList, indexerid): - """ - Find a show by indexer ID in the show list - - :param showList: List of shows to search in (needle) - :param indexerid: Show to look for - :return: result list - """ - - results = [] - - if not isinstance(indexerid, list): - indexerid = [indexerid] - - if showList and indexerid: - results = [show for show in showList if show.indexerid in indexerid] - - if not results: - return None - - if len(results) == 1: - return results[0] - elif len(results) > 1: - raise MultipleShowObjectsException() - - def makeDir(path): """ Make a directory on the filesystem @@ -367,7 +336,7 @@ def searchIndexerForShowID(regShowName, indexer=None, indexer_id=None, ui=None): if not (seriesname and series_id): continue - ShowObj = findCertainShow(sickbeard.showList, int(series_id)) + ShowObj = Show.find(sickbeard.showList, int(series_id)) # Check if we can find the show in our list (if not, it's not the right show) if (indexer_id is None) and (ShowObj is not None) and (ShowObj.indexerid == int(series_id)): return seriesname, i, int(series_id) @@ -449,7 +418,7 @@ def link(src, dst): if ctypes.windll.kernel32.CreateHardLinkW(unicode(dst), unicode(src), 0) == 0: raise ctypes.WinError() else: - os.link(src, dst) + ek(os.link, src, dst) def hardlinkFile(srcFile, destFile): @@ -478,10 +447,10 @@ def symlink(src, dst): """ if os.name == 'nt': - if ctypes.windll.kernel32.CreateSymbolicLinkW(unicode(dst), unicode(src), 1 if os.path.isdir(src) else 0) in [0, 1280]: + if ctypes.windll.kernel32.CreateSymbolicLinkW(unicode(dst), unicode(src), 1 if ek(os.path.isdir, src) else 0) in [0, 1280]: raise ctypes.WinError() else: - os.symlink(src, dst) + ek(os.symlink, src, dst) def moveAndSymlinkFile(srcFile, destFile): @@ -558,11 +527,11 @@ def rename_ep_file(cur_path, new_path, old_path_length=0): :param old_path_length: The length of media file path (old name) WITHOUT THE EXTENSION """ - # new_dest_dir, new_dest_name = os.path.split(new_path) # @UnusedVariable + # new_dest_dir, new_dest_name = ek(os.path.split, new_path) # @UnusedVariable if old_path_length == 0 or old_path_length > len(cur_path): # approach from the right - cur_file_name, cur_file_ext = os.path.splitext(cur_path) # @UnusedVariable + cur_file_name, cur_file_ext = ek(os.path.splitext, cur_path) # @UnusedVariable else: # approach from the left cur_file_ext = cur_path[old_path_length:] @@ -570,16 +539,16 @@ def rename_ep_file(cur_path, new_path, old_path_length=0): if cur_file_ext[1:] in subtitle_extensions: # Extract subtitle language from filename - sublang = os.path.splitext(cur_file_name)[1][1:] + sublang = ek(os.path.splitext, cur_file_name)[1][1:] # Check if the language extracted from filename is a valid language - if isValidLanguage(sublang): + if sublang in sickbeard.subtitles.subtitle_code_filter(): cur_file_ext = '.' + sublang + cur_file_ext # put the extension on the incoming file new_path += cur_file_ext - make_dirs(os.path.dirname(new_path)) + make_dirs(ek(os.path.dirname, new_path)) # move the file try: @@ -661,12 +630,12 @@ def chmodAsParent(childPath): logger.log(u"No parent path provided in " + childPath + ", unable to get permissions from it", logger.DEBUG) return - childPath = os.path.join(parentPath, ek(os.path.basename, childPath)) + childPath = ek(os.path.join, parentPath, ek(os.path.basename, childPath)) - parentPathStat = os.stat(parentPath) + parentPathStat = ek(os.stat, parentPath) parentMode = stat.S_IMODE(parentPathStat[stat.ST_MODE]) - childPathStat = os.stat(childPath.encode(sickbeard.SYS_ENCODING)) + childPathStat = ek(os.stat, childPath.encode(sickbeard.SYS_ENCODING)) childPath_mode = stat.S_IMODE(childPathStat[stat.ST_MODE]) if ek(os.path.isfile, childPath): @@ -685,7 +654,7 @@ def chmodAsParent(childPath): return try: - os.chmod(childPath, childMode) + ek(os.chmod, childPath, childMode) logger.log(u"Setting permissions for %s to %o as parent directory has %o" % (childPath, childMode, parentMode), logger.DEBUG) except OSError: @@ -704,14 +673,14 @@ def fixSetGroupID(childPath): return parentPath = ek(os.path.dirname, childPath) - parentStat = os.stat(parentPath) + parentStat = ek(os.stat, parentPath) parentMode = stat.S_IMODE(parentStat[stat.ST_MODE]) - childPath = os.path.join(parentPath, ek(os.path.basename, childPath)) + childPath = ek(os.path.join, parentPath, ek(os.path.basename, childPath)) if parentMode & stat.S_ISGID: parentGID = parentStat[stat.ST_GID] - childStat = os.stat(childPath.encode(sickbeard.SYS_ENCODING)) + childStat = ek(os.stat, childPath.encode(sickbeard.SYS_ENCODING)) childGID = childStat[stat.ST_GID] if childGID == parentGID: @@ -785,7 +754,7 @@ def get_all_episodes_from_absolute_number(show, absolute_numbers, indexer_id=Non if len(absolute_numbers): if not show and indexer_id: - show = findCertainShow(sickbeard.showList, indexer_id) + show = Show.find(sickbeard.showList, indexer_id) for absolute_number in absolute_numbers if show else []: ep = show.getEpisode(None, None, absolute_number=absolute_number) @@ -948,7 +917,7 @@ def restoreVersionedFile(backup_file, version): numTries = 0 - new_file, _ = os.path.splitext(backup_file) + new_file, _ = ek(os.path.splitext, backup_file) restore_file = new_file + '.' + 'v' + str(version) if not ek(os.path.isfile, new_file): @@ -988,22 +957,6 @@ def restoreVersionedFile(backup_file, version): return True -# try to convert to int, if it fails the default will be returned -def tryInt(s, s_default=0): - """ - Try to convert to int, if it fails, the default will be returned - - :param s: Value to attempt to convert to int - :param s_default: Default value to return on failure (defaults to 0) - :return: integer, or default value on failure - """ - - try: - return int(s) - except Exception: - return s_default - - # generates a md5 hash of a file def md5_for_file(filename, block_size=2 ** 16): """ @@ -1140,18 +1093,18 @@ def get_show(name, tryIndexers=False): cache = sickbeard.name_cache.retrieveNameFromCache(name) if cache: fromCache = True - showObj = findCertainShow(sickbeard.showList, int(cache)) + showObj = Show.find(sickbeard.showList, int(cache)) # try indexers if not showObj and tryIndexers: - showObj = findCertainShow(sickbeard.showList, + showObj = Show.find(sickbeard.showList, searchIndexerForShowID(full_sanitizeSceneName(name), ui=classes.ShowListUI)[2]) # try scene exceptions if not showObj: ShowID = sickbeard.scene_exceptions.get_scene_exception_by_name(name)[0] if ShowID: - showObj = findCertainShow(sickbeard.showList, int(ShowID)) + showObj = Show.find(sickbeard.showList, int(ShowID)) # add show to cache if showObj and not fromCache: @@ -1169,7 +1122,7 @@ def is_hidden_folder(folder): :param folder: Full path of folder to check """ def is_hidden(filepath): - name = os.path.basename(os.path.abspath(filepath)) + name = ek(os.path.basename, ek(os.path.abspath, filepath)) return name.startswith('.') or has_hidden_attribute(filepath) def has_hidden_attribute(filepath): @@ -1277,19 +1230,19 @@ def extractZip(archive, targetDir): """ try: - if not os.path.exists(targetDir): - os.mkdir(targetDir) + if not ek(os.path.exists, targetDir): + ek(os.mkdir, targetDir) zip_file = zipfile.ZipFile(archive, 'r', allowZip64=True) for member in zip_file.namelist(): - filename = os.path.basename(member) + filename = ek(os.path.basename, member) # skip directories if not filename: continue # copy file (taken from zipfile's extract) source = zip_file.open(member) - target = file(os.path.join(targetDir, filename), "wb") + target = file(ek(os.path.join, targetDir, filename), "wb") shutil.copyfileobj(source, target) source.close() target.close() @@ -1313,7 +1266,7 @@ def backupConfigZip(fileList, archive, arcname=None): try: a = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) for f in fileList: - a.write(f, os.path.relpath(f, arcname)) + a.write(f, ek(os.path.relpath, f, arcname)) a.close() return True except Exception as e: @@ -1331,14 +1284,14 @@ def restoreConfigZip(archive, targetDir): """ try: - if not os.path.exists(targetDir): - os.mkdir(targetDir) + if not ek(os.path.exists, targetDir): + ek(os.mkdir, targetDir) else: def path_leaf(path): - head, tail = os.path.split(path) - return tail or os.path.basename(head) + head, tail = ek(os.path.split, path) + return tail or ek(os.path.basename, head) bakFilename = '{0}-{1}'.format(path_leaf(targetDir), datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) - shutil.move(targetDir, os.path.join(os.path.dirname(targetDir), bakFilename)) + shutil.move(targetDir, ek(os.path.join, ek(os.path.dirname, targetDir), bakFilename)) zip_file = zipfile.ZipFile(archive, 'r', allowZip64=True) for member in zip_file.namelist(): @@ -1448,20 +1401,9 @@ def _getTempDir(): try: uid = getpass.getuser() except ImportError: - return os.path.join(tempfile.gettempdir(), "sickrage") + return ek(os.path.join, tempfile.gettempdir(), "sickrage") - return os.path.join(tempfile.gettempdir(), "sickrage-%s" % uid) - - -def codeDescription(status_code): - """ - Returns the description of the URL error code - """ - if status_code in clients.http_error_code: - return clients.http_error_code[status_code] - else: - logger.log(u"Unknown error code: %s. Please submit an issue" % status_code, logger.ERROR) - return 'unknown' + return ek(os.path.join, tempfile.gettempdir(), "sickrage-%s" % uid) def _setUpSession(session, headers): @@ -1475,7 +1417,7 @@ def _setUpSession(session, headers): # request session cache_dir = sickbeard.CACHE_DIR or _getTempDir() - session = CacheControl(sess=session, cache=caches.FileCache(os.path.join(cache_dir, 'sessions'), use_dir_lock=True), cache_etags=False) + session = CacheControl(sess=session, cache=caches.FileCache(ek(os.path.join, cache_dir, 'sessions'), use_dir_lock=True), cache_etags=False) # request session clear residual referer # pylint: disable=C0325 @@ -1537,7 +1479,7 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N if not resp.ok: logger.log(u"Requested getURL %s returned status code is %s: %s" - % (url, resp.status_code, codeDescription(resp.status_code)), logger.DEBUG) + % (url, resp.status_code, http_code_description(resp.status_code)), logger.DEBUG) return None except (SocketTimeout, TypeError) as e: @@ -1582,7 +1524,7 @@ def download_file(url, filename, session=None, headers=None): with closing(session.get(url, allow_redirects=True, verify=session.verify)) as resp: if not resp.ok: logger.log(u"Requested download url %s returned status code is %s: %s" - % (url, resp.status_code, codeDescription(resp.status_code)), logger.DEBUG) + % (url, resp.status_code, http_code_description(resp.status_code)), logger.DEBUG) return False try: @@ -1699,7 +1641,7 @@ def verify_freespace(src, dest, oldfile=None): if hasattr(os, 'statvfs'): # POSIX def disk_usage(path): - st = os.statvfs(path) + st = ek(os.statvfs, path) free = st.f_bavail * st.f_frsize return free @@ -1792,12 +1734,12 @@ def isFileLocked(checkfile, writeLockCheck=False): if writeLockCheck: lockFile = checkfile + ".lckchk" - if os.path.exists(lockFile): - os.remove(lockFile) + if ek(os.path.exists, lockFile): + ek(os.remove, lockFile) try: - os.rename(checkfile, lockFile) + ek(os.rename, checkfile, lockFile) time.sleep(1) - os.rename(lockFile, checkfile) + ek(os.rename, lockFile, checkfile) except (OSError, IOError): return True @@ -1809,13 +1751,49 @@ def getDiskSpaceUsage(diskPath=None): returns the free space in human readable bytes for a given path or False if no path given :param diskPath: the filesystem path being checked """ - if diskPath and os.path.exists(diskPath): + if diskPath and ek(os.path.exists, diskPath): if platform.system() == 'Windows': free_bytes = ctypes.c_ulonglong(0) ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(diskPath), None, None, ctypes.pointer(free_bytes)) return pretty_file_size(free_bytes.value) else: - st = os.statvfs(diskPath) + st = ek(os.statvfs, diskPath) return pretty_file_size(st.f_bavail * st.f_frsize) else: return False + + +def getTVDBFromID(indexer_id, indexer): + tvdb_id = '' + if indexer == 'IMDB': + url = "http://www.thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=%s" % (indexer_id) + data = urllib.urlopen(url) + try: + tree = ET.parse(data) + for show in tree.getiterator("Series"): + tvdb_id = show.findtext("seriesid") + + except SyntaxError: + pass + + return tvdb_id + elif indexer == 'ZAP2IT': + url = "http://www.thetvdb.com/api/GetSeriesByRemoteID.php?zap2it=%s" % (indexer_id) + data = urllib.urlopen(url) + try: + tree = ET.parse(data) + for show in tree.getiterator("Series"): + tvdb_id = show.findtext("seriesid") + + except SyntaxError: + pass + + return tvdb_id + elif indexer == 'TVMAZE': + url = "http://api.tvmaze.com/shows/%s" % (indexer_id) + response = urllib2.urlopen(url) + data = json.load(response) + tvdb_id = data['externals']['thetvdb'] + return tvdb_id + else: + return tvdb_id diff --git a/sickbeard/image_cache.py b/sickbeard/image_cache.py index 7ab282fee4c344aba20106db3e39662aab38a6fd..6569c6e81ac0c72864b8ce63fb41800f2591268e 100644 --- a/sickbeard/image_cache.py +++ b/sickbeard/image_cache.py @@ -286,7 +286,7 @@ class ImageCache: logger.log(u"Checking if we can use the show image from the " + cur_provider.name + " metadata", logger.DEBUG) if ek(os.path.isfile, cur_provider.get_poster_path(show_obj)): - cur_file_name = os.path.abspath(cur_provider.get_poster_path(show_obj)) + cur_file_name = ek(os.path.abspath, cur_provider.get_poster_path(show_obj)) cur_file_type = self.which_type(cur_file_name) if cur_file_type is None: diff --git a/sickbeard/imdbPopular.py b/sickbeard/imdbPopular.py index 26af7f89548299e1b901d1f413e28885da5c44e0..540e3eee57e226513ae258c0d60323f4e850d8a0 100644 --- a/sickbeard/imdbPopular.py +++ b/sickbeard/imdbPopular.py @@ -45,7 +45,7 @@ class imdbPopular(object): if image_td: image = image_td.find("img") show['image_url_large'] = self.change_size(image['src'], 3) - show['image_path'] = os.path.join('images', 'imdb_popular', os.path.basename(show['image_url_large'])) + show['image_path'] = ek(os.path.join, 'images', 'imdb_popular', ek(os.path.basename, show['image_url_large'])) self.cache_image(show['image_url_large']) @@ -91,7 +91,7 @@ class imdbPopular(object): if match: matches = match.groups() - os.path.basename(image_url) + ek(os.path.basename, image_url) matches = list(matches) matches[2] = int(matches[2]) * factor matches[4] = int(matches[4]) * factor @@ -111,12 +111,12 @@ class imdbPopular(object): """ path = ek(os.path.abspath, ek(os.path.join, sickbeard.CACHE_DIR, 'images', 'imdb_popular')) - if not os.path.exists(path): - os.makedirs(path) + if not ek(os.path.exists, path): + ek(os.makedirs, path) - full_path = os.path.join(path, os.path.basename(image_url)) + full_path = ek(os.path.join, path, ek(os.path.basename, image_url)) - if not os.path.isfile(full_path): + if not ek(os.path.isfile, full_path): helpers.download_file(image_url, full_path, session=self.session) imdb_popular = imdbPopular() diff --git a/sickbeard/indexers/__init__.py b/sickbeard/indexers/__init__.py index bdbdcca31206955d112052f32f86dbab0cec2093..eb63e387f91550866b4e3a5959a0022b65c85bac 100644 --- a/sickbeard/indexers/__init__.py +++ b/sickbeard/indexers/__init__.py @@ -18,4 +18,5 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. -from . import indexer_api, indexer_exceptions +from . import indexer_api +from . import indexer_exceptions diff --git a/sickbeard/indexers/indexer_api.py b/sickbeard/indexers/indexer_api.py index bf9f09ab6bb74a0081e42869c38edbdee630c934..a8c5e92e86a755b4bcc21a5a2125a4e175511b46 100644 --- a/sickbeard/indexers/indexer_api.py +++ b/sickbeard/indexers/indexer_api.py @@ -17,15 +17,19 @@ # # 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 sickbeard +from sickrage.helper.common import try_int +from sickrage.helper.encoding import ek -from indexer_config import initConfig, indexerConfig +from indexer_config import initConfig +from indexer_config import indexerConfig class indexerApi(object): def __init__(self, indexerID=None): - self.indexerID = int(indexerID) if indexerID else None + self.indexerID = try_int(indexerID, None) def __del__(self): pass @@ -54,7 +58,7 @@ class indexerApi(object): def api_params(self): if self.indexerID: if sickbeard.CACHE_DIR: - indexerConfig[self.indexerID]['api_params']['cache'] = os.path.join(sickbeard.CACHE_DIR, 'indexers', self.name) + indexerConfig[self.indexerID]['api_params']['cache'] = ek(os.path.join, sickbeard.CACHE_DIR, 'indexers', self.name) if sickbeard.PROXY_SETTING and sickbeard.PROXY_INDEXERS: indexerConfig[self.indexerID]['api_params']['proxy'] = sickbeard.PROXY_SETTING diff --git a/sickbeard/logger.py b/sickbeard/logger.py index 0a14f5f36face2b9ae1eddccf184a4b0dd0e24fb..8d8f5ac3118b16159db0b195168cb5e9a7e0e879 100644 --- a/sickbeard/logger.py +++ b/sickbeard/logger.py @@ -33,10 +33,11 @@ from github import Github, InputFileContent import sickbeard from sickbeard import classes -from sickrage.helper.common import dateTimeFormat -from sickrage.helper.exceptions import ex -from sickrage.helper.encoding import ss +from sickrage.helper.encoding import ss +from sickrage.helper.encoding import ek +from sickrage.helper.exceptions import ex +from sickrage.helper.common import dateTimeFormat # log levels ERROR = logging.ERROR @@ -102,7 +103,7 @@ class Logger(object): self.submitter_running = False def initLogging(self, consoleLogging=False, fileLogging=False, debugLogging=False): - self.logFile = self.logFile or os.path.join(sickbeard.LOG_DIR, 'sickrage.log') + self.logFile = self.logFile or ek(os.path.join, sickbeard.LOG_DIR, 'sickrage.log') self.debugLogging = debugLogging self.consoleLogging = consoleLogging self.fileLogging = fileLogging @@ -213,12 +214,12 @@ class Logger(object): # read log file log_data = None - if os.path.isfile(self.logFile): + if ek(os.path.isfile, self.logFile): with io.open(self.logFile, 'r', encoding='utf-8') as f: log_data = f.readlines() for i in range(1, int(sickbeard.LOG_NR)): - if os.path.isfile(self.logFile + ".%i" % i) and (len(log_data) <= 500): + if ek(os.path.isfile, self.logFile + ".%i" % i) and (len(log_data) <= 500): with io.open(self.logFile + ".%i" % i, 'r', encoding='utf-8') as f: log_data += f.readlines() diff --git a/sickbeard/metadata/generic.py b/sickbeard/metadata/generic.py index 0a2cc1a414d709ff6f160523906b579d6bebcd42..c9d0c8eeb0baba2757d10989b2b33eebebecd177 100644 --- a/sickbeard/metadata/generic.py +++ b/sickbeard/metadata/generic.py @@ -35,6 +35,7 @@ from sickbeard.metadata import helpers as metadata_helpers from sickbeard.show_name_helpers import allPossibleShowNames from sickrage.helper.common import replace_extension from sickrage.helper.exceptions import ex +from sickrage.helper.encoding import ek from tmdb_api.tmdb_api import TMDB @@ -118,7 +119,7 @@ class GenericMetadata(object): def _check_exists(location): if location: assert isinstance(location, unicode) - result = os.path.isfile(location) + result = ek(os.path.isfile, location) logger.log(u"Checking if " + location + " exists: " + str(result), logger.DEBUG) return result return False @@ -154,19 +155,19 @@ class GenericMetadata(object): return self._check_exists(self.get_season_all_banner_path(show_obj)) def get_show_file_path(self, show_obj): - return os.path.join(show_obj.location, self._show_metadata_filename) + return ek(os.path.join, show_obj.location, self._show_metadata_filename) def get_episode_file_path(self, ep_obj): return replace_extension(ep_obj.location, self._ep_nfo_extension) def get_fanart_path(self, show_obj): - return os.path.join(show_obj.location, self.fanart_name) + return ek(os.path.join, show_obj.location, self.fanart_name) def get_poster_path(self, show_obj): - return os.path.join(show_obj.location, self.poster_name) + return ek(os.path.join, show_obj.location, self.poster_name) def get_banner_path(self, show_obj): - return os.path.join(show_obj.location, self.banner_name) + return ek(os.path.join, show_obj.location, self.banner_name) @staticmethod def get_episode_thumb_path(ep_obj): @@ -175,7 +176,7 @@ class GenericMetadata(object): ep_obj: a TVEpisode instance for which to create the thumbnail """ assert isinstance(ep_obj.location, unicode) - if os.path.isfile(ep_obj.location): + if ek(os.path.isfile, ep_obj.location): tbn_filename = ep_obj.location.rpartition(".") @@ -204,7 +205,7 @@ class GenericMetadata(object): else: season_poster_filename = 'season' + str(season).zfill(2) - return os.path.join(show_obj.location, season_poster_filename + '-poster.jpg') + return ek(os.path.join, show_obj.location, season_poster_filename + '-poster.jpg') @staticmethod def get_season_banner_path(show_obj, season): @@ -222,13 +223,13 @@ class GenericMetadata(object): else: season_banner_filename = 'season' + str(season).zfill(2) - return os.path.join(show_obj.location, season_banner_filename + '-banner.jpg') + return ek(os.path.join, show_obj.location, season_banner_filename + '-banner.jpg') def get_season_all_poster_path(self, show_obj): - return os.path.join(show_obj.location, self.season_all_poster_name) + return ek(os.path.join, show_obj.location, self.season_all_poster_name) def get_season_all_banner_path(self, show_obj): - return os.path.join(show_obj.location, self.season_all_banner_name) + return ek(os.path.join, show_obj.location, self.season_all_banner_name) # pylint: disable=W0613,R0201 def _show_data(self, show_obj): @@ -400,12 +401,12 @@ class GenericMetadata(object): nfo_file_path = self.get_show_file_path(show_obj) assert isinstance(nfo_file_path, unicode) - nfo_file_dir = os.path.dirname(nfo_file_path) + nfo_file_dir = ek(os.path.dirname, nfo_file_path) try: - if not os.path.isdir(nfo_file_dir): + if not ek(os.path.isdir, nfo_file_dir): logger.log(u"Metadata dir didn't exist, creating it at " + nfo_file_dir, logger.DEBUG) - os.makedirs(nfo_file_dir) + ek(os.makedirs, nfo_file_dir) helpers.chmodAsParent(nfo_file_dir) logger.log(u"Writing show nfo file to " + nfo_file_path, logger.DEBUG) @@ -445,12 +446,12 @@ class GenericMetadata(object): nfo_file_path = self.get_episode_file_path(ep_obj) assert isinstance(nfo_file_path, unicode) - nfo_file_dir = os.path.dirname(nfo_file_path) + nfo_file_dir = ek(os.path.dirname, nfo_file_path) try: - if not os.path.isdir(nfo_file_dir): + if not ek(os.path.isdir, nfo_file_dir): logger.log(u"Metadata dir didn't exist, creating it at " + nfo_file_dir, logger.DEBUG) - os.makedirs(nfo_file_dir) + ek(os.makedirs, nfo_file_dir) helpers.chmodAsParent(nfo_file_dir) logger.log(u"Writing episode nfo file to " + nfo_file_path, logger.DEBUG) @@ -686,20 +687,20 @@ class GenericMetadata(object): assert isinstance(image_path, unicode) # don't bother overwriting it - if os.path.isfile(image_path): + if ek(os.path.isfile, image_path): logger.log(u"Image already exists, not downloading", logger.DEBUG) return False - image_dir = os.path.dirname(image_path) + image_dir = ek(os.path.dirname, image_path) if not image_data: logger.log(u"Unable to retrieve image to save in %s, skipping" % image_path, logger.DEBUG) return False try: - if not os.path.isdir(image_dir): + if not ek(os.path.isdir, image_dir): logger.log(u"Metadata dir didn't exist, creating it at " + image_dir, logger.DEBUG) - os.makedirs(image_dir) + ek(os.makedirs, image_dir) helpers.chmodAsParent(image_dir) outFile = io.open(image_path, 'wb') @@ -905,9 +906,9 @@ class GenericMetadata(object): assert isinstance(folder, unicode) - metadata_path = os.path.join(folder, self._show_metadata_filename) + metadata_path = ek(os.path.join, folder, self._show_metadata_filename) - if not os.path.isdir(folder) or not os.path.isfile(metadata_path): + if not ek(os.path.isdir, folder) or not ek(os.path.isfile, metadata_path): logger.log(u"Can't load the metadata file from " + metadata_path + ", it doesn't exist", logger.DEBUG) return empty_return diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 1394505ac1b8c158a3df531bf66ef45eb7dcc0a0..5284c09d8c28149ea91e898213952c1d5d55d64a 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -421,7 +421,7 @@ class NameParser(object): file_name_result = self._parse_string(base_file_name) # use only the direct parent dir - dir_name = os.path.basename(dir_name) + dir_name = ek(os.path.basename, dir_name) # parse the dirname for extra info if needed dir_name_result = self._parse_string(dir_name) diff --git a/sickbeard/name_parser/regexes.py b/sickbeard/name_parser/regexes.py index 211375f92273c29b139ab8b1048abbccd63b2591..b12fffb4c420114ca4f56b43d8ee8ffe87438c91 100644 --- a/sickbeard/name_parser/regexes.py +++ b/sickbeard/name_parser/regexes.py @@ -340,6 +340,24 @@ anime_regexes = [ .*? '''), + ('anime_SxxExx', + # Show.Name.S01E02.Source.Quality.Etc-Group + # Show Name - S01E02 - My Ep Name + # Show.Name.S01.E03.My.Ep.Name + # Show.Name.S01E02E03.Source.Quality.Etc-Group + # Show Name - S01E02-03 - My Ep Name + # Show.Name.S01.E02.E03 + r''' + ^((?P<series_name>.+?)[. _-]+)? # Show_Name and separator + (\()?s(?P<season_num>\d+)[. _-]* # S01 and optional separator + e(?P<ep_num>\d+)(\))? # E02 and separator + (([. _-]*e|-) # linking e/- char + (?P<extra_ep_num>(?!(1080|720|480)[pi])\d+)(\))?)* # additional E03/etc + [. _-]*((?P<extra_info>.+?) # Source_Quality_Etc- + ((?<![. _-])(?<!WEB) # Make sure this is really the release group + -(?P<release_group>[^- ]+([. _-]\[.*\])?))?)?$ # Group + '''), + ('anime_and_normal', # Bleach - s16e03-04 - 313-314 # Bleach.s16e03-04.313-314 diff --git a/sickbeard/network_timezones.py b/sickbeard/network_timezones.py index 15f09d15b2df9a4e9e2ec56596c2cbcd42e28da4..ae1b2eafeaee808dc8886f6f7457b30abad172fc 100644 --- a/sickbeard/network_timezones.py +++ b/sickbeard/network_timezones.py @@ -25,6 +25,7 @@ from dateutil import tz from sickbeard import db from sickbeard import helpers from sickbeard import logger +from sickrage.helper.common import try_int # regex to parse time (12/24 hour format) time_regex = re.compile(r'(\d{1,2})(([:.](\d{2,2}))? ?([PA][. ]? ?M)|[:.](\d{2,2}))\b', flags=re.IGNORECASE) @@ -136,8 +137,8 @@ def parse_date_time(d, t, network): if mo is not None and len(mo.groups()) >= 5: if mo.group(5) is not None: try: - hr = helpers.tryInt(mo.group(1)) - m = helpers.tryInt(mo.group(4)) + hr = try_int(mo.group(1)) + m = try_int(mo.group(4)) ap = mo.group(5) # convert am/pm to 24 hour clock if ap is not None: @@ -150,8 +151,8 @@ def parse_date_time(d, t, network): m = 0 else: try: - hr = helpers.tryInt(mo.group(1)) - m = helpers.tryInt(mo.group(6)) + hr = try_int(mo.group(1)) + m = try_int(mo.group(6)) except Exception: hr = 0 m = 0 @@ -162,7 +163,7 @@ def parse_date_time(d, t, network): hr = 0 m = 0 - te = datetime.datetime.fromordinal(helpers.tryInt(d) or 1) + te = datetime.datetime.fromordinal(try_int(d) or 1) try: foreign_timezone = get_network_timezone(network, network_dict) return datetime.datetime(te.year, te.month, te.day, hr, m, tzinfo=foreign_timezone) diff --git a/sickbeard/notifiers/libnotify.py b/sickbeard/notifiers/libnotify.py index 162dfe2189890cce139041396d2034fcacb1858e..ab4efff673a9e51ba30a22db6e920d880d56381a 100644 --- a/sickbeard/notifiers/libnotify.py +++ b/sickbeard/notifiers/libnotify.py @@ -23,7 +23,7 @@ import cgi import sickbeard from sickbeard import logger, common - +from sickrage.helper.encoding import ek def diagnose(): """ @@ -95,7 +95,7 @@ class LibnotifyNotifier(object): def notify_subtitle_download(self, ep_name, lang): if sickbeard.LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD: self._notify(common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], ep_name + ": " + lang) - + def notify_git_update(self, new_version="??"): if sickbeard.USE_LIBNOTIFY: update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] @@ -113,7 +113,7 @@ class LibnotifyNotifier(object): # Can't make this a global constant because PROG_DIR isn't available # when the module is imported. - icon_path = os.path.join(sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'ico', 'favicon-120.png') + icon_path = ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'ico', 'favicon-120.png') # If the session bus can't be acquired here a bunch of warning messages # will be printed but the call to show() will still return True. diff --git a/sickbeard/nzbget.py b/sickbeard/nzbget.py index 5e12b521aac631e9e36b8b9290dd5dbaf6860891..d5ea16b16663353cbf76284df0966f9d94e473f1 100644 --- a/sickbeard/nzbget.py +++ b/sickbeard/nzbget.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. - import httplib import datetime from base64 import standard_b64encode @@ -25,8 +24,9 @@ import xmlrpclib import sickbeard from sickbeard.providers.generic import GenericProvider -from sickbeard import logger, helpers +from sickbeard import logger from sickbeard.common import Quality +from sickrage.helper.common import try_int def sendNZB(nzb, proper=False): @@ -108,7 +108,7 @@ def sendNZB(nzb, proper=False): try: # Find out if nzbget supports priority (Version 9.0+), old versions beginning with a 0.x will use the old command nzbget_version_str = nzbGetRPC.version() - nzbget_version = helpers.tryInt(nzbget_version_str[:nzbget_version_str.find(".")]) + nzbget_version = try_int(nzbget_version_str[:nzbget_version_str.find(".")]) if nzbget_version == 0: if nzbcontent64 is not None: nzbget_result = nzbGetRPC.append(nzb.name + ".nzb", category, addToTop, nzbcontent64) diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 0dee5ce8001386351f736d265ecfa954e12bc3d3..0e33b65de3d96861666ffd15c0249c4070749584 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -39,6 +39,7 @@ from sickrage.helper.common import remove_extension, replace_extension, subtitle from sickrage.helper.encoding import ek from sickrage.helper.exceptions import EpisodeNotFoundException, EpisodePostProcessingFailedException, ex from sickrage.helper.exceptions import ShowDirectoryNotFoundException +from sickrage.show.Show import Show from babelfish import language_converters import adba @@ -158,9 +159,9 @@ class PostProcessor(object): """ def recursive_glob(treeroot, pattern): results = [] - for base, _, files in os.walk(treeroot.encode(sickbeard.SYS_ENCODING)): + for base, _, files in ek(os.walk, treeroot.encode(sickbeard.SYS_ENCODING)): goodfiles = fnmatch.filter(files, pattern) - results.extend(os.path.join(base, f) for f in goodfiles) + results.extend(ek(os.path.join, base, f) for f in goodfiles) return results if not file_path: @@ -335,13 +336,13 @@ 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 subtitle_extensions: - cur_lang = os.path.splitext(cur_extension)[0] + if ek(os.path.splitext, cur_extension)[1][1:] in subtitle_extensions: + cur_lang = ek(os.path.splitext, cur_extension)[0] if cur_lang: cur_lang = cur_lang.lower() if cur_lang == 'pt-br': cur_lang = 'pt-BR' - cur_extension = cur_lang + os.path.splitext(cur_extension)[1] + cur_extension = cur_lang + ek(os.path.splitext, cur_extension)[1] # replace .nfo with .nfo-orig to avoid conflicts if cur_extension == 'nfo' and sickbeard.NFO_RENAME is True: @@ -499,7 +500,7 @@ class PostProcessor(object): if quality == common.Quality.UNKNOWN: quality = None - show = helpers.findCertainShow(sickbeard.showList, indexer_id) + show = Show.find(sickbeard.showList, indexer_id) self.in_history = True self.version = version @@ -1139,7 +1140,7 @@ class PostProcessor(object): with cur_ep.lock: cur_ep.location = ek(os.path.join, dest_path, new_file_name) cur_ep.refreshSubtitles() - cur_ep.downloadSubtitles(force=True) + cur_ep.download_subtitles(force=True) # now that processing has finished, we can put the info in the DB. If we do it earlier, then when processing fails, it won't try again. if len(sql_l) > 0: diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index 9e2ed2431096cda5cc396d8f0a95bcb3ccd7f644..9e03f21d49f014feba9759be0836f75dfd426319 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -79,7 +79,7 @@ def delete_folder(folder, check_empty=True): try: logger.log(u"Deleting folder (if it's empty): " + folder) - os.rmdir(folder) + ek(os.rmdir, folder) except (OSError, IOError), e: logger.log(u"Warning: unable to delete folder: " + folder + ": " + ex(e), logger.WARNING) return False @@ -331,18 +331,18 @@ def validateDir(path, dirName, nzbNameOriginal, failed, result): return False if failed: - process_failed(os.path.join(path, dirName), nzbNameOriginal, result) + process_failed(ek(os.path.join, path, dirName), nzbNameOriginal, result) result.missedfiles.append(dirName + " : Failed download") return False - if helpers.is_hidden_folder(os.path.join(path, dirName)): + if helpers.is_hidden_folder(ek(os.path.join, path, dirName)): result.output += logHelper(u"Ignoring hidden folder: " + dirName, logger.DEBUG) result.missedfiles.append(dirName + " : Hidden folder") return False # make sure the dir isn't inside a show dir myDB = db.DBConnection() - sqlResults = myDB.select("SELECT * FROM tv_shows") + sqlResults = myDB.select("SELECT location FROM tv_shows") for sqlShow in sqlResults: if dirName.lower().startswith(ek(os.path.realpath, sqlShow["location"]).lower() + os.sep) or \ @@ -414,11 +414,11 @@ def unRAR(path, rarFiles, force, result): result.output += logHelper(u"Unpacking archive: " + archive, logger.DEBUG) try: - rar_handle = RarFile(os.path.join(path, archive)) + rar_handle = RarFile(ek(os.path.join, path, archive)) # Skip extraction if any file in archive has previously been extracted skip_file = False - for file_in_archive in [os.path.basename(x.filename) for x in rar_handle.infolist() if not x.isdir]: + for file_in_archive in [ek(os.path.basename, x.filename) for x in rar_handle.infolist() if not x.isdir]: if already_postprocessed(path, file_in_archive, force, result): result.output += logHelper( u"Archive file already post-processed, extraction skipped: " + file_in_archive, @@ -432,7 +432,7 @@ def unRAR(path, rarFiles, force, result): rar_handle.extract(path=path, withSubpath=False, overwrite=False) for x in rar_handle.infolist(): if not x.isdir: - basename = os.path.basename(x.filename) + basename = ek(os.path.basename, x.filename) if basename not in unpacked_files: unpacked_files.append(basename) del rar_handle @@ -488,38 +488,31 @@ def already_postprocessed(dirName, videofile, force, result): # Avoid processing the same dir again if we use a process method <> move myDB = db.DBConnection() - sqlResult = myDB.select("SELECT * FROM tv_episodes WHERE release_name = ?", [dirName]) + sqlResult = myDB.select("SELECT release_name FROM tv_episodes WHERE release_name in (?, ?) LIMIT 1", [dirName, videofile.rpartition('.')[0]]) if sqlResult: # result.output += logHelper(u"You're trying to post process a dir that's already been processed, skipping", logger.DEBUG) return True - else: - sqlResult = myDB.select("SELECT * FROM tv_episodes WHERE release_name = ?", [videofile.rpartition('.')[0]]) - if sqlResult: - # result.output += logHelper(u"You're trying to post process a video that's already been processed, skipping", logger.DEBUG) - return True - - # Needed if we have downloaded the same episode @ different quality - # But we need to make sure we check the history of the episode we're going to PP, and not others - np = NameParser(dirName, tryIndexers=True) - try: # if it fails to find any info (because we're doing an unparsable folder (like the TV root dir) it will throw an exception, which we want to ignore - parse_result = np.parse(dirName) - except Exception: # ignore the exception, because we kind of expected it, but create parse_result anyway so we can perform a check on it. - parse_result = False - - - search_sql = "SELECT tv_episodes.indexerid, history.resource FROM tv_episodes INNER JOIN history ON history.showid=tv_episodes.showid" # This part is always the same - search_sql += " WHERE history.season=tv_episodes.season and history.episode=tv_episodes.episode" - # If we find a showid, a season number, and one or more episode numbers then we need to use those in the query - if parse_result and (parse_result.show.indexerid and parse_result.episode_numbers and parse_result.season_number): - search_sql += " and tv_episodes.showid = '" + str(parse_result.show.indexerid) + "' and tv_episodes.season = '" + str(parse_result.season_number) + "' and tv_episodes.episode = '" + str(parse_result.episode_numbers[0]) + "'" - - search_sql += " and tv_episodes.status IN (" + ",".join([str(x) for x in common.Quality.DOWNLOADED]) + ")" - search_sql += " and history.resource LIKE ?" - sqlResult = myDB.select(search_sql, ['%' + videofile]) - if sqlResult: - # result.output += logHelper(u"You're trying to post process a video that's already been processed, skipping", logger.DEBUG) - return True + # Needed if we have downloaded the same episode @ different quality + # But we need to make sure we check the history of the episode we're going to PP, and not others + np = NameParser(dirName, tryIndexers=True) + try: # if it fails to find any info (because we're doing an unparsable folder (like the TV root dir) it will throw an exception, which we want to ignore + parse_result = np.parse(dirName) + except Exception: # ignore the exception, because we kind of expected it, but create parse_result anyway so we can perform a check on it. + parse_result = False + + search_sql = "SELECT tv_episodes.indexerid, history.resource FROM tv_episodes INNER JOIN history ON history.showid=tv_episodes.showid" # This part is always the same + search_sql += " WHERE history.season=tv_episodes.season and history.episode=tv_episodes.episode" + # If we find a showid, a season number, and one or more episode numbers then we need to use those in the query + if parse_result and (parse_result.show.indexerid and parse_result.episode_numbers and parse_result.season_number): + search_sql += " and tv_episodes.showid = '" + str(parse_result.show.indexerid) + "' and tv_episodes.season = '" + str(parse_result.season_number) + "' and tv_episodes.episode = '" + str(parse_result.episode_numbers[0]) + "'" + + search_sql += " and tv_episodes.status IN (" + ",".join([str(x) for x in common.Quality.DOWNLOADED]) + ")" + search_sql += " and history.resource LIKE ? LIMIT 1" + sqlResult = myDB.select(search_sql, ['%' + videofile]) + if sqlResult: + # result.output += logHelper(u"You're trying to post process a video that's already been processed, skipping", logger.DEBUG) + return True return False @@ -592,10 +585,10 @@ def get_path_dir_files(dirName, nzbName, proc_type): break else: path, dirs = ek(os.path.split, dirName) # Script Post Processing - if not nzbName is None and not nzbName.endswith('.nzb') and os.path.isfile( - os.path.join(dirName, nzbName)): # For single torrent file without Dir + if not nzbName is None and not nzbName.endswith('.nzb') and ek(os.path.isfile, + ek(os.path.join, dirName, nzbName)): # For single torrent file without Dir dirs = [] - files = [os.path.join(dirName, nzbName)] + files = [ek(os.path.join, dirName, nzbName)] else: dirs = [dirs] files = [] diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index 60116727e358a89c64080c7c41c3c43f82b0a153..1d838ce81f302b50738ec923c46b9bcb68bfed77 100644 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -26,56 +26,18 @@ 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, elitetorrent - -__all__ = ['womble', - 'btn', - 'thepiratebay', - 'kat', - 'torrentleech', - 'scc', - 'hdtorrents', - 'torrentday', - 'hdbits', - 'hounddawgs', - 'iptorrents', - 'omgwtfnzbs', - 'nextgen', - 'speedcd', - 'nyaatorrents', - 'animenzb', - 'torrentbytes', - 'freshontv', - 'titansoftv', - 'libertalia', - 'morethantv', - 'bitsoup', - 't411', - 'tokyotoshokan', - 'alpharatio', - 'shazbat', - 'rarbg', - 'tntvillage', - 'binsearch', - 'bluetigers', - 'cpasbien', - 'fnt', - 'xthor', - 'scenetime', - 'btdigg', - 'strike', - 'transmitthenet', - 'tvchaosuk', - 'torrentproject', - 'extratorrent', - 'bitcannon', - 'torrentz', - 'pretome', - 'gftracker', - 'hdspace', - 'newpct', - 'elitetorrent' - ] + scenetime, btdigg, strike, transmitthenet, tvchaosuk, bitcannon, pretome, gftracker, hdspace, newpct, elitetorrent, bitsnoop + +__all__ = [ + 'womble', 'btn', 'thepiratebay', 'kat', 'torrentleech', 'scc', 'hdtorrents', + 'torrentday', 'hdbits', 'hounddawgs', 'iptorrents', 'omgwtfnzbs', 'nextgen', + 'speedcd', 'nyaatorrents', 'animenzb', 'torrentbytes', 'freshontv', 'titansoftv', + 'libertalia', 'morethantv', 'bitsoup', 't411', 'tokyotoshokan', 'alpharatio', + 'shazbat', 'rarbg', 'tntvillage', 'binsearch', 'bluetigers', 'cpasbien', + 'fnt', 'xthor', 'scenetime', 'btdigg', 'strike', 'transmitthenet', 'tvchaosuk', + 'torrentproject', 'extratorrent', 'bitcannon', 'torrentz', 'pretome', 'gftracker', + 'hdspace', 'newpct', 'elitetorrent', 'bitsnoop' +] def sortedProviderList(randomize=False): diff --git a/sickbeard/providers/alpharatio.py b/sickbeard/providers/alpharatio.py index 0e58915fa0e4e706f6c9e0334d592e71f2fb31dd..c8549a7a3b813576d5c2533f201146037d098e82 100644 --- a/sickbeard/providers/alpharatio.py +++ b/sickbeard/providers/alpharatio.py @@ -33,7 +33,6 @@ class AlphaRatioProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "AlphaRatio") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/animenzb.py b/sickbeard/providers/animenzb.py index f06300540ef867677d43a1db47833a792efbe351..db95585cd5f91e6cfa03b366af461179c1d6a5d5 100644 --- a/sickbeard/providers/animenzb.py +++ b/sickbeard/providers/animenzb.py @@ -42,12 +42,11 @@ class animenzb(generic.NZBProvider): self.supportsAbsoluteNumbering = True self.anime_only = True - self.cache = animenzbCache(self) - self.urls = {'base_url': 'http://animenzb.com//'} - self.url = self.urls['base_url'] + self.cache = animenzbCache(self) + def _get_season_search_strings(self, ep_obj): return [x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] diff --git a/sickbeard/providers/binsearch.py b/sickbeard/providers/binsearch.py index c71d6e3123cb63f09dff32b5806b8b660eb326e7..9c911276d5d5d2a4fe1161ab6f34d6b78ec14e78 100644 --- a/sickbeard/providers/binsearch.py +++ b/sickbeard/providers/binsearch.py @@ -31,7 +31,7 @@ class BinSearchProvider(generic.NZBProvider): self.cache = BinSearchCache(self) self.urls = {'base_url': 'https://www.binsearch.info/'} self.url = self.urls['base_url'] - + self.supportsBacklog = False class BinSearchCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/bitcannon.py b/sickbeard/providers/bitcannon.py index a3b9481d1c8c0a50c731bd8c6d247308f43452f9..a4984066b41f8d7ca6c50837c1bbe0592583b97c 100644 --- a/sickbeard/providers/bitcannon.py +++ b/sickbeard/providers/bitcannon.py @@ -29,7 +29,6 @@ class BitCannonProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "BitCannon") - self.supportsBacklog = True self.public = True self.minseed = None diff --git a/sickbeard/providers/bitsnoop.py b/sickbeard/providers/bitsnoop.py new file mode 100644 index 0000000000000000000000000000000000000000..831a763a6ac71a8dabac3e3597a42d339b9e4d69 --- /dev/null +++ b/sickbeard/providers/bitsnoop.py @@ -0,0 +1,143 @@ +# coding=utf-8 +# Author: Gonçalo (aka duramato) <matigonkas@outlook.com> +# 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/>. + +import traceback +from bs4 import BeautifulSoup + +import sickbeard +from sickbeard import logger +from sickbeard import tvcache +from sickbeard.providers import generic +from sickrage.helper.common import try_int + + +class BitSnoopProvider(generic.TorrentProvider): # pylint: disable=R0902,R0913 + def __init__(self): + generic.TorrentProvider.__init__(self, "BitSnoop") + + self.urls = { + 'index': 'http://bitsnoop.com', + 'search': 'http://bitsnoop.com/search/video/', + 'rss': 'http://bitsnoop.com/new_video.html?fmt=rss' + } + + self.url = self.urls['index'] + + self.public = True + self.ratio = None + self.minseed = None + self.minleech = None + + self.proper_strings = ['PROPER', 'REPACK'] + + self.cache = BitSnoopCache(self) + + def _doSearch(self, search_strings, search_mode='eponly', epcount=0, age=0, epObj=None): # pylint: disable=R0912,R0913,R0914 + + results = [] + items = {'Season': [], 'Episode': [], 'RSS': []} + + for mode in search_strings.keys(): + logger.log(u"Search Mode: %s" % mode, logger.DEBUG) + for search_string in search_strings[mode]: + + if mode != 'RSS': + logger.log(u"Search string: %s " % search_string, logger.DEBUG) + + try: + url = (self.urls['rss'], self.urls['search'] + search_string + '/s/d/1/?fmt=rss')[mode != 'RSS'] + data = self.getURL(url) + if not data: + logger.log(u"No data returned from provider", logger.DEBUG) + continue + + if not data.startswith('<?xml'): + logger.log(u'Expected xml but got something else, is your mirror failing?', logger.INFO) + continue + + data = BeautifulSoup(data, features=["html5lib", "permissive"]) + + entries = entries = data.findAll('item') + + for item in entries: + try: + if not item.category.text.endswith(('TV', 'Anime')): + continue + + title = item.title.text + assert isinstance(title, unicode) + # Use the torcache link bitsnoop 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'] + if sickbeard.TORRENT_METHOD != "blackhole" or 'torcache' not in download_url: + download_url = item.find('magneturi').next.replace('CDATA', '').strip('[]') + self._custom_trackers + + if not (title and download_url): + continue + + seeders = try_int(item.find('numseeders').text, 0) + leechers = try_int(item.find('numleechers').text, 0) + size = try_int(item.find('size').text, -1) + + info_hash = item.find('infohash').text + + except (AttributeError, TypeError, KeyError, ValueError): + continue + + # Filter unseeded torrent + if seeders < self.minseed or leechers < self.minleech: + 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, info_hash + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) + + items[mode].append(item) + + except (AttributeError, TypeError, KeyError, ValueError): + logger.log(u"Failed parsing provider. Traceback: %r" % traceback.format_exc(), logger.ERROR) + + # 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 + + + def seedRatio(self): + return self.ratio + + +class BitSnoopCache(tvcache.TVCache): + def __init__(self, provider_obj): + + tvcache.TVCache.__init__(self, provider_obj) + + self.minTime = 20 + + def _getRSSData(self): + search_strings = {'RSS': ['rss']} + return {'entries': self.provider._doSearch(search_strings)} + + +provider = BitSnoopProvider() diff --git a/sickbeard/providers/bitsoup.py b/sickbeard/providers/bitsoup.py index e25855c9deee2c8029bee140220fb234dc4fc253..a3e10b6e8e94211b171de77b944da663949470b3 100644 --- a/sickbeard/providers/bitsoup.py +++ b/sickbeard/providers/bitsoup.py @@ -38,7 +38,6 @@ class BitSoupProvider(generic.TorrentProvider): self.url = self.urls['base_url'] - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/bluetigers.py b/sickbeard/providers/bluetigers.py index 561503a401732b3df83c43ef7da75950493d16fc..c6d2902bd59ffe8e868214be9a6de7ce9f12acef 100644 --- a/sickbeard/providers/bluetigers.py +++ b/sickbeard/providers/bluetigers.py @@ -33,7 +33,6 @@ class BLUETIGERSProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "BLUETIGERS") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/btdigg.py b/sickbeard/providers/btdigg.py index 4eda679d1cdf9e9371d3566cdcf4b02ad4aaaa8f..c97d225e9986c154083d66cd08a1ba9cef0ec599 100644 --- a/sickbeard/providers/btdigg.py +++ b/sickbeard/providers/btdigg.py @@ -31,12 +31,13 @@ class BTDIGGProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "BTDigg") - self.supportsBacklog = True self.public = True self.ratio = 0 self.urls = {'url': u'https://btdigg.org/', 'api': u'https://api.btdigg.org/api/private-341ada3245790954/s02'} + self.proper_strings = ['PROPER', 'REPACK'] + self.url = self.urls['url'] # Unsupported diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py index 6b3b767289e6ea00e8d6e80c2d7f97cf65274513..47f825d497ad9b9a3df299fa4c7c2c12b8c6c95d 100644 --- a/sickbeard/providers/btn.py +++ b/sickbeard/providers/btn.py @@ -38,7 +38,6 @@ class BTNProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "BTN") - self.supportsBacklog = True self.supportsAbsoluteNumbering = True diff --git a/sickbeard/providers/cpasbien.py b/sickbeard/providers/cpasbien.py index e91160f8db116325b9629173d66b48db37b3678e..a2456108c1b1484e899192c8b5e49da59ec7769d 100644 --- a/sickbeard/providers/cpasbien.py +++ b/sickbeard/providers/cpasbien.py @@ -31,7 +31,6 @@ class CpasbienProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "Cpasbien") - self.supportsBacklog = True self.public = True self.ratio = None self.url = "http://www.cpasbien.io" diff --git a/sickbeard/providers/elitetorrent.py b/sickbeard/providers/elitetorrent.py index 63a9492e2c50d32af0d90572d04160f15075148b..f63582e602226b411215c4946d1349f0f97d9118 100644 --- a/sickbeard/providers/elitetorrent.py +++ b/sickbeard/providers/elitetorrent.py @@ -33,7 +33,6 @@ class elitetorrentProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "EliteTorrent") - self.supportsBacklog = True self.onlyspasearch = None self.minseed = None self.minleech = None @@ -77,16 +76,16 @@ class elitetorrentProvider(generic.TorrentProvider): 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': + if self.onlyspasearch and lang_info != 'es' and mode != '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': + if mode != '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 '' + self.search_params['buscar'] = search_string.strip() if mode != 'RSS' else '' searchURL = self.urls['search'] + '?' + urllib.parse.urlencode(self.search_params) logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) @@ -130,12 +129,12 @@ class elitetorrentProvider(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/extratorrent.py b/sickbeard/providers/extratorrent.py index eda874561dfa78266ab8f8fe4bd592cd64100af4..e760fbbb4526351cf5867a08b72b867c0f1ec864 100644 --- a/sickbeard/providers/extratorrent.py +++ b/sickbeard/providers/extratorrent.py @@ -23,9 +23,9 @@ from xml.parsers.expat import ExpatError from sickbeard import logger from sickbeard import tvcache -from sickbeard import helpers from sickbeard.common import USER_AGENT from sickbeard.providers import generic +from sickrage.helper.common import try_int class ExtraTorrentProvider(generic.TorrentProvider): @@ -39,7 +39,6 @@ class ExtraTorrentProvider(generic.TorrentProvider): self.url = self.urls['index'] - self.supportsBacklog = True self.public = True self.ratio = None self.minseed = None @@ -90,8 +89,8 @@ class ExtraTorrentProvider(generic.TorrentProvider): title = item['title'].decode('utf-8') # info_hash = item['info_hash'] size = int(item['size']) - seeders = helpers.tryInt(item['seeders'], 0) - leechers = helpers.tryInt(item['leechers'], 0) + seeders = try_int(item['seeders'], 0) + leechers = try_int(item['leechers'], 0) download_url = item['enclosure']['@url'] if 'enclosure' in item else self._magnet_from_details(item['link']) if not all([title, download_url]): diff --git a/sickbeard/providers/fnt.py b/sickbeard/providers/fnt.py index 78bc2d1ea74e333a8bdb062758d06ed2e91a7de2..adae3518cb659b3dbb1c6eafaada3d72466d2920 100644 --- a/sickbeard/providers/fnt.py +++ b/sickbeard/providers/fnt.py @@ -31,7 +31,6 @@ class FNTProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "FNT") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/freshontv.py b/sickbeard/providers/freshontv.py index d0cc75351f1bbf564358aa889cd56d40f9ff5c04..c875e022d2fd4e7c8ee2ae2dffb60e69108e65e8 100644 --- a/sickbeard/providers/freshontv.py +++ b/sickbeard/providers/freshontv.py @@ -23,17 +23,16 @@ import traceback from sickbeard import logger from sickbeard import tvcache -from sickbeard.helpers import tryInt from sickbeard.providers import generic from sickbeard.bs4_parser import BS4Parser +from sickrage.helper.common import try_int -class FreshOnTVProvider(generic.TorrentProvider): +class FreshOnTVProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "FreshOnTV") - self.supportsBacklog = True self._uid = None @@ -203,8 +202,8 @@ class FreshOnTVProvider(generic.TorrentProvider): details_url = individual_torrent.find('a', {'class': 'torrent_name_link'})['href'] torrent_id = int((re.match('.*?([0-9]+)$', details_url).group(1)).strip()) download_url = self.urls['download'] % (str(torrent_id)) - seeders = tryInt(individual_torrent.find('td', {'class': 'table_seeders'}).find('span').text.strip(), 1) - leechers = tryInt(individual_torrent.find('td', {'class': 'table_leechers'}).find('a').text.strip(), 0) + seeders = try_int(individual_torrent.find('td', {'class': 'table_seeders'}).find('span').text.strip(), 1) + leechers = try_int(individual_torrent.find('td', {'class': 'table_leechers'}).find('a').text.strip(), 0) # FIXME size = -1 except Exception: diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index 89f1526be4a1656e2e8d578d6493e40140bd76ca..99b80f6e7148bc331683fab26b61923c95bd8e0e 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -37,6 +37,7 @@ from sickbeard.common import user_agents from sickrage.helper.common import sanitize_filename from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex +from sickrage.show.Show import Show from sickbeard import show_name_helpers @@ -57,7 +58,7 @@ class GenericProvider(object): self.show = None - self.supportsBacklog = False + self.supportsBacklog = True self.supportsAbsoluteNumbering = False self.anime_only = False @@ -655,7 +656,7 @@ class TorrentProvider(GenericProvider): ) for sqlshow in sqlResults or []: - show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) + show = Show.find(sickbeard.showList, int(sqlshow["showid"])) if show: curEp = show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) for term in self.proper_strings: diff --git a/sickbeard/providers/gftracker.py b/sickbeard/providers/gftracker.py index 14d6656cdda3a1050548120b441c249d78675049..c709b14a20e8345ff44ff2485cf9e2b9fd20cef9 100644 --- a/sickbeard/providers/gftracker.py +++ b/sickbeard/providers/gftracker.py @@ -32,7 +32,6 @@ class GFTrackerProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "GFTracker") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/hdbits.py b/sickbeard/providers/hdbits.py index fe2f6b91f45375a80100c93172804fa6cfa4c8d0..33c37dbd1582fd92ed902512ab33563b243cbe1e 100644 --- a/sickbeard/providers/hdbits.py +++ b/sickbeard/providers/hdbits.py @@ -33,7 +33,6 @@ class HDBitsProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "HDBits") - self.supportsBacklog = True self.username = None diff --git a/sickbeard/providers/hdspace.py b/sickbeard/providers/hdspace.py index 5a61844d13a233194b2cd0faa0800669f8b24e55..c91255e07c9258f3699036fa5013d349e62353e2 100644 --- a/sickbeard/providers/hdspace.py +++ b/sickbeard/providers/hdspace.py @@ -31,7 +31,6 @@ class HDSpaceProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "HDSpace") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/hdtorrents.py b/sickbeard/providers/hdtorrents.py index 4183f702686d7d68f975bb8e59e5e86fff1798b0..2428c1932048522d1664f34ff4323fe0f78ed4bc 100644 --- a/sickbeard/providers/hdtorrents.py +++ b/sickbeard/providers/hdtorrents.py @@ -32,7 +32,6 @@ class HDTorrentsProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "HDTorrents") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/hounddawgs.py b/sickbeard/providers/hounddawgs.py index 704320e3e57ffd5b3cbd2374716d5e7b29ff7d3f..48067378a5032eb4d54ad541d9c9572d859444d8 100644 --- a/sickbeard/providers/hounddawgs.py +++ b/sickbeard/providers/hounddawgs.py @@ -30,7 +30,6 @@ class HoundDawgsProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "HoundDawgs") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/iptorrents.py b/sickbeard/providers/iptorrents.py index 12c29eb991a7c1cfecb980ba437a8d78bc60f239..dc412eb108d29daa376543e889d7c7011a1528b4 100644 --- a/sickbeard/providers/iptorrents.py +++ b/sickbeard/providers/iptorrents.py @@ -28,7 +28,6 @@ class IPTorrentsProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "IPTorrents") - self.supportsBacklog = True self.username = None diff --git a/sickbeard/providers/kat.py b/sickbeard/providers/kat.py index ec62eafa106a4d15b7d5c7f77a5c5a40d80dc51f..0c580a7ad7120454fd7fbac45c2817581382bd06 100755 --- a/sickbeard/providers/kat.py +++ b/sickbeard/providers/kat.py @@ -28,14 +28,14 @@ from sickbeard import logger from sickbeard import tvcache from sickbeard.common import USER_AGENT from sickbeard.providers import generic -from sickbeard.helpers import tryInt +from sickrage.helper.common import try_int + class KATProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "KickAssTorrents") - self.supportsBacklog = True self.public = True self.confirmed = True @@ -115,10 +115,10 @@ class KATProvider(generic.TorrentProvider): if not (title and download_url): continue - 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) + seeders = try_int(item.find('torrent:seeds').text, 0) + leechers = try_int(item.find('torrent:peers').text, 0) + verified = bool(try_int(item.find('torrent:verified').text, 0)) + size = try_int(item.find('torrent:contentlength').text) info_hash = item.find('torrent:infohash').text # link = item['link'] @@ -126,7 +126,6 @@ class KATProvider(generic.TorrentProvider): except (AttributeError, TypeError, KeyError, ValueError): continue - # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': diff --git a/sickbeard/providers/libertalia.py b/sickbeard/providers/libertalia.py index 657f3be0e50398ff0d602e7d20d9beb6c4e3d386..a329f52ccca6db91ac06d22ec92e38aa63b4fd7e 100644 --- a/sickbeard/providers/libertalia.py +++ b/sickbeard/providers/libertalia.py @@ -35,7 +35,6 @@ class LibertaliaProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "Libertalia") - self.supportsBacklog = True self.cj = cookielib.CookieJar() diff --git a/sickbeard/providers/morethantv.py b/sickbeard/providers/morethantv.py index 82ad0842a77235dde2fa4cd4252a2eaa931a2558..253a166f109cc0df4ba60dac6c779e57d684261d 100644 --- a/sickbeard/providers/morethantv.py +++ b/sickbeard/providers/morethantv.py @@ -37,7 +37,6 @@ class MoreThanTVProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "MoreThanTV") - self.supportsBacklog = True self._uid = None self._hash = None diff --git a/sickbeard/providers/newpct.py b/sickbeard/providers/newpct.py index 6365f9640944200b244cf2126e9ac22e88d67dd6..e4350ccfc8dc4906a4af58bd5a1e7dc55e2c1a4b 100644 --- a/sickbeard/providers/newpct.py +++ b/sickbeard/providers/newpct.py @@ -35,7 +35,6 @@ class newpctProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "Newpct") - self.supportsBacklog = True self.onlyspasearch = None self.cache = newpctCache(self) @@ -52,19 +51,19 @@ class newpctProvider(generic.TorrentProvider): """ Search query: - http://www.newpct.com/index.php?l=doSearch&q=fringe&category_=767&idioma_=1&bus_de_=All + http://www.newpct.com/index.php?l=doSearch&q=fringe&category_=All&idioma_=1&bus_de_=All q => Show name category_ = Category "Shows" (767) idioma_ = Language Spanish (1) - bus_de_ = Date from (All, Semana) + bus_de_ = Date from (All, hoy) """ self.search_params = { 'l': 'doSearch', 'q': '', - 'category_': 767, + 'category_': 'All', 'idioma_': 1, 'bus_de_': 'All' } @@ -81,7 +80,7 @@ class newpctProvider(generic.TorrentProvider): 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': + if self.onlyspasearch and lang_info != 'es' and mode != 'RSS': logger.log(u"Show info is not spanish, skipping provider search", logger.DEBUG) continue @@ -89,8 +88,8 @@ class newpctProvider(generic.TorrentProvider): if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - self.search_params['q'] = search_string.strip() if mode is not 'RSS' else '' - self.search_params['bus_de_'] = 'All' if mode is not 'RSS' else 'semana' + self.search_params['q'] = search_string.strip() if mode != 'RSS' else '' + self.search_params['bus_de_'] = 'All' if mode != 'RSS' else 'hoy' searchURL = self.urls['search'] + '?' + urllib.parse.urlencode(self.search_params) logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) @@ -221,21 +220,23 @@ class newpctProvider(generic.TorrentProvider): title = title[22:] # Quality - Use re module to avoid case sensitive problems with replace - title = re.sub('\[HDTV 1080p.*]', '1080p HDTV x264', title, flags=re.IGNORECASE) - title = re.sub('\[HDTV 720p.*]', '720p HDTV x264', title, flags=re.IGNORECASE) + title = re.sub('\[HDTV 1080p[^\[]*]', '1080p HDTV x264', title, flags=re.IGNORECASE) + title = re.sub('\[HDTV 720p[^\[]*]', '720p HDTV x264', title, flags=re.IGNORECASE) + title = re.sub('\[ALTA DEFINICION 720p[^\[]*]', '720p HDTV x264', title, flags=re.IGNORECASE) title = re.sub('\[HDTV]', 'HDTV x264', title, flags=re.IGNORECASE) - title = re.sub('\[DVD.*]', 'DVDrip x264', title, flags=re.IGNORECASE) - title = re.sub('\[BluRay 1080p.*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BluRay MicroHD.*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[MicroHD 1080p.*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BLuRay.*]', '720p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BRrip.*]', '720p BlueRay x264', title, flags=re.IGNORECASE) - title = re.sub('\[BDrip.*]', '720p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub('\[DVD[^\[]*]', 'DVDrip x264', title, flags=re.IGNORECASE) + title = re.sub('\[BluRay 1080p[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub('\[BluRay MicroHD[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub('\[MicroHD 1080p[^\[]*]', '1080p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub('\[BLuRay[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub('\[BRrip[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) + title = re.sub('\[BDrip[^\[]*]', '720p BlueRay x264', title, flags=re.IGNORECASE) #Language - title = re.sub('\[Spanish.*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) - title = re.sub(ur'\[Español.*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) - title = re.sub(u'\[AC3 5\.1 Español.*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) + title = re.sub('\[Spanish[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) + title = re.sub('\[Castellano[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) + title = re.sub(ur'\[Español[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) + title = re.sub(u'\[AC3 5\.1 Español[^\[]*]', 'SPANISH AUDIO', title, flags=re.IGNORECASE) title += '-NEWPCT' diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py index d8b26fe3f0b19992c9f79af6e12a68c1a75492b4..a250100f4ac03d750b0d67d5fb1a097d02fea98d 100644 --- a/sickbeard/providers/newznab.py +++ b/sickbeard/providers/newznab.py @@ -37,6 +37,8 @@ from sickbeard import db from sickbeard.common import Quality from sickbeard.providers import generic from sickrage.helper.encoding import ek +from sickrage.show.Show import Show +from sickrage.helper.common import try_int from sickbeard.common import USER_AGENT @@ -63,7 +65,6 @@ class NewznabProvider(generic.NZBProvider): self.enable_daily = enable_daily self.enable_backlog = enable_backlog - self.supportsBacklog = True # 0 in the key spot indicates that no key is needed self.needs_auth = self.key != '0' @@ -110,7 +111,7 @@ class NewznabProvider(generic.NZBProvider): if self.needs_auth and self.key: params['apikey'] = self.key - url = os.path.join(self.url, 'api?') + urllib.urlencode(params) + url = ek(os.path.join, self.url, 'api?') + urllib.urlencode(params) data = self.getURL(url) if not data: error_string = u"Error getting xml for [%s]" % url @@ -258,7 +259,7 @@ class NewznabProvider(generic.NZBProvider): params['maxage'] = min(params['maxage'], sickbeard.USENET_RETENTION) - search_url = os.path.join(self.url, 'api?') + urllib.urlencode(params) + search_url = ek(os.path.join, self.url, 'api?') + urllib.urlencode(params) logger.log(u"Search url: %s" % search_url, logger.DEBUG) data = self.getURL(search_url) if not data: @@ -286,11 +287,11 @@ class NewznabProvider(generic.NZBProvider): continue seeders = leechers = None - size = helpers.tryInt(item.size, -1) + size = try_int(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 + size = try_int(attr['value'], -1) if attr['name'] == 'size' else size + seeders = try_int(attr['value'], 1) if attr['name'] == 'seeders' else seeders + leechers = try_int(attr['value'], 0) if attr['name'] == 'peers' else leechers if not size or (torznab and (seeders is None or leechers is None)): continue @@ -305,14 +306,12 @@ class NewznabProvider(generic.NZBProvider): return results - def _get_size(self, item): """ Gets size info from a result item Returns int size or -1 """ - return helpers.tryInt(item.get('size', -1), -1) - + return try_int(item.get('size', -1), -1) def findPropers(self, search_date=datetime.datetime.today()): """ @@ -334,7 +333,7 @@ class NewznabProvider(generic.NZBProvider): return results for sqlshow in sqlResults: - self.show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) + self.show = Show.find(sickbeard.showList, int(sqlshow["showid"])) if self.show: curEp = self.show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) searchStrings = self._get_episode_search_strings(curEp, add_string='PROPER|REPACK') diff --git a/sickbeard/providers/nextgen.py b/sickbeard/providers/nextgen.py index 5915ff6a5f999b815dc9bd88e35bb92caa5850b7..172af632dbd7f6b7c752a48ac8869f008304f65b 100644 --- a/sickbeard/providers/nextgen.py +++ b/sickbeard/providers/nextgen.py @@ -32,7 +32,6 @@ class NextGenProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "NextGen") - self.supportsBacklog = True self.username = None diff --git a/sickbeard/providers/nyaatorrents.py b/sickbeard/providers/nyaatorrents.py index 1b58081c1697783adab1a487496cec72168a57ac..75943032c72c0aade3e564aaf48b7ce01ca7f6d3 100644 --- a/sickbeard/providers/nyaatorrents.py +++ b/sickbeard/providers/nyaatorrents.py @@ -29,7 +29,6 @@ class NyaaProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "NyaaTorrents") - self.supportsBacklog = True self.public = True self.supportsAbsoluteNumbering = True self.anime_only = True diff --git a/sickbeard/providers/omgwtfnzbs.py b/sickbeard/providers/omgwtfnzbs.py index 775061259bcb461e15cb85774571944815fc0d3b..edf5b00dc91a60e34ae252568491e1de557e33fc 100644 --- a/sickbeard/providers/omgwtfnzbs.py +++ b/sickbeard/providers/omgwtfnzbs.py @@ -25,6 +25,7 @@ from sickbeard import classes from sickbeard import logger from sickbeard import show_name_helpers from sickbeard.providers import generic +from sickrage.helper.common import try_int class OmgwtfnzbsProvider(generic.NZBProvider): @@ -38,7 +39,6 @@ class OmgwtfnzbsProvider(generic.NZBProvider): self.urls = {'base_url': 'https://omgwtfnzbs.org/'} self.url = self.urls['base_url'] - self.supportsBacklog = True def _checkAuth(self): @@ -83,12 +83,7 @@ class OmgwtfnzbsProvider(generic.NZBProvider): return (item['release'], item['getnzb']) def _get_size(self, item): - try: - size = int(item['sizebytes']) - except (ValueError, TypeError, AttributeError, KeyError): - return -1 - - return size + return try_int(item['sizebytes'], -1) def _doSearch(self, search, search_mode='eponly', epcount=0, retention=0, epObj=None): diff --git a/sickbeard/providers/pretome.py b/sickbeard/providers/pretome.py index 30dbac2d2efaa2d74f989ae7ab462f28c16315f0..4164e1826f1e5b9ebda4db91651798ef10e4fedb 100644 --- a/sickbeard/providers/pretome.py +++ b/sickbeard/providers/pretome.py @@ -32,7 +32,6 @@ class PretomeProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "Pretome") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/rarbg.py b/sickbeard/providers/rarbg.py index f2b2bb362f26604ff8559eacfa3625d7b8456ee9..6227b7362bd1b85f29d298bf757870a81e7819fd 100644 --- a/sickbeard/providers/rarbg.py +++ b/sickbeard/providers/rarbg.py @@ -39,7 +39,6 @@ class RarbgProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "Rarbg") - self.supportsBacklog = True self.public = True self.ratio = None self.minseed = None diff --git a/sickbeard/providers/rsstorrent.py b/sickbeard/providers/rsstorrent.py index 434174d3654945b27a5c5e8f8acca359e1470317..deb0d2e681e808eb841cdf977d4552cef50dd2b0 100644 --- a/sickbeard/providers/rsstorrent.py +++ b/sickbeard/providers/rsstorrent.py @@ -65,7 +65,7 @@ class TorrentRssProvider(generic.TorrentProvider): ) def imageName(self): - if os.path.isfile(ek(os.path.join, sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME, 'images', 'providers', self.getID() + '.png')): + if ek(os.path.isfile, ek(os.path.join, sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME, 'images', 'providers', self.getID() + '.png')): return self.getID() + '.png' return 'torrentrss.png' diff --git a/sickbeard/providers/scc.py b/sickbeard/providers/scc.py index c02ac02d5a63bd6fb2dc7b404fbf5d7525129953..7fcfa6138696c60dbe19019ee94d182e0f292d5e 100644 --- a/sickbeard/providers/scc.py +++ b/sickbeard/providers/scc.py @@ -34,7 +34,6 @@ class SCCProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "SceneAccess") - self.supportsBacklog = True self.username = None diff --git a/sickbeard/providers/scenetime.py b/sickbeard/providers/scenetime.py index c4136a07c4fc8a385fce958b5a2b1dc36e35ceda..139e8873ea9a0d5d7eb7f2250868bd1a88c29344 100644 --- a/sickbeard/providers/scenetime.py +++ b/sickbeard/providers/scenetime.py @@ -32,7 +32,6 @@ class SceneTimeProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "SceneTime") - self.supportsBacklog = True self.username = None diff --git a/sickbeard/providers/speedcd.py b/sickbeard/providers/speedcd.py index b5fe4feab1f0d11e383934e3c1e9f06172bb4cbb..21498130ad4b6fa305f2f2522a5fa014e73b7d36 100644 --- a/sickbeard/providers/speedcd.py +++ b/sickbeard/providers/speedcd.py @@ -29,7 +29,6 @@ class SpeedCDProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "Speedcd") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/strike.py b/sickbeard/providers/strike.py index a761eb5b893df139042b0afce2c2c95a6f9e05f1..478746cf5599a199869054f7967ff2d384f21f5d 100644 --- a/sickbeard/providers/strike.py +++ b/sickbeard/providers/strike.py @@ -26,7 +26,6 @@ class STRIKEProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "Strike") - self.supportsBacklog = True self.public = True self.url = 'https://getstrike.net/' self.ratio = 0 diff --git a/sickbeard/providers/t411.py b/sickbeard/providers/t411.py index 55375b41a8fff87e4a3c5844b70ed59a12af272b..9c45f29275d416126c784025c527b4ce3307c7de 100644 --- a/sickbeard/providers/t411.py +++ b/sickbeard/providers/t411.py @@ -30,7 +30,6 @@ class T411Provider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "T411") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/thepiratebay.py b/sickbeard/providers/thepiratebay.py index 159a375cef615d116630c7d9a535c4035b50e98d..c0cdcf9fb3640ee68b341ea3bc6cd643a3a04690 100644 --- a/sickbeard/providers/thepiratebay.py +++ b/sickbeard/providers/thepiratebay.py @@ -31,7 +31,6 @@ class ThePirateBayProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "ThePirateBay") - self.supportsBacklog = True self.public = True self.ratio = None diff --git a/sickbeard/providers/titansoftv.py b/sickbeard/providers/titansoftv.py index cea172b8bd6cd41ed9a5afab296725cd490a8e14..7f77f7c88ed438ad44943e30a10c3af245422249 100644 --- a/sickbeard/providers/titansoftv.py +++ b/sickbeard/providers/titansoftv.py @@ -29,7 +29,6 @@ from sickrage.helper.exceptions import AuthException class TitansOfTVProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, 'TitansOfTV') - self.supportsBacklog = True self.supportsAbsoluteNumbering = True self.api_key = None diff --git a/sickbeard/providers/tntvillage.py b/sickbeard/providers/tntvillage.py index c00842148d894428c82c6f309e7afa44dc9d37f7..047aa36ac09f14748c06d92469e73df6c751d711 100644 --- a/sickbeard/providers/tntvillage.py +++ b/sickbeard/providers/tntvillage.py @@ -60,7 +60,6 @@ class TNTVillageProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, "TNTVillage") - self.supportsBacklog = True self._uid = None self._hash = None diff --git a/sickbeard/providers/tokyotoshokan.py b/sickbeard/providers/tokyotoshokan.py index 6a078faa8de2c8459c887366350a89d79b1d4992..17f6cd66505889cf3a88dfc5877e5e4225d773f1 100644 --- a/sickbeard/providers/tokyotoshokan.py +++ b/sickbeard/providers/tokyotoshokan.py @@ -31,7 +31,6 @@ class TokyoToshokanProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "TokyoToshokan") - self.supportsBacklog = True self.public = True self.supportsAbsoluteNumbering = True self.anime_only = True diff --git a/sickbeard/providers/torrentbytes.py b/sickbeard/providers/torrentbytes.py index 0a0d3a68a453a52169ef4ee46912320b052ea94d..0f32e03d32870443fe998e12139ab368471f2c89 100644 --- a/sickbeard/providers/torrentbytes.py +++ b/sickbeard/providers/torrentbytes.py @@ -32,7 +32,6 @@ class TorrentBytesProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "TorrentBytes") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/torrentday.py b/sickbeard/providers/torrentday.py index 62550dab1691fff5d1d277d98ce36e2beb7ced46..ea1b67e6b56555a623241814a89551c369867f9e 100644 --- a/sickbeard/providers/torrentday.py +++ b/sickbeard/providers/torrentday.py @@ -27,7 +27,6 @@ class TorrentDayProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "TorrentDay") - self.supportsBacklog = True self._uid = None diff --git a/sickbeard/providers/torrentleech.py b/sickbeard/providers/torrentleech.py index 7ccac63bb57895d8fd88f7fac4a85e164b373c66..a969138c694c4e66fbe65446a21db0be6db1a91f 100644 --- a/sickbeard/providers/torrentleech.py +++ b/sickbeard/providers/torrentleech.py @@ -32,7 +32,6 @@ class TorrentLeechProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "TorrentLeech") - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/torrentproject.py b/sickbeard/providers/torrentproject.py index 2f84b81c8309da3ba1b787babf7bfc507026e772..b7df83d7e62b533561d7f71044ba2e62584b50d1 100644 --- a/sickbeard/providers/torrentproject.py +++ b/sickbeard/providers/torrentproject.py @@ -20,17 +20,15 @@ from urllib import quote_plus from sickbeard import logger from sickbeard import tvcache -from sickbeard import helpers from sickbeard.providers import generic from sickbeard.common import USER_AGENT +from sickrage.helper.common import try_int class TORRENTPROJECTProvider(generic.TorrentProvider): - def __init__(self): generic.TorrentProvider.__init__(self, "TorrentProject") - self.supportsBacklog = True self.public = True self.ratio = 0 self.urls = {'api': u'https://torrentproject.se/',} @@ -65,8 +63,8 @@ class TORRENTPROJECTProvider(generic.TorrentProvider): results = [] for i in torrents: title = torrents[i]["title"] - seeders = helpers.tryInt(torrents[i]["seeds"], 1) - leechers = helpers.tryInt(torrents[i]["leechs"], 0) + seeders = try_int(torrents[i]["seeds"], 1) + leechers = try_int(torrents[i]["leechs"], 0) if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': logger.log(u"Torrent doesn't meet minimum seeds & leechers not selecting : %s" % title, logger.DEBUG) diff --git a/sickbeard/providers/torrentz.py b/sickbeard/providers/torrentz.py index 576a8ca114fea8a6aef3a229e2cb20761e4d8e2a..6282d965c60ade8d7670ea2cfc92d000d436de80 100644 --- a/sickbeard/providers/torrentz.py +++ b/sickbeard/providers/torrentz.py @@ -37,7 +37,6 @@ class TORRENTZProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "Torrentz") self.public = True - self.supportsBacklog = True self.confirmed = True self.ratio = None self.minseed = None diff --git a/sickbeard/providers/transmitthenet.py b/sickbeard/providers/transmitthenet.py index 2049b638ddb4b600d2425a1e32268f8db2d1c2c4..bd534091e190ddb2271122308758200bdb8bc629 100644 --- a/sickbeard/providers/transmitthenet.py +++ b/sickbeard/providers/transmitthenet.py @@ -22,7 +22,7 @@ from sickbeard import tvcache from sickbeard.bs4_parser import BS4Parser from sickbeard.providers import generic from sickrage.helper.exceptions import AuthException - +from sickrage.helper.common import try_int class TransmitTheNetProvider(generic.TorrentProvider): def __init__(self): @@ -31,27 +31,21 @@ class TransmitTheNetProvider(generic.TorrentProvider): self.urls = { 'base_url': 'https://transmithe.net/', - 'index': 'https://transmithe.net/index.php', + 'login': 'https://transmithe.net/login.php', + 'search': 'https://transmithe.net/torrents.php', } self.url = self.urls['base_url'] - self.supportsBacklog = True - self.username = None self.password = None self.ratio = None self.minseed = None self.minleech = None + self.freeleech = None self.cache = TransmitTheNetCache(self) - self.search_params = { - "page": 'torrents', - "category": 0, - "active": 1 - } - def _checkAuth(self): if not self.username or not self.password: @@ -62,13 +56,13 @@ class TransmitTheNetProvider(generic.TorrentProvider): def _doLogin(self): login_params = { - 'uid': self.username, - 'pwd': self.password, - 'remember_me': 'on', - 'login': 'submit' + 'username': self.username, + 'password': self.password, + 'keeplogged': 'on', + 'login': 'Login' } - response = self.getURL(self.urls['index'], params={'page': 'login'}, post_data=login_params, timeout=30) + 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 @@ -93,40 +87,48 @@ class TransmitTheNetProvider(generic.TorrentProvider): if mode != 'RSS': logger.log(u"Search string: %s " % search_string, logger.DEBUG) - data = self.getURL(self.urls['index'], params=self.search_params) - searchURL = self.urls['index'] + "?" + urlencode(self.search_params) + search_params = { + 'searchtext': search_string, + 'filter_freeleech': (0, 1)[self.freeleech is True], + 'order_by': ('seeders', 'time')[mode == 'RSS'], + "order_way": "desc" + } + + if not search_string: + del search_params['searchtext'] + + searchURL = self.urls['search'] + "?" + urlencode(search_params) logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + data = self.getURL(self.urls['search'], params=search_params) if not data: logger.log(u"No data returned from provider", logger.DEBUG) continue try: - with BS4Parser(data) as html: - - torrent_rows = [] - - down_elems = html.findAll("img", {"alt": "Download Torrent"}) - for down_elem in down_elems: - if down_elem: - torr_row = down_elem.findParent('tr') - if torr_row: - torrent_rows.append(torr_row) - + with BS4Parser(data, features=["html5lib", "permissive"]) as html: + torrent_table = html.find('table', {'id':'torrent_table'}) + torrent_rows = torrent_table.findAll('tr', {'class':'torrent'}) # Continue only if one Release is found - if len(torrent_rows) < 1: - logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + if not torrent_rows: + logger.log(u"Data returned from %s does not contain any torrents" % self.name, logger.DEBUG) continue for torrent_row in torrent_rows: + freeleech = torrent_row.find('img', alt="Freeleech") is not None + if self.freeleech and not freeleech: + continue + + download_url = self.urls['base_url'] + torrent_row.find('a', {"title": 'Download Torrent'})['href'] + + temp_anchor = torrent_row.find('a', {"data-src": True}) + title = temp_anchor['data-src'].rsplit('.', 1)[0] + size = try_int(temp_anchor['data-filesize']) + + temp_anchor = torrent_row.find('span', class_='time').parent.find_next_sibling() + seeders = try_int(temp_anchor.text.strip()) + leechers = try_int(temp_anchor.find_next_sibling().text.strip()) - title = torrent_row.find('a', {"data-src": True})['data-src'].rsplit('.', 1)[0] - download_href = torrent_row.find('img', {"alt": 'Download Torrent'}).findParent()['href'] - seeders = int(torrent_row.findAll('a', {'title': 'Click here to view peers details'})[0].text.strip()) - leechers = int(torrent_row.findAll('a', {'title': 'Click here to view peers details'})[1].text.strip()) - download_url = self.urls['base_url'] + download_href - # FIXME - size = -1 if not all([title, download_url]): continue diff --git a/sickbeard/providers/tvchaosuk.py b/sickbeard/providers/tvchaosuk.py index dee2176c41791fff58c385a2e5429c28f4b228b4..fac4c38c23a3f2d83ac43cd7a35a055896afb8ba 100644 --- a/sickbeard/providers/tvchaosuk.py +++ b/sickbeard/providers/tvchaosuk.py @@ -37,7 +37,6 @@ class TVChaosUKProvider(generic.TorrentProvider): self.url = self.urls['base_url'] - self.supportsBacklog = True self.username = None self.password = None diff --git a/sickbeard/providers/womble.py b/sickbeard/providers/womble.py index 136d649fc061ebb986ef2ad728c054bf5ed9772f..7a3d1ef902359dd7df7341d1505b617f05efc8f0 100644 --- a/sickbeard/providers/womble.py +++ b/sickbeard/providers/womble.py @@ -29,7 +29,7 @@ class WombleProvider(generic.NZBProvider): self.cache = WombleCache(self) self.urls = {'base_url': 'http://newshost.co.za/'} self.url = self.urls['base_url'] - + self.supportsBacklog = False class WombleCache(tvcache.TVCache): def __init__(self, provider_obj): diff --git a/sickbeard/providers/xthor.py b/sickbeard/providers/xthor.py index 2805fbe5b6270d4f4b9cf6ea7511fab4600510a4..bc40a10e342f5d4303451ced417115c1af80ddb9 100644 --- a/sickbeard/providers/xthor.py +++ b/sickbeard/providers/xthor.py @@ -33,7 +33,6 @@ class XthorProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, "Xthor") - self.supportsBacklog = True self.cj = cookielib.CookieJar() diff --git a/sickbeard/scene_numbering.py b/sickbeard/scene_numbering.py index d4bb071d542420c676dcd2580a7dfd04b1b50f4a..23ba5f06c53477973a852cad0cc4817f54039453 100644 --- a/sickbeard/scene_numbering.py +++ b/sickbeard/scene_numbering.py @@ -29,8 +29,8 @@ import traceback import sickbeard from sickbeard import logger from sickbeard import db -from sickbeard import helpers from sickrage.helper.exceptions import ex +from sickrage.show.Show import Show def get_scene_numbering(indexer_id, indexer, season, episode, fallback_to_xem=True): @@ -49,7 +49,7 @@ def get_scene_numbering(indexer_id, indexer, season, episode, fallback_to_xem=Tr if indexer_id is None or season is None or episode is None: return (season, episode) - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(indexer_id)) + showObj = Show.find(sickbeard.showList, int(indexer_id)) if showObj and not showObj.is_scene: return (season, episode) @@ -101,7 +101,7 @@ def get_scene_absolute_numbering(indexer_id, indexer, absolute_number, fallback_ indexer_id = int(indexer_id) indexer = int(indexer) - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, indexer_id) + showObj = Show.find(sickbeard.showList, indexer_id) if showObj and not showObj.is_scene: return absolute_number @@ -219,7 +219,7 @@ def set_scene_numbering(indexer_id, indexer, season=None, episode=None, absolute [sceneAbsolute, indexer, indexer_id, absolute_number]) # Reload data from DB so that cache and db are in sync - show = helpers.findCertainShow(sickbeard.showList, indexer_id) + show = Show.find(sickbeard.showList, indexer_id) show.flushEpisodes() diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py index 9cec79e7257cbdbd4844e319cfb7ffba7faf8995..457b6f8ed5da22b3de640cbbcbcb3af2defa75b6 100644 --- a/sickbeard/show_queue.py +++ b/sickbeard/show_queue.py @@ -133,7 +133,7 @@ class ShowQueue(generic_queue.GenericQueue): return queueItemObj - def downloadSubtitles(self, show, force=False): + def download_subtitles(self, show, force=False): queueItemObj = QueueItemSubtitle(show) @@ -560,7 +560,7 @@ class QueueItemSubtitle(ShowQueueItem): logger.log(u"Downloading subtitles for " + self.show.name) - self.show.downloadSubtitles() + self.show.download_subtitles() self.finish() class QueueItemUpdate(ShowQueueItem): diff --git a/sickbeard/subtitles.py b/sickbeard/subtitles.py index 402e6a61683c88b1faa4e771fba34ccbe26c9812..b862646d75683cfb7934f954b633cfc0ae987d54 100644 --- a/sickbeard/subtitles.py +++ b/sickbeard/subtitles.py @@ -1,5 +1,7 @@ -# Author: Nyaran <nyayukko@gmail.com>, based on Antoine Bertin <diaoulael@gmail.com> work -# URL: http://code.google.com/p/sickbeard/ +# Author: medariox <dariox@gmx.com>, +# based on Antoine Bertin's <diaoulael@gmail.com> work +# and originally written by Nyaran <nyayukko@gmail.com> +# URL: https://github.com/SickRage/SickRage/ # # This file is part of SickRage. # @@ -16,7 +18,6 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. -import io import os import re import datetime @@ -25,9 +26,8 @@ import subliminal import subprocess import pkg_resources import sickbeard -from enzyme import MKV, MalformedMKVError from subliminal.api import provider_manager -from babelfish import Error as BabelfishError, Language, language_converters +from babelfish import Language, language_converters from sickbeard import logger from sickbeard import history from sickbeard import db @@ -35,11 +35,12 @@ from sickbeard import processTV from sickrage.helper.common import media_extensions, dateTimeFormat from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex +from sickrage.show.Show import Show -distribution = pkg_resources.Distribution(location=os.path.dirname(os.path.dirname(__file__)), +DISTRIBUTION = pkg_resources.Distribution(location=os.path.dirname(os.path.dirname(__file__)), project_name='fake_entry_points', version='1.0.0') -entry_points = { +ENTRY_POINTS = { 'subliminal.providers': [ 'addic7ed = subliminal.providers.addic7ed:Addic7edProvider', 'legendastv = subliminal.providers.legendastv:LegendasTvProvider', @@ -59,14 +60,14 @@ entry_points = { # pylint: disable=W0212 # Access to a protected member of a client class -distribution._ep_map = pkg_resources.EntryPoint.parse_map(entry_points, distribution) -pkg_resources.working_set.add(distribution) +DISTRIBUTION._ep_map = pkg_resources.EntryPoint.parse_map(ENTRY_POINTS, DISTRIBUTION) +pkg_resources.working_set.add(DISTRIBUTION) provider_manager.ENTRY_POINT_CACHE.pop('subliminal.providers') subliminal.region.configure('dogpile.cache.memory') -provider_urls = { +PROVIDER_URLS = { 'addic7ed': 'http://www.addic7ed.com', 'legendastv': 'http://www.legendas.tv', 'napiprojekt': 'http://www.napiprojekt.pl', @@ -77,154 +78,195 @@ provider_urls = { } -def sortedServiceList(): - newList = [] +def sorted_service_list(): + new_list = [] lmgtfy = 'http://lmgtfy.com/?q=%s' - curIndex = 0 - for curService in sickbeard.SUBTITLES_SERVICES_LIST: - if curService in subliminal.provider_manager.names(): - newList.append({'name': curService, - 'url': provider_urls[curService] if curService in provider_urls else lmgtfy % curService, - 'image': curService + '.png', - 'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[curIndex] == 1 - }) - curIndex += 1 - - for curService in subliminal.provider_manager.names(): - if curService not in [x['name'] for x in newList]: - newList.append({'name': curService, - 'url': provider_urls[curService] if curService in provider_urls else lmgtfy % curService, - 'image': curService + '.png', - 'enabled': False, - }) - - return newList - -def getEnabledServiceList(): - return [x['name'] for x in sortedServiceList() if x['enabled']] + current_index = 0 + for current_service in sickbeard.SUBTITLES_SERVICES_LIST: + if current_service in subliminal.provider_manager.names(): + new_list.append({'name': current_service, + 'url': PROVIDER_URLS[current_service] if current_service in PROVIDER_URLS else + lmgtfy % current_service, + 'image': current_service + '.png', + 'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[current_index] == 1 + }) + current_index += 1 + + for current_service in subliminal.provider_manager.names(): + if current_service not in [service['name'] for service in new_list]: + new_list.append({'name': current_service, + 'url': PROVIDER_URLS[current_service] if current_service in PROVIDER_URLS else + lmgtfy % current_service, + 'image': current_service + '.png', + 'enabled': False, + }) + + return new_list + + +def enabled_service_list(): + return [service['name'] for service in sorted_service_list() if service['enabled']] + + +def wanted_languages(sql_like=None): + wanted = frozenset(sickbeard.SUBTITLES_LANGUAGES).intersection(subtitle_code_filter()) + return (wanted, '%' + ','.join(wanted) + '%')[bool(sql_like)] + + +def get_needed_languages(subtitles): + return {from_code(language) for language in wanted_languages().difference(subtitles)} + + +def subtitle_code_filter(): + return {code for code in language_converters['opensubtitles'].codes if len(code) == 3} + + +def needs_subtitles(subtitles): + if isinstance(subtitles, basestring): + subtitles = {subtitle.strip() for subtitle in subtitles.split(',')} + + if sickbeard.SUBTITLES_MULTI: + return len(wanted_languages().difference(subtitles)) > 0 + else: + return len(subtitles) == 0 + # Hack around this for now. -def fromietf(language): - return Language.fromopensubtitles(language) +def from_code(language): + language = language.strip() + if language not in language_converters['opensubtitles'].codes: + return Language('und') -def isValidLanguage(language): - try: - fromietf(language) - except Exception: - return False - return True + return Language.fromopensubtitles(language) # pylint: disable=no-member -def getLanguageName(language): - return fromietf(language).name -def downloadSubtitles(subtitles_info): +def name_from_code(code): + return from_code(code).name + + +def code_from_code(code): + return from_code(code).opensubtitles + + +def download_subtitles(subtitles_info): existing_subtitles = subtitles_info['subtitles'] - # First of all, check if we need subtitles - languages = getNeededLanguages(existing_subtitles) + + if not needs_subtitles(existing_subtitles): + logger.log(u'Episode already has all needed subtitles, skipping episode %dx%d of show %s' + % (subtitles_info['season'], subtitles_info['episode'], subtitles_info['show_name']), logger.DEBUG) + return (existing_subtitles, None) + + # Check if we really need subtitles + languages = get_needed_languages(existing_subtitles) if not languages: - logger.log(u'%s: No missing subtitles for S%02dE%02d' % (subtitles_info['show.indexerid'], subtitles_info['season'], subtitles_info['episode']), logger.DEBUG) + logger.log(u'No subtitles needed for %s S%02dE%02d' + % (subtitles_info['show_name'], subtitles_info['season'], + subtitles_info['episode']), logger.DEBUG) return (existing_subtitles, None) - subtitles_path = getSubtitlesPath(subtitles_info['location']).encode(sickbeard.SYS_ENCODING) + subtitles_path = get_subtitles_path(subtitles_info['location']).encode(sickbeard.SYS_ENCODING) video_path = subtitles_info['location'].encode(sickbeard.SYS_ENCODING) - providers = getEnabledServiceList() - try: - video = subliminal.scan_video(video_path, subtitles=False, embedded_subtitles=False) - except Exception: - logger.log(u'%s: Exception caught in subliminal.scan_video for S%02dE%02d' % - (subtitles_info['show.indexerid'], subtitles_info['season'], subtitles_info['episode']), logger.DEBUG) + video = get_video(video_path, subtitles_path=subtitles_path) + if not video: + logger.log(u'Exception caught in subliminal.scan_video for %s S%02dE%02d' + % (subtitles_info['show_name'], subtitles_info['season'], + subtitles_info['episode']), logger.DEBUG) return (existing_subtitles, None) - provider_configs = {'addic7ed': {'username': sickbeard.ADDIC7ED_USER, 'password': sickbeard.ADDIC7ED_PASS}, - 'legendastv': {'username': sickbeard.LEGENDASTV_USER, 'password': sickbeard.LEGENDASTV_PASS}, - 'opensubtitles': {'username': sickbeard.OPENSUBTITLES_USER, 'password': sickbeard.OPENSUBTITLES_PASS}} + providers = enabled_service_list() + provider_configs = {'addic7ed': {'username': sickbeard.ADDIC7ED_USER, + 'password': sickbeard.ADDIC7ED_PASS}, + 'legendastv': {'username': sickbeard.LEGENDASTV_USER, + 'password': sickbeard.LEGENDASTV_PASS}, + 'opensubtitles': {'username': sickbeard.OPENSUBTITLES_USER, + 'password': sickbeard.OPENSUBTITLES_PASS}} pool = subliminal.api.ProviderPool(providers=providers, provider_configs=provider_configs) try: subtitles_list = pool.list_subtitles(video, languages) if not subtitles_list: - logger.log(u'%s: No subtitles found for S%02dE%02d on any provider' % (subtitles_info['show.indexerid'], subtitles_info['season'], subtitles_info['episode']), logger.DEBUG) + logger.log(u'No subtitles found for %s S%02dE%02d on any provider' + % (subtitles_info['show_name'], subtitles_info['season'], + subtitles_info['episode']), logger.DEBUG) return (existing_subtitles, None) - found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages, hearing_impaired=sickbeard.SUBTITLES_HEARING_IMPAIRED, only_one=not sickbeard.SUBTITLES_MULTI) - - save_subtitles(video, found_subtitles, directory=subtitles_path, single=not sickbeard.SUBTITLES_MULTI) + found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages, + hearing_impaired=sickbeard.SUBTITLES_HEARING_IMPAIRED, + only_one=not sickbeard.SUBTITLES_MULTI) - if not sickbeard.EMBEDDED_SUBTITLES_ALL and sickbeard.SUBTITLES_EXTRA_SCRIPTS and video_path.rsplit(".", 1)[1] in media_extensions: - run_subs_extra_scripts(subtitles_info, found_subtitles, video, single=not sickbeard.SUBTITLES_MULTI) - - current_subtitles = subtitlesLanguages(video_path)[0] - new_subtitles = frozenset(current_subtitles).difference(existing_subtitles) + subliminal.save_subtitles(video, found_subtitles, directory=subtitles_path, + single=not sickbeard.SUBTITLES_MULTI) except Exception: logger.log(u"Error occurred when downloading subtitles for: %s" % video_path) logger.log(traceback.format_exc(), logger.ERROR) return (existing_subtitles, None) + for subtitle in found_subtitles: + subtitle_path = subliminal.subtitle.get_subtitle_path(video.name, + None if not sickbeard.SUBTITLES_MULTI else + subtitle.language) + if subtitles_path is not None: + subtitle_path = ek(os.path.join, subtitles_path, ek(os.path.split, subtitle_path)[1]) + sickbeard.helpers.chmodAsParent(subtitle_path) + sickbeard.helpers.fixSetGroupID(subtitle_path) + + if (not sickbeard.EMBEDDED_SUBTITLES_ALL and sickbeard.SUBTITLES_EXTRA_SCRIPTS and + video_path.rsplit(".", 1)[1] in media_extensions): + run_subs_extra_scripts(subtitles_info, found_subtitles, video, single=not sickbeard.SUBTITLES_MULTI) + + current_subtitles = [subtitle.language.opensubtitles for subtitle in found_subtitles] + new_subtitles = frozenset(current_subtitles).difference(existing_subtitles) + current_subtitles += existing_subtitles + if sickbeard.SUBTITLES_HISTORY: for subtitle in found_subtitles: - logger.log(u'history.logSubtitle %s, %s' % (subtitle.provider_name, subtitle.language.opensubtitles), logger.DEBUG) - history.logSubtitle(subtitles_info['show.indexerid'], subtitles_info['season'], subtitles_info['episode'], subtitles_info['status'], subtitle) + logger.log(u'history.logSubtitle %s, %s' % + (subtitle.provider_name, subtitle.language.opensubtitles), logger.DEBUG) - return (current_subtitles, new_subtitles) + history.logSubtitle(subtitles_info['show_indexerid'], subtitles_info['season'], + subtitles_info['episode'], subtitles_info['status'], subtitle) -def save_subtitles(video, subtitles, single=False, directory=None): - saved_subtitles = [] - for subtitle in subtitles: - # check content - if subtitle.content is None: - logger.log(u"Skipping subtitle for %s: no content" % video.name, logger.DEBUG) - continue - - # check language - if subtitle.language in set(s.language for s in saved_subtitles): - logger.log(u"Skipping subtitle for %s: language already saved" % video.name, logger.DEBUG) - continue - - # create subtitle path - subtitle_path = subliminal.subtitle.get_subtitle_path(video.name, None if single else subtitle.language) - if directory is not None: - subtitle_path = os.path.join(directory, os.path.split(subtitle_path)[1]) - - # save content as is or in the specified encoding - logger.log(u"Saving subtitle for %s to %s" % (video.name, subtitle_path), logger.DEBUG) - if subtitle.encoding: - with io.open(subtitle_path, 'w', encoding=subtitle.encoding) as f: - f.write(subtitle.text) - else: - with io.open(subtitle_path, 'wb') as f: - f.write(subtitle.content) + return (current_subtitles, new_subtitles) - # chmod and set group for the saved subtitle - sickbeard.helpers.chmodAsParent(subtitle_path) - sickbeard.helpers.fixSetGroupID(subtitle_path) - # check single - if single: - break +def refresh_subtitles(episode_info, existing_subtitles): + video = get_video(episode_info['location'].encode(sickbeard.SYS_ENCODING)) + if not video: + logger.log(u"Exception caught in subliminal.scan_video, subtitles couldn't be refreshed", logger.DEBUG) + return (existing_subtitles, None) + current_subtitles = get_subtitles(video) + if existing_subtitles == current_subtitles: + logger.log(u'No changed subtitles for %s S%02dE%02d' + % (episode_info['show_name'], episode_info['season'], + episode_info['episode']), logger.DEBUG) + return (existing_subtitles, None) + else: + return (current_subtitles, True) - return saved_subtitles -def getNeededLanguages(current_subtitles): - languages = set() - for language in frozenset(wantedLanguages()).difference(current_subtitles): - languages.add(fromietf(language)) +def get_video(video_path, subtitles_path=None): + if not subtitles_path: + subtitles_path = get_subtitles_path(video_path).encode(sickbeard.SYS_ENCODING) - return languages + try: + if not sickbeard.EMBEDDED_SUBTITLES_ALL and video_path.endswith('.mkv'): + video = subliminal.scan_video(video_path, subtitles=True, embedded_subtitles=True, + subtitles_dir=subtitles_path) + else: + video = subliminal.scan_video(video_path, subtitles=True, embedded_subtitles=False, + subtitles_dir=subtitles_path) + except Exception: + return None -# TODO: Filter here for non-languages in sickbeard.SUBTITLES_LANGUAGES -def wantedLanguages(sqlLike=False): - wanted = [x for x in sorted(sickbeard.SUBTITLES_LANGUAGES) if x in subtitleCodeFilter()] - if sqlLike: - return '%' + ','.join(wanted) + '%' + return video - return wanted -def getSubtitlesPath(video_path): - if os.path.isabs(sickbeard.SUBTITLES_DIR): +def get_subtitles_path(video_path): + if ek(os.path.isabs, sickbeard.SUBTITLES_DIR): new_subtitles_path = sickbeard.SUBTITLES_DIR elif sickbeard.SUBTITLES_DIR: new_subtitles_path = ek(os.path.join, ek(os.path.dirname, video_path), sickbeard.SUBTITLES_DIR) @@ -238,110 +280,21 @@ def getSubtitlesPath(video_path): return new_subtitles_path -def subtitlesLanguages(video_path): - """Return a list detected subtitles for the given video file""" - resultList = [] - should_save_subtitles = None - if not sickbeard.EMBEDDED_SUBTITLES_ALL and video_path.endswith('.mkv'): - embedded_subtitle_languages = getEmbeddedLanguages(video_path.encode(sickbeard.SYS_ENCODING)) +def get_subtitles(video): + """Return a sorted list of detected subtitles for the given video file""" - # Search subtitles with the absolute path - if os.path.isabs(sickbeard.SUBTITLES_DIR): - video_path = ek(os.path.join, sickbeard.SUBTITLES_DIR, ek(os.path.basename, video_path)) - # Search subtitles with the relative path - elif sickbeard.SUBTITLES_DIR: - check_subtitles_path = ek(os.path.join, ek(os.path.dirname, video_path), sickbeard.SUBTITLES_DIR) - if not os.path.exists(check_subtitles_path): - getSubtitlesPath(video_path) - video_path = ek(os.path.join, ek(os.path.dirname, video_path), sickbeard.SUBTITLES_DIR, ek(os.path.basename, video_path)) - else: - video_path = ek(os.path.join, ek(os.path.dirname, video_path), ek(os.path.basename, video_path)) - - if not sickbeard.EMBEDDED_SUBTITLES_ALL and video_path.endswith('.mkv'): - external_subtitle_languages = scan_subtitle_languages(video_path) - subtitle_languages = external_subtitle_languages.union(embedded_subtitle_languages) - if not sickbeard.SUBTITLES_MULTI: - currentWantedLanguages = wantedLanguages() - if len(currentWantedLanguages) == 1 and Language('und') in external_subtitle_languages: - if embedded_subtitle_languages not in currentWantedLanguages and Language('und') in embedded_subtitle_languages: - subtitle_languages.add(fromietf(currentWantedLanguages[0])) - should_save_subtitles = True - elif embedded_subtitle_languages not in currentWantedLanguages and Language('und') not in embedded_subtitle_languages: - subtitle_languages.remove(Language('und')) - subtitle_languages.add(fromietf(currentWantedLanguages[0])) - should_save_subtitles = True - else: - subtitle_languages = scan_subtitle_languages(video_path) - if not sickbeard.SUBTITLES_MULTI: - if len(wantedLanguages()) == 1 and Language('und') in subtitle_languages: - subtitle_languages.remove(Language('und')) - subtitle_languages.add(fromietf(wantedLanguages()[0])) - should_save_subtitles = True - - for language in subtitle_languages: + result_list = [] + + if not video.subtitle_languages: + return result_list + + for language in video.subtitle_languages: if hasattr(language, 'opensubtitles') and language.opensubtitles: - resultList.append(language.opensubtitles) - elif hasattr(language, 'alpha3b') and language.alpha3b: - resultList.append(language.alpha3b) - elif hasattr(language, 'alpha3t') and language.alpha3t: - resultList.append(language.alpha3t) - elif hasattr(language, 'alpha2') and language.alpha2: - resultList.append(language.alpha2) - - return (sorted(resultList), should_save_subtitles) - -def getEmbeddedLanguages(video_path): - embedded_subtitle_languages = set() - try: - with io.open(video_path, 'rb') as f: - mkv = MKV(f) - if mkv.subtitle_tracks: - for st in mkv.subtitle_tracks: - if st.language: - try: - embedded_subtitle_languages.add(Language.fromalpha3b(st.language)) - except BabelfishError: - logger.log(u'Embedded subtitle track is not a valid language', logger.DEBUG) - embedded_subtitle_languages.add(Language('und')) - elif st.name: - try: - embedded_subtitle_languages.add(Language.fromname(st.name)) - except BabelfishError: - logger.log(u'Embedded subtitle track is not a valid language', logger.DEBUG) - embedded_subtitle_languages.add(Language('und')) - else: - embedded_subtitle_languages.add(Language('und')) - else: - logger.log(u'MKV has no subtitle track', logger.DEBUG) - except MalformedMKVError: - logger.log(u'MKV seems to be malformed ( %s ), ignoring embedded subtitles' % video_path, logger.INFO) - - return embedded_subtitle_languages - -def scan_subtitle_languages(path): - language_extensions = tuple('.' + c for c in language_converters['opensubtitles'].codes) - dirpath, filename = os.path.split(path) - subtitles = set() - for p in os.listdir(dirpath): - if not isinstance(p, bytes) and p.startswith(os.path.splitext(filename)[0]) and p.endswith(subliminal.video.SUBTITLE_EXTENSIONS): - if os.path.splitext(p)[0].endswith(language_extensions) and len(os.path.splitext(p)[0].rsplit('.', 1)[1]) is 2: - subtitles.add(Language.fromopensubtitles(os.path.splitext(p)[0][-2:])) - elif os.path.splitext(p)[0].endswith(language_extensions) and len(os.path.splitext(p)[0].rsplit('.', 1)[1]) is 3: - subtitles.add(Language.fromopensubtitles(os.path.splitext(p)[0][-3:])) - elif os.path.splitext(p)[0].endswith('pt-BR') and len(os.path.splitext(p)[0].rsplit('.', 1)[1]) is 5: - subtitles.add(Language.fromopensubtitles('pob')) - else: - subtitles.add(Language('und')) - - return subtitles - -# TODO: Return only languages our providers allow -def subtitleLanguageFilter(): - return [Language.fromopensubtitles(language) for language in language_converters['opensubtitles'].codes if len(language) == 3] - -def subtitleCodeFilter(): - return [Language.fromopensubtitles(language).opensubtitles for language in language_converters['opensubtitles'].codes if len(language) == 3] + result_list.append(language.opensubtitles) + + return sorted(result_list) + class SubtitlesFinder(object): """ @@ -350,57 +303,84 @@ class SubtitlesFinder(object): """ def __init__(self): self.amActive = False - - def subtitles_download_in_pp(self): + + @staticmethod + def subtitles_download_in_pp(): # pylint: disable=R0914 logger.log(u'Checking for needed subtitles in Post-Process folder', logger.INFO) - providers = getEnabledServiceList() - provider_configs = {'addic7ed': {'username': sickbeard.ADDIC7ED_USER, 'password': sickbeard.ADDIC7ED_PASS}, - 'legendastv': {'username': sickbeard.LEGENDASTV_USER, 'password': sickbeard.LEGENDASTV_PASS}, - 'opensubtitles': {'username': sickbeard.OPENSUBTITLES_USER, 'password': sickbeard.OPENSUBTITLES_PASS}} + providers = enabled_service_list() + provider_configs = {'addic7ed': {'username': sickbeard.ADDIC7ED_USER, + 'password': sickbeard.ADDIC7ED_PASS}, + 'legendastv': {'username': sickbeard.LEGENDASTV_USER, + 'password': sickbeard.LEGENDASTV_PASS}, + 'opensubtitles': {'username': sickbeard.OPENSUBTITLES_USER, + 'password': sickbeard.OPENSUBTITLES_PASS}} pool = subliminal.api.ProviderPool(providers=providers, provider_configs=provider_configs) # Search for all wanted languages - languages = set() - for language in frozenset(wantedLanguages()): - languages.add(fromietf(language)) + languages = {from_code(language) for language in wanted_languages()} if not languages: return - - runPostProcess = False + + run_post_process = False # Check if PP folder is set - if sickbeard.TV_DOWNLOAD_DIR and os.path.isdir(sickbeard.TV_DOWNLOAD_DIR): - for root, _, files in os.walk(sickbeard.TV_DOWNLOAD_DIR, topdown=False): - for videoFilename in sorted(files): - if videoFilename.rsplit(".", 1)[1] in media_extensions: + if sickbeard.TV_DOWNLOAD_DIR and ek(os.path.isdir, sickbeard.TV_DOWNLOAD_DIR): + for root, _, files in ek(os.walk, sickbeard.TV_DOWNLOAD_DIR, topdown=False): + for video_filename in sorted(files): + if video_filename.rsplit(".", 1)[1] in media_extensions: try: - video = subliminal.scan_video(os.path.join(root, videoFilename), subtitles=False, embedded_subtitles=False) + video = subliminal.scan_video(os.path.join(root, video_filename), + subtitles=False, embedded_subtitles=False) subtitles_list = pool.list_subtitles(video, languages) - + if not subtitles_list: - logger.log(u'No subtitles found for %s' % os.path.join(root, videoFilename), logger.DEBUG) + logger.log(u'No subtitles found for %s' + % ek(os.path.join, root, video_filename), logger.DEBUG) continue - - found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages, hearing_impaired=sickbeard.SUBTITLES_HEARING_IMPAIRED, only_one=not sickbeard.SUBTITLES_MULTI) - + + hearing_impaired = sickbeard.SUBTITLES_HEARING_IMPAIRED + found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages, + hearing_impaired=hearing_impaired, + only_one=not sickbeard.SUBTITLES_MULTI) + + downloaded_languages = set() for subtitle in found_subtitles: - logger.log(u"Found subtitle for %s in %s provider with language %s" % (os.path.join(root, videoFilename), subtitle.provider_name, subtitle.language.opensubtitles), logger.DEBUG) - save_subtitles(video, found_subtitles, directory=root, single=not sickbeard.SUBTITLES_MULTI) - runPostProcess = True - except Exception as e: - logger.log(u"Error occurred when downloading subtitles for: %s. Error: %r" % (os.path.join(root, videoFilename), ex(e))) - if runPostProcess: - logger.log(u"Starting post-process with defaults settings now that we found subtitles") + logger.log(u"Found subtitle for %s in %s provider with language %s" + % (os.path.join(root, video_filename), subtitle.provider_name, + subtitle.language.opensubtitles), logger.DEBUG) + subliminal.save_subtitles(video, found_subtitles, directory=root, + single=not sickbeard.SUBTITLES_MULTI) + + subtitles_multi = not sickbeard.SUBTITLES_MULTI + subtitle_path = subliminal.subtitle.get_subtitle_path(video.name, + None if subtitles_multi else + subtitle.language) + if root is not None: + subtitle_path = ek(os.path.join, root, ek(os.path.split, subtitle_path)[1]) + sickbeard.helpers.chmodAsParent(subtitle_path) + sickbeard.helpers.fixSetGroupID(subtitle_path) + + downloaded_languages.add(subtitle.language.opensubtitles) + + # Don't run post processor unless at least one file has all of the needed subtitles + if not needs_subtitles(downloaded_languages): + run_post_process = True + except Exception as error: + logger.log(u"Error occurred when downloading subtitles for: %s. Error: %r" + % (os.path.join(root, video_filename), ex(error))) + if run_post_process: + logger.log(u"Starting post-process with default settings now that we found subtitles") processTV.processDir(sickbeard.TV_DOWNLOAD_DIR) - def run(self, force=False): + def run(self, force=False): # pylint: disable=unused-argument if not sickbeard.USE_SUBTITLES: return - if len(sickbeard.subtitles.getEnabledServiceList()) < 1: - logger.log(u'Not enough services selected. At least 1 service is required to search subtitles in the background', logger.WARNING) + if len(sickbeard.subtitles.enabled_service_list()) < 1: + logger.log(u'Not enough services selected. At least 1 service is required to ' + 'search subtitles in the background', logger.WARNING) return self.amActive = True @@ -418,70 +398,80 @@ class SubtitlesFinder(object): # - search count < 7 and diff(airdate, now) <= 1 week : now -> 4h -> 8h -> 16h -> 1d -> 1d -> 1d today = datetime.date.today().toordinal() + database = db.DBConnection() - # you have 5 minutes to understand that one. Good luck - myDB = db.DBConnection() - - sqlResults = myDB.select( + sql_results = database.select( 'SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, ' + - 'e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) AS airdate_daydiff ' + + 'e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, ' + '(? - e.airdate) AS airdate_daydiff ' + 'FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) ' + 'WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?) ' + 'AND (e.subtitles_searchcount <= 2 OR (e.subtitles_searchcount <= 7 AND airdate_daydiff <= 7)) ' + - 'AND e.location != ""', [today, wantedLanguages(True)]) + 'AND e.location != ""', [today, wanted_languages(True)]) - if len(sqlResults) == 0: + if len(sql_results) == 0: logger.log(u'No subtitles to download', logger.INFO) self.amActive = False return - rules = self._getRules() + rules = self._get_rules() now = datetime.datetime.now() - for epToSub in sqlResults: + for ep_to_sub in sql_results: - if not ek(os.path.isfile, epToSub['location']): - logger.log(u'Episode file does not exist, cannot download subtitles for episode %dx%d of show %s' % (epToSub['season'], epToSub['episode'], epToSub['show_name']), logger.DEBUG) + if not ek(os.path.isfile, ep_to_sub['location']): + logger.log(u'Episode file does not exist, cannot download subtitles for episode %dx%d of show %s' + % (ep_to_sub['season'], ep_to_sub['episode'], ep_to_sub['show_name']), logger.DEBUG) + continue + + if not needs_subtitles(ep_to_sub['subtitles']): + logger.log(u'Episode already has all needed subtitles, skipping episode %dx%d of show %s' + % (ep_to_sub['season'], ep_to_sub['episode'], ep_to_sub['show_name']), logger.DEBUG) continue # http://bugs.python.org/issue7980#msg221094 - # I dont think this needs done here, but keeping to be safe + # I dont think this needs done here, but keeping to be safe (Recent shows rule) datetime.datetime.strptime('20110101', '%Y%m%d') - if ((epToSub['airdate_daydiff'] > 7 and epToSub['searchcount'] < 2 and now - datetime.datetime.strptime(epToSub['lastsearch'], dateTimeFormat) > datetime.timedelta(hours=rules['old'][epToSub['searchcount']])) or - # Recent shows rule - (epToSub['airdate_daydiff'] <= 7 and epToSub['searchcount'] < 7 and now - datetime.datetime.strptime(epToSub['lastsearch'], dateTimeFormat) > datetime.timedelta(hours=rules['new'][epToSub['searchcount']]))): - - logger.log(u'Downloading subtitles for episode %dx%d of show %s' % (epToSub['season'], epToSub['episode'], epToSub['show_name']), logger.DEBUG) - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(epToSub['showid'])) - if not showObj: + if ((ep_to_sub['airdate_daydiff'] > 7 and ep_to_sub['searchcount'] < 2 and + now - datetime.datetime.strptime(ep_to_sub['lastsearch'], dateTimeFormat) > + datetime.timedelta(hours=rules['old'][ep_to_sub['searchcount']])) or + (ep_to_sub['airdate_daydiff'] <= 7 and ep_to_sub['searchcount'] < 7 and + now - datetime.datetime.strptime(ep_to_sub['lastsearch'], dateTimeFormat) > + datetime.timedelta(hours=rules['new'][ep_to_sub['searchcount']]))): + + logger.log(u'Downloading subtitles for episode %dx%d of show %s' + % (ep_to_sub['season'], ep_to_sub['episode'], ep_to_sub['show_name']), logger.DEBUG) + + show_object = Show.find(sickbeard.showList, int(ep_to_sub['showid'])) + if not show_object: logger.log(u'Show not found', logger.DEBUG) self.amActive = False return - epObj = showObj.getEpisode(int(epToSub["season"]), int(epToSub["episode"])) - if isinstance(epObj, str): + episode_object = show_object.getEpisode(int(ep_to_sub["season"]), int(ep_to_sub["episode"])) + if isinstance(episode_object, str): logger.log(u'Episode not found', logger.DEBUG) self.amActive = False return - existing_subtitles = epObj.subtitles + existing_subtitles = episode_object.subtitles try: - epObj.downloadSubtitles() - except Exception as e: + episode_object.download_subtitles() + except Exception as error: logger.log(u'Unable to find subtitles', logger.DEBUG) - logger.log(str(e), logger.DEBUG) + logger.log(str(error), logger.DEBUG) self.amActive = False return - newSubtitles = frozenset(epObj.subtitles).difference(existing_subtitles) - if newSubtitles: - logger.log(u'Downloaded subtitles for S%02dE%02d in %s' % (epToSub["season"], epToSub["episode"], ', '.join(newSubtitles))) + new_subtitles = frozenset(episode_object.subtitles).difference(existing_subtitles) + if new_subtitles: + logger.log(u'Downloaded subtitles for S%02dE%02d in %s' + % (ep_to_sub["season"], ep_to_sub["episode"], ', '.join(new_subtitles))) self.amActive = False @staticmethod - def _getRules(): + def _get_rules(): """ Define the hours to wait between 2 subtitles search depending on: - the episode: new or old @@ -490,26 +480,28 @@ class SubtitlesFinder(object): return {'old': [0, 24], 'new': [0, 4, 8, 4, 16, 24, 24]} -def run_subs_extra_scripts(epObj, found_subtitles, video, single=False): +def run_subs_extra_scripts(episode_object, found_subtitles, video, single=False): - for curScriptName in sickbeard.SUBTITLES_EXTRA_SCRIPTS: - script_cmd = [piece for piece in re.split("( |\\\".*?\\\"|'.*?')", curScriptName) if piece.strip()] + for script_name in sickbeard.SUBTITLES_EXTRA_SCRIPTS: + script_cmd = [piece for piece in re.split("( |\\\".*?\\\"|'.*?')", script_name) if piece.strip()] script_cmd[0] = ek(os.path.abspath, script_cmd[0]) logger.log(u"Absolute path to script: " + script_cmd[0], logger.DEBUG) for subtitle in found_subtitles: subtitle_path = subliminal.subtitle.get_subtitle_path(video.name, None if single else subtitle.language) - inner_cmd = script_cmd + [video.name, subtitle_path, subtitle.language.opensubtitles, epObj['show.name'], - str(epObj['season']), str(epObj['episode']), epObj['name'], str(epObj['show.indexerid'])] + inner_cmd = script_cmd + [video.name, subtitle_path, subtitle.language.opensubtitles, + episode_object['show_name'], str(episode_object['season']), + str(episode_object['episode']), episode_object['name'], + str(episode_object['show_indexerid'])] # use subprocess to run the command and capture output logger.log(u"Executing command: %s" % inner_cmd) try: - p = subprocess.Popen(inner_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR) - out, _ = p.communicate() # @UnusedVariable + process = subprocess.Popen(inner_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR) + out, _ = process.communicate() # @UnusedVariable logger.log(u"Script result: %s" % out, logger.DEBUG) - except Exception as e: - logger.log(u"Unable to run subs_extra_script: " + ex(e)) + except Exception as error: + logger.log(u"Unable to run subs_extra_script: " + ex(error)) diff --git a/sickbeard/traktChecker.py b/sickbeard/traktChecker.py index 530673a228e351b55e971255f569d212ba008b64..651dcb08f51f65b4de70f924360f8a308663205e 100644 --- a/sickbeard/traktChecker.py +++ b/sickbeard/traktChecker.py @@ -36,6 +36,7 @@ from sickbeard.common import Quality from sickrage.helper.common import sanitize_filename from sickrage.helper.encoding import ek from sickrage.helper.exceptions import ex +from sickrage.show.Show import Show def setEpisodeToWanted(show, s, e): @@ -388,7 +389,7 @@ class TraktChecker(object): self.addDefaultShow(indexer, indexer_id, show['title'], WANTED) if int(sickbeard.TRAKT_METHOD_ADD) == 1: - newShow = helpers.findCertainShow(sickbeard.showList, indexer_id) + newShow = Show.find(sickbeard.showList, indexer_id) if newShow is not None: setEpisodeToWanted(newShow, 1, 1) @@ -415,7 +416,7 @@ class TraktChecker(object): indexer_id = int(show_el) show = self.EpisodeWatchlist[trakt_id][show_el] - newShow = helpers.findCertainShow(sickbeard.showList, indexer_id) + newShow = Show.find(sickbeard.showList, indexer_id) try: if newShow is None: @@ -444,7 +445,7 @@ class TraktChecker(object): """ Adds a new show with the default settings """ - if not helpers.findCertainShow(sickbeard.showList, int(indexer_id)): + if not Show.find(sickbeard.showList, int(indexer_id)): logger.log(u"Adding show " + str(indexer_id)) root_dirs = sickbeard.ROOT_DIRS.split('|') diff --git a/sickbeard/tv.py b/sickbeard/tv.py index 6559e9c6a7797c9102aa3f11965a7bb47a0257c9..c831f1ea0057e872a95876ee084c9c4dc2729caf 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -47,12 +47,13 @@ from sickbeard.blackandwhitelist import BlackAndWhiteList from sickbeard import network_timezones from sickbeard.indexers.indexer_config import INDEXER_TVRAGE from sickbeard.name_parser.parser import NameParser, InvalidNameException, InvalidShowException -from sickrage.helper.common import dateTimeFormat, remove_extension, replace_extension, sanitize_filename +from sickrage.helper.common import dateTimeFormat, remove_extension, replace_extension, sanitize_filename, try_int from sickrage.helper.encoding import ek from sickrage.helper.exceptions import EpisodeDeletedException, EpisodeNotFoundException, ex from sickrage.helper.exceptions import MultipleEpisodesInDatabaseException, MultipleShowsInDatabaseException from sickrage.helper.exceptions import MultipleShowObjectsException, NoNFOException, ShowDirectoryNotFoundException from sickrage.helper.exceptions import ShowNotFoundException +from sickrage.show.Show import Show from sickbeard.common import Quality, Overview, statusStrings from sickbeard.common import DOWNLOADED, SNATCHED, SNATCHED_PROPER, ARCHIVED, IGNORED, UNAIRED, WANTED, SKIPPED, UNKNOWN @@ -112,7 +113,7 @@ class TVShow(object): self.nextaired = "" self.release_groups = None - otherShow = helpers.findCertainShow(sickbeard.showList, self.indexerid) + otherShow = Show.find(sickbeard.showList, self.indexerid) if otherShow is not None: raise MultipleShowObjectsException("Can't create a show if it already exists") @@ -464,13 +465,16 @@ class TVShow(object): def loadEpisodesFromDB(self): logger.log(u"Loading all episodes from the DB", logger.DEBUG) - - myDB = db.DBConnection() - sql = "SELECT * FROM tv_episodes WHERE showid = ?" - sqlResults = myDB.select(sql, [self.indexerid]) - scannedEps = {} + try: + myDB = db.DBConnection() + sql = "SELECT season, episode, showid, show_name FROM tv_episodes JOIN tv_shows WHERE showid = indexer_id and showid = ?" + sqlResults = myDB.select(sql, [self.indexerid]) + except Exception as error: + logger.log(u"Could not load episodes from the DB. Error: %s" % error, logger.ERROR) + return scannedEps + lINDEXER_API_PARMS = sickbeard.indexerApi(self.indexer).api_params.copy() if self.lang: @@ -492,23 +496,24 @@ class TVShow(object): curSeason = int(curResult["season"]) curEpisode = int(curResult["episode"]) curShowid = int(curResult['showid']) + curShowName = str(curResult['show_name']) - logger.log(u"%s: loading Episodes from DB" % curShowid, logger.DEBUG) + logger.log(u"%s: Loading %s episodes from DB" % (curShowid, curShowName), logger.DEBUG) deleteEp = False if curSeason not in cachedSeasons: try: cachedSeasons[curSeason] = cachedShow[curSeason] - except sickbeard.indexer_seasonnotfound, e: - logger.log(u"%s: Error when trying to load the episode from %s. Message: %s " % - (curShowid, sickbeard.indexerApi(self.indexer).name, e.message), logger.WARNING) + except sickbeard.indexer_seasonnotfound as error: + logger.log(u"%s: %s (unaired/deleted) in the indexer %s for %s. Removing existing records from database" % + (curShowid, error.message, sickbeard.indexerApi(self.indexer).name, curShowName), logger.DEBUG) deleteEp = True if not curSeason in scannedEps: - logger.log(u"Not curSeason in scannedEps", logger.DEBUG) + logger.log(u"%s: Not curSeason in scannedEps" % curShowid, logger.DEBUG) scannedEps[curSeason] = {} - logger.log(u"%s: Loading episode S%02dE%02d from the DB" % (curShowid, curSeason or 0, curEpisode or 0), logger.DEBUG) + logger.log(u"%s: Loading %s S%02dE%02d from the DB" % (curShowid, curShowName, curSeason or 0, curEpisode or 0), logger.DEBUG) try: curEp = self.getEpisode(curSeason, curEpisode) @@ -523,11 +528,11 @@ class TVShow(object): curEp.loadFromIndexer(tvapi=t, cachedSeason=cachedSeasons[curSeason]) scannedEps[curSeason][curEpisode] = True except EpisodeDeletedException: - logger.log(u"Tried loading an episode from the DB that should have been deleted, skipping it", + logger.log(u"%s: Tried loading %s S%02dE%02d from the DB that should have been deleted, skipping it" % (curShowid, curShowName, curSeason or 0, curEpisode or 0), logger.DEBUG) continue - logger.log(u"Finished loading all episodes from the DB", logger.DEBUG) + logger.log(u"%s: Finished loading all episodes for %s from the DB" % (curShowName, curShowid), logger.DEBUG) return scannedEps @@ -1005,7 +1010,7 @@ class TVShow(object): if sickbeard.TRASH_REMOVE_SHOW: send2trash(cache_file) else: - os.remove(cache_file) + ek(os.remove, cache_file) except OSError, e: logger.log(u'Unable to %s %s: %s / %s' % (action, cache_file, repr(e), str(e)), logger.WARNING) @@ -1065,7 +1070,7 @@ class TVShow(object): sql_l = [] for ep in sqlResults: - curLoc = os.path.normpath(ep["location"]) + curLoc = ek(os.path.normpath, ep["location"]) season = int(ep["season"]) episode = int(ep["episode"]) @@ -1079,8 +1084,8 @@ class TVShow(object): continue # if the path doesn't exist or if it's not in our show dir - if not ek(os.path.isfile, curLoc) or not os.path.normpath(curLoc).startswith( - os.path.normpath(self.location)): + if not ek(os.path.isfile, curLoc) or not ek(os.path.normpath, curLoc).startswith( + ek(os.path.normpath, self.location)): # check if downloaded files still exist, update our data if this has changed if not sickbeard.SKIP_REMOVED_FILES: @@ -1116,7 +1121,7 @@ class TVShow(object): myDB = db.DBConnection() myDB.mass_action(sql_l) - def downloadSubtitles(self, force=False): + def download_subtitles(self, force=False): # TODO: Add support for force option if not ek(os.path.isdir, self._location): logger.log(str(self.indexerid) + ": Show dir doesn't exist, can't download subtitles", logger.DEBUG) @@ -1131,7 +1136,7 @@ class TVShow(object): return for episode in episodes: - episode.downloadSubtitles(force=force) + episode.download_subtitles(force=force) except Exception: logger.log(u"%s: Error occurred when downloading subtitles for %s" % (self.indexerid, self.name), logger.DEBUG) @@ -1413,41 +1418,46 @@ class TVEpisode(object): def refreshSubtitles(self): """Look for subtitles files and refresh the subtitles property""" - self.subtitles, save_subtitles = subtitles.subtitlesLanguages(self.location) + episode_info = {'show_name': self.show.name, 'location': self.location, + 'season': self.season, 'episode': self.episode} + self.subtitles, save_subtitles = subtitles.refresh_subtitles(episode_info, self.subtitles) if save_subtitles: self.saveToDB() - def downloadSubtitles(self, force=False): + def download_subtitles(self, force=False): if not ek(os.path.isfile, self.location): logger.log(u"%s: Episode file doesn't exist, can't download subtitles for S%02dE%02d" % (self.show.indexerid, self.season or 0, self.episode or 0), logger.DEBUG) return - logger.log(u"%s: Downloading subtitles for S%02dE%02d" % (self.show.indexerid, self.season or 0, self.episode or 0), logger.DEBUG) + if not subtitles.needs_subtitles(self.subtitles): + logger.log(u'Episode already has all needed subtitles, skipping episode %dx%d of show %s' + % (self.season or 0, self.episode or 0, self.show.name), logger.DEBUG) + return - # logging.getLogger('subliminal.api').addHandler(logging.StreamHandler()) - # logging.getLogger('subliminal.api').setLevel(logging.DEBUG) - # logging.getLogger('subliminal').addHandler(logging.StreamHandler()) - # logging.getLogger('subliminal').setLevel(logging.DEBUG) + logger.log(u"%s: Downloading subtitles for %s S%02dE%02d" + % (self.show.indexerid, self.show.name, self.season or 0, self.episode or 0), logger.DEBUG) - subtitles_info = {'location': self.location, 'subtitles': self.subtitles, 'show.indexerid': self.show.indexerid, 'season': self.season, - 'episode': self.episode, 'name': self.name, 'show.name': self.show.name, 'status': self.status} + subtitles_info = {'location': self.location, 'subtitles': self.subtitles, 'season': self.season, + 'episode': self.episode, 'name': self.name, 'show_name': self.show.name, + 'show_indexerid': self.show.indexerid, 'status': self.status} - self.subtitles, newSubtitles = subtitles.downloadSubtitles(subtitles_info) + self.subtitles, new_subtitles = subtitles.download_subtitles(subtitles_info) self.subtitles_searchcount += 1 if self.subtitles_searchcount else 1 self.subtitles_lastsearch = datetime.datetime.now().strftime(dateTimeFormat) self.saveToDB() - if newSubtitles: - subtitleList = ", ".join([subtitles.fromietf(newSub).name for newSub in newSubtitles]) - logger.log(u"%s: Downloaded %s subtitles for S%02dE%02d" % - (self.show.indexerid, subtitleList, self.season or 0, self.episode or 0), logger.DEBUG) + if new_subtitles: + subtitle_list = ", ".join([subtitles.name_from_code(code) for code in new_subtitles]) + logger.log(u"%s: Downloaded %s subtitles for %s S%02dE%02d" % + (self.show.indexerid, subtitle_list, self.show.name, self.season or 0, + self.episode or 0), logger.DEBUG) - notifiers.notify_subtitle_download(self.prettyName(), subtitleList) + notifiers.notify_subtitle_download(self.prettyName(), subtitle_list) else: - logger.log(u"%s: No subtitles downloaded for S%02dE%02d" % - (self.show.indexerid, self.season or 0, self.episode or 0), logger.DEBUG) + logger.log(u"%s: No subtitles downloaded for %s S%02dE%02d" % + (self.show.indexerid, self.show.name, self.season or 0, self.episode or 0), logger.DEBUG) def checkForMetaFiles(self): @@ -1502,7 +1512,7 @@ class TVEpisode(object): raise EpisodeNotFoundException("Couldn't find episode S%02dE%02d" % (season or 0, episode or 0)) def loadFromDB(self, season, episode): - logger.log(u"%s: Loading episode details from DB for episode %s S%02dE%02d" % (self.show.indexerid, self.show.name, season or 0, episode or 0), logger.DEBUG) + logger.log(u"%s: Loading episode details for %s S%02dE%02d from DB" % (self.show.indexerid, self.show.name, season or 0, episode or 0), logger.DEBUG) myDB = db.DBConnection() sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?", @@ -1534,7 +1544,7 @@ class TVEpisode(object): # don't overwrite my location if sqlResults[0]["location"] and sqlResults[0]["location"]: - self.location = os.path.normpath(sqlResults[0]["location"]) + self.location = ek(os.path.normpath, sqlResults[0]["location"]) if sqlResults[0]["file_size"]: self.file_size = int(sqlResults[0]["file_size"]) else: @@ -1545,9 +1555,9 @@ class TVEpisode(object): sickbeard.scene_numbering.xem_refresh(self.show.indexerid, self.show.indexer) - self.scene_season = helpers.tryInt(sqlResults[0]["scene_season"], 0) - self.scene_episode = helpers.tryInt(sqlResults[0]["scene_episode"], 0) - self.scene_absolute_number = helpers.tryInt(sqlResults[0]["scene_absolute_number"], 0) + self.scene_season = try_int(sqlResults[0]["scene_season"], 0) + self.scene_episode = try_int(sqlResults[0]["scene_episode"], 0) + self.scene_absolute_number = try_int(sqlResults[0]["scene_absolute_number"], 0) if self.scene_absolute_number == 0: self.scene_absolute_number = sickbeard.scene_numbering.get_scene_absolute_numbering( @@ -1585,8 +1595,8 @@ class TVEpisode(object): if episode is None: episode = self.episode - logger.log(u"%s: Loading episode details from %s for episode S%02dE%02d" % - (self.show.indexerid, sickbeard.indexerApi(self.show.indexer).name, season or 0, episode or 0), logger.DEBUG) + logger.log(u"%s: Loading episode details for %s S%02dE%02d from %s" % + (self.show.indexerid, self.show.name, season or 0, episode or 0, sickbeard.indexerApi(self.show.indexer).name), logger.DEBUG) indexer_lang = self.show.lang @@ -1640,7 +1650,7 @@ class TVEpisode(object): # return False if getattr(myEp, 'absolute_number', None) is None: - logger.log(u"This episode %s - S%02dE%02d has no absolute number on %s" %(self.show.name, season or 0, episode or 0, sickbeard.indexerApi(self.indexer).name), logger.DEBUG) + logger.log(u"%s: This episode %s - S%02dE%02d has no absolute number on %s" %(self.show.indexerid, self.show.name, season or 0, episode or 0, sickbeard.indexerApi(self.indexer).name), logger.DEBUG) else: logger.log(u"%s: The absolute_number for S%02dE%02d is: %s " % (self.show.indexerid, season or 0, episode or 0, myEp["absolute_number"]), logger.DEBUG) self.absolute_number = int(myEp["absolute_number"]) @@ -1698,7 +1708,7 @@ class TVEpisode(object): if not ek(os.path.isfile, self.location): if self.airdate >= datetime.date.today() or self.airdate == datetime.date.fromordinal(1): - logger.log(u"Episode airs in the future or has no airdate, marking it %s" % statusStrings[UNAIRED], logger.DEBUG) + logger.log(u"%s: Episode airs in the future or has no airdate, marking it %s" % (self.show.indexerid, statusStrings[UNAIRED]), logger.DEBUG) self.status = UNAIRED elif self.status in [UNAIRED, UNKNOWN]: # Only do UNAIRED/UNKNOWN, it could already be snatched/ignored/skipped, or downloaded/archived to disconnected media @@ -2524,7 +2534,7 @@ class TVEpisode(object): if sickbeard.FILE_TIMESTAMP_TIMEZONE == 'local': airdatetime = airdatetime.astimezone(network_timezones.sb_timezone) - filemtime = datetime.datetime.fromtimestamp(os.path.getmtime(self.location)).replace(tzinfo=network_timezones.sb_timezone) + filemtime = datetime.datetime.fromtimestamp(ek(os.path.getmtime, self.location)).replace(tzinfo=network_timezones.sb_timezone) if filemtime != airdatetime: import time @@ -2534,13 +2544,13 @@ class TVEpisode(object): "' to show air date " + time.strftime("%b %d,%Y (%H:%M)", airdatetime), logger.DEBUG) try: if helpers.touchFile(self.location, time.mktime(airdatetime)): - logger.log(str(self.show.indexerid) + u": Changed modify date of " + os.path.basename(self.location) + logger.log(str(self.show.indexerid) + u": Changed modify date of " + ek(os.path.basename, self.location) + " to show air date " + time.strftime("%b %d,%Y (%H:%M)", airdatetime)) else: - logger.log(str(self.show.indexerid) + u": Unable to modify date of " + os.path.basename(self.location) + logger.log(str(self.show.indexerid) + u": Unable to modify date of " + ek(os.path.basename, self.location) + " to show air date " + time.strftime("%b %d,%Y (%H:%M)", airdatetime), logger.WARNING) except Exception as e: - logger.log(str(self.show.indexerid) + u": Failed to modify date of '" + os.path.basename(self.location) + logger.log(str(self.show.indexerid) + u": Failed to modify date of '" + ek(os.path.basename, self.location) + "' to show air date " + time.strftime("%b %d,%Y (%H:%M)", airdatetime) + ". Error: %s" % ex(e), logger.WARNING) def __getstate__(self): diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py index 5a9229bffa4c9fd47f5f07c2a828592891489ac3..6ace5591a924705406e53fc3e043c9489fef182b 100644 --- a/sickbeard/tvcache.py +++ b/sickbeard/tvcache.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. - import time import datetime import itertools @@ -25,12 +24,12 @@ import urllib2 import sickbeard from sickbeard import db from sickbeard import logger -from sickbeard import helpers from sickbeard.common import Quality from sickbeard.rssfeeds import getFeed from sickbeard import show_name_helpers from sickrage.helper.encoding import ss from sickrage.helper.exceptions import AuthException, ex +from sickrage.show.Show import Show from sickbeard.name_parser.parser import NameParser, InvalidNameException, InvalidShowException @@ -246,7 +245,7 @@ class TVCache(object): # create showObj from indexer_id if available showObj = None if indexer_id: - showObj = helpers.findCertainShow(sickbeard.showList, indexer_id) + showObj = Show.find(sickbeard.showList, indexer_id) try: myParser = NameParser(showObj=showObj) @@ -333,7 +332,7 @@ class TVCache(object): continue # get the show object, or if it's not one of our shows then ignore it - showObj = helpers.findCertainShow(sickbeard.showList, int(curResult["indexerid"])) + showObj = Show.find(sickbeard.showList, int(curResult["indexerid"])) if not showObj: continue diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index 64a05f2f2a52d9e27698220848c3af85f8645ed5..1a9d96168aeb5d165e95b1561a4a1a40eb4e0acd 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -92,9 +92,9 @@ class CheckVersion(object): logger.log(u"Config backup in progress...") ui.notifications.message('Backup', 'Config backup in progress...') try: - backupDir = os.path.join(sickbeard.DATA_DIR, 'backup') - if not os.path.isdir(backupDir): - os.mkdir(backupDir) + backupDir = ek(os.path.join, sickbeard.DATA_DIR, 'backup') + if not ek(os.path.isdir, backupDir): + ek(os.mkdir, backupDir) if self._keeplatestbackup(backupDir) and self._backup(backupDir): logger.log(u"Config backup successful, updating...") @@ -115,20 +115,20 @@ class CheckVersion(object): return False import glob - files = glob.glob(os.path.join(backupDir, '*.zip')) + files = glob.glob(ek(os.path.join, backupDir, '*.zip')) if not files: return True now = time.time() - newest = files[0], now - os.path.getctime(files[0]) + newest = files[0], now - ek(os.path.getctime, files[0]) for f in files[1:]: - age = now - os.path.getctime(f) + age = now - ek(os.path.getctime, f) if age < newest[1]: newest = f, age files.remove(newest[0]) for f in files: - os.remove(f) + ek(os.remove, f) return True @@ -138,17 +138,17 @@ class CheckVersion(object): def _backup(backupDir=None): if not backupDir: return False - source = [os.path.join(sickbeard.DATA_DIR, 'sickbeard.db'), sickbeard.CONFIG_FILE] - source.append(os.path.join(sickbeard.DATA_DIR, 'failed.db')) - source.append(os.path.join(sickbeard.DATA_DIR, 'cache.db')) - target = os.path.join(backupDir, 'sickrage-' + time.strftime('%Y%m%d%H%M%S') + '.zip') + source = [ek(os.path.join, sickbeard.DATA_DIR, 'sickbeard.db'), sickbeard.CONFIG_FILE] + source.append(ek(os.path.join, sickbeard.DATA_DIR, 'failed.db')) + source.append(ek(os.path.join, sickbeard.DATA_DIR, 'cache.db')) + target = ek(os.path.join, backupDir, 'sickrage-' + time.strftime('%Y%m%d%H%M%S') + '.zip') - for (path, dirs, files) in os.walk(sickbeard.CACHE_DIR, topdown=True): + for (path, dirs, files) in ek(os.walk, sickbeard.CACHE_DIR, topdown=True): for dirname in dirs: if path == sickbeard.CACHE_DIR and dirname not in ['images']: dirs.remove(dirname) for filename in files: - source.append(os.path.join(path, filename)) + source.append(ek(os.path.join, path, filename)) return helpers.backupConfigZip(source, target, sickbeard.DATA_DIR) @@ -241,7 +241,7 @@ class CheckVersion(object): # check if we're a windows build if sickbeard.BRANCH.startswith('build '): install_type = 'win' - elif os.path.isdir(ek(os.path.join, sickbeard.PROG_DIR, u'.git')): + elif ek(os.path.isdir, ek(os.path.join, sickbeard.PROG_DIR, u'.git')): install_type = 'git' else: install_type = 'source' @@ -813,16 +813,16 @@ class SourceUpdateManager(UpdateManager): # prepare the update dir sr_update_dir = ek(os.path.join, sickbeard.PROG_DIR, u'sr-update') - if os.path.isdir(sr_update_dir): + if ek(os.path.isdir, sr_update_dir): logger.log(u"Clearing out update folder " + sr_update_dir + " before extracting") shutil.rmtree(sr_update_dir) logger.log(u"Creating update folder " + sr_update_dir + " before extracting") - os.makedirs(sr_update_dir) + ek(os.makedirs, sr_update_dir) # retrieve file logger.log(u"Downloading update from " + repr(tar_download_url)) - tar_download_path = os.path.join(sr_update_dir, u'sr-update.tar') + tar_download_path = ek(os.path.join, sr_update_dir, u'sr-update.tar') helpers.download_file(tar_download_url, tar_download_path, session=self.session) if not ek(os.path.isfile, tar_download_path): @@ -841,40 +841,40 @@ class SourceUpdateManager(UpdateManager): # delete .tar.gz logger.log(u"Deleting file " + tar_download_path) - os.remove(tar_download_path) + ek(os.remove, tar_download_path) # find update dir name - update_dir_contents = [x for x in os.listdir(sr_update_dir) if - os.path.isdir(os.path.join(sr_update_dir, x))] + update_dir_contents = [x for x in ek(os.listdir, sr_update_dir) if + ek(os.path.isdir, ek(os.path.join, sr_update_dir, x))] if len(update_dir_contents) != 1: logger.log(u"Invalid update data, update failed: " + str(update_dir_contents), logger.ERROR) return False - content_dir = os.path.join(sr_update_dir, update_dir_contents[0]) + content_dir = ek(os.path.join, sr_update_dir, update_dir_contents[0]) # walk temp folder and move files to main folder logger.log(u"Moving files from " + content_dir + " to " + sickbeard.PROG_DIR) - for dirname, _, filenames in os.walk(content_dir): # @UnusedVariable + for dirname, _, filenames in ek(os.walk, content_dir): # @UnusedVariable dirname = dirname[len(content_dir) + 1:] for curfile in filenames: - old_path = os.path.join(content_dir, dirname, curfile) - new_path = os.path.join(sickbeard.PROG_DIR, dirname, curfile) + old_path = ek(os.path.join, content_dir, dirname, curfile) + new_path = ek(os.path.join, sickbeard.PROG_DIR, dirname, curfile) # Avoid DLL access problem on WIN32/64 # These files needing to be updated manually # or find a way to kill the access from memory if curfile in ('unrar.dll', 'unrar64.dll'): try: - os.chmod(new_path, stat.S_IWRITE) - os.remove(new_path) - os.renames(old_path, new_path) + ek(os.chmod, new_path, stat.S_IWRITE) + ek(os.remove, new_path) + ek(os.renames, old_path, new_path) except Exception, e: logger.log(u"Unable to update " + new_path + ': ' + ex(e), logger.DEBUG) - os.remove(old_path) # Trash the updated file without moving in new path + ek(os.remove, old_path) # Trash the updated file without moving in new path continue - if os.path.isfile(new_path): - os.remove(new_path) - os.renames(old_path, new_path) + if ek(os.path.isfile, new_path): + ek(os.remove, new_path) + ek(os.renames, old_path, new_path) sickbeard.CUR_COMMIT_HASH = self._newest_commit_hash sickbeard.CUR_COMMIT_BRANCH = self.branch diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py index e3092be6c6a6cea6d955207dec19a7dbf8742d42..ad07a1c54f75525f27978bd6549e22fe250ec1e7 100644 --- a/sickbeard/webapi.py +++ b/sickbeard/webapi.py @@ -31,7 +31,7 @@ import datetime import traceback import sickbeard -from sickrage.helper.common import dateFormat, dateTimeFormat, pretty_file_size, sanitize_filename, timeFormat +from sickrage.helper.common import dateFormat, dateTimeFormat, pretty_file_size, sanitize_filename, timeFormat, try_int from sickrage.helper.encoding import ek from sickrage.helper.exceptions import CantUpdateShowException, ex, ShowDirectoryNotFoundException from sickrage.helper.quality import get_quality_string @@ -719,7 +719,7 @@ class CMD_Episode(ApiCall): def run(self): """ Get detailed information about an episode """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -746,7 +746,7 @@ class CMD_Episode(ApiCall): episode["location"] = episode["location"][show_path_length:] # convert stuff to human form - if helpers.tryInt(episode['airdate'], 1) > 693595: # 1900 + if try_int(episode['airdate'], 1) > 693595: # 1900 episode['airdate'] = sbdatetime.sbdatetime.sbfdate(sbdatetime.sbdatetime.convert_to_setting( network_timezones.parse_date_time(int(episode['airdate']), show_obj.airs, show_obj.network)), d_preset=dateFormat) else: @@ -784,7 +784,7 @@ class CMD_EpisodeSearch(ApiCall): def run(self): """ Search for an episode """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -840,7 +840,7 @@ class CMD_EpisodeSetStatus(ApiCall): def run(self): """ Set the status of an episode or a season (when no episode is provided) """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -953,7 +953,7 @@ class CMD_SubtitleSearch(ApiCall): def run(self): """ Search for an episode subtitles """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -966,15 +966,15 @@ class CMD_SubtitleSearch(ApiCall): previous_subtitles = ep_obj.subtitles try: - subtitles = ep_obj.downloadSubtitles() + subtitles = ep_obj.download_subtitles() except Exception: return _responds(RESULT_FAILURE, msg='Unable to find subtitles') # return the correct json value 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]) + new_languages = [subtitles.name_from_code(code) for code in new_subtitles] + status = 'New subtitles downloaded: %s' % ', '.join(new_languages) response = _responds(RESULT_SUCCESS, msg='New subtitles found') else: status = 'No subtitles downloaded' @@ -1016,7 +1016,7 @@ class CMD_Exceptions(ApiCall): scene_exceptions[indexerid].append(row["show_name"]) else: - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -1068,8 +1068,8 @@ class CMD_History(ApiCall): del row["action"] _rename_element(row, "show_id", "indexerid") - row["resource_path"] = os.path.dirname(row["resource"]) - row["resource"] = os.path.basename(row["resource"]) + row["resource_path"] = ek(os.path.dirname, row["resource"]) + row["resource"] = ek(os.path.basename, row["resource"]) # Add tvdbid for backward compatibility row['tvdbid'] = row['indexerid'] @@ -1205,7 +1205,7 @@ class CMD_Logs(ApiCall): min_level = logger.reverseNames[str(self.min_level).upper()] data = [] - if os.path.isfile(logger.logFile): + if ek(os.path.isfile, logger.logFile): with io.open(logger.logFile, 'r', encoding='utf-8') as f: data = f.readlines() @@ -1853,7 +1853,7 @@ class CMD_Show(ApiCall): def run(self): """ Get detailed information about a show """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -1913,7 +1913,7 @@ class CMD_Show(ApiCall): show_dict["network"] = "" show_dict["status"] = show_obj.status - if helpers.tryInt(show_obj.nextaired, 1) > 693595: + if try_int(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( @@ -1961,7 +1961,7 @@ class CMD_ShowAddExisting(ApiCall): def run(self): """ Add an existing show in SickRage """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if show_obj: return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in the database") @@ -2076,7 +2076,7 @@ class CMD_ShowAddNew(ApiCall): def run(self): """ Add a new show to SickRage """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if show_obj: return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in database") @@ -2211,7 +2211,7 @@ class CMD_ShowCache(ApiCall): def run(self): """ Check SickRage's cache to see if the images (poster, banner, fanart) for a show are valid """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2283,7 +2283,7 @@ class CMD_ShowGetQuality(ApiCall): def run(self): """ Get the quality setting of a show """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2478,7 +2478,7 @@ class CMD_ShowSeasonList(ApiCall): def run(self): """ Get the list of seasons of a show """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2518,7 +2518,7 @@ class CMD_ShowSeasons(ApiCall): def run(self): """ Get the list of episodes for one or all seasons of a show """ - sho_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + sho_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not sho_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2533,7 +2533,7 @@ class CMD_ShowSeasons(ApiCall): status, quality = Quality.splitCompositeStatus(int(row["status"])) row["status"] = _get_status_strings(status) row["quality"] = get_quality_string(quality) - if helpers.tryInt(row['airdate'], 1) > 693595: # 1900 + if try_int(row['airdate'], 1) > 693595: # 1900 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) @@ -2560,7 +2560,7 @@ class CMD_ShowSeasons(ApiCall): status, quality = Quality.splitCompositeStatus(int(row["status"])) row["status"] = _get_status_strings(status) row["quality"] = get_quality_string(quality) - if helpers.tryInt(row['airdate'], 1) > 693595: # 1900 + if try_int(row['airdate'], 1) > 693595: # 1900 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) @@ -2604,7 +2604,7 @@ 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. """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2659,7 +2659,7 @@ class CMD_ShowStats(ApiCall): def run(self): """ Get episode statistics for a given show """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2764,7 +2764,7 @@ class CMD_ShowUpdate(ApiCall): def run(self): """ Update a show in SickRage """ - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + show_obj = Show.find(sickbeard.showList, int(self.indexerid)) if not show_obj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2818,7 +2818,7 @@ class CMD_Shows(ApiCall): "subtitles": (0, 1)[curShow.subtitles], } - if helpers.tryInt(curShow.nextaired, 1) > 693595: # 1900 + if try_int(curShow.nextaired, 1) > 693595: # 1900 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) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 79d20f81c6a9612f013c5dbc3fc228249ff2d0a5..2ad46f506e5bbbbd589225ba887f5295e5bb31ed 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -53,7 +53,7 @@ from unrar2 import RarFile import adba from libtrakt import TraktAPI from libtrakt.exceptions import traktException -from sickrage.helper.common import sanitize_filename +from sickrage.helper.common import sanitize_filename, try_int from sickrage.helper.encoding import ek, ss from sickrage.helper.exceptions import CantRefreshShowException, CantUpdateShowException, ex from sickrage.helper.exceptions import MultipleShowObjectsException, NoNFOException, ShowDirectoryNotFoundException @@ -98,9 +98,9 @@ def get_lookup(): global mako_path # pylint: disable=W0603 if mako_path is None: - mako_path = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/views/") + mako_path = ek(os.path.join, sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/views/") if mako_cache is None: - mako_cache = os.path.join(sickbeard.CACHE_DIR, 'mako') + mako_cache = ek(os.path.join, sickbeard.CACHE_DIR, 'mako') if mako_lookup is None: mako_lookup = TemplateLookup(directories=[mako_path], module_directory=mako_cache, format_exceptions=True) return mako_lookup @@ -557,7 +557,7 @@ class CalendarHandler(BaseHandler): air_date_time = network_timezones.parse_date_time(episode['airdate'], show["airs"], show['network']).astimezone(utc) air_date_time_end = air_date_time + datetime.timedelta( - minutes=helpers.tryInt(show["runtime"], 60)) + minutes=try_int(show["runtime"], 60)) # Create event for episode ical += 'BEGIN:VEVENT\r\n' @@ -631,7 +631,7 @@ class WebFileBrowser(WebRoot): def complete(self, term, includeFiles=0, *args, **kwargs): self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') self.set_header("Content-Type", "application/json") - paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term), includeFiles=bool(int(includeFiles))) + paths = [entry['path'] for entry in foldersAtPath(ek(os.path.dirname, term), includeFiles=bool(int(includeFiles))) if 'path' in entry] return json.dumps(paths) @@ -651,7 +651,7 @@ class Home(WebRoot): if show is None: return "Invalid show parameters" - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if showObj is None: return "Invalid show paramaters" @@ -702,7 +702,8 @@ class Home(WebRoot): sql_statement += ' OR (status IN ' + status_quality + ') OR (status IN ' + status_download + '))) AS ep_total, ' sql_statement += ' (SELECT airdate FROM tv_episodes WHERE showid=tv_eps.showid AND airdate >= ' + today + ' AND (status = ' + str(UNAIRED) + ' OR status = ' + str(WANTED) + ') ORDER BY airdate ASC LIMIT 1) AS ep_airs_next, ' - sql_statement += ' (SELECT airdate FROM tv_episodes WHERE showid=tv_eps.showid AND airdate > 1 AND status <> ' + str(UNAIRED) + ' ORDER BY airdate DESC LIMIT 1) AS ep_airs_prev ' + sql_statement += ' (SELECT airdate FROM tv_episodes WHERE showid=tv_eps.showid AND airdate > 1 AND status <> ' + str(UNAIRED) + ' ORDER BY airdate DESC LIMIT 1) AS ep_airs_prev, ' + sql_statement += ' (SELECT SUM(file_size) FROM tv_episodes WHERE showid=tv_eps.showid) AS show_size' sql_statement += ' FROM tv_episodes tv_eps GROUP BY showid' sql_result = myDB.select(sql_statement) @@ -1141,7 +1142,7 @@ class Home(WebRoot): if branch: checkversion.updater.branch = branch - if checkversion.updater.update(): + if checkversion.updater.need_update() and checkversion.updater.update(): # do a hard restart sickbeard.events.put(sickbeard.events.SystemEvent.RESTART) @@ -1185,7 +1186,7 @@ class Home(WebRoot): # 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) + showObj = Show.find(sickbeard.showList, show) except (ValueError, TypeError): return self._genericMessage("Error", "Invalid show ID: %s" % str(show)) @@ -1358,7 +1359,7 @@ class Home(WebRoot): else: return self._genericMessage("Error", errString) - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if not showObj: errString = "Unable to find the specified show: " + str(show) @@ -1369,7 +1370,7 @@ class Home(WebRoot): showObj.exceptions = sickbeard.scene_exceptions.get_scene_exceptions(showObj.indexerid) - if helpers.tryInt(quality_preset, None): + if try_int(quality_preset, None): bestQualities = [] if not location and not anyQualities and not bestQualities and not flatten_folders: @@ -1491,8 +1492,8 @@ class Home(WebRoot): location = location.decode('UTF-8') # if we change location clear the db of episodes, change it, write to db, and rescan - if os.path.normpath(showObj._location) != os.path.normpath(location): - logger.log(os.path.normpath(showObj._location) + " != " + os.path.normpath(location), logger.DEBUG) + if ek(os.path.normpath, showObj._location) != ek(os.path.normpath, location): + logger.log(ek(os.path.normpath, showObj._location) + " != " + ek(os.path.normpath, location), logger.DEBUG) if not ek(os.path.isdir, location) and not sickbeard.CREATE_MISSING_SHOW_DIRS: errors.append("New location <tt>%s</tt> does not exist" % location) @@ -1597,7 +1598,7 @@ class Home(WebRoot): if show is None: return self._genericMessage("Error", "Invalid show ID") - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if showObj is None: return self._genericMessage("Error", "Unable to find the specified show") @@ -1618,13 +1619,13 @@ class Home(WebRoot): if show is None: return self._genericMessage("Error", "Invalid show ID") - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if showObj is None: return self._genericMessage("Error", "Unable to find the specified show") # search and download subtitles - sickbeard.showQueueScheduler.action.downloadSubtitles(showObj, bool(force)) + sickbeard.showQueueScheduler.action.download_subtitles(showObj, bool(force)) time.sleep(cpu_presets[sickbeard.CPU_PRESET]) @@ -1635,7 +1636,7 @@ class Home(WebRoot): showObj = None if show: - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if showObj: showName = urllib.quote_plus(showObj.name.encode('utf-8')) @@ -1666,7 +1667,7 @@ class Home(WebRoot): showObj = None if show: - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if notifiers.emby_notifier.update_library(showObj): ui.notifications.message( @@ -1698,7 +1699,7 @@ class Home(WebRoot): else: return self._genericMessage("Error", errMsg) - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if not showObj: errMsg = "Error", "Show not in show list" @@ -1828,7 +1829,7 @@ class Home(WebRoot): if show is None: return self._genericMessage("Error", "You must specify a show") - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if showObj is None: return self._genericMessage("Error", "Show not in show list") @@ -1874,7 +1875,7 @@ class Home(WebRoot): errMsg = "You must specify a show and at least one episode" return self._genericMessage("Error", errMsg) - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + show_obj = Show.find(sickbeard.showList, int(show)) if show_obj is None: errMsg = "Error", "Show not in show list" @@ -1941,7 +1942,7 @@ class Home(WebRoot): def getManualSearchStatus(self, show=None): def getEpisodes(searchThread, searchstatus): results = [] - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(searchThread.show.indexerid)) + showObj = Show.find(sickbeard.showList, int(searchThread.show.indexerid)) if not showObj: logger.log(u'No Show Object found for show with indexerID: ' + str(searchThread.show.indexerid), logger.ERROR) @@ -2025,15 +2026,15 @@ class Home(WebRoot): # try do download subtitles for that episode previous_subtitles = ep_obj.subtitles try: - ep_obj.downloadSubtitles() + ep_obj.download_subtitles() except Exception: return json.dumps({'result': 'failure'}) # return the correct json value - newSubtitles = frozenset(ep_obj.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.name_from_code(code) for code in new_subtitles] + status = 'New subtitles downloaded: %s' % ', '.join(new_languages) else: status = 'No subtitles downloaded' ui.notifications.message(ep_obj.show.name, status) @@ -2056,7 +2057,7 @@ class Home(WebRoot): if sceneAbsolute in ['null', '']: sceneAbsolute = None - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + showObj = Show.find(sickbeard.showList, int(show)) if showObj.is_anime: result = { @@ -2143,10 +2144,13 @@ class Home(WebRoot): def fetch_releasegroups(show_name): logger.log(u'ReleaseGroups: %s' % show_name, logger.INFO) if helpers.set_up_anidb_connection(): - anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=show_name) - groups = anime.get_groups() - logger.log(u'ReleaseGroups: %s' % groups, logger.INFO) - return json.dumps({'result': 'success', 'groups': groups}) + try: + anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=show_name) + groups = anime.get_groups() + logger.log(u'ReleaseGroups: %s' % groups, logger.INFO) + return json.dumps({'result': 'success', 'groups': groups}) + except AttributeError as error: + logger.log(u'Unable to get ReleaseGroups: %s' % error, logger.DEBUG) return json.dumps({'result': 'failure'}) @@ -2246,14 +2250,14 @@ class HomePostProcess(Home): return self._genericMessage("Postprocessing results", result) -@route('/home/addShows(/?.*)') +@route('/addShows(/?.*)') class HomeAddShows(Home): def __init__(self, *args, **kwargs): super(HomeAddShows, self).__init__(*args, **kwargs) def index(self): - t = PageTemplate(rh=self, filename="home_addShows.mako") - return t.render(title='Add Shows', header='Add Shows', topmenu='home', controller="home", action="addShows") + t = PageTemplate(rh=self, filename="addShows.mako") + return t.render(title='Add Shows', header='Add Shows', topmenu='home', controller="addShows", action="index") @staticmethod def getIndexerLanguages(): @@ -2393,7 +2397,7 @@ class HomeAddShows(Home): cur_dir['existing_info'] = (indexer_id, show_name, indexer) - if indexer_id and helpers.findCertainShow(sickbeard.showList, indexer_id): + if indexer_id and Show.find(sickbeard.showList, indexer_id): cur_dir['added_already'] = True return t.render(dirList=dir_list) @@ -2402,7 +2406,7 @@ class HomeAddShows(Home): Display the new show page which collects a tvdb id, folder, and extra options and posts them to addNewShow """ - t = PageTemplate(rh=self, filename="home_newShow.mako") + t = PageTemplate(rh=self, filename="addShows_newShow.mako") indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add) @@ -2441,7 +2445,8 @@ class HomeAddShows(Home): provided_show_dir=show_dir, provided_indexer_id=provided_indexer_id, provided_indexer_name=provided_indexer_name, provided_indexer=provided_indexer, indexers=sickbeard.indexerApi().indexers, whitelist=[], blacklist=[], groups=[], - title='New Show', header='New Show', topmenu='home' + title='New Show', header='New Show', topmenu='home', + controller="addShows", action="newShow" ) def recommendedShows(self): @@ -2449,8 +2454,9 @@ class HomeAddShows(Home): Display the new show page which collects a tvdb id, folder, and extra options and posts them to addNewShow """ - t = PageTemplate(rh=self, filename="home_recommendedShows.mako") - return t.render(title="Recommended Shows", header="Recommended Shows", enable_anime_options=False) + t = PageTemplate(rh=self, filename="addShows_recommendedShows.mako") + return t.render(title="Recommended Shows", header="Recommended Shows", enable_anime_options=False, + controller="addShows", action="recommendedShows") def getRecommendedShows(self): t = PageTemplate(rh=self, filename="trendingShows.mako") @@ -2473,7 +2479,7 @@ class HomeAddShows(Home): for show_detail in shows: show = {'show': show_detail} try: - if not helpers.findCertainShow(sickbeard.showList, [int(show['show']['ids']['tvdb'])]): + if not Show.find(sickbeard.showList, [int(show['show']['ids']['tvdb'])]): if show['show']['ids']['tvdb'] not in (lshow['show']['ids']['tvdb'] for lshow in library_shows): if not_liked_show: if show['show']['ids']['tvdb'] not in (show['show']['ids']['tvdb'] for show in not_liked_show if show['type'] == 'show'): @@ -2499,8 +2505,9 @@ class HomeAddShows(Home): Display the new show page which collects a tvdb id, folder, and extra options and posts them to addNewShow """ - t = PageTemplate(rh=self, filename="home_trendingShows.mako") - return t.render(title="Trending Shows", header="Trending Shows", enable_anime_options=False) + t = PageTemplate(rh=self, filename="addShows_trendingShows.mako") + return t.render(title="Trending Shows", header="Trending Shows", enable_anime_options=False, + controller="addShows", action="trendingShows") def getTrendingShows(self): """ @@ -2527,7 +2534,7 @@ class HomeAddShows(Home): library_shows = trakt_api.traktRequest("sync/collection/shows?extended=full") or [] for show in shows: try: - if not helpers.findCertainShow(sickbeard.showList, [int(show['show']['ids']['tvdb'])]): + if not Show.find(sickbeard.showList, [int(show['show']['ids']['tvdb'])]): if show['show']['ids']['tvdb'] not in (lshow['show']['ids']['tvdb'] for lshow in library_shows): if not_liked_show: if show['show']['ids']['tvdb'] not in (show['show']['ids']['tvdb'] for show in not_liked_show if show['type'] == 'show'): @@ -2552,7 +2559,7 @@ class HomeAddShows(Home): """ Fetches data from IMDB to show a list of popular shows. """ - t = PageTemplate(rh=self, filename="home_popularShows.mako") + t = PageTemplate(rh=self, filename="addShows_popularShows.mako") e = None try: @@ -2561,7 +2568,8 @@ class HomeAddShows(Home): # print traceback.format_exc() popular_shows = None - return t.render(title="Popular Shows", header="Popular Shows", popular_shows=popular_shows, imdb_exception=e, topmenu="home") + return t.render(title="Popular Shows", header="Popular Shows", popular_shows=popular_shows, imdb_exception=e, topmenu="home", + controller="addShows", action="popularShows") def addShowToBlacklist(self, indexer_id): # URL parameters @@ -2571,17 +2579,24 @@ class HomeAddShows(Home): trakt_api.traktRequest("users/" + sickbeard.TRAKT_USERNAME + "/lists/" + sickbeard.TRAKT_BLACKLIST_NAME + "/items", data, method='POST') - return self.redirect('/home/addShows/trendingShows/') + return self.redirect('/addShows/trendingShows/') def existingShows(self): """ Prints out the page to add existing shows from a root dir """ - t = PageTemplate(rh=self, filename="home_addExistingShow.mako") - return t.render(enable_anime_options=False, title='Existing Show', header='Existing Show', topmenu="home") + t = PageTemplate(rh=self, filename="addShows_addExistingShow.mako") + return t.render(enable_anime_options=False, title='Existing Show', header='Existing Show', topmenu="home", + controller="addShows", action="addExistingShow") + + def addShowByID(self, indexer_id, showName, indexer="TVDB"): + + if indexer is not "TVDB": + tvdb_id = helpers.getTVDBFromID(indexer_id, indexer.upper()) + if tvdb_id is not '': + indexer_id = tvdb_id - def addTraktShow(self, indexer_id, showName): - if helpers.findCertainShow(sickbeard.showList, int(indexer_id)): + if Show.find(sickbeard.showList, int(indexer_id)): return if sickbeard.ROOT_DIRS: @@ -2663,7 +2678,7 @@ class HomeAddShows(Home): logger.log(u"Unable to add show due to show selection. Not anough arguments: %s" % (repr(series_pieces)), logger.ERROR) ui.notifications.error("Unknown error. Unable to add show due to problem with show selection.") - return self.redirect('/home/addShows/existingShows/') + return self.redirect('/addShows/existingShows/') indexer = int(series_pieces[1]) indexer_id = int(series_pieces[3]) @@ -2676,7 +2691,7 @@ class HomeAddShows(Home): indexer = int(providedIndexer) indexer_id = int(whichSeries) - show_name = os.path.basename(os.path.normpath(fullShowPath)) + show_name = ek(os.path.basename, ek(os.path.normpath, fullShowPath)) # use the whole path if it's given, or else append the show name to the root dir to get the full show path if fullShowPath: @@ -2687,7 +2702,7 @@ class HomeAddShows(Home): # blanket policy - if the dir exists you should have used "add existing show" numbnuts if ek(os.path.isdir, show_dir) and not fullShowPath: ui.notifications.error("Unable to add show", "Folder " + show_dir + " exists already") - return self.redirect('/home/addShows/existingShows/') + return self.redirect('/addShows/existingShows/') # don't create show dir if config says not to if sickbeard.ADD_SHOWS_WO_DIR: @@ -2717,7 +2732,7 @@ class HomeAddShows(Home): if not anyQualities: anyQualities = [] - if not bestQualities or helpers.tryInt(quality_preset, None): + if not bestQualities or try_int(quality_preset, None): bestQualities = [] if not isinstance(anyQualities, list): anyQualities = [anyQualities] @@ -2937,7 +2952,7 @@ class Manage(Home, WebRoot): result = {} for cur_result in cur_show_results: if whichSubs == 'all': - if not frozenset(subtitles.wantedLanguages()).difference(cur_result["subtitles"].split(',')): + if not frozenset(subtitles.wanted_languages()).difference(cur_result["subtitles"].split(',')): continue elif whichSubs in cur_result["subtitles"]: continue @@ -2977,7 +2992,7 @@ class Manage(Home, WebRoot): sorted_show_ids = [] for cur_status_result in status_results: if whichSubs == 'all': - if not frozenset(subtitles.wantedLanguages()).difference(cur_status_result["subtitles"].split(',')): + if not frozenset(subtitles.wanted_languages()).difference(cur_status_result["subtitles"].split(',')): continue elif whichSubs in cur_status_result["subtitles"]: continue @@ -3024,13 +3039,13 @@ class Manage(Home, WebRoot): for epResult in to_download[cur_indexer_id]: season, episode = epResult.split('x') - show = sickbeard.helpers.findCertainShow(sickbeard.showList, int(cur_indexer_id)) - show.getEpisode(int(season), int(episode)).downloadSubtitles() + show = Show.find(sickbeard.showList, int(cur_indexer_id)) + show.getEpisode(int(season), int(episode)).download_subtitles() return self.redirect('/manage/subtitleMissed/') def backlogShow(self, indexer_id): - show_obj = helpers.findCertainShow(sickbeard.showList, int(indexer_id)) + show_obj = Show.find(sickbeard.showList, int(indexer_id)) if show_obj: sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj]) @@ -3086,7 +3101,7 @@ class Manage(Home, WebRoot): showNames = [] for curID in showIDs: curID = int(curID) - showObj = helpers.findCertainShow(sickbeard.showList, curID) + showObj = Show.find(sickbeard.showList, curID) if showObj: showList.append(showObj) showNames.append(showObj.name) @@ -3226,7 +3241,7 @@ class Manage(Home, WebRoot): errors = [] for curShow in showIDs: curErrors = [] - showObj = helpers.findCertainShow(sickbeard.showList, int(curShow)) + showObj = Show.find(sickbeard.showList, int(curShow)) if not showObj: continue @@ -3295,7 +3310,7 @@ class Manage(Home, WebRoot): if quality_preset == 'keep': anyQualities, bestQualities = Quality.splitQuality(showObj.quality) - elif helpers.tryInt(quality_preset, None): + elif try_int(quality_preset, None): bestQualities = [] exceptions_list = [] @@ -3369,7 +3384,7 @@ class Manage(Home, WebRoot): if curShowID == '': continue - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(curShowID)) + showObj = Show.find(sickbeard.showList, int(curShowID)) if showObj is None: continue @@ -3404,7 +3419,7 @@ class Manage(Home, WebRoot): renames.append(showObj.name) if curShowID in toSubtitle: - sickbeard.showQueueScheduler.action.downloadSubtitles(showObj) + sickbeard.showQueueScheduler.action.download_subtitles(showObj) subtitles.append(showObj.name) if errors: @@ -3758,10 +3773,10 @@ class ConfigGeneral(Config): sickbeard.DEBUG = config.checkbox_to_value(debug) sickbeard.SSL_VERIFY = config.checkbox_to_value(ssl_verify) # sickbeard.LOG_DIR is set in config.change_LOG_DIR() - sickbeard.COMING_EPS_MISSED_RANGE = config.to_int(coming_eps_missed_range, default=7) + sickbeard.COMING_EPS_MISSED_RANGE = try_int(coming_eps_missed_range, 7) sickbeard.DISPLAY_ALL_SEASONS = config.checkbox_to_value(display_all_seasons) - sickbeard.WEB_PORT = config.to_int(web_port) + sickbeard.WEB_PORT = try_int(web_port) sickbeard.WEB_IPV6 = config.checkbox_to_value(web_ipv6) # sickbeard.WEB_LOG is set in config.change_LOG_DIR() if config.checkbox_to_value(encryption_version) == 1: @@ -3778,10 +3793,10 @@ class ConfigGeneral(Config): sickbeard.DATE_PRESET = date_preset if indexer_default: - sickbeard.INDEXER_DEFAULT = config.to_int(indexer_default) + sickbeard.INDEXER_DEFAULT = try_int(indexer_default) if indexer_timeout: - sickbeard.INDEXER_TIMEOUT = config.to_int(indexer_timeout) + sickbeard.INDEXER_TIMEOUT = try_int(indexer_timeout) if time_preset: sickbeard.TIME_PRESET_W_SECONDS = time_preset @@ -3790,7 +3805,7 @@ class ConfigGeneral(Config): sickbeard.TIMEZONE_DISPLAY = timezone_display if not config.change_LOG_DIR(log_dir, web_log): - results += ["Unable to create directory " + os.path.normpath(log_dir) + ", log directory not changed."] + results += ["Unable to create directory " + ek(os.path.normpath, log_dir) + ", log directory not changed."] sickbeard.API_KEY = api_key @@ -3798,11 +3813,11 @@ class ConfigGeneral(Config): if not config.change_HTTPS_CERT(https_cert): results += [ - "Unable to create directory " + os.path.normpath(https_cert) + ", https cert directory not changed."] + "Unable to create directory " + ek(os.path.normpath, https_cert) + ", https cert directory not changed."] if not config.change_HTTPS_KEY(https_key): results += [ - "Unable to create directory " + os.path.normpath(https_key) + ", https key directory not changed."] + "Unable to create directory " + ek(os.path.normpath, https_key) + ", https key directory not changed."] sickbeard.HANDLE_REVERSE_PROXY = config.checkbox_to_value(handle_reverse_proxy) @@ -3841,17 +3856,17 @@ class ConfigBackupRestore(Config): finalResult = '' if backupDir: - source = [os.path.join(sickbeard.DATA_DIR, 'sickbeard.db'), sickbeard.CONFIG_FILE, - os.path.join(sickbeard.DATA_DIR, 'failed.db'), - os.path.join(sickbeard.DATA_DIR, 'cache.db')] - target = os.path.join(backupDir, 'sickrage-' + time.strftime('%Y%m%d%H%M%S') + '.zip') + source = [ek(os.path.join, sickbeard.DATA_DIR, 'sickbeard.db'), sickbeard.CONFIG_FILE, + ek(os.path.join, sickbeard.DATA_DIR, 'failed.db'), + ek(os.path.join, sickbeard.DATA_DIR, 'cache.db')] + target = ek(os.path.join, backupDir, 'sickrage-' + time.strftime('%Y%m%d%H%M%S') + '.zip') - for (path, dirs, files) in os.walk(sickbeard.CACHE_DIR, topdown=True): + for (path, dirs, files) in ek(os.walk, sickbeard.CACHE_DIR, topdown=True): for dirname in dirs: if path == sickbeard.CACHE_DIR and dirname not in ['images']: dirs.remove(dirname) for filename in files: - source.append(os.path.join(path, filename)) + source.append(ek(os.path.join, path, filename)) if helpers.backupConfigZip(source, target, sickbeard.DATA_DIR): finalResult += "Successful backup to " + target @@ -3871,7 +3886,7 @@ class ConfigBackupRestore(Config): if backupFile: source = backupFile - target_dir = os.path.join(sickbeard.DATA_DIR, 'restore') + target_dir = ek(os.path.join, sickbeard.DATA_DIR, 'restore') if helpers.restoreConfigZip(source, target_dir): finalResult += "Successfully extracted restore files to " + target_dir @@ -3913,22 +3928,22 @@ class ConfigSearch(Config): results = [] if not config.change_NZB_DIR(nzb_dir): - results += ["Unable to create directory " + os.path.normpath(nzb_dir) + ", dir not changed."] + results += ["Unable to create directory " + ek(os.path.normpath, nzb_dir) + ", dir not changed."] if not config.change_TORRENT_DIR(torrent_dir): - results += ["Unable to create directory " + os.path.normpath(torrent_dir) + ", dir not changed."] + results += ["Unable to create directory " + ek(os.path.normpath, torrent_dir) + ", dir not changed."] config.change_DAILYSEARCH_FREQUENCY(dailysearch_frequency) config.change_BACKLOG_FREQUENCY(backlog_frequency) - sickbeard.BACKLOG_DAYS = config.to_int(backlog_days, default=7) + sickbeard.BACKLOG_DAYS = try_int(backlog_days, 7) sickbeard.USE_NZBS = config.checkbox_to_value(use_nzbs) sickbeard.USE_TORRENTS = config.checkbox_to_value(use_torrents) sickbeard.NZB_METHOD = nzb_method sickbeard.TORRENT_METHOD = torrent_method - sickbeard.USENET_RETENTION = config.to_int(usenet_retention, default=500) + sickbeard.USENET_RETENTION = try_int(usenet_retention, 500) sickbeard.IGNORE_WORDS = ignore_words if ignore_words else "" sickbeard.TRACKERS_LIST = trackers_list if trackers_list else "" @@ -3964,7 +3979,7 @@ class ConfigSearch(Config): sickbeard.NZBGET_CATEGORY_ANIME_BACKLOG = nzbget_category_anime_backlog sickbeard.NZBGET_HOST = config.clean_host(nzbget_host) sickbeard.NZBGET_USE_HTTPS = config.checkbox_to_value(nzbget_use_https) - sickbeard.NZBGET_PRIORITY = config.to_int(nzbget_priority, default=100) + sickbeard.NZBGET_PRIORITY = try_int(nzbget_priority, 100) sickbeard.TORRENT_USERNAME = torrent_username sickbeard.TORRENT_PASSWORD = torrent_password @@ -4023,7 +4038,7 @@ class ConfigPostProcessing(Config): results = [] if not config.change_TV_DOWNLOAD_DIR(tv_download_dir): - results += ["Unable to create directory " + os.path.normpath(tv_download_dir) + ", dir not changed."] + results += ["Unable to create directory " + ek(os.path.normpath, tv_download_dir) + ", dir not changed."] config.change_AUTOPOSTPROCESSER_FREQUENCY(autopostprocesser_frequency) config.change_PROCESS_AUTOMATICALLY(process_automatically) @@ -4179,7 +4194,7 @@ class ConfigPostProcessing(Config): """ try: - rar_path = os.path.join(sickbeard.PROG_DIR, 'lib', 'unrar2', 'test.rar') + rar_path = ek(os.path.join, sickbeard.PROG_DIR, 'lib', 'unrar2', 'test.rar') testing = RarFile(rar_path).read_files('*test.txt') if testing[0][1] == 'This is only a test.': return 'supported' @@ -4454,7 +4469,7 @@ class ConfigProviders(Config): # do the enable/disable for curProviderStr in provider_str_list: curProvider, curEnabled = curProviderStr.split(':') - curEnabled = config.to_int(curEnabled) + curEnabled = try_int(curEnabled) curProvObj = [x for x in sickbeard.providers.sortedProviderList() if x.getID() == curProvider and hasattr(x, 'enabled')] @@ -4860,7 +4875,7 @@ class ConfigNotifications(Config): sickbeard.EMAIL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(email_notify_ondownload) sickbeard.EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(email_notify_onsubtitledownload) sickbeard.EMAIL_HOST = config.clean_host(email_host) - sickbeard.EMAIL_PORT = config.to_int(email_port, default=25) + sickbeard.EMAIL_PORT = try_int(email_port, 25) sickbeard.EMAIL_FROM = email_from sickbeard.EMAIL_TLS = config.checkbox_to_value(email_tls) sickbeard.EMAIL_USER = email_user @@ -4931,7 +4946,7 @@ class ConfigSubtitles(Config): config.change_SUBTITLES_FINDER_FREQUENCY(subtitles_finder_frequency) config.change_USE_SUBTITLES(use_subtitles) - sickbeard.SUBTITLES_LANGUAGES = [lang.strip() for lang in subtitles_languages.split(',') if subtitles.isValidLanguage(lang.strip())] if subtitles_languages else [] + sickbeard.SUBTITLES_LANGUAGES = [code.strip() for code in subtitles_languages.split(',') if code.strip() in subtitles.subtitle_code_filter()] if subtitles_languages else [] sickbeard.SUBTITLES_DIR = subtitles_dir sickbeard.SUBTITLES_HISTORY = config.checkbox_to_value(subtitles_history) sickbeard.EMBEDDED_SUBTITLES_ALL = config.checkbox_to_value(embedded_subtitles_all) @@ -5128,12 +5143,12 @@ class ErrorLogs(WebRoot): data = [] - if os.path.isfile(logger.logFile): + if ek(os.path.isfile, logger.logFile): with io.open(logger.logFile, 'r', encoding='utf-8') as f: data = Get_Data(minLevel, f.readlines(), 0, regex, logFilter, logSearch, maxLines) for i in range(1, int(sickbeard.LOG_NR)): - if os.path.isfile(logger.logFile + "." + str(i)) and (len(data) <= maxLines): + if ek(os.path.isfile, logger.logFile + "." + str(i)) and (len(data) <= maxLines): with io.open(logger.logFile + "." + str(i), 'r', encoding='utf-8') as f: data += Get_Data(minLevel, f.readlines(), len(data), regex, logFilter, logSearch, maxLines) diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index c9636d67580e87192f6b0d5badec46b60efc66fc..dd709e2e3bd87f7b7769725ae9691b138da33d4b 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -6,6 +6,8 @@ from sickbeard.webserve import LoginHandler, LogoutHandler, KeyHandler, Calendar from sickbeard.webapi import ApiHandler from sickbeard import logger from sickbeard.helpers import create_https_certificates, generateApiKey +from sickrage.helper.encoding import ek + from tornado.web import Application, StaticFileHandler, RedirectHandler from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop @@ -52,14 +54,14 @@ class SRWebServer(threading.Thread): if self.enable_https: # If either the HTTPS certificate or key do not exist, make some self-signed ones. - if not (self.https_cert and os.path.exists(self.https_cert)) or not ( - self.https_key and os.path.exists(self.https_key)): + if not (self.https_cert and ek(os.path.exists, self.https_cert)) or not ( + self.https_key and ek(os.path.exists, self.https_key)): if not create_https_certificates(self.https_cert, self.https_key): logger.log(u"Unable to create CERT/KEY files, disabling HTTPS") sickbeard.ENABLE_HTTPS = False self.enable_https = False - if not (os.path.exists(self.https_cert) and os.path.exists(self.https_key)): + if not (ek(os.path.exists, self.https_cert) and ek(os.path.exists, self.https_key)): logger.log(u"Disabled HTTPS because of missing CERT and KEY files", logger.WARNING) sickbeard.ENABLE_HTTPS = False self.enable_https = False @@ -101,23 +103,23 @@ class SRWebServer(threading.Thread): self.app.add_handlers(".*$", [ # favicon (r'%s/(favicon\.ico)' % self.options['web_root'], StaticFileHandler, - {"path": os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}), + {"path": ek(os.path.join, self.options['data_root'], 'images/ico/favicon.ico')}), # images (r'%s/images/(.*)' % self.options['web_root'], StaticFileHandler, - {"path": os.path.join(self.options['data_root'], 'images')}), + {"path": ek(os.path.join, self.options['data_root'], 'images')}), # cached images (r'%s/cache/images/(.*)' % self.options['web_root'], StaticFileHandler, - {"path": os.path.join(sickbeard.CACHE_DIR, 'images')}), + {"path": ek(os.path.join, sickbeard.CACHE_DIR, 'images')}), # css (r'%s/css/(.*)' % self.options['web_root'], StaticFileHandler, - {"path": os.path.join(self.options['data_root'], 'css')}), + {"path": ek(os.path.join, self.options['data_root'], 'css')}), # javascript (r'%s/js/(.*)' % self.options['web_root'], StaticFileHandler, - {"path": os.path.join(self.options['data_root'], 'js')}), + {"path": ek(os.path.join, self.options['data_root'], 'js')}), # videos ] + [(r'%s/videos/(.*)' % self.options['web_root'], StaticFileHandler, diff --git a/sickrage/helper/common.py b/sickrage/helper/common.py index f63111cbd498632dc5dac61c7c5b42e60a1fbed5..34e0f5a107321a11dbdca0fd3748ad6fde85f481 100644 --- a/sickrage/helper/common.py +++ b/sickrage/helper/common.py @@ -21,6 +21,84 @@ import sickbeard dateFormat = '%Y-%m-%d' dateTimeFormat = '%Y-%m-%d %H:%M:%S' +# Mapping HTTP status codes to official W3C names +http_status_code = { + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 306: 'Switch Proxy', + 307: 'Temporary Redirect', + 308: 'Permanent Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Request Entity Too Large', + 414: 'Request-URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Requested Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'Im a teapot', + 419: 'Authentication Timeout', + 420: 'Enhance Your Calm', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 440: 'Login Timeout', + 444: 'No Response', + 449: 'Retry With', + 450: 'Blocked by Windows Parental Controls', + 451: [ + 'Redirect', + 'Unavailable For Legal Reasons', + ], + 494: 'Request Header Too Large', + 495: 'Cert Error', + 496: 'No Cert', + 497: 'HTTP to HTTPS', + 498: 'Token expired/invalid', + 499: [ + 'Client Closed Request', + 'Token required', + ], + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 509: 'Bandwidth Limit Exceeded', + 510: 'Not Extended', + 511: 'Network Authentication Required', + 520: 'Cloudfare - Web server is returning an unknown error', + 521: 'Cloudfare - Web server is down', + 522: 'Cloudfare - Connection timed out', + 523: 'Cloudfare - Origin is unreachable', + 524: 'Cloudfare - A timeout occurred', + 525: 'Cloudfare - SSL handshake failed', + 526: 'Cloudfare - Invalid SSL certificate', + 598: 'Network read timeout error', + 599: 'Network connect timeout error', +} 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', @@ -29,6 +107,27 @@ subtitle_extensions = ['ass', 'idx', 'srt', 'ssa', 'sub'] timeFormat = '%A %I:%M %p' +def http_code_description(http_code): + """ + Get the description of the provided HTTP status code. + :param http_code: The HTTP status code + :return: The description of the provided ``http_code`` + """ + + if http_code in http_status_code: + description = http_status_code[http_code] + + if isinstance(description, list): + return '(%s)' % ', '.join(description) + + return description + + # TODO Restore logger import + # logger.log(u'Unknown HTTP status code %s. Please submit an issue' % http_code, logger.ERROR) + + return None + + def is_sync_file(filename): """ Check if the provided ``filename`` is a sync file, based on its name. @@ -63,11 +162,9 @@ def pretty_file_size(size): :param size: The size to convert :return: The converted size """ - - if isinstance(size, str) and size.isdigit(): + if isinstance(size, (str, unicode)) and size.isdigit(): size = float(size) - - if not isinstance(size, (int, long, float)): + elif not isinstance(size, (int, long, float)): return '' remaining_size = size @@ -133,3 +230,17 @@ def sanitize_filename(filename): return filename return '' + + +def try_int(candidate, default_value=0): + """ + Try to convert ``candidate`` to int, or return the ``default_value``. + :param candidate: The value to convert to int + :param default_value: The value to return if the conversion fails + :return: ``candidate`` as int, or ``default_value`` if the conversion fails + """ + + try: + return int(candidate) + except Exception: + return default_value diff --git a/sickrage/media/GenericMedia.py b/sickrage/media/GenericMedia.py index d323e2909e113b35aa824c2aa14c6932919ee865..968aac52cb74997069d78048ec7e9e5bf1d85867 100644 --- a/sickrage/media/GenericMedia.py +++ b/sickrage/media/GenericMedia.py @@ -21,9 +21,10 @@ import sickbeard from abc import abstractmethod from mimetypes import guess_type from os.path import isfile, join, normpath -from sickbeard.helpers import findCertainShow +from sickrage.helper.common import try_int from sickrage.helper.encoding import ek from sickrage.helper.exceptions import MultipleShowObjectsException +from sickrage.show.Show import Show class GenericMedia: @@ -33,16 +34,13 @@ class GenericMedia: :param media_format: The format of the media to get. Must be either 'normal' or 'thumb' """ + self.indexer_id = try_int(indexer_id, 0) + if media_format in ('normal', 'thumb'): self.media_format = media_format else: self.media_format = 'normal' - try: - self.indexer_id = int(indexer_id) - except ValueError: - self.indexer_id = 0 - @abstractmethod def get_default_media_name(self): """ @@ -98,7 +96,7 @@ class GenericMedia: """ try: - return findCertainShow(sickbeard.showList, self.indexer_id) + return Show.find(sickbeard.showList, self.indexer_id) except MultipleShowObjectsException: return None diff --git a/sickrage/show/History.py b/sickrage/show/History.py index c015da8a8d420fe4f134205b52b3a30a5a037b0f..b46d0024e10fc0a617af1a078d49cb7d82557bf2 100644 --- a/sickrage/show/History.py +++ b/sickrage/show/History.py @@ -20,6 +20,7 @@ from datetime import datetime from datetime import timedelta from sickbeard.common import Quality from sickbeard.db import DBConnection +from sickrage.helper.common import try_int class History: @@ -96,7 +97,7 @@ class History: @staticmethod def _get_actions(action): - action = action.lower() if isinstance(action, str) else '' + action = action.lower() if isinstance(action, (str, unicode)) else '' if action == 'downloaded': return Quality.DOWNLOADED @@ -108,12 +109,6 @@ class History: @staticmethod def _get_limit(limit): - try: - limit = int(limit) - except (TypeError, ValueError): - return 0 + limit = try_int(limit, 0) - if limit < 0: - return 0 - - return int(limit) + return max(limit, 0) diff --git a/sickrage/show/Show.py b/sickrage/show/Show.py index 0634beb0175d985ffeb154750e812d9e5672d66c..441695a405cb95886b22a83ca58efb75d14b2e79 100644 --- a/sickrage/show/Show.py +++ b/sickrage/show/Show.py @@ -21,7 +21,6 @@ import sickbeard from datetime import date from sickbeard.common import Quality, SKIPPED, WANTED from sickbeard.db import DBConnection -from sickbeard.helpers import findCertainShow from sickrage.helper.exceptions import CantRefreshShowException, CantRemoveShowException, ex from sickrage.helper.exceptions import MultipleShowObjectsException @@ -53,6 +52,30 @@ class Show: return None, show + @staticmethod + def find(shows, indexer_id): + """ + Find a show by its indexer id in the provided list of shows + :param shows: The list of shows to search in + :param indexer_id: The indexer id of the desired show + :return: The desired show if found, ``None`` if not found + :throw: ``MultipleShowObjectsException`` if multiple shows match the provided ``indexer_id`` + """ + + if indexer_id is None or shows is None or len(shows) == 0: + return None + + indexer_ids = [indexer_id] if not isinstance(indexer_id, list) else indexer_id + results = [show for show in shows if show.indexerid in indexer_ids] + + if len(results) == 0: + return None + + if len(results) == 1: + return results[0] + + raise MultipleShowObjectsException() + @staticmethod def overall_stats(): db = DBConnection() @@ -158,7 +181,7 @@ class Show: return 'Invalid show ID', None try: - show = findCertainShow(sickbeard.showList, int(indexer_id)) + show = Show.find(sickbeard.showList, indexer_id) except MultipleShowObjectsException: return 'Unable to find the specified show', None diff --git a/tests/__init__.py b/tests/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..78b5f9dff0d33aff7981ffad3aca187949df3161 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,4 @@ +# coding=utf-8 +""" +Test all modules for SickRage +""" \ No newline at end of file diff --git a/tests/all_tests.py b/tests/all_tests.py index ca1533cfc9da4e89a6fb1b45c2f1fc89026b9402..47aea896f0f49e69e44dfb3cf51770a417364551 100755 --- a/tests/all_tests.py +++ b/tests/all_tests.py @@ -18,12 +18,11 @@ # 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=line-too-long - """ Perform all tests in tests/ """ +# pylint: disable=line-too-long import fnmatch import os diff --git a/tests/common_tests.py b/tests/common_tests.py index 83a77e752553f881eefff862f6b292916ec5eaf9..9dd29d9d17808fbbc0bd1d6d494c62bf20f67216 100644 --- a/tests/common_tests.py +++ b/tests/common_tests.py @@ -27,9 +27,8 @@ Classes: # TODO: Implement skipped tests - -import sys import os.path +import sys import unittest sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) diff --git a/tests/config_tests.py b/tests/config_tests.py index b396896919ec140558c9f4799f4f90d7770ba1f2..e52c082fe223671ab42a87819f4f02ae00543b53 100644 --- a/tests/config_tests.py +++ b/tests/config_tests.py @@ -37,7 +37,6 @@ Methods clean_host clean_hosts clean_url - to_int minimax check_setting_int check_setting_float @@ -46,11 +45,11 @@ Methods # pylint: disable=line-too-long -import sys +from collections import namedtuple +import logging import os.path +import sys import unittest -import logging -from collections import namedtuple 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__), '..'))) @@ -119,18 +118,11 @@ class ConfigTestBasic(unittest.TestCase): self.assertEqual(config.clean_url(test_url.dirty), test_url.clean) elif test_url.expected_result is True: self.assertEqual(config.clean_url(test_url.dirty), test_url.clean) - elif not test_url.expected_result is False: + elif test_url.expected_result is False: self.assertNotEqual(config.clean_url(test_url.dirty), test_url.clean) else: log.error('Test not defined for %s', test_url) - @unittest.skip('Test not implemented') - def test_to_int(self): - """ - Test to_int - """ - pass - @unittest.skip('Test not implemented') def test_mini_max(self): """ diff --git a/tests/db_tests.py b/tests/db_tests.py index fca31fec0458a010af0ebd25556163befae4c912..6088dc342ed987a1c75f344b353a5267c80aa02c 100644 --- a/tests/db_tests.py +++ b/tests/db_tests.py @@ -25,10 +25,10 @@ Tests: DBMultiTests """ -import sys import os.path -import unittest +import sys import threading +import unittest 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__), '..'))) diff --git a/tests/encoding_tests.py b/tests/encoding_tests.py index b6d042f4aa37dfb0e7b7b18dd256b6ed6167b2ed..626ea831d67f40a66ce5198026d7b72f94096a1a 100644 --- a/tests/encoding_tests.py +++ b/tests/encoding_tests.py @@ -6,18 +6,17 @@ Test encoding # pylint: disable=line-too-long -import sys -import os.path import locale +import os.path +import sys import unittest 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 import ek, ex from sickrage.helper.common import sanitize_filename -from sickrage.helper.encoding import ek -from sickrage.helper.exceptions import ex class EncodingTests(unittest.TestCase): diff --git a/tests/exceptions_helper_tests.py b/tests/exceptions_helper_tests.py index a1d4e4d29ff9030b41ea5968535de6c0ded513f7..a603893809819be6914cc817a81e179b1607becf 100644 --- a/tests/exceptions_helper_tests.py +++ b/tests/exceptions_helper_tests.py @@ -1,42 +1,75 @@ # -*- coding: utf-8 -*- -import sys, os.path +""" +Test exceptions helpers +""" + +# pylint: disable=line-too-long + +import os.path +import sys +import unittest + 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 +from sickbeard import ex -import test_lib as test -from sickrage.helper.exceptions import ex class ExceptionsHelperTestCase(unittest.TestCase): + """ + Test exceptions helper + """ def test_none_returns_empty(self): + """ + Test none returns empty + """ self.assertEqual(ex(None), u'') def test_empty_args_returns_empty(self): + """ + Test empty args returns empty + """ self.assertEqual(ex(Exception()), u'') def test_args_of_none_returns_empty(self): + """ + Test args of none returns empty + """ self.assertEqual(ex(Exception(None, None)), u'') - def test_Exception_returns_args_string(self): + def test_ex_ret_args_string(self): + """ + Test exception returns args strings + :return: + """ self.assertEqual(ex(Exception('hi')), 'hi') -# TODO why doesn't this work? -# def test_Exception_returns_args_ustring(self): -# self.assertEqual(ex(Exception('\xc3\xa4h')), u'äh') + # TODO why doesn't this work?@ + @unittest.skip('Errors with unicode conversion') + def test_ex_ret_args_ustring(self): + """ + Test exception returns args ustring + """ + self.assertEqual(ex(Exception('\xc3\xa4h')), u'äh') - def test_Exception_returns_concatenated_args_strings(self): + def test_ex_ret_concat_args_strings(self): + """ + Test exception returns concatenated args and strings + """ self.assertEqual(ex(Exception('lots', 'of', 'strings')), 'lots : of : strings') - def test_Exception_returns_stringified_args(self): + def test_ex_ret_stringed_args(self): + """ + Test exception returns stringed args + """ self.assertEqual(ex(Exception(303)), 'error 303') if __name__ == '__main__': if len(sys.argv) > 1: - suite = unittest.TestLoader().loadTestsFromName('exceptions_helper_tests.ExceptionsHelperTestCase.test_' + sys.argv[1]) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromName('exceptions_helper_tests.ExceptionsHelperTestCase.test_' + sys.argv[1]) + unittest.TextTestRunner(verbosity=2).run(SUITE) else: - suite = unittest.TestLoader().loadTestsFromTestCase(ExceptionsHelperTestCase) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(ExceptionsHelperTestCase) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/feedparser_tests.py b/tests/feedparser_tests.py index 701c23a421edfb8c09f558a02878be9596a20b31..95b64de3988d854069b0379e1b6cea2343563399 100644 --- a/tests/feedparser_tests.py +++ b/tests/feedparser_tests.py @@ -4,8 +4,8 @@ Test Feed Parser """ -import sys import os.path +import sys import unittest sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) diff --git a/tests/helpers_tests.py b/tests/helpers_tests.py index 87b9e1c5dc9c1fbc9b3a4b4c365542d4120cca6b..b605837d48c8903fdfb6f1c426a95b96c9d3b2f3 100755 --- a/tests/helpers_tests.py +++ b/tests/helpers_tests.py @@ -1,6 +1,7 @@ #!/usr/bin/env python2.7 +# coding=utf-8 # Author: Dustyn Gibson <miigotu@gmail.com> -# URL: http://github.com/SiCKRAGETV/SickRage +# URL: http://github.com/SickRage/SickRage # # This file is part of SickRage. # @@ -21,7 +22,6 @@ Test sickbeard.helpers Methods: - isValidLanguage fixGlob indentXML remove_non_release_groups @@ -29,7 +29,6 @@ Methods: isRarFile isBeingWritten remove_file_failed - findCertainShow makeDir searchDBForShow searchIndexerForShowID @@ -55,7 +54,6 @@ Methods: create_https_certificates backupVersionedFile restoreVersionedFile - tryInt md5_for_file get_lan_ip check_url @@ -76,7 +74,6 @@ Methods: mapIndexersToShow touchFile _getTempDir - codeDescription _setUpSession getURL download_file @@ -90,16 +87,14 @@ Methods: getDiskSpaceUsage """ -import sys import os.path +import sys +import unittest 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 - -from sickbeard.helpers import isValidLanguage, remove_non_release_groups -from babelfish import language # pylint: disable=import-error +from sickbeard.helpers import remove_non_release_groups TEST_RESULT = 'Show.Name.S01E01.HDTV.x264-RLSGROUP' TEST_CASES = { @@ -461,13 +456,6 @@ class HelpersShowTests(unittest.TestCase): """ Test show methods """ - @unittest.skip('Not yet implemented') - def test_find_certain_show(self): - """ - Test findCertainShow - """ - pass - @unittest.skip('Not yet implemented') def test_search_db_for_show(self): """ @@ -590,26 +578,6 @@ class HelpersMiscTests(unittest.TestCase): """ Test misc helper methods """ - @unittest.expectedFailure - def test_is_valid_language(self): - # TODO: Determine why this fails and at such a high failure % - """ - Test isValidLanguage - """ - exception_count = 0 - total = 0 - for lang in language.LANGUAGES: - total += 1 - try: - self.assertTrue(isValidLanguage(lang), lang) - except NameError: - exception_count += 1 - except Exception as error: - raise error - if exception_count > 0: - raise Exception('Language failure ratio: %s [%s/%s]' % - (100.0 * exception_count/total, exception_count, total)) - @unittest.skip('Not yet implemented') def test_fix_glob(self): """ @@ -659,13 +627,6 @@ class HelpersMiscTests(unittest.TestCase): """ pass - @unittest.skip('Not yet implemented') - def test_try_int(self): - """ - Test tryInt - """ - pass - @unittest.skip('Not yet implemented') def test_full_sanitize_scene_name(self): """ @@ -673,13 +634,6 @@ class HelpersMiscTests(unittest.TestCase): """ pass - @unittest.skip('Not yet implemented') - def test_code_description(self): - """ - Test codeDescription - """ - pass - @unittest.skip('Not yet implemented') def test_remove_article(self): """ diff --git a/tests/issue_submitter_tests.py b/tests/issue_submitter_tests.py index 79829f5b45821a1084e969a3e7e7f0422a4bad2c..c929ee284587eae0e1d75a51c9cfacbeff794631 100644 --- a/tests/issue_submitter_tests.py +++ b/tests/issue_submitter_tests.py @@ -21,15 +21,14 @@ Test exception logging """ -import sys import os.path +import sys import unittest 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 import logger -from sickrage.helper.exceptions import ex +from sickbeard import logger, ex def exception_generator(): diff --git a/tests/name_parser_tests.py b/tests/name_parser_tests.py index 50293a9078039849bb8b8a4ddb0cb57c0e838987..c71a904e2f680cb00e4af8f686303860f62dfb35 100644 --- a/tests/name_parser_tests.py +++ b/tests/name_parser_tests.py @@ -1,23 +1,23 @@ +# coding=utf-8 """ Test name parsing """ # pylint: disable=line-too-long -import sys -import os.path import datetime +import os.path +import sys import unittest 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 tests import test_lib as test -import sickbeard from sickbeard import tv from sickbeard.name_parser import parser +import tests.test_lib as test -sickbeard.SYS_ENCODING = 'UTF-8' +SYS_ENCODING = 'UTF-8' DEBUG = VERBOSE = False diff --git a/tests/notifier_tests.py b/tests/notifier_tests.py index b6b2b9d99bb3c096e66dd63429325838807c789a..9d72f62f30523a2ca355bd5640deb3be5d2853bd 100644 --- a/tests/notifier_tests.py +++ b/tests/notifier_tests.py @@ -30,22 +30,20 @@ Test notifiers """ -import sys import os.path +import sys import unittest 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 - 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 +import tests.test_lib as test class NotifierTests(test.SickbeardTestDBCase): # pylint: disable=too-many-public-methods diff --git a/tests/numdict_tests.py b/tests/numdict_tests.py index 4802550bf9ee0c8ccfd5eafb7c1980d198e1145f..54f752a20288fb6082fd60abb32d2044a400b8d4 100644 --- a/tests/numdict_tests.py +++ b/tests/numdict_tests.py @@ -6,8 +6,8 @@ Unit Tests for sickbeard/numdict.py # pylint: disable=line-too-long -import sys import os.path +import sys import unittest sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) diff --git a/tests/pp_tests.py b/tests/pp_tests.py index c11b90338b1c8746d0b99d51bfeea538a75e016a..30ecdc4750928f9d253f526fca0e0708a17aac96 100644 --- a/tests/pp_tests.py +++ b/tests/pp_tests.py @@ -17,50 +17,70 @@ # 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 post processing +""" -import random +import os.path +import sys import unittest -import test_lib as test +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.name_cache import addNameToCache from sickbeard.postProcessor import PostProcessor -import sickbeard from sickbeard.tv import TVEpisode, TVShow -from sickbeard.name_cache import addNameToCache +import sickbeard +import tests.test_lib as test class PPInitTests(unittest.TestCase): - + """ + Init tests + """ def setUp(self): - self.pp = PostProcessor(test.FILE_PATH) + """ + Set up tests + """ + self.post_processor = PostProcessor(test.FILE_PATH) def test_init_file_name(self): - self.assertEqual(self.pp.file_name, test.FILENAME) + """ + Test file name + """ + self.assertEqual(self.post_processor.file_name, test.FILENAME) def test_init_folder_name(self): - self.assertEqual(self.pp.folder_name, test.SHOW_NAME) + """ + Test folder name + """ + self.assertEqual(self.post_processor.folder_name, test.SHOW_NAME) -class PPBasicTests(test.SickbeardTestDBCase): +class PPBasicTests(test.SickbeardTestDBCase): + """ + Basic tests + """ def test_process(self): - show = TVShow(1,3) + """ + Test process + """ + show = TVShow(1, 3) show.name = test.SHOW_NAME show.location = test.SHOW_DIR show.saveToDB() sickbeard.showList = [show] - ep = TVEpisode(show, test.SEASON, test.EPISODE) - ep.name = "some ep name" - ep.saveToDB() + episode = TVEpisode(show, test.SEASON, test.EPISODE) + episode.name = "some episode name" + episode.saveToDB() addNameToCache('show name', 3) sickbeard.PROCESS_METHOD = 'move' - pp = PostProcessor(test.FILE_PATH) - self.assertTrue(pp.process()) + post_processor = PostProcessor(test.FILE_PATH) + self.assertTrue(post_processor.process()) if __name__ == '__main__': @@ -68,8 +88,11 @@ if __name__ == '__main__': print "STARTING - PostProcessor TESTS" print "==================" print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(PPInitTests) - unittest.TextTestRunner(verbosity=2).run(suite) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(PPInitTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) + print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(PPBasicTests) - unittest.TextTestRunner(verbosity=2).run(suite) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(PPBasicTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/scene_helpers_tests.py b/tests/scene_helpers_tests.py index f064bf44bcf10fe7879ad2326ced11755b3aab10..9241c69fa22de4eb087c89b0370173a40decd4bd 100644 --- a/tests/scene_helpers_tests.py +++ b/tests/scene_helpers_tests.py @@ -1,20 +1,20 @@ +# coding=utf-8 """ Test scene helpers """ # pylint: disable=line-too-long -import sys import os.path +import sys import unittest -import tests.test_lib as test 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 import show_name_helpers, scene_exceptions, common, name_cache -from sickbeard import db +from sickbeard import show_name_helpers, scene_exceptions, common, name_cache, db from sickbeard.tv import TVShow as Show +import tests.test_lib as test class SceneTests(test.SickbeardTestDBCase): diff --git a/tests/search_tests.py b/tests/search_tests.py index c90378db4430547c0011d79c593374f3d20c85f0..56601e158f017946195131442a69c79ae85a37a3 100644 --- a/tests/search_tests.py +++ b/tests/search_tests.py @@ -18,60 +18,81 @@ # 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__), '..'))) +# pylint: disable=line-too-long + +""" +Test searches +""" -import random +import os.path +import sys import unittest -import test_lib as test +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.search as search -import sickbeard from sickbeard.tv import TVEpisode, TVShow -import sickbeard.common as c - from sickbeard.providers.generic import GenericProvider +import sickbeard +import sickbeard.common as common +import tests.test_lib as test -tests = {"Game of Thrones": - {"tvdbid": 121361, "s": 5, "e": [10], - "s_strings": [{"Season": [u"Game of Thrones S05"]}], - "e_strings": [{"Episode": [u"Game of Thrones S05E10"]}]}} +TESTS = { + "Game of Thrones": { + "tvdbid": 121361, "s": 5, "e": [10], + "s_strings": [{"Season": [u"Game of Thrones S05"]}], + "e_strings": [{"Episode": [u"Game of Thrones S05E10"]}] + } +} -class SearchTest(test.SickbeardTestDBCase): +class SearchTest(test.SickbeardTestDBCase): + """ + Test search + """ def __init__(self, something): super(SearchTest, self).__init__(something) -def test_generator(curData, name, provider, forceSearch): +def test_generator(cur_data, cur_name, cur_provider): + """ + Generate test + + :param cur_data: + :param cur_name: + :param cur_provider: + :return: + """ - def test(self): - show = TVShow(1, int(curData["tvdbid"])) - show.name = name - show.quality = c.ANY | c.Quality.UNKNOWN | c.Quality.RAWHDTV + def do_test(): + """ + Test to perform + """ + show = TVShow(1, int(cur_data["tvdbid"])) + show.name = cur_name + show.quality = common.ANY | common.Quality.UNKNOWN | common.Quality.RAWHDTV show.saveToDB() sickbeard.showList.append(show) - for epNumber in curData["e"]: - episode = TVEpisode(show, curData["s"], epNumber) - episode.status = c.WANTED + for ep_number in cur_data["e"]: + episode = TVEpisode(show, cur_data["s"], ep_number) + episode.status = common.WANTED - # We arent updating scene numbers, so fake it here - episode.scene_season = curData["s"] - episode.scene_episode = epNumber + # We aren't updating scene numbers, so fake it here + episode.scene_season = cur_data["s"] + episode.scene_episode = ep_number episode.saveToDB() - provider.show = show - season_strings = provider._get_season_search_strings(episode) - episode_strings = provider._get_episode_search_strings(episode) + cur_provider.show = show + season_strings = cur_provider._get_season_search_strings(episode) # pylint: disable=protected-access + episode_strings = cur_provider._get_episode_search_strings(episode) # pylint: disable=protected-access fail = False + cur_string = '' for cur_string in season_strings, episode_strings: if not all([isinstance(cur_string, list), isinstance(cur_string[0], dict)]): - print " %s is using a wrong string format!" % provider.name + print " %s is using a wrong string format!" % cur_provider.name print cur_string fail = True continue @@ -80,44 +101,45 @@ def test_generator(curData, name, provider, forceSearch): continue try: - assert(season_strings == curData["s_strings"]) - assert(episode_strings == curData["e_strings"]) + assert season_strings == cur_data["s_strings"] + assert episode_strings == cur_data["e_strings"] except AssertionError: - print " %s is using a wrong string format!" % provider.name + print " %s is using a wrong string format!" % cur_provider.name print cur_string continue search_strings = episode_strings[0] - #search_strings.update(season_strings[0]) - #search_strings.update({"RSS":['']}) + # search_strings.update(season_strings[0]) + # search_strings.update({"RSS":['']}) - #print search_strings + # print search_strings - if not provider.public: + if not cur_provider.public: continue - items = provider._doSearch(search_strings) + items = cur_provider._doSearch(search_strings) # pylint: disable=protected-access if not items: - print "No results from provider?" + print "No results from cur_provider?" continue - title, url = provider._get_title_and_url(items[0]) + title, url = cur_provider._get_title_and_url(items[0]) # pylint: disable=protected-access for word in show.name.split(" "): if not word.lower() in title.lower(): - print "Show name not in title: %s. URL: %s" % (title, url) + print "Show cur_name not in title: %s. URL: %s" % (title, url) continue if not url: print "url is empty" continue - quality = provider.getQuality(items[0]) - size = provider._get_size(items[0]) + quality = cur_provider.getQuality(items[0]) + size = cur_provider._get_size(items[0]) # pylint: disable=protected-access + if not show.quality & quality: - print "Quality not in common.ANY, %r" % quality + print "Quality not in common.ANY, %r %s" % (quality, size) continue - return test + return do_test if __name__ == '__main__': print "==================" @@ -126,17 +148,17 @@ if __name__ == '__main__': print "######################################################################" # create the test methods for forceSearch in (True, False): - for name, curData in tests.items(): - fname = name.replace(' ', '_') + for name, data in TESTS.items(): + filename = name.replace(' ', '_') for provider in sickbeard.providers.sortedProviderList(): if provider.providerType == GenericProvider.TORRENT: if forceSearch: - test_name = 'test_manual_%s_%s_%s' % (fname, curData["tvdbid"], provider.name) + test_name = 'test_manual_%s_%s_%s' % (filename, data["tvdbid"], provider.name) else: - test_name = 'test_%s_%s_%s' % (fname, curData["tvdbid"], provider.name) - test = test_generator(curData, name, provider, forceSearch) + test_name = 'test_%s_%s_%s' % (filename, data["tvdbid"], provider.name) + test = test_generator(data, name, provider) setattr(SearchTest, test_name, test) - suite = unittest.TestLoader().loadTestsFromTestCase(SearchTest) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(SearchTest) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/__init__.py b/tests/sickrage_tests/__init__.py index f250b9e1d019f65aea1bae0aa142457a68e294ab..65f1d24f7a0f64e3e96303df7d9349c13fe99e93 100644 --- a/tests/sickrage_tests/__init__.py +++ b/tests/sickrage_tests/__init__.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,28 +17,28 @@ # 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.common_tests import CommonTests -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 +""" +Tests for SickRage +""" + +from __future__ import print_function + +import helper +import media +import show +import system +import unittest if __name__ == '__main__': print('=====> Running all test in "sickrage_tests" <=====') - test_classes = [ - ComingEpisodesTests, - CommonTests, - HistoryTests, - QualityTests, - RestartTests, - ShowTests, - ShutdownTests, + TEST_MODULES = [ + helper, + media, + show, + system, ] - for test_class in test_classes: - suite = TestLoader().loadTestsFromTestCase(test_class) - TextTestRunner(verbosity=2).run(suite) + for test_module in TEST_MODULES: + SUITE = unittest.TestLoader().loadTestsFromModule(test_module) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/helper/__init__.py b/tests/sickrage_tests/helper/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..21e2b67f8fc095b5b8ccb5f4471c2ed2983d30f3 100644 --- a/tests/sickrage_tests/helper/__init__.py +++ b/tests/sickrage_tests/helper/__init__.py @@ -0,0 +1,21 @@ +# coding=utf-8 +""" +Tests for SickRage helpers +""" + +import unittest + +from common_tests import CommonTests +from quality_tests import QualityTests + +if __name__ == '__main__': + print('=====> Running all test in "sickrage_tests.helper" <=====') + + TEST_CLASSES = [ + CommonTests, + QualityTests, + ] + + for test_class in TEST_CLASSES: + SUITE = unittest.TestLoader().loadTestsFromTestCase(test_class) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/helper/common_tests.py b/tests/sickrage_tests/helper/common_tests.py index 8991ab3ef7cb98dc474c6be241a0aded27f6bbef..2f4eb47cc193cc96b53e82157c0c52ff0bfae49d 100644 --- a/tests/sickrage_tests/helper/common_tests.py +++ b/tests/sickrage_tests/helper/common_tests.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,8 +17,15 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. -from unittest import TestCase, TestLoader, TextTestRunner +# pylint: disable=line-too-long +""" +Test sickrage.common +""" + +from __future__ import print_function + +import unittest import os import sys @@ -25,85 +33,136 @@ sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../. sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) import sickbeard +from sickrage.helper.common import http_code_description, is_sync_file, is_torrent_or_nzb_file, pretty_file_size +from sickrage.helper.common import remove_extension, replace_extension, sanitize_filename, try_int + -from sickrage.helper.common import is_sync_file, is_torrent_or_nzb_file, pretty_file_size, remove_extension -from sickrage.helper.common import replace_extension, sanitize_filename +class CommonTests(unittest.TestCase): + """ + Test common + """ + def test_http_code_description(self): + test_cases = { + None: None, + '': None, + '123': None, + '12.3': None, + '-123': None, + '-12.3': None, + '300': None, + 0: None, + 123: None, + 12.3: None, + -123: None, + -12.3: None, + 300: 'Multiple Choices', + 451: '(Redirect, Unavailable For Legal Reasons)', + 497: 'HTTP to HTTPS', + 499: '(Client Closed Request, Token required)', + 600: None, + } + + unicode_test_cases = { + u'': None, + u'123': None, + u'12.3': None, + u'-123': None, + u'-12.3': None, + u'300': None, + } + + for test in test_cases, unicode_test_cases: + for (http_code, result) in test.iteritems(): + self.assertEqual(http_code_description(http_code), result) -class CommonTests(TestCase): def test_is_sync_file(self): + """ + Test is sync file + """ sickbeard.SYNC_FILES = '!sync,lftp-pget-status,part' - tests = { + test_cases = { None: False, 42: False, '': False, - u'': False, 'filename': False, - u'filename': False, '.syncthingfilename': True, - u'.syncthingfilename': True, '.syncthing.filename': True, - u'.syncthing.filename': True, '.syncthing-filename': True, - u'.syncthing-filename': True, '.!sync': True, - u'.!sync': True, 'file.!sync': True, - u'file.!sync': True, 'file.!sync.ext': False, - u'file.!sync.ext': False, '.lftp-pget-status': True, - u'.lftp-pget-status': True, 'file.lftp-pget-status': True, - u'file.lftp-pget-status': True, 'file.lftp-pget-status.ext': False, - u'file.lftp-pget-status.ext': False, '.part': True, - u'.part': True, 'file.part': True, - u'file.part': True, 'file.part.ext': False, + } + + unicode_test_cases = { + u'': False, + u'filename': False, + u'.syncthingfilename': True, + u'.syncthing.filename': True, + u'.syncthing-filename': True, + u'.!sync': True, + u'file.!sync': True, + u'file.!sync.ext': False, + u'.lftp-pget-status': True, + u'file.lftp-pget-status': True, + u'file.lftp-pget-status.ext': False, + u'.part': True, + u'file.part': True, u'file.part.ext': False, } - for (filename, result) in tests.iteritems(): - self.assertEqual(is_sync_file(filename), result) + for tests in test_cases, unicode_test_cases: + for (filename, result) in tests.iteritems(): + self.assertEqual(is_sync_file(filename), result) def test_is_torrent_or_nzb_file(self): - tests = { + """ + Test is torrent or nzb file + """ + test_cases = { None: False, 42: False, '': False, - u'': False, 'filename': False, - u'filename': False, '.nzb': True, - u'.nzb': True, 'file.nzb': True, - u'file.nzb': True, 'file.nzb.part': False, - u'file.nzb.part': False, '.torrent': True, - u'.torrent': True, 'file.torrent': True, - u'file.torrent': True, 'file.torrent.part': False, + } + + unicode_test_cases = { + u'': False, + u'filename': False, + u'.nzb': True, + u'file.nzb': True, + u'file.nzb.part': False, + u'.torrent': True, + u'file.torrent': True, u'file.torrent.part': False, } - for (filename, result) in tests.iteritems(): - self.assertEqual(is_torrent_or_nzb_file(filename), result) + for tests in test_cases, unicode_test_cases: + for (filename, result) in tests.iteritems(): + self.assertEqual(is_torrent_or_nzb_file(filename), result) def test_pretty_file_size(self): - tests = { + """ + Test pretty file size + """ + test_cases = { None: '', '': '', - u'': '', '1024': '1.00 KB', - u'1024': '1.00 KB', '1024.5': '', - u'1024.5': '', -42.5: '-42.50 B', -42: '-42.00 B', 0: '0.00 B', @@ -122,102 +181,121 @@ class CommonTests(TestCase): 2 ** 60: 2 ** 60, } - for (size, result) in tests.iteritems(): - self.assertEqual(pretty_file_size(size), result) + unicode_test_cases = { + u'': '', + u'1024': '1.00 KB', + u'1024.5': '', + } + + for tests in test_cases, unicode_test_cases: + for (size, result) in tests.iteritems(): + self.assertEqual(pretty_file_size(size), result) def test_remove_extension(self): - tests = { + """ + Test remove extension + """ + test_cases = { None: None, 42: 42, '': '', - u'': u'', '.': '.', - u'.': u'.', 'filename': 'filename', - u'filename': u'filename', '.bashrc': '.bashrc', - u'.bashrc': u'.bashrc', '.nzb': '.nzb', - u'.nzb': u'.nzb', 'file.nzb': 'file', - u'file.nzb': u'file', 'file.name.nzb': 'file.name', - u'file.name.nzb': u'file.name', '.torrent': '.torrent', - u'.torrent': u'.torrent', 'file.torrent': 'file', - u'file.torrent': u'file', 'file.name.torrent': 'file.name', - u'file.name.torrent': u'file.name', '.avi': '.avi', - u'.avi': u'.avi', 'file.avi': 'file', - u'file.avi': u'file', 'file.name.avi': 'file.name', - u'file.name.avi': u'file.name', } - for (extension, result) in tests.iteritems(): - self.assertEqual(remove_extension(extension), result) + unicode_test_cases = { + u'': u'', + u'.': u'.', + u'filename': u'filename', + u'.bashrc': u'.bashrc', + u'.nzb': u'.nzb', + u'file.nzb': u'file', + u'file.name.nzb': u'file.name', + u'.torrent': u'.torrent', + u'file.torrent': u'file', + u'file.name.torrent': u'file.name', + u'.avi': u'.avi', + u'file.avi': u'file', + u'file.name.avi': u'file.name', + } + for tests in test_cases, unicode_test_cases: + for (extension, result) in tests.iteritems(): + self.assertEqual(remove_extension(extension), result) def test_replace_extension(self): - tests = { + """ + Test replace extension + """ + test_cases = { (None, None): None, (None, ''): None, - (None, u''): None, (42, None): 42, (42, ''): 42, - (42, u''): 42, ('', None): '', ('', ''): '', + ('.', None): '.', + ('.', ''): '.', + ('.', 'avi'): '.', + ('filename', None): 'filename', + ('filename', ''): 'filename', + ('filename', 'avi'): 'filename', + ('.bashrc', None): '.bashrc', + ('.bashrc', ''): '.bashrc', + ('.bashrc', 'avi'): '.bashrc', + ('file.mkv', None): 'file.None', + ('file.mkv', ''): 'file.', + ('file.mkv', 'avi'): 'file.avi', + ('file.name.mkv', None): 'file.name.None', + ('file.name.mkv', ''): 'file.name.', + ('file.name.mkv', 'avi'): 'file.name.avi', + } + + unicode_test_cases = { + (None, u''): None, + (42, u''): 42, ('', u''): '', (u'', None): u'', (u'', ''): u'', (u'', u''): u'', - ('.', None): '.', - ('.', ''): '.', ('.', u''): '.', - ('.', 'avi'): '.', ('.', u'avi'): '.', (u'.', None): u'.', (u'.', ''): u'.', (u'.', u''): u'.', (u'.', 'avi'): u'.', (u'.', u'avi'): u'.', - ('filename', None): 'filename', - ('filename', ''): 'filename', ('filename', u''): 'filename', - ('filename', 'avi'): 'filename', ('filename', u'avi'): 'filename', (u'filename', None): u'filename', (u'filename', ''): u'filename', (u'filename', u''): u'filename', (u'filename', 'avi'): u'filename', (u'filename', u'avi'): u'filename', - ('.bashrc', None): '.bashrc', - ('.bashrc', ''): '.bashrc', ('.bashrc', u''): '.bashrc', - ('.bashrc', 'avi'): '.bashrc', ('.bashrc', u'avi'): '.bashrc', (u'.bashrc', None): u'.bashrc', (u'.bashrc', ''): u'.bashrc', (u'.bashrc', u''): u'.bashrc', (u'.bashrc', 'avi'): u'.bashrc', (u'.bashrc', u'avi'): u'.bashrc', - ('file.mkv', None): 'file.None', - ('file.mkv', ''): 'file.', ('file.mkv', u''): 'file.', - ('file.mkv', 'avi'): 'file.avi', ('file.mkv', u'avi'): 'file.avi', (u'file.mkv', None): u'file.None', (u'file.mkv', ''): u'file.', (u'file.mkv', u''): u'file.', (u'file.mkv', 'avi'): u'file.avi', (u'file.mkv', u'avi'): u'file.avi', - ('file.name.mkv', None): 'file.name.None', - ('file.name.mkv', ''): 'file.name.', ('file.name.mkv', u''): 'file.name.', - ('file.name.mkv', 'avi'): 'file.name.avi', ('file.name.mkv', u'avi'): 'file.name.avi', (u'file.name.mkv', None): u'file.name.None', (u'file.name.mkv', ''): u'file.name.', @@ -226,33 +304,102 @@ class CommonTests(TestCase): (u'file.name.mkv', u'avi'): u'file.name.avi', } - for ((filename, extension), result) in tests.iteritems(): - self.assertEqual(replace_extension(filename, extension), result) + for tests in test_cases, unicode_test_cases: + for ((filename, extension), result) in tests.iteritems(): + self.assertEqual(replace_extension(filename, extension), result) def test_sanitize_filename(self): - tests = { + """ + Test sanitize filename + """ + test_cases = { None: '', 42: '', '': '', - u'': u'', 'filename': 'filename', - u'filename': u'filename', 'fi\\le/na*me': 'fi-le-na-me', - u'fi\\le/na*me': u'fi-le-na-me', 'fi:le"na<me': 'filename', - u'fi:le"na<me': u'filename', 'fi>le|na?me': 'filename', + ' . file\u2122name. .': 'file-u2122name', # pylint: disable=anomalous-unicode-escape-in-string + } + + unicode_test_cases = { + u'': u'', + u'filename': u'filename', + u'fi\\le/na*me': u'fi-le-na-me', + u'fi:le"na<me': u'filename', u'fi>le|na?me': u'filename', - ' . file\u2122name. .': 'file-u2122name', u' . file\u2122name. .': u'filename', } - for (filename, result) in tests.iteritems(): - self.assertEqual(sanitize_filename(filename), result) + for tests in test_cases, unicode_test_cases: + for (filename, result) in tests.iteritems(): + self.assertEqual(sanitize_filename(filename), result) + + def test_try_int(self): + """ + Test try int + """ + test_cases = { + None: 0, + '': 0, + '123': 123, + '-123': -123, + '12.3': 0, + '-12.3': 0, + 0: 0, + 123: 123, + -123: -123, + 12.3: 12, + -12.3: -12, + } + + unicode_test_cases = { + u'': 0, + u'123': 123, + u'-123': -123, + u'12.3': 0, + u'-12.3': 0, + } + + for test in test_cases, unicode_test_cases: + for (candidate, result) in test.iteritems(): + self.assertEqual(try_int(candidate), result) + + def test_try_int_with_default(self): + """ + Test try int + """ + default_value = 42 + test_cases = { + None: default_value, + '': default_value, + '123': 123, + '-123': -123, + '12.3': default_value, + '-12.3': default_value, + 0: 0, + 123: 123, + -123: -123, + 12.3: 12, + -12.3: -12, + } + + unicode_test_cases = { + u'': default_value, + u'123': 123, + u'-123': -123, + u'12.3': default_value, + u'-12.3': default_value, + } + + for test in test_cases, unicode_test_cases: + for (candidate, result) in test.iteritems(): + self.assertEqual(try_int(candidate, default_value), result) if __name__ == '__main__': print('=====> Testing %s' % __file__) - suite = TestLoader().loadTestsFromTestCase(CommonTests) - TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(CommonTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/helper/quality_tests.py b/tests/sickrage_tests/helper/quality_tests.py index fcb7ff3ca209e478862c4679607df12dd54e0f19..be0021fadabd078fac6f21b8a46a0a1af93a5278 100644 --- a/tests/sickrage_tests/helper/quality_tests.py +++ b/tests/sickrage_tests/helper/quality_tests.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,19 +17,31 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test qualities +""" + +from __future__ import print_function + import os import sys +import unittest 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): +class QualityTests(unittest.TestCase): + """ + Test qualities + """ def test_get_quality_string(self): + """ + Test get quality string + """ tests = { ANY: 'Any', HD: 'HD', @@ -56,5 +69,5 @@ class QualityTests(TestCase): if __name__ == '__main__': print('=====> Testing %s' % __file__) - suite = TestLoader().loadTestsFromTestCase(QualityTests) - TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(QualityTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/media/__init__.py b/tests/sickrage_tests/media/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..914787236e6e1997f2d376b4b8ea9ff8e45e852f --- /dev/null +++ b/tests/sickrage_tests/media/__init__.py @@ -0,0 +1,27 @@ +# coding=utf-8 +""" +Tests for SickRage media +""" + +import unittest + +from generic_media_tests import GenericMediaTests +from show_banner_tests import ShowBannerTests +from show_fan_art_tests import ShowFanArtTests +from show_network_logo_tests import ShowNetworkLogoTests +from show_poster_tests import ShowPosterTests + +if __name__ == '__main__': + print('=====> Running all test in "sickrage_tests.media" <=====') + + TEST_CLASSES = [ + GenericMediaTests, + ShowBannerTests, + ShowFanArtTests, + ShowNetworkLogoTests, + ShowPosterTests, + ] + + for test_class in TEST_CLASSES: + SUITE = unittest.TestLoader().loadTestsFromTestCase(test_class) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/media/generic_media_tests.py b/tests/sickrage_tests/media/generic_media_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..172d191fb45bec9b6deff7f4b9f9b3ee57bd3199 --- /dev/null +++ b/tests/sickrage_tests/media/generic_media_tests.py @@ -0,0 +1,145 @@ +# coding=utf-8 +# 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/>. + +""" +Test GenericMedia +""" + +from __future__ import print_function + +import os +import sys +import unittest + +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 sickrage.media.GenericMedia import GenericMedia + + +class GenericMediaTests(unittest.TestCase): + """ + Test GenericMedia + """ + + def test___init__(self): + """ + Test __init__ + """ + test_cases = { + (None, None): (0, 'normal'), + ('', None): (0, 'normal'), + ('123', None): (123, 'normal'), + ('12.3', None): (0, 'normal'), + (123, None): (123, 'normal'), + (12.3, None): (12, 'normal'), + (None, ''): (0, 'normal'), + ('', ''): (0, 'normal'), + ('123', ''): (123, 'normal'), + ('12.3', ''): (0, 'normal'), + (123, ''): (123, 'normal'), + (12.3, ''): (12, 'normal'), + (None, 'normal'): (0, 'normal'), + ('', 'normal'): (0, 'normal'), + ('123', 'normal'): (123, 'normal'), + ('12.3', 'normal'): (0, 'normal'), + (123, 'normal'): (123, 'normal'), + (12.3, 'normal'): (12, 'normal'), + (None, 'thumb'): (0, 'thumb'), + ('', 'thumb'): (0, 'thumb'), + ('123', 'thumb'): (123, 'thumb'), + ('12.3', 'thumb'): (0, 'thumb'), + (123, 'thumb'): (123, 'thumb'), + (12.3, 'thumb'): (12, 'thumb'), + (None, 'foo'): (0, 'normal'), + ('', 'foo'): (0, 'normal'), + ('123', 'foo'): (123, 'normal'), + ('12.3', 'foo'): (0, 'normal'), + (123, 'foo'): (123, 'normal'), + (12.3, 'foo'): (12, 'normal'), + } + + unicode_test_cases = { + (u'', None): (0, 'normal'), + (u'123', None): (123, 'normal'), + (u'12.3', None): (0, 'normal'), + (None, u''): (0, 'normal'), + (u'', u''): (0, 'normal'), + (u'123', u''): (123, 'normal'), + (u'12.3', u''): (0, 'normal'), + (123, u''): (123, 'normal'), + (12.3, u''): (12, 'normal'), + (None, u'normal'): (0, 'normal'), + (u'', u'normal'): (0, 'normal'), + (u'123', u'normal'): (123, 'normal'), + (u'12.3', u'normal'): (0, 'normal'), + (123, u'normal'): (123, 'normal'), + (12.3, u'normal'): (12, 'normal'), + (None, u'thumb'): (0, 'thumb'), + (u'', u'thumb'): (0, 'thumb'), + (u'123', u'thumb'): (123, 'thumb'), + (u'12.3', u'thumb'): (0, 'thumb'), + (123, u'thumb'): (123, 'thumb'), + (12.3, u'thumb'): (12, 'thumb'), + (None, u'foo'): (0, 'normal'), + (u'', u'foo'): (0, 'normal'), + (u'123', u'foo'): (123, 'normal'), + (u'12.3', u'foo'): (0, 'normal'), + (123, u'foo'): (123, 'normal'), + (12.3, u'foo'): (12, 'normal'), + } + + for test in test_cases, unicode_test_cases: + for ((indexer_id, media_format), (expected_indexer_id, expected_media_format)) in test.iteritems(): + generic_media = GenericMedia(indexer_id, media_format) + + self.assertEqual(generic_media.indexer_id, expected_indexer_id) + self.assertEqual(generic_media.media_format, expected_media_format) + + def test_get_default_media_name(self): + """ + Test get_default_media_name + """ + + self.assertEqual(GenericMedia(0, '').get_default_media_name(), '') + + def test_get_media_path(self): + """ + Test get_media_path + """ + + self.assertEqual(GenericMedia(0, '').get_media_path(), '') + + def test_get_media_root(self): + """ + Test get_media_root + """ + + sickbeard.PROG_DIR = '/home/SickRage/' + + self.assertEqual(GenericMedia.get_media_root(), '/home/SickRage/gui/slick') + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(GenericMediaTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/media/show_banner_tests.py b/tests/sickrage_tests/media/show_banner_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..68233b51e015edd236746bfbfba8f2d920739f70 --- /dev/null +++ b/tests/sickrage_tests/media/show_banner_tests.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# 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/>. + +""" +Test ShowBanner +""" + +from generic_media_tests import GenericMediaTests + +import os +import sys +import unittest + +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.media.ShowBanner import ShowBanner + + +class ShowBannerTests(GenericMediaTests): + """ + Test ShowBanner + """ + + def test_get_default_media_name(self): + """ + Test get_default_media_name + """ + + self.assertEqual(ShowBanner(0, '').get_default_media_name(), 'banner.png') + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(ShowBannerTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/media/show_fan_art_tests.py b/tests/sickrage_tests/media/show_fan_art_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..6e5be0582097ab9f9a9711894ceedd86d0e2053c --- /dev/null +++ b/tests/sickrage_tests/media/show_fan_art_tests.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# 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/>. + +""" +Test ShowFanArt +""" + +from generic_media_tests import GenericMediaTests + +import os +import sys +import unittest + +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.media.ShowFanArt import ShowFanArt + + +class ShowFanArtTests(GenericMediaTests): + """ + Test ShowFanArt + """ + + def test_get_default_media_name(self): + """ + Test get_default_media_name + """ + + self.assertEqual(ShowFanArt(0, '').get_default_media_name(), 'fanart.png') + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(ShowFanArtTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/media/show_network_logo_tests.py b/tests/sickrage_tests/media/show_network_logo_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..0cb3ee9b3e6e2f1cb33886aa3cbe93c1d18c2c92 --- /dev/null +++ b/tests/sickrage_tests/media/show_network_logo_tests.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# 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/>. + +""" +Test ShowNetworkLogo +""" + +from generic_media_tests import GenericMediaTests + +import os +import sys +import unittest + +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.media.ShowNetworkLogo import ShowNetworkLogo + + +class ShowNetworkLogoTests(GenericMediaTests): + """ + Test ShowNetworkLogo + """ + + def test_get_default_media_name(self): + """ + Test get_default_media_name + """ + + self.assertEqual(ShowNetworkLogo(0, '').get_default_media_name(), 'network/nonetwork.png') + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(ShowNetworkLogoTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/media/show_poster_tests.py b/tests/sickrage_tests/media/show_poster_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..16a8a8bc21eb7e2d24760c5d7a1603d9496f56d7 --- /dev/null +++ b/tests/sickrage_tests/media/show_poster_tests.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# 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/>. + +""" +Test ShowPoster +""" + +from generic_media_tests import GenericMediaTests + +import os +import sys +import unittest + +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.media.ShowPoster import ShowPoster + + +class ShowPosterTests(GenericMediaTests): + """ + Test ShowPoster + """ + + def test_get_default_media_name(self): + """ + Test get_default_media_name + """ + + self.assertEqual(ShowPoster(0, '').get_default_media_name(), 'poster.png') + + +if __name__ == '__main__': + print('=====> Testing %s' % __file__) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(ShowPosterTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/show/__init__.py b/tests/sickrage_tests/show/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..906cca08b7eb2dc00af58e2bec32302db2f55cff 100644 --- a/tests/sickrage_tests/show/__init__.py +++ b/tests/sickrage_tests/show/__init__.py @@ -0,0 +1,23 @@ +# coding=utf-8 +""" +Tests for SickRage show +""" + +import unittest + +from coming_episodes_tests import ComingEpisodesTests +from history_tests import HistoryTests +from show_tests import ShowTests + +if __name__ == '__main__': + print('=====> Running all test in "sickrage_tests.show" <=====') + + TEST_CLASSES = [ + ComingEpisodesTests, + HistoryTests, + ShowTests, + ] + + for test_class in TEST_CLASSES: + SUITE = unittest.TestLoader().loadTestsFromTestCase(test_class) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/show/coming_episodes_tests.py b/tests/sickrage_tests/show/coming_episodes_tests.py index 0bcf26f7fd68710d8a7a33506a972eebbb87b1c9..387f90839a1c9b6b65ee36a5594a8ced99ea5a3b 100644 --- a/tests/sickrage_tests/show/coming_episodes_tests.py +++ b/tests/sickrage_tests/show/coming_episodes_tests.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,18 +17,32 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test coming episodes +""" + +# pylint: disable=line-too-long + +from __future__ import print_function + import os import sys +import unittest 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): +class ComingEpisodesTests(unittest.TestCase): + """ + Test comping episodes + """ def test_get_categories(self): + """ + Test get categories + """ categories_list = [ None, [], ['A', 'B'], [u'A', u'B'], '', 'A|B', u'A|B', ] @@ -36,14 +51,17 @@ class ComingEpisodesTests(TestCase): ] self.assertEqual( - len(categories_list), len(results_list), - 'Number of parameters (%d) and results (%d) does not match' % (len(categories_list), len(results_list)) + 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]) + self.assertEqual(ComingEpisodes._get_categories(categories), results_list[index]) # pylint: disable=protected-access def test_get_categories_map(self): + """ + Test get categories map + """ categories_list = [ None, [], ['A', 'B'], [u'A', u'B'] ] @@ -52,40 +70,47 @@ class ComingEpisodesTests(TestCase): ] self.assertEqual( - len(categories_list), len(results_list), - 'Number of parameters (%d) and results (%d) does not match' % (len(categories_list), len(results_list)) + 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]) + self.assertEqual(ComingEpisodes._get_categories_map(categories), results_list[index]) # pylint: disable=protected-access def test_get_sort(self): - tests = { + """ + Test get sort + """ + test_cases = { 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', + } + + unicode_test_cases = { + u'': 'date', + u'wrong': 'date', + u'date': 'date', + u'Date': 'date', + u'network': 'network', + u'NetWork': 'network', + u'show': 'show', u'Show': 'show', } - for (sort, result) in tests.iteritems(): - self.assertEqual(ComingEpisodes._get_sort(sort), result) + for tests in test_cases, unicode_test_cases: + for (sort, result) in tests.iteritems(): + self.assertEqual(ComingEpisodes._get_sort(sort), result) # pylint: disable=protected-access if __name__ == '__main__': print('=====> Testing %s' % __file__) - suite = TestLoader().loadTestsFromTestCase(ComingEpisodesTests) - TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(ComingEpisodesTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/show/history_tests.py b/tests/sickrage_tests/show/history_tests.py index 0b8c28738f80477e7e267d98b153fe8a4a0e9045..74aac381b2ffbfe2398e934325d66549d383bb41 100644 --- a/tests/sickrage_tests/show/history_tests.py +++ b/tests/sickrage_tests/show/history_tests.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,65 +17,88 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test history +""" + +from __future__ import print_function + import os import sys +import unittest 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): +class HistoryTests(unittest.TestCase): + """ + Test history + """ def test_get_actions(self): - tests = { + """ + Test get actions + """ + test_cases = { 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, + } + + unicode_test_cases = { + u'': [], + u'wrong': [], + u'downloaded': Quality.DOWNLOADED, + u'Downloaded': Quality.DOWNLOADED, + u'snatched': Quality.SNATCHED, u'Snatched': Quality.SNATCHED, } - for (action, result) in tests.iteritems(): - self.assertEqual(History._get_actions(action), result) + for tests in test_cases, unicode_test_cases: + for (action, result) in tests.iteritems(): + self.assertEqual(History._get_actions(action), result) # pylint: disable=protected-access def test_get_limit(self): - tests = { + """ + Test get limit + """ + test_cases = { 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) + unicode_test_cases = { + u'': 0, + u'0': 0, + u'5': 5, + u'-5': 0, + u'1.5': 0, + u'-1.5': 0, + } + + for tests in test_cases, unicode_test_cases: + for (action, result) in tests.iteritems(): + self.assertEqual(History._get_limit(action), result) # pylint: disable=protected-access if __name__ == '__main__': print('=====> Testing %s' % __file__) - suite = TestLoader().loadTestsFromTestCase(HistoryTests) - TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(HistoryTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/show/show_tests.py b/tests/sickrage_tests/show/show_tests.py index 1e6044a915e752e2582808fcf7477ed84cbbf6bb..ab53550f74df3e19ee60403ec98af27044d6c335 100644 --- a/tests/sickrage_tests/show/show_tests.py +++ b/tests/sickrage_tests/show/show_tests.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,8 +17,17 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test shows +""" + +# pylint: disable=line-too-long + +from __future__ import print_function + import os import sys +import unittest 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__), '../../..'))) @@ -26,12 +36,63 @@ import sickbeard from sickbeard.common import Quality from sickbeard.tv import TVShow +from sickrage.helper.exceptions import MultipleShowObjectsException from sickrage.show.Show import Show -from unittest import TestCase, TestLoader, TextTestRunner -class ShowTests(TestCase): +class ShowTests(unittest.TestCase): + """ + Test shows + """ + + def test_find(self): + """ + Test find + """ + sickbeard.QUALITY_DEFAULT = Quality.FULLHDTV + + show123 = TestTVShow(0, 123) + show456 = TestTVShow(0, 456) + show789 = TestTVShow(0, 789) + shows = [show123, show456, show789] + shows_duplicate = shows + shows + + test_cases = { + (False, None): None, + (False, ''): None, + (False, '123'): None, + (False, 123): None, + (False, 12.3): None, + (True, None): None, + (True, ''): None, + (True, '123'): None, + (True, 123): show123, + (True, 12.3): None, + (True, 456): show456, + (True, 789): show789, + } + + unicode_test_cases = { + (False, u''): None, + (False, u'123'): None, + (True, u''): None, + (True, u'123'): None, + } + + for tests in test_cases, unicode_test_cases: + for ((use_shows, indexer_id), result) in tests.iteritems(): + if use_shows: + self.assertEqual(Show.find(shows, indexer_id), result) + else: + self.assertEqual(Show.find(None, indexer_id), result) + + with self.assertRaises(MultipleShowObjectsException): + Show.find(shows_duplicate, 456) + def test_validate_indexer_id(self): + """ + Test validate indexer id + """ sickbeard.QUALITY_DEFAULT = Quality.FULLHDTV show123 = TestTVShow(0, 123) @@ -56,28 +117,33 @@ class ShowTests(TestCase): ] 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)) + 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]) + self.assertEqual(Show._validate_indexer_id(indexer_id), results_list[index]) # pylint: disable=protected-access class TestTVShow(TVShow): """ - A test ``TVShow`` object that do not need DB access. + A test `TVShow` object that does not need DB access. """ def __init__(self, indexer, indexer_id): super(TestTVShow, self).__init__(indexer, indexer_id) def loadFromDB(self, skip_nfo=False): + """ + Override TVShow.loadFromDB to avoid DB access during testing + + :param skip_nfo: ...not used + """ pass if __name__ == '__main__': print('=====> Testing %s' % __file__) - suite = TestLoader().loadTestsFromTestCase(ShowTests) - TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(ShowTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/system/__init__.py b/tests/sickrage_tests/system/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..816646d0bbaf1e058d9fc9ecdf4e8c82f482e576 100644 --- a/tests/sickrage_tests/system/__init__.py +++ b/tests/sickrage_tests/system/__init__.py @@ -0,0 +1,21 @@ +# coding=utf-8 +""" +Tests for SickRage system +""" + +import unittest + +from restart_tests import RestartTests +from shutdown_tests import ShutdownTests + +if __name__ == '__main__': + print('=====> Running all test in "sickrage_tests.system" <=====') + + TEST_CLASSES = [ + RestartTests, + ShutdownTests, + ] + + for test_class in TEST_CLASSES: + SUITE = unittest.TestLoader().loadTestsFromTestCase(test_class) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/system/restart_tests.py b/tests/sickrage_tests/system/restart_tests.py index 70da92d250b03c43a9b5a2c96bd716704656fcf0..e0b6b6c47cf4e80a0c1ebf8824d7d73d250dfe29 100644 --- a/tests/sickrage_tests/system/restart_tests.py +++ b/tests/sickrage_tests/system/restart_tests.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,42 +17,57 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test restart +""" + +from __future__ import print_function + import os import sys +import unittest 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): +class RestartTests(unittest.TestCase): + """ + Test restart + """ def test_restart(self): + """ + Test restart + """ sickbeard.PID = 123456 sickbeard.events = Events(None) - tests = { + test_cases = { 0: False, '0': False, - u'0': False, 123: False, '123': False, - u'123': False, 123456: True, '123456': True, + } + + unicode_test_cases = { + u'0': False, + u'123': False, u'123456': True, } - for (pid, result) in tests.iteritems(): - self.assertEqual(Restart.restart(pid), result) + for tests in test_cases, unicode_test_cases: + 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) + SUITE = unittest.TestLoader().loadTestsFromTestCase(RestartTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/sickrage_tests/system/shutdown_tests.py b/tests/sickrage_tests/system/shutdown_tests.py index 6c84b64cc17bff0b0b800406ee2c27c20e4d9002..7b425c0d4669104fe86c66de23018064f4b721d7 100644 --- a/tests/sickrage_tests/system/shutdown_tests.py +++ b/tests/sickrage_tests/system/shutdown_tests.py @@ -1,6 +1,7 @@ +# coding=utf-8 # This file is part of SickRage. # -# URL: https://sickrage.github.io +# URL: https://SickRage.GitHub.io # Git: https://github.com/SickRage/SickRage.git # # SickRage is free software: you can redistribute it and/or modify @@ -16,42 +17,57 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test shutdown +""" + +from __future__ import print_function + import os import sys +import unittest 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): +class ShutdownTests(unittest.TestCase): + """ + Test shutdown + """ def test_shutdown(self): + """ + Test shutdown + """ sickbeard.PID = 123456 sickbeard.events = Events(None) - tests = { + test_cases = { 0: False, '0': False, - u'0': False, 123: False, '123': False, - u'123': False, 123456: True, '123456': True, + } + + unicode_test_cases = { + u'0': False, + u'123': False, u'123456': True, } - for (pid, result) in tests.iteritems(): - self.assertEqual(Shutdown.stop(pid), result) + for tests in test_cases, unicode_test_cases: + 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) + SUITE = unittest.TestLoader().loadTestsFromTestCase(ShutdownTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/snatch_tests.py b/tests/snatch_tests.py index d22926594e1c73c0b25c84c8192e876af4f12aed..fee4517c031d207aa00c30b6d0346d9a87c46511 100644 --- a/tests/snatch_tests.py +++ b/tests/snatch_tests.py @@ -17,27 +17,39 @@ # 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__), '..'))) +# pylint: disable=line-too-long -import random +""" +Test snatching +""" + +import os.path +import sys import unittest -import test_lib as test +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.search as search -import sickbeard from sickbeard.tv import TVEpisode, TVShow -import sickbeard.common as c +import sickbeard +import sickbeard.search as search +import sickbeard.common as common +import tests.test_lib as test -tests = {"Dexter": {"a": 1, "q": c.HD, "s": 5, "e": [7], "b": 'Dexter.S05E07.720p.BluRay.X264-REWARD', "i": ['Dexter.S05E07.720p.BluRay.X264-REWARD', 'Dexter.S05E07.720p.X264-REWARD']}, - "House": {"a": 1, "q": c.HD, "s": 4, "e": [5], "b": 'House.4x5.720p.BluRay.X264-REWARD', "i": ['Dexter.S05E04.720p.X264-REWARD', 'House.4x5.720p.BluRay.X264-REWARD']}, - "Hells Kitchen": {"a": 1, "q": c.SD, "s": 6, "e": [14, 15], "b": 'Hells.Kitchen.s6e14e15.HDTV.XviD-ASAP', "i": ['Hells.Kitchen.S06E14.HDTV.XviD-ASAP', 'Hells.Kitchen.6x14.HDTV.XviD-ASAP', 'Hells.Kitchen.s6e14e15.HDTV.XviD-ASAP']} - } +TESTS = { + "Dexter": {"a": 1, "q": common.HD, "s": 5, "e": [7], "b": 'Dexter.S05E07.720p.BluRay.X264-REWARD', "i": ['Dexter.S05E07.720p.BluRay.X264-REWARD', 'Dexter.S05E07.720p.X264-REWARD']}, + "House": {"a": 1, "q": common.HD, "s": 4, "e": [5], "b": 'House.4x5.720p.BluRay.X264-REWARD', "i": ['Dexter.S05E04.720p.X264-REWARD', 'House.4x5.720p.BluRay.X264-REWARD']}, + "Hells Kitchen": {"a": 1, "q": common.SD, "s": 6, "e": [14, 15], "b": 'Hells.Kitchen.s6e14e15.HDTV.XviD-ASAP', "i": ['Hells.Kitchen.S06E14.HDTV.XviD-ASAP', 'Hells.Kitchen.6x14.HDTV.XviD-ASAP', 'Hells.Kitchen.s6e14e15.HDTV.XviD-ASAP']} +} def _create_fake_xml(items): + """ + Create fake xml + + :param items: + :return: + """ xml = '<?xml version="1.0" encoding="UTF-8" ?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:newznab="http://www.newznab.com/DTD/2010/feeds/attributes/" encoding="utf-8"><channel>' for item in items: xml += '<item><title>' + item + '</title>\n' @@ -45,49 +57,83 @@ def _create_fake_xml(items): xml += '</channel></rss>' return xml - -searchItems = [] +# pylint: disable=invalid-name +search_items = [] class SearchTest(test.SickbeardTestDBCase): - - def _fake_getURL(self, url, headers=None): - global searchItems - return _create_fake_xml(searchItems) - - def _fake_isActive(self): + """ + Perform search tests + """ + + @staticmethod + def _fake_get_url(url, headers=None): + """ + Fake getting a url + + :param url: + :param headers: + :return: + """ + _ = url, headers + return _create_fake_xml(search_items) + + @staticmethod + def _fake_is_active(): + """ + Fake is active + """ return True def __init__(self, something): + """ + Initialize tests + + :param something: + :return: + """ + for provider in sickbeard.providers.sortedProviderList(): - provider.getURL = self._fake_getURL - #provider.isActive = self._fake_isActive + provider.getURL = self._fake_get_url + # provider.isActive = self._fake_is_active super(SearchTest, self).__init__(something) -def test_generator(tvdbdid, show_name, curData, forceSearch): - - def test(self): - global searchItems - searchItems = curData["i"] - show = TVShow(1, tvdbdid) +def test_generator(tvdb_id, show_name, cur_data, force_search): + """ + Generate tests + + :param tvdb_id: + :param show_name: + :param cur_data: + :param force_search: + :return: + """ + def do_test(): + """ + Test to perform + """ + global search_items # pylint: disable=global-statement + search_items = cur_data["i"] + show = TVShow(1, tvdb_id) show.name = show_name - show.quality = curData["q"] + show.quality = cur_data["q"] show.saveToDB() sickbeard.showList.append(show) episode = None - for epNumber in curData["e"]: - episode = TVEpisode(show, curData["s"], epNumber) - episode.status = c.WANTED + for epNumber in cur_data["e"]: + episode = TVEpisode(show, cur_data["s"], epNumber) + episode.status = common.WANTED episode.saveToDB() - bestResult = search.searchProviders(show, episode.episode, forceSearch) - if not bestResult: - self.assertEqual(curData["b"], bestResult) - self.assertEqual(curData["b"], bestResult.name) #first is expected, second is choosen one - return test + best_result = search.searchProviders(show, episode.episode, force_search) + if not best_result: + assert cur_data["b"] == best_result + # pylint: disable=no-member + assert cur_data["b"] == best_result.name # first is expected, second is chosen one + return do_test if __name__ == '__main__': print "==================" @@ -95,20 +141,20 @@ if __name__ == '__main__': print "==================" print "######################################################################" # create the test methods - tvdbdid = 1 + cur_tvdb_id = 1 for forceSearch in (True, False): - for name, curData in tests.items(): - if not curData["a"]: + for name, data in TESTS.items(): + if not data["a"]: continue - fname = name.replace(' ', '_') + filename = name.replace(' ', '_') if forceSearch: - test_name = 'test_manual_%s_%s' % (fname, tvdbdid) + test_name = 'test_manual_%s_%s' % (filename, cur_tvdb_id) else: - test_name = 'test_%s_%s' % (fname, tvdbdid) + test_name = 'test_%s_%s' % (filename, cur_tvdb_id) - test = test_generator(tvdbdid, name, curData, forceSearch) + test = test_generator(cur_tvdb_id, name, data, forceSearch) setattr(SearchTest, test_name, test) - tvdbdid += 1 + cur_tvdb_id += 1 suite = unittest.TestLoader().loadTestsFromTestCase(SearchTest) unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/ssl_sni_tests.py b/tests/ssl_sni_tests.py index 59d58f6557c3c62b26562dac070ee829f1ba9ad0..b4c6e0b380c2e31ffb67be25704b479ab2dbf133 100644 --- a/tests/ssl_sni_tests.py +++ b/tests/ssl_sni_tests.py @@ -17,23 +17,38 @@ # 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__), '..'))) +# pylint: disable=line-too-long +""" +Test SNI and SSL +""" + +import os.path +import sys import unittest -import requests +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 import ex +import certifi # pylint: disable=import-error +import requests # pylint: disable=import-error import sickbeard.providers as providers -import certifi -from sickrage.helper.exceptions import ex -class SNI_Tests(unittest.TestCase): +class SniTests(unittest.TestCase): + """ + Test SNI + """ self_signed_cert_providers = ["Womble's Index", "Libertalia"] - def test_SNI_URLS(self): + + def test_sni_urls(self): + """ + Test SNI urls + :return: + """ print '' - #Just checking all providers - we should make this error on non-existent urls. + # Just checking all providers - we should make this error on non-existent urls. for provider in [provider for provider in providers.makeProviderList() if provider.name not in self.self_signed_cert_providers]: print 'Checking %s' % provider.name try: @@ -45,10 +60,10 @@ class SNI_Tests(unittest.TestCase): print 'SSLError on %s: %s' % (provider.name, ex(error.message)) raise else: - print 'Cannot verify certificate for %s' % provider.name - except Exception: + print 'Cannot verify certificate for %s' % provider.name + except Exception: # pylint: disable=broad-except pass if __name__ == "__main__": - suite = unittest.TestLoader().loadTestsFromTestCase(SNI_Tests) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(SniTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/test_lib.py b/tests/test_lib.py index 86a12da9d5751125cad468a801835afdb8f801c7..caf136fa5951b669e5400a2bd142980d7f6961d0 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -40,27 +40,23 @@ Classes: TestCacheDBConnection """ -import sys import os.path -import unittest import shutil +import sys +import unittest 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__), '..'))) -# pylint: disable=F0401 -# Unable to import -import shutil_custom - -import sickbeard +from configobj import ConfigObj from sickbeard import db, providers -from sickbeard.databases import mainDB -from sickbeard.databases import cache_db, failed_db +from sickbeard.databases import cache_db, failed_db, mainDB from sickbeard.tv import TVEpisode +import shutil_custom # pylint: disable=import-error +import sickbeard # pylint: disable=F0401 # Unable to import -from configobj import ConfigObj shutil.copyfile = shutil_custom.copyfile_custom @@ -159,16 +155,15 @@ def _dummy_save_config(): mainDB.sickbeard.save_config = _dummy_save_config -# pylint: disable=W0613 -# Unused arguments def _fake_specify_ep(self, season, episode): """ Override contact to TVDB indexer. - :param self: + :param self: ...not used :param season: Season to search for ...not used :param episode: Episode to search for ...not used """ + _ = self, season, episode # throw away unused variables pass # the real one tries to contact TVDB just stop it from getting more info on the ep diff --git a/tests/torrent_tests.py b/tests/torrent_tests.py index 7ebe155953aefee44b452c1e004f53ead971c170..62fd6a970f78e209c174b93f7759323e70fc2cb6 100644 --- a/tests/torrent_tests.py +++ b/tests/torrent_tests.py @@ -17,24 +17,32 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test torrents +""" -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__), '..'))) +# pylint: disable=line-too-long +import os.path +import sys import unittest -import urlparse -import tests.test_lib as test +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 bs4 import BeautifulSoup from sickbeard.helpers import getURL +from sickbeard.providers.bitcannon import BitCannonProvider from sickbeard.tv import TVEpisode, TVShow -import requests +import requests # pylint: disable=import-error +import tests.test_lib as test +import urlparse -from sickbeard.providers.bitcannon import BitCannonProvider class TorrentBasicTests(test.SickbeardTestDBCase): - + """ + Test torrents + """ @classmethod def setUpClass(cls): cls.shows = [] @@ -50,18 +58,26 @@ class TorrentBasicTests(test.SickbeardTestDBCase): cls.shows.append(show) def test_bitcannon(self): + """ + Test bitcannon + """ bitcannon = BitCannonProvider() bitcannon.custom_url = "" # true testing requires a valid URL here (e.g., "http://localhost:3000/") bitcannon.api_key = "" if len(bitcannon.custom_url) > 0: + # pylint: disable=protected-access search_strings_list = bitcannon._get_episode_search_strings(self.shows[0].episodes[0]) # [{'Episode': ['Italian Works S05E10']}] for search_strings in search_strings_list: - bitcannon._doSearch(search_strings) # {'Episode': ['Italian Works S05E10']} + bitcannon._doSearch(search_strings) # {'Episode': ['Italian Works S05E10']} # pylint: disable=protected-access return True - def test_search(self): + @staticmethod + def test_search(): # pylint: disable=too-many-locals + """ + Test searching + """ url = 'http://kickass.to/' search_url = 'http://kickass.to/usearch/American%20Dad%21%20S08%20-S08E%20category%3Atv/?field=seeders&sorder=desc' @@ -77,23 +93,23 @@ class TorrentBasicTests(test.SickbeardTestDBCase): # cleanup memory soup.clear(True) - #Continue only if one Release is found + # Continue only if one Release is found if len(torrent_rows) < 2: print "The data returned does not contain any torrents" return - for tr in torrent_rows[1:]: - + for row in torrent_rows[1:]: try: - link = urlparse.urljoin(url, (tr.find('div', {'class': 'torrentname'}).find_all('a')[1])['href']) - _id = tr.get('id')[-7:] - title = (tr.find('div', {'class': 'torrentname'}).find_all('a')[1]).text \ - or (tr.find('div', {'class': 'torrentname'}).find_all('a')[2]).text - url = tr.find('a', 'imagnet')['href'] - verified = True if tr.find('a', 'iverify') else False - trusted = True if tr.find('img', {'alt': 'verified'}) else False - seeders = int(tr.find_all('td')[-2].text) - leechers = int(tr.find_all('td')[-1].text) + link = urlparse.urljoin(url, (row.find('div', {'class': 'torrentname'}).find_all('a')[1])['href']) + _id = row.get('id')[-7:] + title = (row.find('div', {'class': 'torrentname'}).find_all('a')[1]).text \ + or (row.find('div', {'class': 'torrentname'}).find_all('a')[2]).text + url = row.find('a', 'imagnet')['href'] + verified = True if row.find('a', 'iverify') else False + trusted = True if row.find('img', {'alt': 'verified'}) else False + seeders = int(row.find_all('td')[-2].text) + leechers = int(row.find_all('td')[-1].text) + _ = link, _id, verified, trusted, seeders, leechers except (AttributeError, TypeError): continue @@ -104,5 +120,5 @@ if __name__ == "__main__": print "STARTING - Torrent Basic TESTS" print "==================" print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(TorrentBasicTests) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(TorrentBasicTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/tv_tests.py b/tests/tv_tests.py index cefe61d6891877cdb03fae4e845e43f3d96c56dc..810d95c99a7bf0f435a02d8711d44a1168e624dc 100644 --- a/tests/tv_tests.py +++ b/tests/tv_tests.py @@ -17,28 +17,44 @@ # 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 tv +""" +import os.path +import sys import unittest -import test_lib as test +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.tv import TVEpisode, TVShow +import sickbeard +import tests.test_lib as test -class TVShowTests(test.SickbeardTestDBCase): +class TVShowTests(test.SickbeardTestDBCase): + """ + Test tv shows + """ def setUp(self): + """ + Set up tests + """ super(TVShowTests, self).setUp() sickbeard.showList = [] def test_init_indexerid(self): + """ + test init indexer id + """ show = TVShow(1, 0001, "en") self.assertEqual(show.indexerid, 0001) def test_change_indexerid(self): + """ + test change indexer id + """ show = TVShow(1, 0001, "en") show.name = "show name" show.network = "cbs" @@ -59,6 +75,9 @@ class TVShowTests(test.SickbeardTestDBCase): self.assertEqual(show.indexerid, 0002) def test_set_name(self): + """ + test set name + """ show = TVShow(1, 0001, "en") show.name = "newName" show.saveToDB() @@ -67,27 +86,44 @@ class TVShowTests(test.SickbeardTestDBCase): class TVEpisodeTests(test.SickbeardTestDBCase): - + """ + Test tv episode + """ def setUp(self): + """ + Set up + """ super(TVEpisodeTests, self).setUp() sickbeard.showList = [] def test_init_empty_db(self): + """ + test init empty db + """ show = TVShow(1, 0001, "en") - ep = TVEpisode(show, 1, 1) - ep.name = "asdasdasdajkaj" - ep.saveToDB() - ep.loadFromDB(1, 1) - self.assertEqual(ep.name, "asdasdasdajkaj") + episode = TVEpisode(show, 1, 1) + episode.name = "asdasdasdajkaj" + episode.saveToDB() + episode.loadFromDB(1, 1) + self.assertEqual(episode.name, "asdasdasdajkaj") class TVTests(test.SickbeardTestDBCase): - + """ + Test tv + """ def setUp(self): + """ + Set up + """ super(TVTests, self).setUp() sickbeard.showList = [] - def test_getEpisode(self): + @staticmethod + def test_get_episode(): + """ + Test get episodes + """ show = TVShow(1, 0001, "en") show.name = "show name" show.network = "cbs" @@ -99,7 +135,7 @@ class TVTests(test.SickbeardTestDBCase): show.startyear = 1987 show.saveToDB() sickbeard.showList = [show] - #TODO: implement + # TODO: implement if __name__ == '__main__': @@ -107,11 +143,11 @@ if __name__ == '__main__': print "STARTING - TV TESTS" print "==================" print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(TVShowTests) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(TVShowTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(TVEpisodeTests) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(TVEpisodeTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(TVTests) - unittest.TextTestRunner(verbosity=2).run(suite) + SUITE = unittest.TestLoader().loadTestsFromTestCase(TVTests) + unittest.TextTestRunner(verbosity=2).run(SUITE) diff --git a/tests/xem_tests.py b/tests/xem_tests.py index 97fa2d425c1c48b176eafa6fdf5c62abead2f874..4341c6625bf49cbedf285643a8e0028e03c13b9c 100644 --- a/tests/xem_tests.py +++ b/tests/xem_tests.py @@ -17,65 +17,81 @@ # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. +""" +Test XEM +""" -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__), '..'))) +# pylint: disable=line-too-long -import datetime -import unittest +import os.path import re +import sys +import unittest + +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 test_lib as test -import sickbeard -from sickbeard.helpers import sanitizeSceneName -from sickbeard.name_parser.parser import NameParser from sickbeard.tv import TVShow +import sickbeard +import tests.test_lib as test + class XEMBasicTests(test.SickbeardTestDBCase): - def loadShowsFromDB(self): + """ + Perform basic xem tests + """ + + @staticmethod + def load_shows_from_db(): """ Populates the showList with shows from the database """ - myDB = test.db.DBConnection() - sqlResults = myDB.select("SELECT * FROM tv_shows") + my_db = test.db.DBConnection() + sql_results = my_db.select("SELECT * FROM tv_shows") - for sqlShow in sqlResults: + for sql_show in sql_results: try: - curShow = TVShow(int(sqlShow["indexer"]), int(sqlShow["indexer_id"])) - sickbeard.showList.append(curShow) - except Exception: + cur_show = TVShow(int(sql_show["indexer"]), int(sql_show["indexer_id"])) + sickbeard.showList.append(cur_show) + except Exception: # pylint: disable=broad-except pass - def loadFromDB(self): + @staticmethod + def load_from_db(): """ Populates the showList with shows from the database """ + my_db = test.db.DBConnection() + sql_results = my_db.select("SELECT * FROM tv_shows") - myDB = test.db.DBConnection() - sqlResults = myDB.select("SELECT * FROM tv_shows") - - for sqlShow in sqlResults: + for sql_show in sql_results: try: - curShow = TVShow(int(sqlShow["indexer"]), int(sqlShow["indexer_id"])) - sickbeard.showList.append(curShow) - except Exception, e: - print "There was an error creating the show" + cur_show = TVShow(int(sql_show["indexer"]), int(sql_show["indexer_id"])) + sickbeard.showList.append(cur_show) + except Exception as error: # pylint: disable=broad-except + print "There was an error creating the show %s" % error - def test_formating(self): + @staticmethod + def test_formatting(): + """ + Test formatting + """ name = "Game.of.Thrones.S03.720p.HDTV.x264-CtrlHD" release = "Game of Thrones" # m = re.match('(?P<ep_ab_num>(?>\d{1,3})(?![ip])).+', name) + # pylint: disable=anomalous-backslash-in-string escaped_name = re.sub('\\\\[\\s.-]', '\W+', re.escape(release)) - curRegex = '^' + escaped_name + '\W+(?:(?:S\d[\dE._ -])|(?:\d\d?x)|(?:\d{4}\W\d\d\W\d\d)|(?:(?:part|pt)[\._ -]?(\d|[ivx]))|Season\W+\d+\W+|E\d+\W+|(?:\d{1,3}.+\d{1,}[a-zA-Z]{2}\W+[a-zA-Z]{3,}\W+\d{4}.+))' + # pylint: disable=anomalous-backslash-in-string + cur_regex = '^' + escaped_name + '\W+(?:(?:S\d[\dE._ -])|(?:\d\d?x)|(?:\d{4}\W\d\d\W\d\d)|(?:(?:part|pt)[\._ -]?(\d|[ivx]))|Season\W+\d+\W+|E\d+\W+|(?:\d{1,3}.+\d{1,}[a-zA-Z]{2}\W+[a-zA-Z]{3,}\W+\d{4}.+))' # print(u"Checking if show " + name + " matches " + curRegex) - match = re.search(curRegex, name, re.I) - # if match: - # print(u"Matched " + curRegex + " to " + name) + match = re.search(cur_regex, name, re.I) + if match: + # print(u"Matched " + curRegex + " to " + name) + pass if __name__ == "__main__": @@ -83,5 +99,6 @@ if __name__ == "__main__": print "STARTING - XEM Scene Numbering TESTS" print "==================" print "######################################################################" - suite = unittest.TestLoader().loadTestsFromTestCase(XEMBasicTests) - unittest.TextTestRunner(verbosity=2).run(suite) + + SUITE = unittest.TestLoader().loadTestsFromTestCase(XEMBasicTests) + unittest.TextTestRunner(verbosity=2).run(SUITE)