From 7837adec1d3425515ab73558a991e07134a4e734 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 17 Apr 2011 23:36:23 +0200 Subject: [PATCH] Scheduler NZBmatrix, newznab Scores --- couchpotato/cli.py | 5 +- couchpotato/core/__init__.py | 73 -------- couchpotato/core/_base/__init__.py | 0 couchpotato/core/_base/_core/__init__.py | 74 +++++++++ .../clientscript}/__init__.py | 0 .../clientscript}/main.py | 0 couchpotato/core/_base/scheduler/__init__.py | 6 + couchpotato/core/_base/scheduler/main.py | 85 ++++++++++ couchpotato/core/event.py | 20 ++- couchpotato/core/helpers/encoding.py | 2 +- couchpotato/core/helpers/rss.py | 14 +- couchpotato/core/helpers/variable.py | 8 +- couchpotato/core/loader.py | 5 +- couchpotato/core/plugins/__init__.py | 74 +++++++++ couchpotato/core/plugins/base.py | 2 +- .../{file_browser => browser}/__init__.py | 0 .../plugins/{file_browser => browser}/main.py | 0 couchpotato/core/plugins/quality/main.py | 9 + couchpotato/core/plugins/renamer/main.py | 9 + couchpotato/core/plugins/score/__init__.py | 6 + couchpotato/core/plugins/score/main.py | 22 +++ couchpotato/core/plugins/score/scores.py | 51 ++++++ couchpotato/core/plugins/searcher/__init__.py | 78 +++++++++ couchpotato/core/plugins/searcher/main.py | 156 ++++++++++++++++++ couchpotato/core/plugins/wizard/__init__.py | 2 +- .../core/plugins/wizard/static/spotlight.js | 4 +- .../core/plugins/wizard/static/wizard.js | 11 +- couchpotato/core/providers/base.py | 120 +++++++++++++- .../core/providers/nzb/newzbin/main.py | 5 +- .../core/providers/nzb/newznab/main.py | 96 ++++++----- .../core/providers/nzb/nzbmatrix/__init__.py | 7 + .../core/providers/nzb/nzbmatrix/main.py | 110 ++++++------ couchpotato/core/providers/nzb/nzbs/main.py | 5 +- couchpotato/core/settings/__init__.py | 4 +- couchpotato/static/scripts/page/settings.js | 6 +- couchpotato/static/style/main.css | 2 +- couchpotato/static/style/page/settings.css | 2 +- couchpotato/templates/_desktop.html | 2 + 38 files changed, 856 insertions(+), 219 deletions(-) create mode 100644 couchpotato/core/_base/__init__.py create mode 100644 couchpotato/core/_base/_core/__init__.py rename couchpotato/core/{plugins/_clientscript => _base/clientscript}/__init__.py (100%) rename couchpotato/core/{plugins/_clientscript => _base/clientscript}/main.py (100%) create mode 100644 couchpotato/core/_base/scheduler/__init__.py create mode 100644 couchpotato/core/_base/scheduler/main.py rename couchpotato/core/plugins/{file_browser => browser}/__init__.py (100%) rename couchpotato/core/plugins/{file_browser => browser}/main.py (100%) create mode 100644 couchpotato/core/plugins/score/__init__.py create mode 100644 couchpotato/core/plugins/score/main.py create mode 100644 couchpotato/core/plugins/score/scores.py create mode 100644 couchpotato/core/plugins/searcher/__init__.py create mode 100644 couchpotato/core/plugins/searcher/main.py diff --git a/couchpotato/cli.py b/couchpotato/cli.py index cd7373bc..3425480d 100644 --- a/couchpotato/cli.py +++ b/couchpotato/cli.py @@ -1,7 +1,7 @@ from argparse import ArgumentParser from couchpotato import web from couchpotato.api import api -from couchpotato.core.event import fireEvent +from couchpotato.core.event import fireEventAsync from libs.daemon import createDaemon from logging import handlers from werkzeug.contrib.cache import FileSystemCache @@ -90,7 +90,6 @@ def cmd_couchpotato(base_path, args): # Load configs & plugins loader = Env.get('loader') loader.preload(root = base_path) - loader.addModule(0, 'core', 'couchpotato.core', 'core') loader.run() @@ -116,7 +115,7 @@ def cmd_couchpotato(base_path, args): from couchpotato.core.settings.model import setup setup() - fireEvent('app.load') + fireEventAsync('app.load') # Create app from couchpotato import app diff --git a/couchpotato/core/__init__.py b/couchpotato/core/__init__.py index 98d081c4..8b137891 100644 --- a/couchpotato/core/__init__.py +++ b/couchpotato/core/__init__.py @@ -1,74 +1 @@ -from uuid import uuid4 -def start(): - pass - -config = [{ - 'name': 'core', - 'groups': [ - { - 'tab': 'general', - 'name': 'basics', - 'description': 'Needs restart before changes take effect.', - 'options': [ - { - 'name': 'username', - 'default': '', - }, - { - 'name': 'password', - 'default': '', - 'type': 'password', - }, - { - 'name': 'host', - 'advanced': True, - 'default': '0.0.0.0', - 'label': 'IP', - 'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.', - }, - { - 'name': 'port', - 'default': 5000, - 'type': 'int', - 'description': 'The port I should listen to.', - }, - { - 'name': 'launch_browser', - 'default': 1, - 'type': 'bool', - 'label': 'Launch Browser', - 'description': 'Launch the browser when I start.', - }, - ], - }, - { - 'tab': 'general', - 'name': 'advanced', - 'description': "For those who know what the're doing", - 'advanced': True, - 'options': [ - { - 'name': 'api_key', - 'default': uuid4().hex, - 'readonly': 1, - 'label': 'Api Key', - 'description': "This is top-secret! Don't share this!", - }, - { - 'name': 'debug', - 'default': 0, - 'type': 'bool', - 'label': 'Debug', - 'description': 'Enable debugging.', - }, - { - 'name': 'url_base', - 'default': '', - 'label': 'Url Base', - 'description': 'When using mod_proxy use this to append the url with this.', - }, - ], - }, - ], -}] diff --git a/couchpotato/core/_base/__init__.py b/couchpotato/core/_base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/couchpotato/core/_base/_core/__init__.py b/couchpotato/core/_base/_core/__init__.py new file mode 100644 index 00000000..98d081c4 --- /dev/null +++ b/couchpotato/core/_base/_core/__init__.py @@ -0,0 +1,74 @@ +from uuid import uuid4 + +def start(): + pass + +config = [{ + 'name': 'core', + 'groups': [ + { + 'tab': 'general', + 'name': 'basics', + 'description': 'Needs restart before changes take effect.', + 'options': [ + { + 'name': 'username', + 'default': '', + }, + { + 'name': 'password', + 'default': '', + 'type': 'password', + }, + { + 'name': 'host', + 'advanced': True, + 'default': '0.0.0.0', + 'label': 'IP', + 'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.', + }, + { + 'name': 'port', + 'default': 5000, + 'type': 'int', + 'description': 'The port I should listen to.', + }, + { + 'name': 'launch_browser', + 'default': 1, + 'type': 'bool', + 'label': 'Launch Browser', + 'description': 'Launch the browser when I start.', + }, + ], + }, + { + 'tab': 'general', + 'name': 'advanced', + 'description': "For those who know what the're doing", + 'advanced': True, + 'options': [ + { + 'name': 'api_key', + 'default': uuid4().hex, + 'readonly': 1, + 'label': 'Api Key', + 'description': "This is top-secret! Don't share this!", + }, + { + 'name': 'debug', + 'default': 0, + 'type': 'bool', + 'label': 'Debug', + 'description': 'Enable debugging.', + }, + { + 'name': 'url_base', + 'default': '', + 'label': 'Url Base', + 'description': 'When using mod_proxy use this to append the url with this.', + }, + ], + }, + ], +}] diff --git a/couchpotato/core/plugins/_clientscript/__init__.py b/couchpotato/core/_base/clientscript/__init__.py similarity index 100% rename from couchpotato/core/plugins/_clientscript/__init__.py rename to couchpotato/core/_base/clientscript/__init__.py diff --git a/couchpotato/core/plugins/_clientscript/main.py b/couchpotato/core/_base/clientscript/main.py similarity index 100% rename from couchpotato/core/plugins/_clientscript/main.py rename to couchpotato/core/_base/clientscript/main.py diff --git a/couchpotato/core/_base/scheduler/__init__.py b/couchpotato/core/_base/scheduler/__init__.py new file mode 100644 index 00000000..aa1c5c90 --- /dev/null +++ b/couchpotato/core/_base/scheduler/__init__.py @@ -0,0 +1,6 @@ +from .main import Scheduler + +def start(): + return Scheduler() + +config = [] diff --git a/couchpotato/core/_base/scheduler/main.py b/couchpotato/core/_base/scheduler/main.py new file mode 100644 index 00000000..31aa123a --- /dev/null +++ b/couchpotato/core/_base/scheduler/main.py @@ -0,0 +1,85 @@ +from apscheduler.scheduler import Scheduler as Sched +from couchpotato.core.event import addEvent +from couchpotato.core.logger import CPLog +from couchpotato.core.plugins.base import Plugin + +log = CPLog(__name__) + + +class Scheduler(Plugin): + + crons = {} + intervals = {} + started = False + + def __init__(self): + + addEvent('schedule.cron', self.cron) + addEvent('schedule.interval', self.interval) + addEvent('schedule.start', self.start) + addEvent('schedule.restart', self.start) + + addEvent('app.load', self.start) + + self.sched = Sched(misfire_grace_time = 60) + + def remove(self, identifier): + for type in ['interval', 'cron']: + try: + self.sched.unschedule_job(getattr(self, type)[identifier]['job']) + log.debug('%s unscheduled %s' % (type.capitalize(), identifier)) + except: + pass + + def start(self): + + # Stop all running + self.stop() + + # Crons + for identifier in self.crons: + self.remove(identifier) + cron = self.crons[identifier] + job = self.sched.add_cron_job(cron['handle'], day = cron['day'], hour = cron['hour'], minute = cron['minute']) + cron['job'] = job + + # Intervals + for identifier in self.intervals: + self.remove(identifier) + interval = self.intervals[identifier] + job = self.sched.add_interval_job(interval['handle'], hours = interval['hours'], minutes = interval['minutes'], seconds = interval['seconds'], repeat = interval['repeat']) + interval['job'] = job + + # Start it + self.sched.start() + self.started = True + + def stop(self): + + if self.started: + self.sched.shutdown() + + self.started = False + + def cron(self, identifier = '', handle = None, day = '*', hour = '*', minute = '*'): + log.info('Scheduling "%s", cron: day = %s, hour = %s, minute = %s' % (identifier, day, hour, minute)) + + self.remove(identifier) + self.crons[identifier] = { + 'handle': handle, + 'day': day, + 'hour': hour, + 'minute': minute, + } + + def interval(self, identifier = '', handle = None, hours = 0, minutes = 0, seconds = 0, repeat = 0): + log.info('Scheduling %s, interval: hours = %s, minutes = %s, seconds = %s, repeat = %s' % (identifier, hours, minutes, seconds, repeat)) + + self.remove(identifier) + self.intervals[identifier] = { + 'handle': handle, + 'repeat': repeat, + 'hours': hours, + 'minutes': minutes, + 'seconds': seconds, + } diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index bfcd65a8..054d5bb2 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -1,6 +1,7 @@ from axl.axel import Event -from couchpotato.core.helpers.variable import merge_dicts +from couchpotato.core.helpers.variable import mergeDicts from couchpotato.core.logger import CPLog +import threading import traceback log = CPLog(__name__) @@ -11,7 +12,7 @@ def addEvent(name, handler): if events.get(name): e = events[name] else: - e = events[name] = Event(threads = 20, exc_info = True, traceback = True) + e = events[name] = Event(threads = 20, exc_info = True, traceback = True, lock = threading.RLock()) e += handler @@ -51,15 +52,24 @@ def fireEvent(name, *args, **kwargs): else: errorHandler(r[1]) - # Merge the results - if merge: + # Merge dict + if merge and type(results[0]) == dict: merged = {} for result in results: - merged = merge_dicts(merged, result) + merged = mergeDicts(merged, result) + + results = merged + # Merg lists + elif merge and type(results[0]) == list: + merged = [] + for result in results: + merged += result results = merged return results + except KeyError: + pass except Exception, e: log.error('%s: %s' % (name, e)) diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py index 791cc8b8..2a493b2f 100644 --- a/couchpotato/core/helpers/encoding.py +++ b/couchpotato/core/helpers/encoding.py @@ -29,7 +29,7 @@ def toUnicode(original, *args): return unicode(ascii_text) -def is_int(value): +def isInt(value): try: int(value) return True diff --git a/couchpotato/core/helpers/rss.py b/couchpotato/core/helpers/rss.py index cacbdec2..72423597 100644 --- a/couchpotato/core/helpers/rss.py +++ b/couchpotato/core/helpers/rss.py @@ -5,7 +5,7 @@ log = CPLog(__name__) class RSS(): - def gettextelements(self, xml, path): + def getTextElements(self, xml, path): ''' Find elements and return tree''' textelements = [] @@ -17,7 +17,17 @@ class RSS(): textelements.append(element.text) return textelements - def gettextelement(self, xml, path): + def getElements(self, xml, path): + + elements = [] + try: + elements = xml.findall(path) + except: + pass + + return elements + + def getTextElement(self, xml, path): ''' Find element and return text''' try: diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index f32e6427..c19d87a6 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -1,12 +1,12 @@ import hashlib import os.path -def is_dict(object): +def isDict(object): return isinstance(object, dict) -def merge_dicts(a, b): - assert is_dict(a), is_dict(b) +def mergeDicts(a, b): + assert isDict(a), isDict(b) dst = a.copy() stack = [(dst, b)] @@ -16,7 +16,7 @@ def merge_dicts(a, b): if key not in current_dst: current_dst[key] = current_src[key] else: - if is_dict(current_src[key]) and is_dict(current_dst[key]) : + if isDict(current_src[key]) and isDict(current_dst[key]) : stack.append((current_dst[key], current_src[key])) else: current_dst[key] = current_src[key] diff --git a/couchpotato/core/loader.py b/couchpotato/core/loader.py index 551b3f4e..e682500c 100644 --- a/couchpotato/core/loader.py +++ b/couchpotato/core/loader.py @@ -18,7 +18,8 @@ class Loader: providers = os.path.join(root, 'couchpotato', 'core', 'providers') self.paths = { - 'plugin' : (0, 'couchpotato.core.plugins', os.path.join(core, 'plugins')), + 'core' : (0, 'couchpotato.core._base', os.path.join(core, '_base')), + 'plugin' : (1, 'couchpotato.core.plugins', os.path.join(core, 'plugins')), 'notifications' : (20, 'couchpotato.core.notifications', os.path.join(core, 'notifications')), 'downloaders' : (20, 'couchpotato.core.downloaders', os.path.join(core, 'downloaders')), 'movie_provider' : (20, 'couchpotato.core.providers.movie', os.path.join(providers, 'movie')), @@ -48,7 +49,7 @@ class Loader: self.loadPlugins(m, plugin.get('name')) except Exception, e: - log.error('Can\'t import %s: %s' % (plugin.get('name'), e)) + log.error('Can\'t import %s: %s' % (module_name, e)) if did_save: fireEvent('settings.save') diff --git a/couchpotato/core/plugins/__init__.py b/couchpotato/core/plugins/__init__.py index 8b137891..84714c75 100644 --- a/couchpotato/core/plugins/__init__.py +++ b/couchpotato/core/plugins/__init__.py @@ -1 +1,75 @@ +from uuid import uuid4 + +def start(): + pass + +config = [{ + 'name': 'core', + 'groups': [ + { + 'tab': 'general', + 'name': 'basics', + 'description': 'Needs restart before changes take effect.', + 'options': [ + { + 'name': 'username', + 'default': '', + }, + { + 'name': 'password', + 'default': '', + 'type': 'password', + }, + { + 'name': 'host', + 'advanced': True, + 'default': '0.0.0.0', + 'label': 'IP', + 'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.', + }, + { + 'name': 'port', + 'default': 5000, + 'type': 'int', + 'description': 'The port I should listen to.', + }, + { + 'name': 'launch_browser', + 'default': 1, + 'type': 'bool', + 'label': 'Launch Browser', + 'description': 'Launch the browser when I start.', + }, + ], + }, + { + 'tab': 'general', + 'name': 'advanced', + 'description': "For those who know what the're doing", + 'advanced': True, + 'options': [ + { + 'name': 'api_key', + 'default': uuid4().hex, + 'readonly': 1, + 'label': 'Api Key', + 'description': "This is top-secret! Don't share this!", + }, + { + 'name': 'debug', + 'default': 0, + 'type': 'bool', + 'label': 'Debug', + 'description': 'Enable debugging.', + }, + { + 'name': 'url_base', + 'default': '', + 'label': 'Url Base', + 'description': 'When using mod_proxy use this to append the url with this.', + }, + ], + }, + ], +}] diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 5021296d..9252e397 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -37,4 +37,4 @@ class Plugin(): return not self.isEnabled() def isEnabled(self): - return self.conf('enabled', True) + return self.conf('enabled') diff --git a/couchpotato/core/plugins/file_browser/__init__.py b/couchpotato/core/plugins/browser/__init__.py similarity index 100% rename from couchpotato/core/plugins/file_browser/__init__.py rename to couchpotato/core/plugins/browser/__init__.py diff --git a/couchpotato/core/plugins/file_browser/main.py b/couchpotato/core/plugins/browser/main.py similarity index 100% rename from couchpotato/core/plugins/file_browser/main.py rename to couchpotato/core/plugins/browser/main.py diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index df5a2ff5..2b0a5b47 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -27,6 +27,7 @@ class QualityPlugin(Plugin): def __init__(self): addEvent('quality.all', self.all) + addEvent('quality.single', self.single) addEvent('app.load', self.fill) path = self.registerStatic(__file__) @@ -46,6 +47,14 @@ class QualityPlugin(Plugin): return temp + def single(self, identifier = ''): + + db = get_session() + + quality = db.query(Quality).filter_by(identifier = identifier).first() + + return dict(self.getQuality(quality.identifier), **quality.to_dict()) + def getQuality(self, identifier): for q in self.qualities: diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 55761788..b617d792 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -1,3 +1,4 @@ +from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin @@ -8,3 +9,11 @@ class Renamer(Plugin): def __init__(self): pass + + addEvent('renamer.scan', self.scan) + addEvent('app.load', self.scan) + + fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every')) + + def scan(self): + print 'scan' diff --git a/couchpotato/core/plugins/score/__init__.py b/couchpotato/core/plugins/score/__init__.py new file mode 100644 index 00000000..2c367f89 --- /dev/null +++ b/couchpotato/core/plugins/score/__init__.py @@ -0,0 +1,6 @@ +from .main import Score + +def start(): + return Score() + +config = [] diff --git a/couchpotato/core/plugins/score/main.py b/couchpotato/core/plugins/score/main.py new file mode 100644 index 00000000..1dd847e3 --- /dev/null +++ b/couchpotato/core/plugins/score/main.py @@ -0,0 +1,22 @@ +from couchpotato.core.event import addEvent +from couchpotato.core.logger import CPLog +from couchpotato.core.plugins.base import Plugin +from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore + +log = CPLog(__name__) + + +class Score(Plugin): + + def __init__(self): + addEvent('score.calculate', self.calculate) + + def calculate(self, nzb, movie): + ''' Calculate the score of a NZB, used for sorting later ''' + + score = nameScore(nzb['name'], movie['library']['year']) + + for movie_title in movie['library']['titles']: + score += nameRatioScore(nzb['name'], movie_title['title']) + + return score diff --git a/couchpotato/core/plugins/score/scores.py b/couchpotato/core/plugins/score/scores.py new file mode 100644 index 00000000..6f389820 --- /dev/null +++ b/couchpotato/core/plugins/score/scores.py @@ -0,0 +1,51 @@ +from couchpotato.core.helpers.encoding import simplifyString +from couchpotato.environment import Env +import re + +name_scores = [ + 'proper:2', 'repack:2', + 'unrated:1', + 'x264:1', + 'DTS:4', 'AC3:2', + '720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1', + 'metis:1', 'diamond:1', 'wiki:1', 'CBGB:1', + 'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10' +] + +def nameScore(name, year): + ''' Calculate score for words in the NZB name ''' + + score = 0 + name = name.lower() + + #give points for the cool stuff + for value in name_scores: + v = value.split(':') + add = int(v.pop()) + if v.pop() in name: + score = score + add + + #points if the year is correct + if str(year) in name: + score = score + 1 + + # Contains preferred word + nzb_words = re.split('\W+', simplifyString(name)) + preferred_words = Env.setting('preferred_words', section = 'searcher').split(',') + for word in preferred_words: + if word.strip() and word.strip().lower() in nzb_words: + score = score + 100 + + return score + +def nameRatioScore(nzb_name, movie_name): + + nzb_words = re.split('\W+', simplifyString(nzb_name)) + movie_words = re.split('\W+', simplifyString(movie_name)) + + # Replace .,-_ with space + left_over = len(nzb_words) - len(movie_words) + if 2 <= left_over <= 6: + return 4 + else: + return 0 diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py new file mode 100644 index 00000000..2d7d3c91 --- /dev/null +++ b/couchpotato/core/plugins/searcher/__init__.py @@ -0,0 +1,78 @@ +from .main import Searcher +import random + +def start(): + return Searcher() + +config = [{ + 'name': 'searcher', + 'groups': [ + { + 'tab': 'searcher', + 'name': 'searcher', + 'label': 'Search', + 'description': 'Options for the searchers', + 'options': [ + { + 'name': 'required_words', + 'label': 'Required words', + 'default': '', + }, + { + 'name': 'ignored_words', + 'label': 'Ignored words', + 'default': '', + }, + ], + }, { + 'tab': 'searcher', + 'name': 'searcher', + 'label': 'Cronjob', + 'advanced': True, + 'description': 'Cron settings for the searcher see: APScheduler for details.', + 'options': [ + { + 'name': 'cron_day', + 'label': 'Day', + 'advanced': True, + 'default': '*', + 'type': 'string', + 'description': '*: Every day, */2: Every 2 days, 1: Every first of the month.', + }, + { + 'name': 'cron_hour', + 'label': 'Hour', + 'advanced': True, + 'default': random.randint(0, 23), + 'type': 'string', + 'description': '*: Every hour, */8: Every 8 hours, 3: At 3, midnight.', + }, + { + 'name': 'cron_minute', + 'label': 'Minute', + 'advanced': True, + 'default': random.randint(0, 59), + 'type': 'string', + 'description': "Just keep it random, so the providers don't get DDOSed by every CP user on a 'full' hour." + }, + ], + }, + ], +}, { + 'name': 'nzb', + 'groups': [ + { + 'tab': 'searcher', + 'name': 'nzb', + 'label': 'NZB', + 'options': [ + { + 'name': 'retention', + 'default': 350, + 'type': 'int', + 'unit': 'days' + }, + ], + }, + ], +}] diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py new file mode 100644 index 00000000..24ab70a0 --- /dev/null +++ b/couchpotato/core/plugins/searcher/main.py @@ -0,0 +1,156 @@ +from couchpotato import get_session +from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.helpers.encoding import simplifyString +from couchpotato.core.logger import CPLog +from couchpotato.core.plugins.base import Plugin +from couchpotato.core.settings.model import Movie +from couchpotato.environment import Env +import re + +log = CPLog(__name__) + + +class Searcher(Plugin): + + def __init__(self): + addEvent('searcher.all', self.all) + addEvent('searcher.single', self.single) + addEvent('searcher.correct_movie', self.correctMovie) + + # Schedule cronjob + fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) + + def all(self): + + db = get_session() + + movies = db.query(Movie).filter( + Movie.status.has(identifier = 'active') + ).all() + + for movie in movies: + self.single(movie.to_dict(deep = { + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + })) + + + def single(self, movie): + + for type in movie['profile']['types']: + results = fireEvent('provider.yarr.search', movie, type['quality'], merge = True) + sorted_results = sorted(results, key = lambda k: k['score'], reverse = True) + for nzb in sorted_results: + print nzb['name'] + + + def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs): + + imdb_results = kwargs.get('imdb_results', False) + single_category = kwargs.get('single_category', False) + retention = Env.setting('retention', section = 'nzb') + + if retention < nzb.get('age', 0): + log.info('Wrong: Outside retention, age = %s, needs = %s: %s' % (nzb['age'], retention, nzb['name'])) + return False + + nzb_words = re.split('\W+', simplifyString(nzb['name'])) + required_words = self.conf('required_words').split(',') + + if self.conf('required_words') and not list(set(nzb_words) & set(required_words)): + log.info("NZB doesn't contain any of the required words.") + return False + + ignored_words = self.conf('ignored_words').split(',') + blacklisted = list(set(nzb_words) & set(ignored_words)) + if self.conf('ignored_words') and blacklisted: + log.info("NZB '%s' contains the following blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted))) + return False + + #qualities = fireEvent('quality.all', single = True) + preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True) + + # Contains lower quality string + if self.containsOtherQuality(nzb['name'], preferred_quality, single_category): + log.info('Wrong: %s, looking for %s' % (nzb['name'], quality['label'])) + return False + + """ + # File to small + minSize = q.minimumSize(qualityType) + if minSize > item.size: + log.info('"%s" is too small to be %s. %sMB instead of the minimal of %sMB.' % (item.name, type['label'], item.size, minSize)) + return False + + # File to large + maxSize = q.maximumSize(qualityType) + if maxSize < item.size: + log.info('"%s" is too large to be %s. %sMB instead of the maximum of %sMB.' % (item.name, type['label'], item.size, maxSize)) + return False + + """ + + if imdb_results: + return True + + # Check if nzb contains imdb link + if self.checkIMDB([nzb['description']], movie['library']['identifier']): + return True + + for movie_title in movie['library']['titles']: + movie_words = re.split('\W+', simplifyString(movie_title['title'])) + + if self.correctName(nzb['name'], movie_title['title']): + # if no IMDB link, at least check year range 1 + if len(movie_words) > 2 and self.correctYear([nzb['name']], movie['library']['year'], 1): + return True + + # if no IMDB link, at least check year + if len(movie_words) == 2 and self.correctYear([nzb['name']], movie['library']['year'], 0): + return True + + return False + + def containsOtherQuality(self, name, preferred_quality = {}, single_category = False): + + nzb_words = re.split('\W+', simplifyString(name)) + + qualities = fireEvent('quality.all', single = True) + + found = {} + for quality in qualities: + # Main in words + if quality['identifier'] in nzb_words: + found[quality['identifier']] = True + + # Alt in words + if list(set(nzb_words) & set(quality['alternative'])): + found[quality['identifier']] = True + + # Allow other qualities + for allowed in preferred_quality.get('allow'): + if found.get(allowed): + del found[allowed] + + if (len(found) == 0 and single_category): + return False + + return not (found.get(preferred_quality['identifier']) and len(found) == 1) + + def checkIMDB(self, haystack, imdbId): + + for string in haystack: + if 'imdb.com/title/' + imdbId in string: + return True + + return False + + def correctYear(self, haystack, year, range): + + for string in haystack: + if str(year) in string or str(int(year) + range) in string or str(int(year) - range) in string: # 1 year of is fine too + return True + + return False diff --git a/couchpotato/core/plugins/wizard/__init__.py b/couchpotato/core/plugins/wizard/__init__.py index 807c32c0..7ee4a45c 100644 --- a/couchpotato/core/plugins/wizard/__init__.py +++ b/couchpotato/core/plugins/wizard/__init__.py @@ -13,7 +13,7 @@ config = [{ { 'name': 'show_wizard', 'label': 'Run the wizard', - 'default': True, + 'default': 1, 'type': 'bool', }, ], diff --git a/couchpotato/core/plugins/wizard/static/spotlight.js b/couchpotato/core/plugins/wizard/static/spotlight.js index 0f249e22..c8237763 100644 --- a/couchpotato/core/plugins/wizard/static/spotlight.js +++ b/couchpotato/core/plugins/wizard/static/spotlight.js @@ -193,7 +193,7 @@ var Spotlight = new Class({ 'position': 'absolute', 'background-color': 'rgba('+self.options.fillColor.join(',')+', '+self.options.fillOpacity+')', 'display': 'block', - 'z-index': 2, + 'z-index': 999, 'top': self.top[nr], 'left': self.left[nr], 'height': self.height[nr], @@ -276,7 +276,7 @@ var Spotlight = new Class({ var self = this; var soften = self.options.soften; - var edge = new Element('div', { + var edge = new Element('div.edge', { 'styles': Object.merge({ 'position': 'absolute', 'width': soften, diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index 2c1d48c8..02b65dfc 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -6,13 +6,22 @@ var WizardBase = new Class({ var self = this; self.steps = steps; - self.start(); + self.spotlight = new Spotlight([], { + 'fillColor': [0,0,0], + 'soften': 0 + }); + window.addEvent('resize', self.spotlight.create.bind(self.spotlight)) }, start: function(){ + var self = this; + var els = $(document.body).getElements("[name='core[username]'], [name='core[password]'], [name='core[launch_browser]']").getParent('.ctrlHolder') + p(els, self.spotlight.setElements(els)) + + self.spotlight.create(); }, diff --git a/couchpotato/core/providers/base.py b/couchpotato/core/providers/base.py index a677a338..b317d001 100644 --- a/couchpotato/core/providers/base.py +++ b/couchpotato/core/providers/base.py @@ -1,5 +1,12 @@ from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin +from couchpotato.environment import Env +from urllib2 import URLError +import math +import re +import socket +import time +import urllib2 log = CPLog(__name__) @@ -7,24 +14,125 @@ log = CPLog(__name__) class Provider(Plugin): type = None # movie, nzb, torrent, subtitle, trailer - timeout = 10 # Default timeout for url requests + time_between_searches = 10 # Default timeout for url requests + + last_use = 0 + last_available_check = 0 + is_available = 0 + + def getCache(self, cache_key): + cache = Env.get('cache').get(cache_key) + if cache: + log.debug('Getting cache %s' % cache_key) + return cache + + def setCache(self, cache_key, value): + log.debug('Setting cache %s' % cache_key) + Env.get('cache').set(cache_key, value) + + def isAvailable(self, test_url): + + if Env.get('debug'): return True + + now = time.time() + + if self.last_available_check < now - 900: + self.last_available_check = now + try: + self.urlopen(test_url, 30) + self.is_available = True + except (IOError, URLError): + log.error('%s unavailable, trying again in an 15 minutes.' % self.name) + self.is_available = False + + return self.is_available + + def urlopen(self, url, timeout = 10, username = None, password = None): + + socket.setdefaulttimeout(timeout) + self.wait() + + try: + if username and password: + passman = urllib2.HTTPPasswordMgrWithDefaultRealm() + passman.add_password(None, url, username, password) + authhandler = urllib2.HTTPBasicAuthHandler(passman) + opener = urllib2.build_opener(authhandler) + data = opener.open(url).read() + else: + data = urllib2.urlopen(url).read() + + except IOError, e: + log.debug(e) + data = '' + + self.last_use = time.time() + + return data + + def wait(self): + now = time.time() + wait = math.ceil(self.last_use - now + self.time_between_searches) + + if wait > 0: + log.debug('Waiting for %s, %d seconds' % (self.getName(), wait)) + time.sleep(self.last_use - now + self.time_between_searches) class MovieProvider(Provider): type = 'movie' -class NZBProvider(Provider): +class YarrProvider(Provider): + + cat_ids = [] + + sizeGb = ['gb', 'gib'] + sizeMb = ['mb', 'mib'] + sizeKb = ['kb', 'kib'] + + def parseSize(self, size): + + sizeRaw = size.lower() + size = re.sub(r'[^0-9.]', '', size).strip() + + for s in self.sizeGb: + if s in sizeRaw: + return float(size) * 1024 + + for s in self.sizeMb: + if s in sizeRaw: + return float(size) + + for s in self.sizeKb: + if s in sizeRaw: + return float(size) / 1024 + + return 0 + + def getCatId(self, identifier): + + for cats in self.cat_ids: + ids, qualities = cats + if identifier in qualities: + return ids + + return False + + def found(self, new): + log.info('Found, score(%(score)s): %(name)s' % new) + + +class NZBProvider(YarrProvider): type = 'nzb' time_between_searches = 10 # Seconds - - def isEnabled(self): - return True # nzb_downloaded is enabled check + def calculateAge(self, unix): + return int(time.time() - unix) / 24 / 60 / 60 -class TorrentProvider(Provider): +class TorrentProvider(YarrProvider): type = 'torrent' diff --git a/couchpotato/core/providers/nzb/newzbin/main.py b/couchpotato/core/providers/nzb/newzbin/main.py index babf91d4..ad2150ca 100644 --- a/couchpotato/core/providers/nzb/newzbin/main.py +++ b/couchpotato/core/providers/nzb/newzbin/main.py @@ -29,13 +29,12 @@ class Newzbin(NZBProvider): def __init__(self): addEvent('provider.nzb.search', self.search) + addEvent('provider.yarr.search', self.search) def search(self, movie, quality): - self.cleanCache(); - results = [] - if not self.enabled() or not self.isAvailable(self.searchUrl): + if self.isDisabled() or not self.isAvailable(self.searchUrl): return results formatId = self.getFormatId(type) diff --git a/couchpotato/core/providers/nzb/newznab/main.py b/couchpotato/core/providers/nzb/newznab/main.py index c8ffbe42..dccd047c 100644 --- a/couchpotato/core/providers/nzb/newznab/main.py +++ b/couchpotato/core/providers/nzb/newznab/main.py @@ -1,20 +1,24 @@ -from couchpotato.core.event import addEvent +from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.helpers.rss import RSS from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog from couchpotato.core.providers.base import NZBProvider +from couchpotato.environment import Env from dateutil.parser import parse from urllib import urlencode from urllib2 import URLError import time +import xml.etree.ElementTree as XMLTree log = CPLog(__name__) -class Newznab(NZBProvider): +class Newznab(NZBProvider, RSS): urls = { - 'download': 'get&id=%s%s', + 'download': 'get&id=%s', 'detail': 'details&id=%s', + 'search': 'movie', } cat_ids = [ @@ -29,43 +33,35 @@ class Newznab(NZBProvider): def __init__(self): addEvent('provider.nzb.search', self.search) + addEvent('provider.yarr.search', self.search) def getUrl(self, type): return cleanHost(self.conf('host')) + 'api?t=' + type def search(self, movie, quality): - self.cleanCache(); - results = [] - if not self.enabled() or not self.isAvailable(self.getUrl(self.searchUrl)): + if self.isDisabled() or not self.isAvailable(self.getUrl(self.urls['search'])): return results - catId = self.getCatId(type) + cat_id = self.getCatId(quality['identifier']) arguments = urlencode({ - 'imdbid': movie.imdb.replace('tt', ''), - 'cat': catId, - 'apikey': self.conf('apikey'), - 't': self.searchUrl, + 'imdbid': movie['library']['identifier'].replace('tt', ''), + 'cat': cat_id[0], + 'apikey': self.conf('api_key'), + 't': self.urls['search'], 'extended': 1 }) - url = "%s&%s" % (self.getUrl(self.searchUrl), arguments) - cacheId = str(movie.imdb) + '-' + str(catId) - singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId) + url = "%s&%s" % (self.getUrl(self.urls['search']), arguments) + + cache_key = '%s-%s' % (movie['library']['identifier'], cat_id[0]) + single_cat = (len(cat_id) == 1 and cat_id[0] != self.cat_backup_id) try: - cached = False - if(self.cache.get(cacheId)): - data = True - cached = True - log.info('Getting RSS from cache: %s.' % cacheId) - else: - log.info('Searching: %s' % url) + data = self.getCache(cache_key) + if not data: data = self.urlopen(url) - self.cache[cacheId] = { - 'time': time.time() - } - + self.setCache(cache_key, data) except (IOError, URLError): log.error('Failed to open %s.' % url) return results @@ -73,17 +69,14 @@ class Newznab(NZBProvider): if data: try: try: - if cached: - xml = self.cache[cacheId]['xml'] - else: - xml = self.getItems(data) - self.cache[cacheId]['xml'] = xml - except: - log.debug('No valid xml or to many requests.' % self.name) + data = XMLTree.fromstring(data) + nzbs = self.getElements(data, 'channel/item') + except Exception, e: + log.debug('%s, %s' % (self.getName(), e)) return results results = [] - for nzb in xml: + for nzb in nzbs: for item in nzb: if item.attrib.get('name') == 'size': @@ -91,20 +84,26 @@ class Newznab(NZBProvider): elif item.attrib.get('name') == 'usenetdate': date = item.attrib.get('value') - new = self.feedItem() - new.id = self.gettextelement(nzb, "guid").split('/')[-1:].pop() - new.type = 'nzb' - new.name = self.gettextelement(nzb, "title") - new.date = int(time.mktime(parse(date).timetuple())) - new.size = int(size) / 1024 / 1024 - new.url = self.downloadLink(new.id) - new.detailUrl = self.detailLink(new.id) - new.content = self.gettextelement(nzb, "description") - new.score = self.calcScore(new, movie) + id = self.getTextElement(nzb, "guid").split('/')[-1:].pop() + new = { + 'id': id, + 'type': 'nzb', + 'name': self.getTextElement(nzb, "title"), + 'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))), + 'size': int(size) / 1024 / 1024, + 'url': (self.getUrl(self.urls['download']) % id) + self.getApiExt(), + 'detail_url': (self.getUrl(self.urls['detail']) % id) + self.getApiExt(), + 'content': self.getTextElement(nzb, "description"), + } + new['score'] = fireEvent('score.calculate', new, movie, single = True) - if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60) and self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat): + is_correct_movie = fireEvent('searcher.correct_movie', + nzb = new, movie = movie, quality = quality, + imdb_results = True, single_category = single_cat, single = True) + + if is_correct_movie: results.append(new) - log.info('Found: %s' % new.name) + self.found(new) return results except SyntaxError: @@ -114,13 +113,10 @@ class Newznab(NZBProvider): return results def isEnabled(self): - return NZBProvider.isEnabled(self) and self.conf('enabled') and self.conf('host') and self.conf('apikey') + return NZBProvider.isEnabled(self) and self.conf('host') and self.conf('api_key') def getApiExt(self): - return '&apikey=%s' % self.conf('apikey') - - def downloadLink(self, id): - return self.getUrl(self.downloadUrl) % (id, self.getApiExt()) + return '&apikey=%s' % self.conf('api_key') def detailLink(self, id): return self.getUrl(self.detailUrl) % id diff --git a/couchpotato/core/providers/nzb/nzbmatrix/__init__.py b/couchpotato/core/providers/nzb/nzbmatrix/__init__.py index 376c1259..37586a16 100644 --- a/couchpotato/core/providers/nzb/nzbmatrix/__init__.py +++ b/couchpotato/core/providers/nzb/nzbmatrix/__init__.py @@ -23,6 +23,13 @@ config = [{ 'default': '', 'label': 'Api Key', }, + { + 'name': 'english_only', + 'default': 1, + 'type': 'bool', + 'label': 'English only', + 'description': 'Only search for English spoken movies on NZBMatrix', + }, ], }, ], diff --git a/couchpotato/core/providers/nzb/nzbmatrix/main.py b/couchpotato/core/providers/nzb/nzbmatrix/main.py index 95b97d3f..ecabe220 100644 --- a/couchpotato/core/providers/nzb/nzbmatrix/main.py +++ b/couchpotato/core/providers/nzb/nzbmatrix/main.py @@ -1,24 +1,27 @@ -from couchpotato.core.event import addEvent +from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.helpers.rss import RSS from couchpotato.core.logger import CPLog from couchpotato.core.providers.base import NZBProvider +from couchpotato.environment import Env from dateutil.parser import parse from urllib import urlencode from urllib2 import URLError import time +import xml.etree.ElementTree as XMLTree log = CPLog(__name__) -class NZBMatrix(NZBProvider): +class NZBMatrix(NZBProvider, RSS): urls = { - 'download': 'https://api.nzbmatrix.com/v1.1/download.php?id=%s%s', + 'download': 'https://api.nzbmatrix.com/v1.1/download.php?id=%s', 'detail': 'https://nzbmatrix.com/nzb-details.php?id=%s&hit=1', 'search': 'http://rss.nzbmatrix.com/rss.php', } cat_ids = [ - ([42], ['720p', '1080p']), + ([42, 53], ['720p', '1080p']), ([2], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), ([54], ['brrip']), ([1], ['dvdr']), @@ -27,41 +30,37 @@ class NZBMatrix(NZBProvider): def __init__(self): addEvent('provider.nzb.search', self.search) + addEvent('provider.yarr.search', self.search) def search(self, movie, quality): - self.cleanCache(); - results = [] - if not self.enabled() or not self.isAvailable(self.searchUrl): + + if self.isDisabled() or not self.isAvailable(self.urls['search']): return results - catId = self.getCatId(type) + cat_ids = ','.join(['%s' % x for x in self.getCatId(quality.get('identifier'))]) + arguments = urlencode({ - 'term': movie.imdb, - 'subcat': catId, + 'term': movie['library']['identifier'], + 'subcat': cat_ids, 'username': self.conf('username'), - 'apikey': self.conf('apikey'), + 'apikey': self.conf('api_key'), 'searchin': 'weblink', - 'english': 1 if self.conf('english') else 0, + 'age': Env.setting('retention', section = 'nzb'), + 'english': self.conf('english_only'), }) - url = "%s?%s" % (self.searchUrl, arguments) - cacheId = str(movie.imdb) + '-' + str(catId) - singleCat = (len(self.catIds.get(catId)) == 1 and catId != self.catBackupId) + url = "%s?%s" % (self.urls['search'], arguments) + log.info('Searching: %s' % url) + + cache_key = '%s-%s' % (movie['library'].get('identifier'), cat_ids) + single_cat = True try: - cached = False - if(self.cache.get(cacheId)): - data = True - cached = True - log.info('Getting RSS from cache: %s.' % cacheId) - else: - log.info('Searching: %s' % url) + data = self.getCache(cache_key) + if not data: data = self.urlopen(url) - self.cache[cacheId] = { - 'time': time.time() - } - + self.setCache(cache_key, data) except (IOError, URLError): log.error('Failed to open %s.' % url) return results @@ -69,42 +68,41 @@ class NZBMatrix(NZBProvider): if data: try: try: - if cached: - xml = self.cache[cacheId]['xml'] - else: - xml = self.getItems(data) - self.cache[cacheId]['xml'] = xml - except: - log.debug('No valid xml or to many requests.. You never know with %s.' % self.name) + data = XMLTree.fromstring(data) + nzbs = self.getElements(data, 'channel/item') + except Exception, e: + log.debug('%s, %s' % (self.getName(), e)) return results - for nzb in xml: + for nzb in nzbs: - title = self.gettextelement(nzb, "title") + title = self.getTextElement(nzb, "title") if 'error' in title.lower(): continue - id = int(self.gettextelement(nzb, "link").split('&')[0].partition('id=')[2]) - size = self.gettextelement(nzb, "description").split('
')[2].split('> ')[1] - date = str(self.gettextelement(nzb, "description").split('
')[3].partition('Added: ')[2]) + id = int(self.getTextElement(nzb, "link").split('&')[0].partition('id=')[2]) + size = self.getTextElement(nzb, "description").split('
')[2].split('> ')[1] + date = str(self.getTextElement(nzb, "description").split('
')[3].partition('Added: ')[2]) - new = self.feedItem() - new.id = id - new.type = 'nzb' - new.name = title - new.date = int(time.mktime(parse(date).timetuple())) - new.size = self.parseSize(size) - new.url = self.downloadLink(id) - new.detailUrl = self.detailLink(id) - new.content = self.gettextelement(nzb, "description") - new.score = self.calcScore(new, movie) - new.checkNZB = True + new = { + 'id': id, + 'type': 'nzb', + 'name': title, + 'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))), + 'size': self.parseSize(size), + 'url': self.urls['download'] % id + self.getApiExt(), + 'detail_url': self.urls['detail'] % id, + 'description': self.getTextElement(nzb, "description"), + 'check_nzb': True, + } + new['score'] = fireEvent('score.calculate', new, movie, single = True) - if new.date > time.time() - (int(self.config.get('NZB', 'retention')) * 24 * 60 * 60): - if self.isCorrectMovie(new, movie, type, imdbResults = True, singleCategory = singleCat): - results.append(new) - log.info('Found: %s' % new.name) - else: - log.info('Found outside retention: %s' % new.name) + is_correct_movie = fireEvent('searcher.correct_movie', + nzb = new, movie = movie, quality = quality, + imdb_results = True, single_category = single_cat, single = True) + + if is_correct_movie: + results.append(new) + self.found(new) return results except SyntaxError: @@ -116,4 +114,4 @@ class NZBMatrix(NZBProvider): return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('apikey')) def isEnabled(self): - return NZBProvider.isEnabled(self) and self.conf('enabled') and self.conf('username') and self.conf('apikey') + return NZBProvider.isEnabled(self) and self.conf('username') and self.conf('api_key') diff --git a/couchpotato/core/providers/nzb/nzbs/main.py b/couchpotato/core/providers/nzb/nzbs/main.py index e328cda8..74767c2a 100644 --- a/couchpotato/core/providers/nzb/nzbs/main.py +++ b/couchpotato/core/providers/nzb/nzbs/main.py @@ -29,13 +29,12 @@ class Nzbs(NZBProvider): def __init__(self): addEvent('provider.nzb.search', self.search) + addEvent('provider.yarr.search', self.search) def search(self, movie, quality): - self.cleanCache(); - results = [] - if not self.enabled() or not self.isAvailable(self.apiUrl + '?test' + self.getApiExt()): + if self.isDisabled() or not self.isAvailable(self.apiUrl + '?test' + self.getApiExt()): return results catId = self.getCatId(type) diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py index 0d0b8669..1c462932 100644 --- a/couchpotato/core/settings/__init__.py +++ b/couchpotato/core/settings/__init__.py @@ -1,7 +1,7 @@ from __future__ import with_statement from couchpotato.api import addApiView from couchpotato.core.event import addEvent -from couchpotato.core.helpers.encoding import is_int +from couchpotato.core.helpers.encoding import isInt from couchpotato.core.helpers.request import getParams, jsonified import ConfigParser import os.path @@ -61,7 +61,7 @@ class Settings(): return default def cleanValue(self, value): - if(is_int(value)): + if(isInt(value)): return int(value) if str(value).lower() in self.bool: diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 9a16ec32..00660231 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -7,6 +7,7 @@ Page.Settings = new Class({ tabs: { 'general': {}, + 'searcher': {}, 'providers': {}, 'downloaders': {}, 'notifications': {}, @@ -177,7 +178,7 @@ Page.Settings = new Class({ 'text': (group.label || group.name).capitalize() }).adopt( new Element('span.hint', { - 'text': group.description + 'html': group.description }) ) ) @@ -243,7 +244,7 @@ var OptionBase = new Class({ var self = this; if(self.options.description) new Element('p.formHint', { - 'text': self.options.description + 'html': self.options.description }).inject(self.el); }, @@ -380,6 +381,7 @@ Option.Checkbox = new Class({ self.el.adopt( self.createLabel().set('for', randomId), self.input = new Element('input', { + 'name': self.postName(), 'type': 'checkbox', 'checked': self.getSettingValue(), 'id': randomId diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 092eef1f..0360e44f 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -138,7 +138,7 @@ form { -webkit-box-shadow: 0 0 30px rgba(0,0,0,0.1); position: fixed; width: 99%; - z-index: 9999; + z-index: 2; } .header > div { diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/page/settings.css index 1ffea423..967b77e8 100644 --- a/couchpotato/static/style/page/settings.css +++ b/couchpotato/static/style/page/settings.css @@ -19,7 +19,7 @@ padding: 10px 15px; border: 1px solid transparent; position: relative; - z-index: 200; + z-index: 1; margin-right: -1px; } .page.settings .tabs .active a { diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index f5c8c483..10557ded 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -56,6 +56,8 @@ App.setup({ 'base_url': '{{ request.path }}' }); + + //Wizard.start.delay(100, Wizard); }) CouchPotato