From 18210b4019831b032cb6d73b9ddcce0c45c23717 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:23:25 +0200 Subject: [PATCH 01/17] Added nzbindex.nl as free config-less alternative --- .../core/providers/nzb/nzbindex/__init__.py | 21 ++++ .../core/providers/nzb/nzbindex/main.py | 97 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 couchpotato/core/providers/nzb/nzbindex/__init__.py create mode 100644 couchpotato/core/providers/nzb/nzbindex/main.py diff --git a/couchpotato/core/providers/nzb/nzbindex/__init__.py b/couchpotato/core/providers/nzb/nzbindex/__init__.py new file mode 100644 index 00000000..cf3139c9 --- /dev/null +++ b/couchpotato/core/providers/nzb/nzbindex/__init__.py @@ -0,0 +1,21 @@ +from .main import NzbIndex + +def start(): + return NzbIndex() + +config = [{ + 'name': 'nzbindex', + 'groups': [ + { + 'tab': 'providers', + 'name': 'nzbindex', + 'description': 'Free provider, but less accurate.', + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + }, + ], + }, + ], +}] diff --git a/couchpotato/core/providers/nzb/nzbindex/main.py b/couchpotato/core/providers/nzb/nzbindex/main.py new file mode 100644 index 00000000..727fb5a0 --- /dev/null +++ b/couchpotato/core/providers/nzb/nzbindex/main.py @@ -0,0 +1,97 @@ +from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.helpers.encoding import simplifyString +from couchpotato.core.helpers.rss import RSS +from couchpotato.core.logger import CPLog +from couchpotato.core.providers.base import NZBProvider +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 NzbIndex(NZBProvider, RSS): + + urls = { + 'download': 'http://www.nzbindex.nl/download/%s/%s', + 'api': 'http://www.nzbindex.nl/rss/', #http://www.nzbindex.nl/rss/?q=due+date+720p&age=1000&sort=agedesc&minsize=3500&maxsize=10000 + } + + time_between_searches = 1 # Seconds + + def __init__(self): + addEvent('provider.nzb.search', self.search) + addEvent('provider.yarr.search', self.search) + + def search(self, movie, quality): + + results = [] + if self.isDisabled() or not self.isAvailable(self.urls['api']): + return results + + arguments = urlencode({ + 'q': '%s %s' % (simplifyString(movie['library']['titles'][0]['title']), quality.get('identifier')), + 'sort': 'agedesc', + 'minsize': quality.get('size_min'), + 'maxsize': quality.get('size_max'), + 'rating': '1', + }) + url = "%s?%s" % (self.urls['api'], arguments) + + cache_key = 'nzbindex.%s.%s' % (movie['library'].get('identifier'), quality.get('identifier')) + + try: + data = self.getCache(cache_key) + if not data: + data = self.urlopen(url) + self.setCache(cache_key, data) + except (IOError, URLError): + log.error('Failed to open %s.' % url) + return results + + if data: + try: + try: + 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 nzbs: + + enclosure = self.getElements(nzb, 'enclosure')[0].attrib + + id = int(self.getTextElement(nzb, "link").split('/')[4]) + new = { + 'id': id, + 'type': 'nzb', + 'name': self.getTextElement(nzb, "title"), + 'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))), + 'size': enclosure['length'], + 'url': enclosure['url'], + 'detail_url': enclosure['url'].replace('/download/', '/release/'), + 'description': self.getTextElement(nzb, "description"), + 'check_nzb': True, + } + new['score'] = fireEvent('score.calculate', new, movie, single = True) + + is_correct_movie = fireEvent('searcher.correct_movie', + nzb = new, movie = movie, quality = quality, + imdb_results = False, single_category = False, single = True) + + if is_correct_movie: + results.append(new) + self.found(new) + + return results + except SyntaxError: + log.error('Failed to parse XML response from NZBMatrix.com') + + return results + + + def isEnabled(self): + return NZBProvider.isEnabled(self) and self.conf('enabled') From ec7164c308c1fd77c06e7717ad974c9000de25a9 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:27:57 +0200 Subject: [PATCH 02/17] Proper JS naming --- .../form_replacement/{Form.CheckGroup.js => form_checkgroup.js} | 0 .../form_replacement/{Form.RadioGroup.js => form_radiogroup.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename couchpotato/static/scripts/library/form_replacement/{Form.CheckGroup.js => form_checkgroup.js} (100%) rename couchpotato/static/scripts/library/form_replacement/{Form.RadioGroup.js => form_radiogroup.js} (100%) diff --git a/couchpotato/static/scripts/library/form_replacement/Form.CheckGroup.js b/couchpotato/static/scripts/library/form_replacement/form_checkgroup.js similarity index 100% rename from couchpotato/static/scripts/library/form_replacement/Form.CheckGroup.js rename to couchpotato/static/scripts/library/form_replacement/form_checkgroup.js diff --git a/couchpotato/static/scripts/library/form_replacement/Form.RadioGroup.js b/couchpotato/static/scripts/library/form_replacement/form_radiogroup.js similarity index 100% rename from couchpotato/static/scripts/library/form_replacement/Form.RadioGroup.js rename to couchpotato/static/scripts/library/form_replacement/form_radiogroup.js From 776a988fb4bf965331695808df3243e9a92527d6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:28:33 +0200 Subject: [PATCH 03/17] Add cp(ttxxxxxx) to downloaders --- couchpotato/core/downloaders/blackhole/main.py | 5 +++-- couchpotato/core/downloaders/sabnzbd/__init__.py | 5 +++++ couchpotato/core/downloaders/sabnzbd/main.py | 15 +++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/couchpotato/core/downloaders/blackhole/main.py b/couchpotato/core/downloaders/blackhole/main.py index fbb6ef35..47650a13 100644 --- a/couchpotato/core/downloaders/blackhole/main.py +++ b/couchpotato/core/downloaders/blackhole/main.py @@ -13,7 +13,7 @@ class Blackhole(Downloader): type = ['nzb', 'torrent'] - def download(self, data = {}): + def download(self, data = {}, movie = {}): if self.isDisabled() or not self.isCorrectType(data.get('type')): return @@ -23,7 +23,8 @@ class Blackhole(Downloader): if not directory or not os.path.isdir(directory): log.error('No directory set for blackhole %s download.' % data.get('type')) else: - fullPath = os.path.join(directory, toSafeString(data.get('name')) + '.' + data.get('type')) + cp_tag = '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else '' + fullPath = os.path.join(directory, '%s%s.%s' % (toSafeString(data.get('name')), cp_tag , data.get('type'))) try: if not os.path.isfile(fullPath): diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index 4d49c4d7..028f139f 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -26,6 +26,11 @@ config = [{ 'label': 'Api Key', 'description': 'Used for all calls to Sabnzbd.', }, + { + 'name': 'category', + 'label': 'Category', + 'description': 'The category CP places the nzb in. Like movies or couchpotato', + }, { 'advanced': True, 'name': 'pp_directory', diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 344aa5f1..eb648f57 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -14,7 +14,7 @@ class Sabnzbd(Downloader): type = ['nzb'] - def download(self, data = {}): + def download(self, data = {}, movie = {}): if self.isDisabled() or not self.isCorrectType(data.get('type')): return @@ -34,11 +34,14 @@ class Sabnzbd(Downloader): else: pp = False + + cp_tag = '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else '' params = { - 'apikey': self.conf('apikey'), + 'apikey': self.conf('api_key'), 'cat': self.conf('category'), 'mode': 'addurl', - 'name': data.get('url') + 'name': data.get('url'), + 'nzbname': '%s%s' % (data.get('name'), cp_tag), } # sabNzbd complains about "invalid archive file" for newzbin urls @@ -53,9 +56,9 @@ class Sabnzbd(Downloader): log.info("URL: " + url) try: - r = urllib2.urlopen(url, timeout = 30) - except: - log.error("Unable to connect to SAB.") + r = urllib2.urlopen(url) + except Exception, e: + log.error("Unable to connect to SAB: %s" % e) return False result = r.read().strip() From e5bd1b9249a8698585041d60d5fa6ed09d0879a6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:30:45 +0200 Subject: [PATCH 04/17] Added basic log page --- couchpotato/core/plugins/log/__init__.py | 6 +++++ couchpotato/core/plugins/log/main.py | 30 ++++++++++++++++++++++++ couchpotato/static/scripts/page/log.js | 21 ++++++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 couchpotato/core/plugins/log/__init__.py create mode 100644 couchpotato/core/plugins/log/main.py diff --git a/couchpotato/core/plugins/log/__init__.py b/couchpotato/core/plugins/log/__init__.py new file mode 100644 index 00000000..33dcf338 --- /dev/null +++ b/couchpotato/core/plugins/log/__init__.py @@ -0,0 +1,6 @@ +from .main import Logging + +def start(): + return Logging() + +config = [] diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py new file mode 100644 index 00000000..a3da5551 --- /dev/null +++ b/couchpotato/core/plugins/log/main.py @@ -0,0 +1,30 @@ +from couchpotato.api import addApiView +from couchpotato.core.helpers.request import jsonified, getParam +from couchpotato.core.plugins.base import Plugin +from couchpotato.environment import Env + + +class Logging(Plugin): + + def __init__(self): + addApiView('logging.get', self.get) + + def get(self): + + nr = int(getParam('nr', 0)) + path = '%s%s' % (Env.get('log_path'), '.%s' % nr if nr > 0 else '') + + # Reverse + f = open(path, 'r') + lines = [] + for line in f.readlines(): + lines.insert(0, line) + + log = '' + for line in lines: + log += line + + return jsonified({ + 'success': True, + 'log': log, + }) diff --git a/couchpotato/static/scripts/page/log.js b/couchpotato/static/scripts/page/log.js index 8c9be331..bdb41e93 100644 --- a/couchpotato/static/scripts/page/log.js +++ b/couchpotato/static/scripts/page/log.js @@ -3,6 +3,25 @@ Page.Log = new Class({ Extends: PageBase, name: 'log', - title: 'Show recent logs.' + title: 'Show recent logs.', + + indexAction: function(){ + var self = this; + + if(self.log) self.log.destroy(); + self.log = new Element('div.log', { + 'text': 'loading...' + }).inject(self.el) + + Api.request('logging.get', { + 'data': { + 'nr': 0 + }, + 'onComplete': function(json){ + self.log.set('html', '
'+json.log+'
') + } + }) + + } }) \ No newline at end of file From b985d07ac2d2bc0f0236e63074f8daf33ad28f92 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:32:41 +0200 Subject: [PATCH 05/17] Save log_path for later use --- couchpotato/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/cli.py b/couchpotato/cli.py index 8341140e..fe265d13 100644 --- a/couchpotato/cli.py +++ b/couchpotato/cli.py @@ -48,6 +48,7 @@ def cmd_couchpotato(base_path, args): Env.get('settings').setFile(os.path.join(options.data_dir, 'settings.conf')) Env.set('app_dir', base_path) Env.set('data_dir', options.data_dir) + Env.set('log_path', os.path.join(log_dir, 'CouchPotato.log')) Env.set('db_path', 'sqlite:///' + os.path.join(options.data_dir, 'couchpotato.db')) Env.set('cache_dir', os.path.join(options.data_dir, 'cache')) Env.set('cache', FileSystemCache(os.path.join(Env.get('cache_dir'), 'python'))) @@ -73,7 +74,7 @@ def cmd_couchpotato(base_path, args): logger.addHandler(hdlr) # To file - hdlr2 = handlers.RotatingFileHandler(os.path.join(log_dir, 'CouchPotato.log'), 'a', 5000000, 4) + hdlr2 = handlers.RotatingFileHandler(Env.get('log_path'), 'a', 5000000, 4) hdlr2.setFormatter(formatter) logger.addHandler(hdlr2) From ecdfe424efae1f3f445962bec0b8f50198725e65 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:33:09 +0200 Subject: [PATCH 06/17] Return results only when result is not None --- couchpotato/core/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index 7765bb8b..231c17e1 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -44,7 +44,7 @@ def fireEvent(name, *args, **kwargs): if single and not merge: results = None - if result[0][0] == True and result[0][1]: + if result[0][0] is True and result[0][1] is not None: results = result[0][1] elif result[0][1]: errorHandler(result[0][1]) From a0b3e13d81f4e29140bbdd443b9a645db0a46519 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:34:18 +0200 Subject: [PATCH 07/17] Front end notification listener --- couchpotato/core/notifications/core/main.py | 14 ++++++++------ .../core/notifications/core/static/notification.js | 4 ++-- couchpotato/core/plugins/library/main.py | 1 + 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index d7d260ba..43bfce29 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -13,9 +13,9 @@ class CoreNotifier(Plugin): messages = [] def __init__(self): + addEvent('notify', self.notify) - addEvent('notify.core_notifier', self.notify) - addEvent('core_notifier.frontend', self.frontend) + addEvent('notify.core', self.frontend) addApiView('core_notifier.listener', self.listener) @@ -29,7 +29,6 @@ class CoreNotifier(Plugin): }) def frontend(self, type = 'notification', data = {}): - self.messages.append({ 'time': time.time(), 'type': type, @@ -38,12 +37,15 @@ class CoreNotifier(Plugin): def listener(self): + messages = [] for message in self.messages: + print message['time'], (time.time() - 5) #delete message older then 15s - if message['time'] < (time.time() - 15): - del message + if message['time'] > (time.time() - 15): + messages.append(message) + self.messages = [] return jsonified({ 'success': True, - 'result': self.messages, + 'result': messages, }) diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index caa2f227..891d7e8a 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -7,7 +7,7 @@ var NotificationBase = new Class({ var self = this; self.setOptions(options); - //App.addEvent('load', self.request.bind(self)); + App.addEvent('load', self.request.bind(self)); self.addEvent('notification', self.notify.bind(self)) @@ -33,7 +33,7 @@ var NotificationBase = new Class({ var self = this; Array.each(json.result, function(result){ - self.fireEvent(result.type, result.data) + App.fireEvent(result.type, result.data) }) } diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index 28b93334..a68f9c53 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -100,5 +100,6 @@ class LibraryPlugin(Plugin): #log.debug('Failed to attach to library: %s' % traceback.format_exc()) fireEvent('library.update.after') + fireEvent('notify.core', type = 'library.update', data = library_dict) return library.to_dict({'titles': {}, 'files':{}}) From 452fdef13634b740af8974ad6ee9891ef02ee8bd Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:34:44 +0200 Subject: [PATCH 08/17] Only update wanted if it's empty --- couchpotato/static/scripts/page/wanted.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 5cb9f393..eda8a672 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -8,11 +8,15 @@ Page.Wanted = new Class({ indexAction: function(param){ var self = this; - self.list = new MovieList({ - 'status': 'active', - 'actions': Wanted.Action - }); - $(self.list).inject(self.el); + if(!self.list){ + self.list = new MovieList({ + 'status': 'active', + 'actions': Wanted.Action + }); + $(self.list).inject(self.el); + + App.addEvent('library.update', self.list.update.bind(self.list)) + } } @@ -21,6 +25,7 @@ Page.Wanted = new Class({ var Wanted = { 'Action': { 'IMBD': IMDBAction + //,'releases': ReleaseAction } } From f1c9ed3ceab4d186acba3fdb8a9e77599d3e4bd4 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:37:19 +0200 Subject: [PATCH 09/17] More specific caching keys --- couchpotato/core/providers/nzb/newznab/main.py | 2 +- couchpotato/core/providers/nzb/nzbmatrix/main.py | 2 +- couchpotato/core/providers/nzb/nzbs/main.py | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/providers/nzb/newznab/main.py b/couchpotato/core/providers/nzb/newznab/main.py index dccd047c..6f86fac7 100644 --- a/couchpotato/core/providers/nzb/newznab/main.py +++ b/couchpotato/core/providers/nzb/newznab/main.py @@ -54,7 +54,7 @@ class Newznab(NZBProvider, RSS): }) url = "%s&%s" % (self.getUrl(self.urls['search']), arguments) - cache_key = '%s-%s' % (movie['library']['identifier'], cat_id[0]) + cache_key = 'newznab.%s.%s' % (movie['library']['identifier'], cat_id[0]) single_cat = (len(cat_id) == 1 and cat_id[0] != self.cat_backup_id) try: diff --git a/couchpotato/core/providers/nzb/nzbmatrix/main.py b/couchpotato/core/providers/nzb/nzbmatrix/main.py index ecabe220..952f4fc2 100644 --- a/couchpotato/core/providers/nzb/nzbmatrix/main.py +++ b/couchpotato/core/providers/nzb/nzbmatrix/main.py @@ -53,7 +53,7 @@ class NZBMatrix(NZBProvider, RSS): url = "%s?%s" % (self.urls['search'], arguments) log.info('Searching: %s' % url) - cache_key = '%s-%s' % (movie['library'].get('identifier'), cat_ids) + cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids) single_cat = True try: diff --git a/couchpotato/core/providers/nzb/nzbs/main.py b/couchpotato/core/providers/nzb/nzbs/main.py index 09227eb8..8ea5408d 100644 --- a/couchpotato/core/providers/nzb/nzbs/main.py +++ b/couchpotato/core/providers/nzb/nzbs/main.py @@ -50,7 +50,7 @@ class Nzbs(NZBProvider, RSS): }) url = "%s?%s" % (self.urls['api'], arguments) - cache_key = '%s-%s' % (movie['library'].get('identifier'), str(cat_id)) + cache_key = 'nzbs.%s.%s' % (movie['library'].get('identifier'), str(cat_id)) try: data = self.getCache(cache_key) @@ -72,8 +72,9 @@ class Nzbs(NZBProvider, RSS): for nzb in nzbs: + id = int(self.getTextElement(nzb, "link").partition('nzbid=')[2]) new = { - 'id': int(self.getTextElement(nzb, "link").partition('nzbid=')[2]), + 'id': id, 'type': 'nzb', 'name': self.getTextElement(nzb, "title"), 'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))), From f4c38c9b1de651e3dc4c7f55c535e52ad4535cec Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:37:49 +0200 Subject: [PATCH 10/17] Always return list with backup_id --- couchpotato/core/providers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/base.py b/couchpotato/core/providers/base.py index 8a1fe327..d7941af4 100644 --- a/couchpotato/core/providers/base.py +++ b/couchpotato/core/providers/base.py @@ -118,7 +118,7 @@ class YarrProvider(Provider): if identifier in qualities: return ids - return False + return [self.cat_backup_id] def found(self, new): log.info('Found: score(%(score)s): %(name)s' % new) From 0ea20a0e348989206edca027069bcd3e1def3be7 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:38:23 +0200 Subject: [PATCH 11/17] Remove 1080p as alternative in BD50 quality --- couchpotato/core/plugins/quality/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index b5377842..3ef0da6a 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -13,7 +13,7 @@ log = CPLog(__name__) class QualityPlugin(Plugin): qualities = [ - {'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['1080p', 'bd25'], 'allow': [], 'ext':[], 'tags': ['x264', 'h264', 'blu ray']}, + {'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['x264', 'h264', 'bluray']}, {'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']}, {'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']}, {'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']}, From 4213af85f3144efd0c8b64c5bc50f198292daed0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:39:41 +0200 Subject: [PATCH 12/17] Seperated release plugin --- couchpotato/core/plugins/release/__init__.py | 6 ++ couchpotato/core/plugins/release/main.py | 75 ++++++++++++++++++++ couchpotato/core/plugins/scanner/main.py | 67 ++--------------- couchpotato/core/plugins/searcher/main.py | 41 ++++++----- 4 files changed, 112 insertions(+), 77 deletions(-) create mode 100644 couchpotato/core/plugins/release/__init__.py create mode 100644 couchpotato/core/plugins/release/main.py diff --git a/couchpotato/core/plugins/release/__init__.py b/couchpotato/core/plugins/release/__init__.py new file mode 100644 index 00000000..b6a667c2 --- /dev/null +++ b/couchpotato/core/plugins/release/__init__.py @@ -0,0 +1,6 @@ +from .main import Release + +def start(): + return Release() + +config = [] diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py new file mode 100644 index 00000000..96f7a3d2 --- /dev/null +++ b/couchpotato/core/plugins/release/main.py @@ -0,0 +1,75 @@ +from couchpotato import get_session +from couchpotato.core.event import fireEvent, addEvent +from couchpotato.core.logger import CPLog +from couchpotato.core.plugins.base import Plugin +from couchpotato.core.settings.model import File, Release, Movie +from sqlalchemy.sql.expression import and_, or_ + +log = CPLog(__name__) + + +class Release(Plugin): + + def __init__(self): + addEvent('release.add', self.add) + + def add(self, group): + db = get_session() + + identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier']) + + # Add movie + done_status = fireEvent('status.get', 'done', single = True) + movie = db.query(Movie).filter_by(library_id = group['library'].get('id')).first() + if not movie: + movie = Movie( + library_id = group['library'].get('id'), + profile_id = 0, + status_id = done_status.get('id') + ) + db.add(movie) + db.commit() + + # Add release + snatched_status = fireEvent('status.get', 'snatched', single = True) + release = db.query(Release).filter( + or_( + Release.identifier == identifier, + and_(Release.identifier.startswith(group['library']['identifier'], Release.status_id == snatched_status.get('id'))) + ) + ).first() + if not release: + release = Release( + identifier = identifier, + movie = movie, + quality_id = group['meta_data']['quality'].get('id'), + status_id = done_status.get('id') + ) + db.add(release) + db.commit() + + # Add each file type + for type in group['files']: + for file in group['files'][type]: + added_file = self.saveFile(file, type = type, include_media_info = type is 'movie') + try: + added_file = db.query(File).filter_by(id = added_file.get('id')).one() + release.files.append(added_file) + db.commit() + except Exception, e: + log.debug('Failed to attach "%s" to release: %s' % (file, e)) + + db.remove() + + + def saveFile(self, file, type = 'unknown', include_media_info = False): + + properties = {} + + # Get media info for files + if include_media_info: + properties = {} + + # Check database and update/insert if necessary + return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True) + diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index caae1ce0..ec1fc67e 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -7,7 +7,7 @@ from couchpotato.core.plugins.base import Plugin from couchpotato.core.settings.model import File, Release, Movie from couchpotato.environment import Env from flask.helpers import json -from themoviedb.tmdb import opensubtitleHashFile +from sqlalchemy.sql.expression import and_, or_ import os import re import subprocess @@ -70,6 +70,7 @@ class Scanner(Plugin): def __init__(self): #addEvent('app.load', self.scanLibrary) + addEvent('scanner.create_file_identifier', self.createStringIdentifier) addEvent('scanner.scan', self.scan) @@ -95,7 +96,7 @@ class Scanner(Plugin): #library = db.query(Library).filter_by(id = library.get('id')).one() # Add release - self.addRelease(group) + fireEvent('release.add', group = group) # Add identifier for library update update_after.append(group['library'].get('identifier')) @@ -133,7 +134,7 @@ class Scanner(Plugin): is_dvd_file = self.isDVDFile(file_path) if os.path.getsize(file_path) > self.minimal_filesize['media'] or is_dvd_file: # Minimal 300MB files or is DVD file - identifier = self.createFileIdentifier(file_path, folder, exclude_filename = is_dvd_file) + identifier = self.createStringIdentifier(file_path, folder, exclude_filename = is_dvd_file) if not movie_files.get(identifier): movie_files[identifier] = { @@ -221,51 +222,6 @@ class Scanner(Plugin): return movie_files - - def addRelease(self, group): - db = get_session() - - identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier']) - - # Add movie - done_status = fireEvent('status.get', 'done', single = True) - movie = db.query(Movie).filter_by(library_id = group['library'].get('id')).first() - if not movie: - movie = Movie( - library_id = group['library'].get('id'), - profile_id = 0, - status_id = done_status.get('id') - ) - db.add(movie) - db.commit() - - # Add release - release = db.query(Release).filter_by(identifier = identifier).first() - if not release: - - release = Release( - identifier = identifier, - movie = movie, - quality_id = group['meta_data']['quality'].get('id'), - status_id = done_status.get('id') - ) - db.add(release) - db.commit() - - # Add each file type - for type in group['files']: - - for file in group['files'][type]: - added_file = self.saveFile(file, type = type, include_media_info = type is 'movie') - try: - added_file = db.query(File).filter_by(id = added_file.get('id')).one() - release.files.append(added_file) - db.commit() - except Exception, e: - log.debug('Failed to attach "%s" to release: %s' % (file, e)) - - db.remove() - def getMetaData(self, group): data = {} @@ -374,17 +330,6 @@ class Scanner(Plugin): log.error('No imdb_id found for %s.' % group['identifiers']) return {} - def saveFile(self, file, type = 'unknown', include_media_info = False): - - properties = {} - - # Get media info for files - if include_media_info: - properties = {} - - # Check database and update/insert if necessary - return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True) - def getCPImdb(self, string): try: @@ -501,9 +446,9 @@ class Scanner(Plugin): return False def getGroupFiles(self, identifier, folder, file_pile): - return set(filter(lambda s:identifier in self.createFileIdentifier(s, folder), file_pile)) + return set(filter(lambda s:identifier in self.createStringIdentifier(s, folder), file_pile)) - def createFileIdentifier(self, file_path, folder, exclude_filename = False): + def createStringIdentifier(self, file_path, folder = '', exclude_filename = False): identifier = file_path.replace(folder, '') # root folder identifier = os.path.splitext(identifier)[0] # ext diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index d2d8372b..0f6ae146 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -3,7 +3,7 @@ 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.core.settings.model import Movie, Release from couchpotato.environment import Env import re @@ -19,7 +19,7 @@ class Searcher(Plugin): # Schedule cronjob fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) - #addEvent('app.load', self.all) + addEvent('app.load', self.all) def all(self): @@ -29,47 +29,56 @@ class Searcher(Plugin): Movie.status.has(identifier = 'active') ).all() - snatched_status = fireEvent('status.get', 'snatched', single = True) - for movie in movies: - success = self.single(movie.to_dict(deep = { + self.single(movie.to_dict(deep = { 'profile': {'types': {'quality': {}}}, 'releases': {'status': {}, 'quality': {}}, 'library': {'titles': {}, 'files':{}}, 'files': {} })) - # Mark as snatched on success - if success: - movie.status_id = snatched_status.get('id') - db.commit() - - def single(self, movie): successful = False for type in movie['profile']['types']: - has_better_quality = False + has_better_quality = 0 + default_title = movie['library']['titles'][0]['title'] # See if beter quality is available for release in movie['releases']: if release['quality']['order'] <= type['quality']['order']: - has_better_quality = True + has_better_quality += 1 # Don't search for quality lower then already available. - if not has_better_quality: + if has_better_quality is 0: - log.info('Search for %s in %s' % (movie['library']['titles'][0]['title'], type['quality']['label'])) + log.info('Search for %s in %s' % (default_title, type['quality']['label'])) 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: - successful = fireEvent('download', data = nzb, single = True) + successful = fireEvent('download', data = nzb, movie = movie, single = True) if successful: log.info('Downloading of %s successful.' % nzb.get('name')) + + # Add release item, should be updated later when renaming + snatched_status = fireEvent('status.get', 'snatched', single = True) + db = get_session() + rls = Release( + identifier = '%s.%s' % (movie['library']['identifier'], type['quality']['identifier']), + movie_id = movie.get('id'), + quality_id = type.get('quality_id'), + status_id = snatched_status.get('id') + ) + db.add(rls) + db.commit() + return True + else: + log.info('Better quality (%s) already available or snatched for %s' % (type['quality']['label'], default_title)) + break return False From 0fb39a439d671d5037261aafb2693f5e4950c1b8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:40:24 +0200 Subject: [PATCH 13/17] Get library dict so it can be used in event --- couchpotato/core/plugins/library/main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index a68f9c53..309cf723 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -96,10 +96,10 @@ class LibraryPlugin(Plugin): library.files.append(file) db.commit() except: - pass - #log.debug('Failed to attach to library: %s' % traceback.format_exc()) + log.debug('Failed to attach to library: %s' % traceback.format_exc()) + + library_dict = library.to_dict({'titles': {}, 'files':{}}) - fireEvent('library.update.after') fireEvent('notify.core', type = 'library.update', data = library_dict) - return library.to_dict({'titles': {}, 'files':{}}) + return library_dict From d1d0a9cf2653d076eb3aebd75b7bfc6adad4b81f Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:41:25 +0200 Subject: [PATCH 14/17] Movie list, navigate by letters --- couchpotato/core/plugins/movie/static/list.js | 51 +++++++++++-------- .../core/plugins/movie/static/movie.css | 4 ++ .../core/plugins/movie/static/movie.js | 48 +++++++++++++++-- 3 files changed, 80 insertions(+), 23 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index 2facc391..73581e1f 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -7,6 +7,7 @@ var MovieList = new Class({ }, movies: [], + letters: {}, initialize: function(options){ var self = this; @@ -19,9 +20,10 @@ var MovieList = new Class({ create: function(){ var self = this; + self.el.empty(); + // Create the alphabet nav - if(self.options.navigation) - self.createNavigation(); + self.createNavigation(); Object.each(self.movies, function(info){ var m = new Movie(self, { @@ -29,6 +31,9 @@ var MovieList = new Class({ }, info); $(m).inject(self.el); m.fireEvent('injected'); + + var first_char = m.getTitle().substr(0, 1); + self.activateLetter(first_char); }); self.el.addEvents({ @@ -44,7 +49,6 @@ var MovieList = new Class({ createNavigation: function(){ var self = this; var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - var selected = 'Z'; self.navigation = new Element('div.alph_nav').adopt( self.alpha = new Element('ul.inlay'), @@ -54,32 +58,39 @@ var MovieList = new Class({ new Element('li.thumbnails'), new Element('li.text') ) - ).inject(this.el, 'top') + ).inject(this.el, 'top'); chars.split('').each(function(c){ - new Element('li', { + self.letters[c] = new Element('li', { 'text': c, - 'class': c == selected ? 'selected' : '' + 'class': 'letter_'+c }).inject(self.alpha) - }) + }); }, + + activateLetter: function(letter){ + this.letters[letter].addClass('active') + }, - getMovies: function(status, onComplete){ + update: function(){ + var self = this; + + self.getMovies(); + }, + + getMovies: function(){ var self = this - if(self.movies.length == 0) - Api.request('movie.list', { - 'data': { - 'status': self.options.status - }, - 'onComplete': function(json){ - self.store(json.movies); - self.create(); - } - }) - else - self.list() + Api.request('movie.list', { + 'data': { + 'status': self.options.status + }, + 'onComplete': function(json){ + self.store(json.movies); + self.create(); + } + }) }, store: function(movies){ diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index 70819a75..d7088690 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -159,7 +159,11 @@ text-align: center; cursor: pointer; margin: 0 -1px 0 0; + color: #666; } + .movies .alph_nav li.active { + color: #fff; + } .movies .alph_nav li:hover, .movies .alph_nav li.onlay { font-weight: bold; diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index cabe5a43..a2adc831 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -127,7 +127,8 @@ var MovieAction = new Class({ self.movie = movie; self.create(); - self.el.addClass(self.class_name) + if(self.el) + self.el.addClass(self.class_name) }, create: function(){}, @@ -141,7 +142,7 @@ var MovieAction = new Class({ }, toElement: function(){ - return this.el + return this.el || null } }); @@ -173,4 +174,45 @@ var IMDBAction = new Class({ window.open('http://www.imdb.com/title/'+self.id+'/'); } -}) \ No newline at end of file +}); + +var ReleaseAction = new Class({ + + Extends: MovieAction, + id: null, + + create: function(){ + var self = this; + + self.id = self.movie.get('identifier'); + + self.el = new Element('a.releases', { + 'title': 'Show the releases that are available for ' + self.movie.getTitle(), + 'events': { + 'click': self.show.bind(self) + } + }); + + }, + + show: function(e){ + var self = this; + (e).stop(); + + if(!self.options_container){ + self.options_container = new Element('div.options').adopt( + $(self.movie.thumbnail).clone(), + self.release_container = new Element('div.releases') + ).inject(self.movie, 'top'); + + Array.each(self.movie.data.releases, function(release){ + new Element('div', { + 'text': release.title + }).inject(self.release_container) + }); + + } + self.movie.slide('in'); + }, + +}); \ No newline at end of file From a76160397cc096072aa0358bda559a01c8828c9c Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:42:32 +0200 Subject: [PATCH 15/17] Renamer now really renames files --- couchpotato/core/plugins/renamer/main.py | 51 ++++++++++++++---------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 45f456aa..83b04930 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -18,13 +18,14 @@ class Renamer(Plugin): def __init__(self): addEvent('renamer.scan', self.scan) - #addEvent('app.load', self.scan) + addEvent('app.load', self.scan) - #fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every')) + fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every')) def scan(self): groups = fireEvent('scanner.scan', folder = self.conf('from'), single = True) + if groups is None: return destination = self.conf('to') folder_name = self.conf('folder_name') @@ -151,42 +152,52 @@ class Renamer(Plugin): # Before renaming, remove the lower quality files db = get_session() library = db.query(Library).filter_by(identifier = group['library']['identifier']).first() + done_status = fireEvent('status.get', 'done', single = True) for movie in library.movies: for release in movie.releases: if release.quality.order < group['meta_data']['quality']['order']: log.info('Removing older release for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label)) - elif release.quality.order is group['meta_data']['quality']['order']: - log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label)) - else: - log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label)) + elif release.status_id is done_status.get('id'): + if release.quality.order is group['meta_data']['quality']['order']: + log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label)) + else: + log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label)) - # Add _EXISTS_ to the parent dir - if group['dirname']: - for rename_me in rename_files: # Don't rename anything in this group - rename_files[rename_me] = None - rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_EXISTS_%s' % group['dirname']) - else: # Add it to filename - for rename_me in rename_files: - filename = os.path.basename(rename_me) - rename_files[rename_me] = rename_me.replace(filename, '_EXISTS_%s' % filename) + # Add _EXISTS_ to the parent dir + if group['dirname']: + for rename_me in rename_files: # Don't rename anything in this group + rename_files[rename_me] = None + rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_EXISTS_%s' % group['dirname']) + else: # Add it to filename + for rename_me in rename_files: + filename = os.path.basename(rename_me) + rename_files[rename_me] = rename_me.replace(filename, '_EXISTS_%s' % filename) - break + break for file in release.files: log.info('Removing "%s"' % file.path) # Rename - for rename_me in rename_files: - if rename_files[rename_me]: - log.info('Renaming "%s" to "%s"' % (rename_me, rename_files[rename_me])) + for src in rename_files: + if rename_files[src]: - path = os.path.dirname(rename_files[rename_me]) + dst = rename_files[src] + + log.info('Renaming "%s" to "%s"' % (src, dst)) + + path = os.path.dirname(dst) try: if not os.path.isdir(path): os.makedirs(path) except: log.error('Failed creating dir %s: %s' % (path, traceback.format_exc())) continue + try: + shutil.move(src, dst) + except: + log.error('Failed moving the file "%s" : %s' % (os.path.basename(src), traceback.format_exc())) + #print rename_me, rename_files[rename_me] # Search for trailers From e14591783ab140324899013d58bed77fd8eabbcb Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 4 Jun 2011 21:44:10 +0200 Subject: [PATCH 16/17] Improved scoring accuracy --- couchpotato/core/plugins/score/scores.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/couchpotato/core/plugins/score/scores.py b/couchpotato/core/plugins/score/scores.py index 6f389820..eb1e8f2c 100644 --- a/couchpotato/core/plugins/score/scores.py +++ b/couchpotato/core/plugins/score/scores.py @@ -1,14 +1,16 @@ +from couchpotato.core.event import fireEvent from couchpotato.core.helpers.encoding import simplifyString from couchpotato.environment import Env import re name_scores = [ - 'proper:2', 'repack:2', + 'proper:5', 'repack:5', 'unrated:1', - 'x264:1', + 'x264:1', 'h264: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', + '720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1', 'bd50:1', 'bd25:1', + 'imbt:1', 'cocain:1', 'vomit:1', 'fico:1', 'arrow:1', 'pukka:1', 'prism:1', 'devise:1', 'esir:1', + 'metis:1', 'diamond:1', 'wiki:1', 'cbgb:1', 'crossbow:1', 'sinners:1', 'amiable:1', 'refined:1', 'twizted:1', 'felony:1', 'hubris:1', 'machd:1', 'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10' ] @@ -40,12 +42,8 @@ def nameScore(name, year): def nameRatioScore(nzb_name, movie_name): - nzb_words = re.split('\W+', simplifyString(nzb_name)) + nzb_words = re.split('\W+', fireEvent('scanner.create_file_identifier', nzb_name, single = True)) 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 + left_over = set(nzb_words) - set(movie_words) + return 10 - len(left_over) From aa24e3e249cc29866d0cc2b0357fb509f100766a Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 2 Aug 2011 12:37:25 +0200 Subject: [PATCH 17/17] Starting simple, removed non working pages for now --- couchpotato/cli.py | 2 +- couchpotato/core/plugins/movie/main.py | 6 +- couchpotato/core/plugins/movie/static/list.js | 23 +- .../core/plugins/movie/static/movie.js | 1 + couchpotato/core/plugins/searcher/main.py | 54 ++- couchpotato/environment.py | 1 + couchpotato/static/scripts/page/wanted.js | 407 +++++++++--------- couchpotato/templates/_desktop.html | 6 +- 8 files changed, 275 insertions(+), 225 deletions(-) diff --git a/couchpotato/cli.py b/couchpotato/cli.py index fe265d13..575f0972 100644 --- a/couchpotato/cli.py +++ b/couchpotato/cli.py @@ -74,7 +74,7 @@ def cmd_couchpotato(base_path, args): logger.addHandler(hdlr) # To file - hdlr2 = handlers.RotatingFileHandler(Env.get('log_path'), 'a', 5000000, 4) + hdlr2 = handlers.RotatingFileHandler(Env.get('log_path'), 'a', 500000, 10) hdlr2.setFormatter(formatter) logger.addHandler(hdlr2) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 646c5843..3ed6216c 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -38,7 +38,7 @@ class MoviePlugin(Plugin): movies = [] for movie in results: temp = movie.to_dict(deep = { - 'releases': {'status': {}, 'quality': {}, 'files':{}}, + 'releases': {'status': {}, 'quality': {}, 'files':{}, 'info': {}}, 'library': {'titles': {}, 'files':{}}, 'files': {} }) @@ -68,7 +68,7 @@ class MoviePlugin(Plugin): fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True) fireEventAsync('searcher.single', movie.to_dict(deep = { 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, + 'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}}, 'library': {'titles': {}, 'files':{}}, 'files': {} })) @@ -119,7 +119,7 @@ class MoviePlugin(Plugin): db.commit() movie_dict = m.to_dict(deep = { - 'releases': {'status': {}, 'quality': {}}, + 'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}}, 'library': {'titles': {}} }) diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index 73581e1f..94ced765 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -3,7 +3,7 @@ var MovieList = new Class({ Implements: [Options], options: { - navigation: true + navigation: false }, movies: [], @@ -23,7 +23,8 @@ var MovieList = new Class({ self.el.empty(); // Create the alphabet nav - self.createNavigation(); + if(self.options.navigation) + self.createNavigation(); Object.each(self.movies, function(info){ var m = new Movie(self, { @@ -32,16 +33,18 @@ var MovieList = new Class({ $(m).inject(self.el); m.fireEvent('injected'); - var first_char = m.getTitle().substr(0, 1); - self.activateLetter(first_char); + if(self.options.navigation){ + var first_char = m.getTitle().substr(0, 1); + self.activateLetter(first_char); + } }); self.el.addEvents({ 'mouseenter:relay(.movie)': function(e, el){ - el.addClass('hover') + el.addClass('hover'); }, 'mouseleave:relay(.movie)': function(e, el){ - el.removeClass('hover') + el.removeClass('hover'); } }); }, @@ -64,13 +67,13 @@ var MovieList = new Class({ self.letters[c] = new Element('li', { 'text': c, 'class': 'letter_'+c - }).inject(self.alpha) + }).inject(self.alpha); }); }, activateLetter: function(letter){ - this.letters[letter].addClass('active') + this.letters[letter].addClass('active'); }, update: function(){ @@ -80,7 +83,7 @@ var MovieList = new Class({ }, getMovies: function(){ - var self = this + var self = this; Api.request('movie.list', { 'data': { @@ -90,7 +93,7 @@ var MovieList = new Class({ self.store(json.movies); self.create(); } - }) + }); }, store: function(movies){ diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index a2adc831..02982e39 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -206,6 +206,7 @@ var ReleaseAction = new Class({ ).inject(self.movie, 'top'); Array.each(self.movie.data.releases, function(release){ + p(release); new Element('div', { 'text': release.title }).inject(self.release_container) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 0f6ae146..64e6daf0 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -1,9 +1,10 @@ from couchpotato import get_session from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import simplifyString +from couchpotato.core.helpers.variable import md5 from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Movie, Release +from couchpotato.core.settings.model import Movie, Release, ReleaseInfo from couchpotato.environment import Env import re @@ -39,15 +40,20 @@ class Searcher(Plugin): def single(self, movie): + downloaded_status = fireEvent('status.get', 'downloaded', single = True) + available_status = fireEvent('status.get', 'available', single = True) + snatched_status = fireEvent('status.get', 'snatched', single = True) + successful = False for type in movie['profile']['types']: + print type has_better_quality = 0 default_title = movie['library']['titles'][0]['title'] # See if beter quality is available for release in movie['releases']: - if release['quality']['order'] <= type['quality']['order']: + if release['quality']['order'] <= type['quality']['order'] and release['status_id'] is not available_status.get('id'): has_better_quality += 1 # Don't search for quality lower then already available. @@ -57,25 +63,51 @@ class Searcher(Plugin): results = fireEvent('provider.yarr.search', movie, type['quality'], merge = True) sorted_results = sorted(results, key = lambda k: k['score'], reverse = True) + # Add them to this movie releases list + for nzb in sorted_results: + db = get_session() + + rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first() + if not rls: + rls = Release( + identifier = md5(nzb['url']), + movie_id = movie.get('id'), + quality_id = type.get('quality_id'), + status_id = available_status.get('id') + ) + db.add(rls) + db.commit() + + for info in nzb: + rls_info = ReleaseInfo( + identifier = info, + value = nzb[info] + ) + rls.info.append(rls_info) + db.commit() + + for nzb in sorted_results: successful = fireEvent('download', data = nzb, movie = movie, single = True) if successful: log.info('Downloading of %s successful.' % nzb.get('name')) - # Add release item, should be updated later when renaming - snatched_status = fireEvent('status.get', 'snatched', single = True) + # Mark release as snatched db = get_session() - rls = Release( - identifier = '%s.%s' % (movie['library']['identifier'], type['quality']['identifier']), - movie_id = movie.get('id'), - quality_id = type.get('quality_id'), - status_id = snatched_status.get('id') - ) - db.add(rls) + rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first() + rls.status_id = snatched_status.get('id') db.commit() + # Mark movie snatched if quality is finish-checked + if type['finish']: + mvie = db.query(Movie).filter_by(id = movie['id']).first() + mvie.status_id = snatched_status.get('id') + db.commit() + return True + + return False else: log.info('Better quality (%s) already available or snatched for %s' % (type['quality']['label'], default_title)) break diff --git a/couchpotato/environment.py b/couchpotato/environment.py index 067ec4be..cb789a34 100644 --- a/couchpotato/environment.py +++ b/couchpotato/environment.py @@ -19,6 +19,7 @@ class Env: _data_dir = "" _cache_dir = "" _db_path = "" + _log_path = "" @staticmethod def doDebug(): diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index eda8a672..3ba0f2bf 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -9,226 +9,239 @@ Page.Wanted = new Class({ var self = this; if(!self.list){ - self.list = new MovieList({ + + // Wanted movies + self.wanted = new MovieList({ 'status': 'active', - 'actions': Wanted.Action + 'actions': WantedActions }); - $(self.list).inject(self.el); - - App.addEvent('library.update', self.list.update.bind(self.list)) + $(self.wanted).inject(self.el); + App.addEvent('library.update', self.wanted.update.bind(self.wanted)); + + // Snatched movies + self.snatched = new MovieList({ + 'status': 'snatched', + 'actions': SnatchedActions + }); + $(self.snatched).inject(self.el); + App.addEvent('library.update', self.snatched.update.bind(self.snatched)); } } }); -var Wanted = { - 'Action': { - 'IMBD': IMDBAction - //,'releases': ReleaseAction - } -} +var WantedActions = { + 'IMBD': IMDBAction + //,'releases': ReleaseAction -Wanted.Action.Edit = new Class({ + ,'Edit': new Class({ - Extends: MovieAction, - - create: function(){ - var self = this; - - self.el = new Element('a.edit', { - 'title': 'Refresh the movie info and do a forced search', - 'events': { - 'click': self.editMovie.bind(self) + Extends: MovieAction, + + create: function(){ + var self = this; + + self.el = new Element('a.edit', { + 'title': 'Refresh the movie info and do a forced search', + 'events': { + 'click': self.editMovie.bind(self) + } + }); + + }, + + editMovie: function(e){ + var self = this; + (e).stop(); + + if(!self.options_container){ + self.options_container = new Element('div.options').adopt( + $(self.movie.thumbnail).clone(), + new Element('div.form', { + 'styles': { + 'line-height': self.movie.getHeight() + } + }).adopt( + self.title_select = new Element('select', { + 'name': 'title' + }), + self.profile_select = new Element('select', { + 'name': 'profile' + }), + new Element('a.button.edit', { + 'text': 'Save', + 'events': { + 'click': self.save.bind(self) + } + }) + ) + ).inject(self.movie, 'top'); + + Array.each(self.movie.data.library.titles, function(alt){ + new Element('option', { + 'text': alt.title + }).inject(self.title_select); + }); + + Object.each(Quality.profiles, function(profile){ + new Element('option', { + 'value': profile.id ? profile.id : profile.data.id, + 'text': profile.label ? profile.label : profile.data.label + }).inject(self.profile_select); + self.profile_select.set('value', self.movie.profile.get('id')); + }); + } - }); + self.movie.slide('in'); + }, + + save: function(e){ + (e).stop(); + var self = this; + + Api.request('movie.edit', { + 'data': { + 'id': self.movie.get('id'), + 'default_title': self.title_select.get('value'), + 'profile_id': self.profile_select.get('value') + }, + 'useSpinner': true, + 'spinnerTarget': $(self.movie), + 'onComplete': function(){ + self.movie.quality.set('text', self.profile_select.getSelected()[0].get('text')); + self.movie.title.set('text', self.title_select.getSelected()[0].get('text')); + } + }); + + self.movie.slide('out'); + } + + }) - }, + ,'Refresh': new Class({ - editMovie: function(e){ - var self = this; - (e).stop(); + Extends: MovieAction, + + create: function(){ + var self = this; + + self.el = new Element('a.refresh', { + 'title': 'Refresh the movie info and do a forced search', + 'events': { + 'click': self.doSearch.bind(self) + } + }); + + }, + + doSearch: function(e){ + var self = this; + (e).stop(); + + Api.request('movie.refresh', { + 'data': { + 'id': self.movie.get('id') + } + }); + } + + }) - if(!self.options_container){ - self.options_container = new Element('div.options').adopt( - $(self.movie.thumbnail).clone(), - new Element('div.form', { + ,'Delete': new Class({ + + Extends: MovieAction, + + Implements: [Chain], + + create: function(){ + var self = this; + + self.el = new Element('a.delete', { + 'title': 'Remove the movie from your wanted list', + 'events': { + 'click': self.showConfirm.bind(self) + } + }); + + }, + + showConfirm: function(e){ + var self = this; + (e).stop(); + + if(!self.delete_container){ + self.delete_container = new Element('div.delete_container', { 'styles': { 'line-height': self.movie.getHeight() } }).adopt( - self.title_select = new Element('select', { - 'name': 'title' - }), - self.profile_select = new Element('select', { - 'name': 'profile' - }), - new Element('a.button.edit', { - 'text': 'Save', + new Element('a.cancel', { + 'text': 'Cancel', 'events': { - 'click': self.save.bind(self) + 'click': self.hideConfirm.bind(self) + } + }), + new Element('span.or', { + 'text': 'or' + }), + new Element('a.button.delete', { + 'text': 'Delete ' + self.movie.title.get('text'), + 'events': { + 'click': self.del.bind(self) } }) - ) - ).inject(self.movie, 'top'); - - Array.each(self.movie.data.library.titles, function(alt){ - new Element('option', { - 'text': alt.title - }).inject(self.title_select) - }); - - Object.each(Quality.profiles, function(profile){ - new Element('option', { - 'value': profile.id ? profile.id : profile.data.id, - 'text': profile.label ? profile.label : profile.data.label - }).inject(self.profile_select); - self.profile_select.set('value', self.movie.profile.get('id')); - }); - - } - self.movie.slide('in'); - }, - - save: function(e){ - (e).stop(); - var self = this; - - Api.request('movie.edit', { - 'data': { - 'id': self.movie.get('id'), - 'default_title': self.title_select.get('value'), - 'profile_id': self.profile_select.get('value') - }, - 'useSpinner': true, - 'spinnerTarget': $(self.movie), - 'onComplete': function(){ - self.movie.quality.set('text', self.profile_select.getSelected()[0].get('text')) - self.movie.title.set('text', self.title_select.getSelected()[0].get('text')) + ).inject(self.movie, 'top'); } - }); - - self.movie.slide('out'); - } - -}) - -Wanted.Action.Refresh = new Class({ - - Extends: MovieAction, - - create: function(){ - var self = this; - - self.el = new Element('a.refresh', { - 'title': 'Refresh the movie info and do a forced search', - 'events': { - 'click': self.doSearch.bind(self) - } - }); - - }, - - doSearch: function(e){ - var self = this; - (e).stop(); - - Api.request('movie.refresh', { - 'data': { - 'id': self.movie.get('id') - } - }) - } - -}) - -Wanted.Action.Delete = new Class({ - - Extends: MovieAction, - - Implements: [Chain], - - create: function(){ - var self = this; - - self.el = new Element('a.delete', { - 'title': 'Remove the movie from your wanted list', - 'events': { - 'click': self.showConfirm.bind(self) - } - }); - - }, - - showConfirm: function(e){ - var self = this; - (e).stop(); - - if(!self.delete_container){ - self.delete_container = new Element('div.delete_container', { - 'styles': { - 'line-height': self.movie.getHeight() + + self.movie.slide('in'); + + }, + + hideConfirm: function(e){ + var self = this; + (e).stop(); + + self.movie.slide('out'); + }, + + del: function(e){ + (e).stop(); + var self = this; + + var movie = $(self.movie); + + self.chain( + function(){ + $(movie).mask().addClass('loading'); + self.callChain(); + }, + function(){ + Api.request('movie.delete', { + 'data': { + 'id': self.movie.get('id') + }, + 'onComplete': function(){ + movie.set('tween', { + 'onComplete': function(){ + movie.destroy(); + } + }); + movie.tween('height', 0); + } + }); } - }).adopt( - new Element('a.cancel', { - 'text': 'Cancel', - 'events': { - 'click': self.hideConfirm.bind(self) - } - }), - new Element('span.or', { - 'text': 'or' - }), - new Element('a.button.delete', { - 'text': 'Delete ' + self.movie.title.get('text'), - 'events': { - 'click': self.del.bind(self) - } - }) - ).inject(self.movie, 'top') + ); + + self.callChain(); + } - self.movie.slide('in'); + }) +}; - }, - - hideConfirm: function(e){ - var self = this; - (e).stop(); - - self.movie.slide('out'); - }, - - del: function(e){ - (e).stop() - var self = this; - - var movie = $(self.movie); - - self.chain( - function(){ - $(movie).mask().addClass('loading') - self.callChain(); - }, - function(){ - Api.request('movie.delete', { - 'data': { - 'id': self.movie.get('id') - }, - 'onComplete': function(){ - movie.set('tween', { - 'onComplete': function(){ - movie.destroy(); - } - }) - movie.tween('height', 0) - } - }) - } - ); - - self.callChain(); - - } - -}) \ No newline at end of file +var SnatchedActions = { + 'IMBD': IMDBAction + ,'Releases': ReleaseAction + ,'Delete': WantedActions.Delete +}; \ No newline at end of file diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index 454a3aff..fe9b0c9a 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -31,10 +31,10 @@ - - - + +