From 64c044acc1789ce1a7f48f0c84a5f9e294e6eba2 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sun, 15 Jul 2012 19:09:48 +0200 Subject: [PATCH 01/73] Shutdown the logger before restarting CPS. Fixes issue #587 --- CouchPotato.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CouchPotato.py b/CouchPotato.py index 21b208a..b1620c0 100755 --- a/CouchPotato.py +++ b/CouchPotato.py @@ -9,7 +9,7 @@ import socket import subprocess import sys import traceback - +import time # Root path base_path = dirname(os.path.abspath(__file__)) @@ -96,6 +96,10 @@ class Loader(object): except: self.log.critical(traceback.format_exc()) + # Release log files and shutdown logger + logging.shutdown() + time.sleep(3) + args = [sys.executable] + [os.path.join(base_path, __file__)] + sys.argv[1:] subprocess.Popen(args) except: From 108044948fef08006d43acdabd1fb2d3e0a816a3 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Tue, 24 Jul 2012 22:49:49 +0200 Subject: [PATCH 02/73] Fix XBMC and NMA Notification tests --- couchpotato/core/notifications/notifymyandroid/main.py | 5 ++++- couchpotato/core/notifications/xbmc/main.py | 14 +++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/notifications/notifymyandroid/main.py b/couchpotato/core/notifications/notifymyandroid/main.py index a6a316d..52e2141 100644 --- a/couchpotato/core/notifications/notifymyandroid/main.py +++ b/couchpotato/core/notifications/notifymyandroid/main.py @@ -21,8 +21,11 @@ class NotifyMyAndroid(Notification): response = nma.push(self.default_title, self.event , message, self.conf('priority'), batch_mode = len(keys) > 1) + successful = 0 for key in keys: if not response[str(key)]['code'] == u'200': log.error('Could not send notification to NotifyMyAndroid (%s). %s', (key, response[key]['message'])) + else: + successful += 1 - return response + return successful == len(keys) \ No newline at end of file diff --git a/couchpotato/core/notifications/xbmc/main.py b/couchpotato/core/notifications/xbmc/main.py index 4f6e62a..5f16bdb 100644 --- a/couchpotato/core/notifications/xbmc/main.py +++ b/couchpotato/core/notifications/xbmc/main.py @@ -13,11 +13,15 @@ class XBMC(Notification): def notify(self, message = '', data = {}, listener = None): if self.isDisabled(): return - for host in [x.strip() for x in self.conf('host').split(",")]: - self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host) - self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host) - - return True + hosts = [x.strip() for x in self.conf('host').split(",")] + successful = 0 + for host in hosts: + if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host): + success += 1 + if self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host): + success += 1 + + return successful == len(hosts)*2 def send(self, command, host): From 80929b5806d498241e404cd352aecfc53e64bfe4 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Tue, 24 Jul 2012 22:55:53 +0200 Subject: [PATCH 03/73] small fix to xbmc notification test --- couchpotato/core/notifications/xbmc/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/notifications/xbmc/main.py b/couchpotato/core/notifications/xbmc/main.py index 5f16bdb..1c83bdd 100644 --- a/couchpotato/core/notifications/xbmc/main.py +++ b/couchpotato/core/notifications/xbmc/main.py @@ -17,9 +17,9 @@ class XBMC(Notification): successful = 0 for host in hosts: if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host): - success += 1 + successful += 1 if self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host): - success += 1 + successful += 1 return successful == len(hosts)*2 From 2406e9ef8554532849b48a9b8644fce243e4f13d Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Tue, 24 Jul 2012 23:27:26 +0200 Subject: [PATCH 04/73] Fixed NMJ test --- couchpotato/core/notifications/nmj/main.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/notifications/nmj/main.py b/couchpotato/core/notifications/nmj/main.py index 8f0e201..1a794fb 100644 --- a/couchpotato/core/notifications/nmj/main.py +++ b/couchpotato/core/notifications/nmj/main.py @@ -19,7 +19,7 @@ class NMJ(Notification): def __init__(self): addEvent('renamer.after', self.addToLibrary) - + addApiView(self.testNotifyName(), self.test) addApiView('notify.nmj.auto_config', self.autoConfig) def autoConfig(self): @@ -76,7 +76,7 @@ class NMJ(Notification): mount = self.conf('mount') database = self.conf('database') - if self.mount: + if mount: log.debug('Try to mount network drive via url: %s', (mount)) try: data = self.urlopen(mount) @@ -114,3 +114,8 @@ class NMJ(Notification): def failed(self): return jsonified({'success': False}) + + def test(self): + return jsonified({'success': self.addToLibrary()}) + + From 46c73dc43cdce4a4b474b26661bc83ee090fdaec Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Wed, 25 Jul 2012 00:09:13 +0200 Subject: [PATCH 05/73] Fix hint being added to the notification test button texts --- couchpotato/core/notifications/core/static/notification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 595a39e..db9db84 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -216,7 +216,7 @@ var NotificationBase = new Class({ }, testButtonName: function(fieldset){ - var name = fieldset.getElement('h2').get('text'); + var name = String(fieldset.getElement('h2').innerHTML).substring(0,String(fieldset.getElement('h2').innerHTML).indexOf(" Date: Sun, 29 Jul 2012 18:56:05 +0200 Subject: [PATCH 06/73] Added failed download handling for sabnzbd. It will check for failed downloads from the sabnzbd api and then set the failed release to ignore and try the search again. --- couchpotato/core/downloaders/base.py | 5 ++- couchpotato/core/downloaders/sabnzbd/main.py | 43 ++++++++++++++++++++++++++ couchpotato/core/plugins/renamer/main.py | 46 +++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 74e04de..e0dad91 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -7,17 +7,20 @@ import os log = CPLog(__name__) - class Downloader(Plugin): type = [] def __init__(self): addEvent('download', self.download) + addEvent('getdownloadfailed', self.getdownloadfailed) def download(self, data = {}): pass + def getdownloadfailed(self, data = {}): + pass + def createNzbName(self, data, movie): return '%s%s' % (toSafeString(data.get('name')), self.cpTag(movie)) diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 64b2450..8983c1a 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -3,6 +3,8 @@ from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog import traceback +import urllib2 +import json log = CPLog(__name__) @@ -61,3 +63,44 @@ class Sabnzbd(Downloader): else: log.error("Unknown error: " + result[:40]) return False + + def getdownloadfailed(self, data = {}, movie = {}, manual = False): + if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): + return + + log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) + + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'ouput': 'json' + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + log.debug('Opening: %s', url) + history = json.load(urllib2.urlopen(url)) + + nzbname = self.createNzbName(data, movie) + + # Go through history items + for slot in history['history']['slots']: + log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) + if slot['name'] == nzbname and slot['status'] == 'Failed': + log.debug('%s failed downloading, deleting...', slot['name']) + + # Delete failed download + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'name': 'delete', + 'value' : slot['id'] + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + try: + data = self.urlopen(url, timeout = 60, show_error = False) + except: + log.error(traceback.format_exc()) + + # Return failed + return True + + return False diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 4c6e6b8..c8a7694 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -6,7 +6,7 @@ from couchpotato.core.helpers.request import jsonified from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Library, File, Profile +from couchpotato.core.settings.model import Library, File, Profile, Release as Relea from couchpotato.environment import Env import os import re @@ -48,6 +48,8 @@ class Renamer(Plugin): log.info('Renamer is disabled to avoid infinite looping of the same error.') return + self.checkSnatchedStatusses() + # Check to see if the "to" folder is inside the "from" folder. if not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')): log.debug('"To" and "From" have to exist.') @@ -471,3 +473,45 @@ class Renamer(Plugin): os.rmdir(folder) except: log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) + + def checkSnatchedStatusses(self): + snatched_status = fireEvent('status.get', 'snatched', single = True) + ignored_status = fireEvent('status.get', 'ignored', single = True) + + db = get_session() + rels = db.query(Relea).filter_by(status_id = snatched_status.get('id')) + + log.debug('Checking snatched releases... %s', 'ops') + + for rel in rels: + log.debug('Checking snatched release: %s' , rel.movie.library.titles[0].title) + item = {} + for info in rel.info: + item[info.identifier] = info.value + + log.debug('Checking status snatched release: %s' , item.get('name')) + + mymovie = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + + log.debug('Checking status snatched release: %s' , mymovie['library'].get('identifier')) + + # check status + downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mymovie) + + if downloadfailed: + log.debug('Download of %s failed', item['name']) + + # if failed set status to ignored + rel.status_id = ignored_status.get('id') + db.commit() + + # search/download again + log.info('Download of %s failed, trying next release...', item['name']) + fireEvent('searcher.single', rel.movie) + + return From 684ef44f841a9f39b07e9a724c550e688bf89a19 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sun, 29 Jul 2012 20:09:11 +0200 Subject: [PATCH 07/73] Fixed typo and added some error handling --- couchpotato/core/downloaders/sabnzbd/main.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 8983c1a..45b4e25 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -4,6 +4,7 @@ from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog import traceback import urllib2 +import requests import json log = CPLog(__name__) @@ -73,11 +74,16 @@ class Sabnzbd(Downloader): params = { 'apikey': self.conf('api_key'), 'mode': 'history', - 'ouput': 'json' + 'output': 'json' } url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) log.debug('Opening: %s', url) - history = json.load(urllib2.urlopen(url)) + + try: + history = json.load(urllib2.urlopen(url)) + except: + log.error(traceback.format_exc()) + return False nzbname = self.createNzbName(data, movie) From 2884488338d5bd98a98b431c80eb17eb509802de Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sun, 29 Jul 2012 22:02:51 +0200 Subject: [PATCH 08/73] Removed double logging, bug fixes --- couchpotato/core/plugins/renamer/main.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index c8a7694..f0887ac 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -481,37 +481,32 @@ class Renamer(Plugin): db = get_session() rels = db.query(Relea).filter_by(status_id = snatched_status.get('id')) - log.debug('Checking snatched releases... %s', 'ops') - for rel in rels: log.debug('Checking snatched release: %s' , rel.movie.library.titles[0].title) + item = {} for info in rel.info: item[info.identifier] = info.value log.debug('Checking status snatched release: %s' , item.get('name')) - mymovie = rel.movie.to_dict({ + mov = rel.movie.to_dict({ 'profile': {'types': {'quality': {}}}, 'releases': {'status': {}, 'quality': {}}, 'library': {'titles': {}, 'files':{}}, 'files': {} }) - log.debug('Checking status snatched release: %s' , mymovie['library'].get('identifier')) - # check status - downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mymovie) + downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mov) if downloadfailed: - log.debug('Download of %s failed', item['name']) - # if failed set status to ignored rel.status_id = ignored_status.get('id') db.commit() # search/download again log.info('Download of %s failed, trying next release...', item['name']) - fireEvent('searcher.single', rel.movie) + fireEvent('searcher.single', mov) return From b262ed59a8d6f07a882da79c09bc049e4e5dd265 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Mon, 30 Jul 2012 19:15:25 +0200 Subject: [PATCH 09/73] Fixed sab api bug --- couchpotato/core/downloaders/sabnzbd/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 45b4e25..1018099 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -98,7 +98,8 @@ class Sabnzbd(Downloader): 'apikey': self.conf('api_key'), 'mode': 'history', 'name': 'delete', - 'value' : slot['id'] + 'del_files': '1', + 'value': slot['nzo_id'] } url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) try: From 4aae3c45eadc76ee276a305d26e9e862ace2eff2 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Wed, 1 Aug 2012 02:38:00 +0200 Subject: [PATCH 10/73] Bug fixes, it should actually restart the download now --- couchpotato/core/downloaders/base.py | 4 ++-- couchpotato/core/downloaders/sabnzbd/main.py | 4 ++-- couchpotato/core/plugins/renamer/main.py | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index e0dad91..07536a4 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -15,10 +15,10 @@ class Downloader(Plugin): addEvent('download', self.download) addEvent('getdownloadfailed', self.getdownloadfailed) - def download(self, data = {}): + def download(self, data = {}, movie = {}, manual = False, filedata = None): pass - def getdownloadfailed(self, data = {}): + def getdownloadfailed(self, data = {}, movie = {}): pass def createNzbName(self, data, movie): diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 1018099..010c1be 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -65,8 +65,8 @@ class Sabnzbd(Downloader): log.error("Unknown error: " + result[:40]) return False - def getdownloadfailed(self, data = {}, movie = {}, manual = False): - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): + def getdownloadfailed(self, data = {}, movie = {}): + if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): return log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index f0887ac..8855df4 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -482,7 +482,13 @@ class Renamer(Plugin): rels = db.query(Relea).filter_by(status_id = snatched_status.get('id')) for rel in rels: - log.debug('Checking snatched release: %s' , rel.movie.library.titles[0].title) + + # Get current selected title + default_title = '' + for title in rel.movie.library.titles: + if title.default: default_title = title.title + + log.debug('Checking snatched movie: %s' , default_title) item = {} for info in rel.info: @@ -506,6 +512,18 @@ class Renamer(Plugin): db.commit() # search/download again + # if downloaded manually: # this is currently not stored... + # log.info('Download of %s failed...', item['name']) + # return + + #update movie to reflect release status update + mov = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + log.info('Download of %s failed, trying next release...', item['name']) fireEvent('searcher.single', mov) From db2c9e51565cd71605ba8484edf35c6f68c53ccf Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Wed, 1 Aug 2012 03:09:34 +0200 Subject: [PATCH 11/73] Added config options --- couchpotato/core/downloaders/sabnzbd/__init__.py | 16 +++++++++- couchpotato/core/downloaders/sabnzbd/main.py | 38 +++++++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index ac0ce05..938c674 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -35,11 +35,25 @@ config = [{ }, { 'name': 'manual', - 'default': 0, + 'default': False, 'type': 'bool', 'advanced': True, 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', }, + { + 'name': 'download failed', + 'default': True, + 'type': 'bool', + 'advanced': True, + 'description': 'Try next the next best release for a movie after a download failed.', + }, + { + 'name': 'delete failed', + 'default': True, + 'type': 'bool', + 'advanced': True, + 'description': 'Delete a release after it\'s download failed.', + }, ], } ], diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 010c1be..bb7a2eb 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -3,8 +3,6 @@ from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog import traceback -import urllib2 -import requests import json log = CPLog(__name__) @@ -69,6 +67,9 @@ class Sabnzbd(Downloader): if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): return + if not self.conf('download failed', default = True): + return False + log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) params = { @@ -80,7 +81,7 @@ class Sabnzbd(Downloader): log.debug('Opening: %s', url) try: - history = json.load(urllib2.urlopen(url)) + history = json.load(self.urlopen(url)) except: log.error(traceback.format_exc()) return False @@ -91,23 +92,24 @@ class Sabnzbd(Downloader): for slot in history['history']['slots']: log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) if slot['name'] == nzbname and slot['status'] == 'Failed': - log.debug('%s failed downloading, deleting...', slot['name']) # Delete failed download - params = { - 'apikey': self.conf('api_key'), - 'mode': 'history', - 'name': 'delete', - 'del_files': '1', - 'value': slot['nzo_id'] - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - try: - data = self.urlopen(url, timeout = 60, show_error = False) - except: - log.error(traceback.format_exc()) - - # Return failed + if self.conf('delete failed', default = True): + log.info('%s failed downloading, deleting...', slot['name']) + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'name': 'delete', + 'del_files': '1', + 'value': slot['nzo_id'] + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + try: + data = self.urlopen(url, timeout = 60, show_error = False) + except: + log.error(traceback.format_exc()) + + # Return download failed return True return False From 100b563e107ec789de93c2ba417eca18da59dfa8 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Wed, 1 Aug 2012 03:11:19 +0200 Subject: [PATCH 12/73] typo --- couchpotato/core/downloaders/sabnzbd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index 938c674..822ba61 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -45,7 +45,7 @@ config = [{ 'default': True, 'type': 'bool', 'advanced': True, - 'description': 'Try next the next best release for a movie after a download failed.', + 'description': 'Try the next best release for a movie after a download failed.', }, { 'name': 'delete failed', From 4223ed4b5aa97ee5aa0871e51a61af3e6c234b57 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 18:59:04 +0200 Subject: [PATCH 13/73] Put the renamer as a function of a download status checker in the searcher class The changed functionality is as follows: - the renamer is not on scheduled interval anymore - the download status checker checks the status of all snatched releases every x minutes - if a release has downloaded it fires up the renamer (if enabled) - if it failed, it sets the release to ignored and snatches the next best release With these additions the renamer wont scan your hd anymore when it is not required, and will retry failed downloads with new releases. To do: - the only downloader implemented is SabNZBd, for the others it defaults to the old behavior when releases are snatched (I think!?) - a button to scan manually: Items added to the renamer folder are only picked up after a download completed --- couchpotato/core/downloaders/base.py | 6 +- couchpotato/core/downloaders/sabnzbd/__init__.py | 7 -- couchpotato/core/downloaders/sabnzbd/main.py | 115 ++++++++++++++++------- couchpotato/core/plugins/renamer/__init__.py | 9 -- couchpotato/core/plugins/renamer/main.py | 57 +---------- couchpotato/core/plugins/searcher/__init__.py | 16 ++++ couchpotato/core/plugins/searcher/main.py | 76 +++++++++++++++ 7 files changed, 175 insertions(+), 111 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 07536a4..1be9dcc 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -13,16 +13,16 @@ class Downloader(Plugin): def __init__(self): addEvent('download', self.download) - addEvent('getdownloadfailed', self.getdownloadfailed) + addEvent('getdownloadstatus', self.getdownloadstatus) def download(self, data = {}, movie = {}, manual = False, filedata = None): pass - def getdownloadfailed(self, data = {}, movie = {}): + def getdownloadstatus(self, data = {}, movie = {}): pass def createNzbName(self, data, movie): - return '%s%s' % (toSafeString(data.get('name')), self.cpTag(movie)) + return '%s%s' % (toSafeString(data.get('name')[:40]), self.cpTag(movie)) def createFileName(self, data, filedata, movie): name = os.path.join(self.createNzbName(data, movie)) diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index 822ba61..1baaea0 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -41,13 +41,6 @@ config = [{ 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', }, { - 'name': 'download failed', - 'default': True, - 'type': 'bool', - 'advanced': True, - 'description': 'Try the next best release for a movie after a download failed.', - }, - { 'name': 'delete failed', 'default': True, 'type': 'bool', diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index bb7a2eb..4144b27 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -40,14 +40,14 @@ class Sabnzbd(Downloader): try: if params.get('mode') is 'addfile': - data = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False) + sab = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False) else: - data = self.urlopen(url, timeout = 60, show_error = False) + sab = self.urlopen(url, timeout = 60, show_error = False) except: log.error(traceback.format_exc()) return False - result = data.strip() + result = sab.strip() if not result: log.error("SABnzbd didn't return anything.") return False @@ -63,53 +63,96 @@ class Sabnzbd(Downloader): log.error("Unknown error: " + result[:40]) return False - def getdownloadfailed(self, data = {}, movie = {}): + def getdownloadstatus(self, data = {}, movie = {}): if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): return - if not self.conf('download failed', default = True): - return False - - log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) + nzbname = self.createNzbName(data, movie) + log.info('Checking download status of "%s" at SABnzbd.', nzbname) + # Go through Queue params = { 'apikey': self.conf('api_key'), - 'mode': 'history', + 'mode': 'queue', 'output': 'json' } url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - log.debug('Opening: %s', url) try: - history = json.load(self.urlopen(url)) + sab = self.urlopen(url, timeout = 60, show_error = False) except: log.error(traceback.format_exc()) - return False + return + try: + history = json.loads(sab) + except: + log.debug("Result text from SAB: " + sab[:40]) + log.error(traceback.format_exc()) + return - nzbname = self.createNzbName(data, movie) + for slot in history['queue']['slots']: + if slot['cat'] == self.conf('category'): + log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft'])) + if slot['filename'] == nzbname: + return slot['status'] # Go through history items + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'output': 'json' + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + + try: + sab = self.urlopen(url, timeout = 60, show_error = False) + except: + log.error(traceback.format_exc()) + return + try: + history = json.loads(sab) + except: + log.debug("Result text from SAB: " + sab[:40]) + log.error(traceback.format_exc()) + return + for slot in history['history']['slots']: - log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) - if slot['name'] == nzbname and slot['status'] == 'Failed': - - # Delete failed download - if self.conf('delete failed', default = True): - log.info('%s failed downloading, deleting...', slot['name']) - params = { - 'apikey': self.conf('api_key'), - 'mode': 'history', - 'name': 'delete', - 'del_files': '1', - 'value': slot['nzo_id'] - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - try: - data = self.urlopen(url, timeout = 60, show_error = False) - except: - log.error(traceback.format_exc()) - - # Return download failed - return True - - return False + if slot['category'] == self.conf('category'): + log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) + if slot['name'] == nzbname: + if slot['status'] == 'Failed' or 'fail' in slot['fail_message'].lower(): + + # Delete failed download + if self.conf('delete failed', default = True): + log.info('%s failed downloading, deleting...', slot['name']) + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'name': 'delete', + 'del_files': '1', + 'value': slot['nzo_id'] + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + try: + sab = self.urlopen(url, timeout = 60, show_error = False) + except: + log.error(traceback.format_exc()) + return False + + result = sab.strip() + if not result: + log.error("SABnzbd didn't return anything.") + + log.debug("Result text from SAB: " + result[:40]) + if result == "ok": + log.info('SabNZBd deleted failed release %s successfully.', slot['name']) + elif result == "Missing authentication": + log.error("Incorrect username/password.") + else: + log.error("Unknown error: " + result[:40]) + + return 'Failed' + else: + return slot['status'] + + return 'Not found' diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py index 5943954..21076d6 100644 --- a/couchpotato/core/plugins/renamer/__init__.py +++ b/couchpotato/core/plugins/renamer/__init__.py @@ -86,15 +86,6 @@ config = [{ 'label': 'Separator', 'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.', }, - { - 'advanced': True, - 'name': 'run_every', - 'label': 'Run every', - 'default': 1, - 'type': 'int', - 'unit': 'min(s)', - 'description': 'Search for new movies inside the folder every X minutes.', - }, ], }, { 'tab': 'renamer', diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 8855df4..4c80fb0 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -29,7 +29,7 @@ class Renamer(Plugin): addEvent('renamer.scan', 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 scanView(self): @@ -48,8 +48,6 @@ class Renamer(Plugin): log.info('Renamer is disabled to avoid infinite looping of the same error.') return - self.checkSnatchedStatusses() - # Check to see if the "to" folder is inside the "from" folder. if not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')): log.debug('"To" and "From" have to exist.') @@ -474,57 +472,4 @@ class Renamer(Plugin): except: log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) - def checkSnatchedStatusses(self): - snatched_status = fireEvent('status.get', 'snatched', single = True) - ignored_status = fireEvent('status.get', 'ignored', single = True) - - db = get_session() - rels = db.query(Relea).filter_by(status_id = snatched_status.get('id')) - - for rel in rels: - - # Get current selected title - default_title = '' - for title in rel.movie.library.titles: - if title.default: default_title = title.title - - log.debug('Checking snatched movie: %s' , default_title) - - item = {} - for info in rel.info: - item[info.identifier] = info.value - - log.debug('Checking status snatched release: %s' , item.get('name')) - - mov = rel.movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) - - # check status - downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mov) - - if downloadfailed: - # if failed set status to ignored - rel.status_id = ignored_status.get('id') - db.commit() - - # search/download again - # if downloaded manually: # this is currently not stored... - # log.info('Download of %s failed...', item['name']) - # return - - #update movie to reflect release status update - mov = rel.movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) - - log.info('Download of %s failed, trying next release...', item['name']) - fireEvent('searcher.single', mov) - return diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py index f499e2b..2d82b61 100644 --- a/couchpotato/core/plugins/searcher/__init__.py +++ b/couchpotato/core/plugins/searcher/__init__.py @@ -39,6 +39,22 @@ config = [{ 'type': 'dropdown', 'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')], }, + { + 'advanced': True, + 'name': 'run_every', + 'label': 'Run every', + 'default': 1, + 'type': 'int', + 'unit': 'min(s)', + 'description': 'Detect movie status every X minutes. Will start the renamer if movie is completed or handle failed download if these options are enabled', + }, + { + 'name': 'failed download', + 'default': True, + 'type': 'bool', + 'advanced': True, + 'description': 'Try the next best release for a movie after a download failed.', + }, ], }, { 'tab': 'searcher', diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 03eb6cf..b1b285e 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -25,9 +25,12 @@ class Searcher(Plugin): addEvent('searcher.single', self.single) addEvent('searcher.correct_movie', self.correctMovie) addEvent('searcher.download', self.download) + addEvent('searcher.checksnatched', self.checksnatched) # Schedule cronjob fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) + fireEvent('schedule.interval', 'searcher.checksnatched', self.checksnatched, minutes = self.conf('run_every')) + def all_movies(self): @@ -439,3 +442,76 @@ class Searcher(Plugin): return False + + def checksnatched(self): + snatched_status = fireEvent('status.get', 'snatched', single = True) + ignored_status = fireEvent('status.get', 'ignored', single = True) + + db = get_session() + rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) + + log.info('Checking snatched releases...') + + for rel in rels: + + # Get current selected title + default_title = '' + for title in rel.movie.library.titles: + if title.default: default_title = title.title + + log.debug('Checking snatched movie: %s' , default_title) + + item = {} + for info in rel.info: + item[info.identifier] = info.value + + movie = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + + # check status + downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie) + log.debug('Download staus: %s' , downloadstatus[0]) + + if downloadstatus[0] == 'Failed': + # if failed set status to ignored + rel.status_id = ignored_status.get('id') + db.commit() + + # search/download again + # if downloaded manually: # this is currently not stored... + # log.info('Download of %s failed...', item['name']) + # return + + if self.conf('failed download', default = True): + + #update movie to reflect release status update + movie = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + log.info('Download of %s failed, trying next release...', item['name']) + fireEvent('searcher.single', movie) + else: + log.info('Download of %s failed.', item['name']) + + elif downloadstatus[0] == 'Completed': + log.info('Download of %s completed!', item['name']) + fireEvent('renamer.scan') + + elif downloadstatus[0] == 'Not found': + log.info('%s not found in SabNZBd', item['name']) + rel.status_id = ignored_status.get('id') + db.commit() + + elif downloadstatus[0] == None: # Downloader not compatible with download status or + fireEvent('renamer.scan') + + # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + + return From 928c440f90b77a793ad64368afcb8cd31a3a883f Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 19:01:19 +0200 Subject: [PATCH 14/73] renamer cleanup --- couchpotato/core/plugins/renamer/main.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 4c80fb0..a774499 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -6,7 +6,7 @@ from couchpotato.core.helpers.request import jsonified from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Library, File, Profile, Release as Relea +from couchpotato.core.settings.model import Library, File, Profile from couchpotato.environment import Env import os import re @@ -471,5 +471,3 @@ class Renamer(Plugin): os.rmdir(folder) except: log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) - - From 0193f7ad8116f681d4666247a34f546f564bff5d Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 20:05:31 +0200 Subject: [PATCH 15/73] scan only once when multiple releases are snatched --- couchpotato/core/plugins/searcher/main.py | 84 ++++++++++++++++--------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index b1b285e..2d2fdac 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -450,7 +450,8 @@ class Searcher(Plugin): db = get_session() rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) - log.info('Checking snatched releases...') + log.info('Checking status snatched releases...') + scanrequired = False for rel in rels: @@ -474,44 +475,47 @@ class Searcher(Plugin): # check status downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie) - log.debug('Download staus: %s' , downloadstatus[0]) - - if downloadstatus[0] == 'Failed': - # if failed set status to ignored - rel.status_id = ignored_status.get('id') - db.commit() - - # search/download again - # if downloaded manually: # this is currently not stored... - # log.info('Download of %s failed...', item['name']) - # return - - if self.conf('failed download', default = True): - - #update movie to reflect release status update - movie = rel.movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) - log.info('Download of %s failed, trying next release...', item['name']) - fireEvent('searcher.single', movie) - else: - log.info('Download of %s failed.', item['name']) - - elif downloadstatus[0] == 'Completed': - log.info('Download of %s completed!', item['name']) - fireEvent('renamer.scan') - - elif downloadstatus[0] == 'Not found': - log.info('%s not found in SabNZBd', item['name']) - rel.status_id = ignored_status.get('id') - db.commit() - - elif downloadstatus[0] == None: # Downloader not compatible with download status or - fireEvent('renamer.scan') - - # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + if downloadstatus == None: # Downloader not compatible with download status + scanrequired = True + + else: + log.debug('Download staus: %s' , downloadstatus[0]) + + if downloadstatus[0] == 'Failed': + # if failed set status to ignored + rel.status_id = ignored_status.get('id') + db.commit() + + # search/download again + # if downloaded manually: # this is currently not stored... + # log.info('Download of %s failed...', item['name']) + # return + + if self.conf('failed download', default = True): + + #update movie to reflect release status update + movie = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + log.info('Download of %s failed, trying next release...', item['name']) + fireEvent('searcher.single', movie) + else: + log.info('Download of %s failed.', item['name']) + + elif downloadstatus[0] == 'Completed': + log.info('Download of %s completed!', item['name']) + scanrequired = True + + elif downloadstatus[0] == 'Not found': + log.info('%s not found in downloaders', item['name']) + rel.status_id = ignored_status.get('id') + db.commit() + + # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + if scanrequired: + fireEvent('renamer.scan') return From 71c181379a9008f14b3c416494d34ea71b05d86a Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 20:56:05 +0200 Subject: [PATCH 16/73] Fixed behavior for not compatible downloaders + typo's --- couchpotato/core/plugins/searcher/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 2d2fdac..fbfe0fa 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -475,11 +475,12 @@ class Searcher(Plugin): # check status downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie) - if downloadstatus == None: # Downloader not compatible with download status + if not downloadstatus: # Downloader not compatible with download status + log.debug('Download status functionality is not implemented for active downloaders.') scanrequired = True else: - log.debug('Download staus: %s' , downloadstatus[0]) + log.debug('Download status: %s' , downloadstatus[0]) if downloadstatus[0] == 'Failed': # if failed set status to ignored @@ -514,7 +515,7 @@ class Searcher(Plugin): rel.status_id = ignored_status.get('id') db.commit() - # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + # Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd if scanrequired: fireEvent('renamer.scan') From 334a5725e1bc3c935f62352f14909bc9ba987049 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 9 Aug 2012 16:00:41 +0200 Subject: [PATCH 17/73] Use magnet links from publichd. fix #682 --- couchpotato/core/providers/torrent/publichd/main.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py index c962f93..14bfab5 100644 --- a/couchpotato/core/providers/torrent/publichd/main.py +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -15,19 +15,9 @@ class PublicHD(TorrentProvider): urls = { 'test': 'http://publichd.eu', - 'download': 'http://publichd.eu/%s', 'detail': 'http://publichd.eu/index.php?page=torrent-details&id=%s', 'search': 'http://publichd.eu/index.php', } - - cat_ids = [ - ([9], ['bd50']), - ([5], ['1080p']), - ([2], ['720p']), - ([15, 16], ['brrip']), - ] - - cat_backup_id = 0 http_time_between_calls = 0 def search(self, movie, quality): @@ -39,9 +29,8 @@ class PublicHD(TorrentProvider): params = tryUrlencode({ 'page':'torrents', - 'search': getTitle(movie['library']) + ' ' + quality['identifier'], + 'search': '%s %s' % (getTitle(movie['library']), movie['library']['year']), 'active': 1, - 'category': self.getCatId(quality['identifier'])[0] }) url = '%s?%s' % (self.urls['search'], params) @@ -58,7 +47,7 @@ class PublicHD(TorrentProvider): for result in entries[2:len(entries) - 1]: info_url = result.find(href = re.compile('torrent-details')) - download = result.find(href = re.compile('\.torrent')) + download = result.find(href = re.compile('magnet:')) if info_url and download: @@ -67,12 +56,11 @@ class PublicHD(TorrentProvider): new = { 'id': url['id'][0], 'name': info_url.string, - 'type': 'torrent', + 'type': 'torrent_magnet', 'check_nzb': False, 'description': '', 'provider': self.getName(), - 'download': self.download, - 'url': self.urls['download'] % download['href'], + 'url': download['href'], 'detail_url': self.urls['detail'] % url['id'][0], 'size': self.parseSize(result.find_all('td')[7].string), 'seeders': tryInt(result.find_all('td')[4].string), From 28f75617b2f1046b1fbc72905c49f7efc96db4b1 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 9 Aug 2012 16:23:05 +0200 Subject: [PATCH 18/73] Use magnet link for kat.ph. fix #644 --- .../core/providers/torrent/kickasstorrents/main.py | 26 +++++----------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/couchpotato/core/providers/torrent/kickasstorrents/main.py b/couchpotato/core/providers/torrent/kickasstorrents/main.py index c57ae8a..e1440c2 100644 --- a/couchpotato/core/providers/torrent/kickasstorrents/main.py +++ b/couchpotato/core/providers/torrent/kickasstorrents/main.py @@ -3,8 +3,6 @@ from couchpotato.core.event import fireEvent from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.providers.torrent.base import TorrentProvider -import StringIO -import gzip import re import traceback @@ -14,10 +12,9 @@ log = CPLog(__name__) class KickAssTorrents(TorrentProvider): urls = { - 'test': 'http://www.kat.ph/', - 'detail': 'http://www.kat.ph/%s-t%s.html', - 'search': 'http://www.kat.ph/i%s/', - 'download': 'http://torcache.net/', + 'test': 'http://kat.ph/', + 'detail': 'http://kat.ph/%s-t%s.html', + 'search': 'http://kat.ph/i%s/', } cat_ids = [ @@ -60,11 +57,10 @@ class KickAssTorrents(TorrentProvider): continue new = { - 'type': 'torrent', + 'type': 'torrent_magnet', 'check_nzb': False, 'description': '', 'provider': self.getName(), - 'download': self.download, 'score': 0, } @@ -77,9 +73,7 @@ class KickAssTorrents(TorrentProvider): link = td.find('div', {'class': 'torrentname'}).find_all('a')[1] new['id'] = temp.get('id')[-8:] new['name'] = link.text - new['url'] = td.find_all('a', 'idownload')[1]['href'] - if new['url'][:2] == '//': - new['url'] = 'http:%s' % new['url'] + new['url'] = td.find('a', 'imagnet')['href'] new['score'] = 20 if td.find('a', 'iverif') else 0 elif column_name is 'size': new['size'] = self.parseSize(td.text) @@ -129,13 +123,3 @@ class KickAssTorrents(TorrentProvider): age += tryInt(nr) * mult return tryInt(age) - - def download(self, url = '', nzb_id = ''): - compressed_data = self.urlopen(url = url, headers = {'Referer': 'http://kat.ph/'}) - - compressedstream = StringIO.StringIO(compressed_data) - gzipper = gzip.GzipFile(fileobj = compressedstream) - data = gzipper.read() - - return data - From da69c2840ac5390000af212e83737c5239e99ef6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 9 Aug 2012 22:11:29 +0200 Subject: [PATCH 19/73] Wrong content tag in search bugs Opera. fix #696 --- couchpotato/core/plugins/movie/static/search.css | 1 - 1 file changed, 1 deletion(-) diff --git a/couchpotato/core/plugins/movie/static/search.css b/couchpotato/core/plugins/movie/static/search.css index 4f43ffe..76130cb 100644 --- a/couchpotato/core/plugins/movie/static/search.css +++ b/couchpotato/core/plugins/movie/static/search.css @@ -186,7 +186,6 @@ .movie_result .info h2 span { padding: 0 5px; - content: ")"; } .movie_result .info h2 span:before { content: "("; } From 4d90b1873b829b7609ddfc9afdd95a3f2b9896f0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 9 Aug 2012 22:23:09 +0200 Subject: [PATCH 20/73] Add SD documentary category NZBMatrix. fix #698 --- couchpotato/core/providers/nzb/nzbmatrix/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/nzb/nzbmatrix/main.py b/couchpotato/core/providers/nzb/nzbmatrix/main.py index c6c5be9..09514a4 100644 --- a/couchpotato/core/providers/nzb/nzbmatrix/main.py +++ b/couchpotato/core/providers/nzb/nzbmatrix/main.py @@ -22,7 +22,7 @@ class NZBMatrix(NZBProvider, RSS): cat_ids = [ ([50], ['bd50']), ([42, 53], ['720p', '1080p']), - ([2], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), + ([2, 9], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), ([54], ['brrip']), ([1], ['dvdr']), ] From 627314dbe1f23355eaac8c794f85c9ef95b7075c Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 10 Aug 2012 20:29:42 +0200 Subject: [PATCH 21/73] Change release info API structure --- couchpotato/core/settings/model.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py index 553c035..2ecb48a 100644 --- a/couchpotato/core/settings/model.py +++ b/couchpotato/core/settings/model.py @@ -103,6 +103,22 @@ class Release(Entity): files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True) info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan') + def to_dict(self, deep = {}, exclude = []): + orig_dict = super(Release, self).to_dict(deep = deep, exclude = exclude) + + new_info = {} + for info in orig_dict.get('info', []): + + value = info['value'] + try: value = int(info['value']) + except: pass + + new_info[info['identifier']] = value + + orig_dict['info'] = new_info + + return orig_dict + class ReleaseInfo(Entity): """Properties that can be bound to a file for off-line usage""" From 9b6261f0b3da9fcd9d6f91910bc9a106b827cda6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 12 Aug 2012 15:17:11 +0200 Subject: [PATCH 22/73] Always check for correct quality --- couchpotato/core/plugins/searcher/main.py | 8 ++------ couchpotato/core/providers/nzb/mysterbin/main.py | 2 +- couchpotato/core/providers/nzb/newznab/main.py | 7 +++---- couchpotato/core/providers/nzb/nzbclub/main.py | 2 +- couchpotato/core/providers/nzb/nzbindex/main.py | 2 +- couchpotato/core/providers/nzb/nzbmatrix/main.py | 3 +-- couchpotato/core/providers/torrent/kickasstorrents/main.py | 2 +- couchpotato/core/providers/torrent/publichd/main.py | 2 +- couchpotato/core/providers/torrent/sceneaccess/main.py | 2 +- couchpotato/core/providers/torrent/scenehd/main.py | 2 +- couchpotato/core/providers/torrent/thepiratebay/main.py | 2 +- couchpotato/core/providers/torrent/torrentleech/main.py | 2 +- 12 files changed, 15 insertions(+), 21 deletions(-) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 6fc6fe4..16e3e5a 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -242,7 +242,6 @@ class Searcher(Plugin): 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 nzb.get('seeds') is None and retention < nzb.get('age', 0): @@ -275,7 +274,7 @@ class Searcher(Plugin): preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True) # Contains lower quality string - if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality, single_category = single_category): + if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality): log.info('Wrong: %s, looking for %s', (nzb['name'], quality['label'])) return False @@ -327,7 +326,7 @@ class Searcher(Plugin): log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year'])) return False - def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False): + def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}): name = nzb['name'] size = nzb.get('size', 0) @@ -358,9 +357,6 @@ class Searcher(Plugin): 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): diff --git a/couchpotato/core/providers/nzb/mysterbin/main.py b/couchpotato/core/providers/nzb/mysterbin/main.py index 008f24f..edf2c70 100644 --- a/couchpotato/core/providers/nzb/mysterbin/main.py +++ b/couchpotato/core/providers/nzb/mysterbin/main.py @@ -88,7 +88,7 @@ class Mysterbin(NZBProvider): 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) + imdb_results = False, single = True) if is_correct_movie: results.append(new) self.found(new) diff --git a/couchpotato/core/providers/nzb/newznab/main.py b/couchpotato/core/providers/nzb/newznab/main.py index ff2aba8..5485c63 100644 --- a/couchpotato/core/providers/nzb/newznab/main.py +++ b/couchpotato/core/providers/nzb/newznab/main.py @@ -96,13 +96,12 @@ class Newznab(NZBProvider, RSS): url = "%s&%s" % (self.getUrl(host['host'], self.urls['search']), arguments) cache_key = 'newznab.%s.%s.%s' % (host['host'], movie['library']['identifier'], cat_id[0]) - single_cat = (len(cat_id) == 1 and cat_id[0] != self.cat_backup_id) - results = self.createItems(url, cache_key, host, single_cat = single_cat, movie = movie, quality = quality) + results = self.createItems(url, cache_key, host, movie = movie, quality = quality) return results - def createItems(self, url, cache_key, host, single_cat = False, movie = None, quality = None, for_feed = False): + def createItems(self, url, cache_key, host, movie = None, quality = None, for_feed = False): results = [] data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()}) @@ -146,7 +145,7 @@ class Newznab(NZBProvider, RSS): if not for_feed: is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = True, single_category = single_cat, single = True) + imdb_results = True, single = True) if is_correct_movie: new['score'] = fireEvent('score.calculate', new, movie, single = True) diff --git a/couchpotato/core/providers/nzb/nzbclub/main.py b/couchpotato/core/providers/nzb/nzbclub/main.py index dedddb8..d047a8a 100644 --- a/couchpotato/core/providers/nzb/nzbclub/main.py +++ b/couchpotato/core/providers/nzb/nzbclub/main.py @@ -86,7 +86,7 @@ class NZBClub(NZBProvider, RSS): is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = False, single_category = False, single = True) + imdb_results = False, single = True) if is_correct_movie: new['score'] = fireEvent('score.calculate', new, movie, single = True) diff --git a/couchpotato/core/providers/nzb/nzbindex/main.py b/couchpotato/core/providers/nzb/nzbindex/main.py index 3d6384c..ee2092c 100644 --- a/couchpotato/core/providers/nzb/nzbindex/main.py +++ b/couchpotato/core/providers/nzb/nzbindex/main.py @@ -93,7 +93,7 @@ class NzbIndex(NZBProvider, RSS): is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = False, single_category = False, single = True) + imdb_results = False, single = True) if is_correct_movie: new['score'] = fireEvent('score.calculate', new, movie, single = True) diff --git a/couchpotato/core/providers/nzb/nzbmatrix/main.py b/couchpotato/core/providers/nzb/nzbmatrix/main.py index 09514a4..9539eac 100644 --- a/couchpotato/core/providers/nzb/nzbmatrix/main.py +++ b/couchpotato/core/providers/nzb/nzbmatrix/main.py @@ -49,7 +49,6 @@ class NZBMatrix(NZBProvider, RSS): url = "%s?%s" % (self.urls['search'], arguments) cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids) - single_cat = True data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()}) if data: @@ -86,7 +85,7 @@ class NZBMatrix(NZBProvider, RSS): is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = True, single_category = single_cat, single = True) + imdb_results = True, single = True) if is_correct_movie: new['score'] = fireEvent('score.calculate', new, movie, single = True) diff --git a/couchpotato/core/providers/torrent/kickasstorrents/main.py b/couchpotato/core/providers/torrent/kickasstorrents/main.py index e1440c2..84a679f 100644 --- a/couchpotato/core/providers/torrent/kickasstorrents/main.py +++ b/couchpotato/core/providers/torrent/kickasstorrents/main.py @@ -89,7 +89,7 @@ class KickAssTorrents(TorrentProvider): new['score'] += fireEvent('score.calculate', new, movie, single = True) is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = True, single_category = False, single = True) + imdb_results = True, single = True) if is_correct_movie: results.append(new) self.found(new) diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py index 14bfab5..cf0e687 100644 --- a/couchpotato/core/providers/torrent/publichd/main.py +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -70,7 +70,7 @@ class PublicHD(TorrentProvider): 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) + imdb_results = False, single = True) if is_correct_movie: results.append(new) diff --git a/couchpotato/core/providers/torrent/sceneaccess/main.py b/couchpotato/core/providers/torrent/sceneaccess/main.py index 62cd7dc..39598d6 100644 --- a/couchpotato/core/providers/torrent/sceneaccess/main.py +++ b/couchpotato/core/providers/torrent/sceneaccess/main.py @@ -86,7 +86,7 @@ class SceneAccess(TorrentProvider): 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) + imdb_results = False, single = True) if is_correct_movie: results.append(new) diff --git a/couchpotato/core/providers/torrent/scenehd/main.py b/couchpotato/core/providers/torrent/scenehd/main.py index 89d847d..596cb5b 100644 --- a/couchpotato/core/providers/torrent/scenehd/main.py +++ b/couchpotato/core/providers/torrent/scenehd/main.py @@ -79,7 +79,7 @@ class SceneHD(TorrentProvider): new['score'] = fireEvent('score.calculate', new, movie, single = True) is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = imdb_results, single_category = False, single = True) + imdb_results = imdb_results, single = True) if is_correct_movie: results.append(new) diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index cdb86a7..7b10295 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -120,7 +120,7 @@ class ThePirateBay(TorrentProvider): 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) + imdb_results = False, single = True) if is_correct_movie: results.append(new) diff --git a/couchpotato/core/providers/torrent/torrentleech/main.py b/couchpotato/core/providers/torrent/torrentleech/main.py index c174a3d..0cfc7ce 100644 --- a/couchpotato/core/providers/torrent/torrentleech/main.py +++ b/couchpotato/core/providers/torrent/torrentleech/main.py @@ -80,7 +80,7 @@ class TorrentLeech(TorrentProvider): new['score'] = fireEvent('score.calculate', new, movie, single = True) is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = imdb_results, single_category = False, single = True) + imdb_results = imdb_results, single = True) if is_correct_movie: results.append(new) From f63e332861bdf149b56198c567a9b6b8a3f6b6ef Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 12 Aug 2012 23:53:53 +0200 Subject: [PATCH 23/73] Cleanup of automated failed download detection. --- couchpotato/core/downloaders/base.py | 7 +- couchpotato/core/downloaders/sabnzbd/__init__.py | 5 +- couchpotato/core/downloaders/sabnzbd/main.py | 34 ++++--- couchpotato/core/plugins/movie/static/movie.css | 26 +++++ couchpotato/core/plugins/movie/static/movie.js | 121 ++++++++++++++++++++--- couchpotato/core/plugins/searcher/__init__.py | 3 +- couchpotato/core/plugins/searcher/main.py | 96 ++++++++++-------- couchpotato/core/plugins/status/main.py | 1 + couchpotato/static/scripts/couchpotato.js | 21 ++-- couchpotato/static/scripts/page/wanted.js | 3 +- 10 files changed, 228 insertions(+), 89 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 1be9dcc..d5f1c42 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -13,16 +13,17 @@ class Downloader(Plugin): def __init__(self): addEvent('download', self.download) - addEvent('getdownloadstatus', self.getdownloadstatus) + addEvent('download.status', self.getDownloadStatus) def download(self, data = {}, movie = {}, manual = False, filedata = None): pass - def getdownloadstatus(self, data = {}, movie = {}): + def getDownloadStatus(self, data = {}, movie = {}): pass def createNzbName(self, data, movie): - return '%s%s' % (toSafeString(data.get('name')[:40]), self.cpTag(movie)) + tag = self.cpTag(movie) + return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag) def createFileName(self, data, filedata, movie): name = os.path.join(self.createNzbName(data, movie)) diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index 1baaea0..927a9ff 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -41,11 +41,10 @@ config = [{ 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', }, { - 'name': 'delete failed', + 'name': 'delete_failed', 'default': True, 'type': 'bool', - 'advanced': True, - 'description': 'Delete a release after it\'s download failed.', + 'description': 'Delete a release after the download has failed.', }, ], } diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 4144b27..e174259 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -44,7 +44,7 @@ class Sabnzbd(Downloader): else: sab = self.urlopen(url, timeout = 60, show_error = False) except: - log.error(traceback.format_exc()) + log.error('Failed sending release: %s', traceback.format_exc()) return False result = sab.strip() @@ -63,7 +63,7 @@ class Sabnzbd(Downloader): log.error("Unknown error: " + result[:40]) return False - def getdownloadstatus(self, data = {}, movie = {}): + def getDownloadStatus(self, data = {}, movie = {}): if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): return @@ -81,20 +81,21 @@ class Sabnzbd(Downloader): try: sab = self.urlopen(url, timeout = 60, show_error = False) except: - log.error(traceback.format_exc()) - return + log.error('Failed checking status: %s', traceback.format_exc()) + return False + try: history = json.loads(sab) except: log.debug("Result text from SAB: " + sab[:40]) - log.error(traceback.format_exc()) - return + log.error('Failed parsing json status: %s', traceback.format_exc()) + return False for slot in history['queue']['slots']: if slot['cat'] == self.conf('category'): log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft'])) if slot['filename'] == nzbname: - return slot['status'] + return slot['status'].lower() # Go through history items params = { @@ -107,13 +108,14 @@ class Sabnzbd(Downloader): try: sab = self.urlopen(url, timeout = 60, show_error = False) except: - log.error(traceback.format_exc()) + log.error('Failed getting history: %s', traceback.format_exc()) return + try: history = json.loads(sab) except: log.debug("Result text from SAB: " + sab[:40]) - log.error(traceback.format_exc()) + log.error('Failed parsing history json: %s', traceback.format_exc()) return for slot in history['history']['slots']: @@ -123,7 +125,8 @@ class Sabnzbd(Downloader): if slot['status'] == 'Failed' or 'fail' in slot['fail_message'].lower(): # Delete failed download - if self.conf('delete failed', default = True): + if self.conf('delete_failed', default = True): + log.info('%s failed downloading, deleting...', slot['name']) params = { 'apikey': self.conf('api_key'), @@ -133,10 +136,11 @@ class Sabnzbd(Downloader): 'value': slot['nzo_id'] } url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + try: sab = self.urlopen(url, timeout = 60, show_error = False) except: - log.error(traceback.format_exc()) + log.error('Failed deleting: %s', traceback.format_exc()) return False result = sab.strip() @@ -147,12 +151,12 @@ class Sabnzbd(Downloader): if result == "ok": log.info('SabNZBd deleted failed release %s successfully.', slot['name']) elif result == "Missing authentication": - log.error("Incorrect username/password.") + log.error("Incorrect username/password or API?.") else: log.error("Unknown error: " + result[:40]) - return 'Failed' + return 'failed' else: - return slot['status'] + return slot['status'].lower() - return 'Not found' + return 'not_found' diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index c30ee99..f809c57 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -340,6 +340,32 @@ .movies .movie .hide_trailer.hide { top: -30px; } + + .movies .movie .try_container { + padding: 5px 10px; + text-align: center; + } + + .movies .movie .try_container a { + margin: 0 5px; + padding: 2px 5px; + } + + .movies .movie .releases .next_release { + border-left: 6px solid #2aa300; + } + + .movies .movie .releases .next_release > :first-child { + margin-left: -6px; + } + + .movies .movie .releases .last_release { + border-left: 6px solid #ffa200; + } + + .movies .movie .releases .last_release > :first-child { + margin-left: -6px; + } .movies .load_more { display: block; diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 92a66ef..2182b88 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -146,7 +146,7 @@ var Movie = new Class({ }); // Add done releases - Array.each(self.data.releases, function(release){ + self.data.releases.each(function(release){ var q = self.quality.getElement('.q_id'+ release.quality_id), status = Status.get(release.status_id); @@ -159,9 +159,9 @@ var Movie = new Class({ }); Object.each(self.options.actions, function(action, key){ - self.actions.adopt( - self.action[key.toLowerCase()] = new self.options.actions[key](self) - ) + self.action[key.toLowerCase()] = action = new self.options.actions[key](self) + if(action.el) + self.actions.adopt(action) }); if(!self.data.library.rating) @@ -280,6 +280,31 @@ var MovieAction = new Class({ this.el.removeClass('disable') }, + createMask: function(){ + var self = this; + self.mask = new Element('div.mask', { + 'styles': { + 'z-index': '1' + } + }).inject(self.movie, 'top').fade('hide'); + self.positionMask(); + }, + + positionMask: function(){ + var self = this, + movie = $(self.movie), + s = movie.getSize() + + return; + + return self.mask.setStyles({ + 'width': s.x, + 'height': s.y + }).position({ + 'relativeTo': movie + }) + }, + toElement: function(){ return this.el || null } @@ -318,13 +343,10 @@ var IMDBAction = new Class({ 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.icon.download', { 'title': 'Show the releases that are available for ' + self.movie.getTitle(), 'events': { @@ -332,15 +354,33 @@ var ReleaseAction = new Class({ } }); + var buttons_done = false; + + self.movie.data.releases.sortBy('-info.score').each(function(release){ + if(buttons_done) return; + + var status = Status.get(release.status_id); + + if((status.identifier == 'ignored' || status.identifier == 'failed') || (!self.next_release && status.identifier == 'available')){ + self.hide_on_click = false; + self.show(); + buttons_done = true; + } + + }); + }, show: function(e){ var self = this; - (e).preventDefault(); + if(e) + (e).preventDefault(); if(!self.options_container){ self.options_container = new Element('div.options').adopt( - self.release_container = new Element('div.releases.table') + self.release_container = new Element('div.releases.table').adopt( + self.trynext_container = new Element('div.buttons.try_container') + ) ).inject(self.movie, 'top'); // Header @@ -354,7 +394,7 @@ var ReleaseAction = new Class({ new Element('span.provider', {'text': 'Provider'}) ).inject(self.release_container) - Array.each(self.movie.data.releases, function(release){ + self.movie.data.releases.sortBy('-info.score').each(function(release){ var status = Status.get(release.status_id), quality = Quality.getProfile(release.quality_id) || {}, @@ -364,8 +404,18 @@ var ReleaseAction = new Class({ var details_url = info.filter(function(item){ return item.identifier == 'detail_url' }).pick().value; } catch(e){} + if( status.identifier == 'ignored' || status.identifier == 'failed'){ + self.last_release = release; + } + else if(!self.next_release && status.identifier == 'available'){ + self.next_release = release; + } + + // Create release new Element('div', { - 'class': 'item '+status.identifier, + 'class': 'item '+status.identifier + + (self.next_release && self.next_release.id == release.id ? ' next_release' : '') + + (self.last_release && self.last_release.id == release.id ? ' last_release' : ''), 'id': 'release_'+release.id }).adopt( new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}), @@ -400,17 +450,37 @@ var ReleaseAction = new Class({ ).inject(self.release_container) }); + self.trynext_container.adopt( + new Element('span.or', { + 'text': 'Download' + }), + self.last_release ? new Element('a.button.orange', { + 'text': 'the same release again', + 'events': { + 'click': self.trySameRelease.bind(self) + } + }) : null, + self.next_release && self.last_release ? new Element('span.or', { + 'text': 'or' + }) : null, + self.next_release ? [new Element('a.button.green', { + 'text': self.last_release ? 'another release' : 'the best release', + 'events': { + 'click': self.tryNextRelease.bind(self) + } + }), + new Element('span.or', { + 'text': 'or pick one below' + })] : null + ) + } self.movie.slide('in', self.options_container); }, get: function(release, type){ - var self = this; - - return (release.info.filter(function(info){ - return type == info.identifier - }).pick() || {}).value || 'n/a' + return release.info[type] || 'n/a' }, download: function(release){ @@ -444,6 +514,25 @@ var ReleaseAction = new Class({ } }) + }, + + tryNextRelease: function(movie_id){ + var self = this; + + if(self.last_release) + self.ignore(self.last_release); + + if(self.next_release) + self.download(self.next_release); + + }, + + trySameRelease: function(movie_id){ + var self = this; + + if(self.last_release) + self.download(self.last_release); + } }); diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py index 2d82b61..e2c2bb8 100644 --- a/couchpotato/core/plugins/searcher/__init__.py +++ b/couchpotato/core/plugins/searcher/__init__.py @@ -49,10 +49,9 @@ config = [{ 'description': 'Detect movie status every X minutes. Will start the renamer if movie is completed or handle failed download if these options are enabled', }, { - 'name': 'failed download', + 'name': 'next_on_failed', 'default': True, 'type': 'bool', - 'advanced': True, 'description': 'Try the next best release for a movie after a download failed.', }, ], diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 16e3e5a..1d966ee 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -1,6 +1,8 @@ from couchpotato import get_session +from couchpotato.api import addApiView from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import simplifyString, toUnicode +from couchpotato.core.helpers.request import jsonified, getParam from couchpotato.core.helpers.variable import md5, getImdb, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin @@ -25,11 +27,15 @@ class Searcher(Plugin): addEvent('searcher.single', self.single) addEvent('searcher.correct_movie', self.correctMovie) addEvent('searcher.download', self.download) - addEvent('searcher.checksnatched', self.checksnatched) + addEvent('searcher.check_snatched', self.checkSnatched) + + addApiView('searcher.try_next', self.tryNextReleaseView, docs = { + 'desc': 'Try next best release', + }) # Schedule cronjob fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) - fireEvent('schedule.interval', 'searcher.checksnatched', self.checksnatched, minutes = self.conf('run_every')) + fireEvent('schedule.interval', 'searcher.check_snatched', self.checkSnatched, minutes = self.conf('run_every')) def all_movies(self): @@ -439,14 +445,17 @@ class Searcher(Plugin): return False - def checksnatched(self): + def checkSnatched(self): snatched_status = fireEvent('status.get', 'snatched', single = True) ignored_status = fireEvent('status.get', 'ignored', single = True) + failed_status = fireEvent('status.get', 'failed', single = True) db = get_session() rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) - log.info('Checking status snatched releases...') + if rels: + log.debug('Checking status snatched releases...') + scanrequired = False for rel in rels: @@ -462,57 +471,66 @@ class Searcher(Plugin): for info in rel.info: item[info.identifier] = info.value - movie = rel.movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) + movie_dict = fireEvent('movie.get', rel.movie_id, single = True) # check status - downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie) - if not downloadstatus: # Downloader not compatible with download status + downloadstatus = fireEvent('download.status', data = item, movie = movie_dict, single = True) + if not downloadstatus: log.debug('Download status functionality is not implemented for active downloaders.') scanrequired = True - else: - log.debug('Download status: %s' , downloadstatus[0]) + log.debug('Download status: %s' , downloadstatus) - if downloadstatus[0] == 'Failed': - # if failed set status to ignored - rel.status_id = ignored_status.get('id') - db.commit() - - # search/download again - # if downloaded manually: # this is currently not stored... - # log.info('Download of %s failed...', item['name']) - # return - - if self.conf('failed download', default = True): - - #update movie to reflect release status update - movie = rel.movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) - log.info('Download of %s failed, trying next release...', item['name']) - fireEvent('searcher.single', movie) + if downloadstatus == 'failed': + if self.conf('next_on_failed'): + self.tryNextRelease(rel.movie_id) else: + rel.status_id = failed_status.get('id') + db.commit() + log.info('Download of %s failed.', item['name']) - elif downloadstatus[0] == 'Completed': + elif downloadstatus == 'completed': log.info('Download of %s completed!', item['name']) scanrequired = True - elif downloadstatus[0] == 'Not found': + elif downloadstatus == 'not_found': log.info('%s not found in downloaders', item['name']) rel.status_id = ignored_status.get('id') db.commit() - # Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd + # Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd if scanrequired: fireEvent('renamer.scan') - return + def tryNextReleaseView(self): + + trynext = self.tryNextRelease(getParam('id')) + + return jsonified({ + 'success': trynext + }) + + def tryNextRelease(self, movie_id, manual = False): + + snatched_status = fireEvent('status.get', 'snatched', single = True) + ignored_status = fireEvent('status.get', 'ignored', single = True) + + try: + movie_dict = fireEvent('movie.get', movie_id, single = True) + + db = get_session() + rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) + + for rel in rels: + rel.status_id = ignored_status.get('id') + db.commit() + + log.info('Trying next release for', getTitle(movie_dict['library'])) + fireEvent('searcher.single', movie_dict) + + return True + + except: + log.error('Failed searching for next release: %s', traceback.format_exc()) + return False diff --git a/couchpotato/core/plugins/status/main.py b/couchpotato/core/plugins/status/main.py index af2e879..91c2858 100644 --- a/couchpotato/core/plugins/status/main.py +++ b/couchpotato/core/plugins/status/main.py @@ -19,6 +19,7 @@ class StatusPlugin(Plugin): 'downloaded': 'Downloaded', 'wanted': 'Wanted', 'snatched': 'Snatched', + 'failed': 'Failed', 'deleted': 'Deleted', 'ignored': 'Ignored', } diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index c0860a9..b99e6c7 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -419,15 +419,18 @@ function randomString(length, extra) { return 0; }; - Array.implement('sortBy', function(){ - keyPaths.empty(); - Array.each(arguments, function(argument) { - switch (typeOf(argument)) { - case "array": saveKeyPath(argument); break; - case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break; - } - }); - return this.sort(comparer); + Array.implement({ + sortBy: function(){ + keyPaths.empty(); + + Array.each(arguments, function(argument) { + switch (typeOf(argument)) { + case "array": saveKeyPath(argument); break; + case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break; + } + }); + return this.sort(comparer); + } }); })(); diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 5f2dcf7..2ce6c14 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -31,7 +31,6 @@ window.addEvent('domready', function(){ 'IMDB': IMDBAction ,'Trailer': TrailerAction ,'Releases': ReleaseAction - ,'Edit': new Class({ Extends: MovieAction, @@ -170,7 +169,7 @@ window.addEvent('domready', function(){ (e).preventDefault(); if(!self.delete_container){ - self.delete_container = new Element('div.delete_container').adopt( + self.delete_container = new Element('div.buttons.delete_container').adopt( new Element('a.cancel', { 'text': 'Cancel', 'events': { From de3832b7b68cc3265aa05149728e88945af2c4e3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 13 Aug 2012 21:50:31 +0200 Subject: [PATCH 24/73] Filter try_next on movie. --- couchpotato/core/plugins/searcher/main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 1d966ee..01358ee 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -30,7 +30,10 @@ class Searcher(Plugin): addEvent('searcher.check_snatched', self.checkSnatched) addApiView('searcher.try_next', self.tryNextReleaseView, docs = { - 'desc': 'Try next best release', + 'desc': 'Marks the snatched results as ignored and try the next best release', + 'params': { + 'id': {'desc': 'The id of the movie'}, + }, }) # Schedule cronjob @@ -520,13 +523,16 @@ class Searcher(Plugin): movie_dict = fireEvent('movie.get', movie_id, single = True) db = get_session() - rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) + rels = db.query(Release).filter_by( + status_id = snatched_status.get('id'), + movie_id = movie_id + ).all() for rel in rels: rel.status_id = ignored_status.get('id') - db.commit() + db.commit() - log.info('Trying next release for', getTitle(movie_dict['library'])) + log.info('Trying next release for: %s', getTitle(movie_dict['library'])) fireEvent('searcher.single', movie_dict) return True From 2d6c31af970bddcfb3e6bb60d41816f1ba33fdba Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sat, 18 Aug 2012 19:52:07 +0200 Subject: [PATCH 25/73] Some failure messages do not contain 'fail'. No message at all means success. --- couchpotato/core/downloaders/sabnzbd/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index e174259..86d5459 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -122,7 +122,7 @@ class Sabnzbd(Downloader): if slot['category'] == self.conf('category'): log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) if slot['name'] == nzbname: - if slot['status'] == 'Failed' or 'fail' in slot['fail_message'].lower(): + if slot['status'] == 'Failed' or slot['fail_message'].strip(): # Delete failed download if self.conf('delete_failed', default = True): From a9545e4fa50017be6423cdbfddc009e031f6ce47 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Aug 2012 16:08:01 +0200 Subject: [PATCH 26/73] Remove paypal link --- couchpotato/static/scripts/page/about.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/couchpotato/static/scripts/page/about.js b/couchpotato/static/scripts/page/about.js index 93687b4..0b34679 100644 --- a/couchpotato/static/scripts/page/about.js +++ b/couchpotato/static/scripts/page/about.js @@ -100,16 +100,7 @@ var AboutSettingTab = new Class({ new Element('li[text=No speed or download limits]'), new Element('li[text=Free SSL Encrypted connections]') ) - ), - new Element('div.donate', { - 'html': - 'Or, buy me a (24 pack) Pepsi, for while I\'m coding ;)' + - '
' + - '' + - '' + - '' + - '
' - }) + ) ); }, From c423cd1f79988bbe190c22d6548647469794a1ef Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Aug 2012 16:23:42 +0200 Subject: [PATCH 27/73] Load new "thanks" frame in about page --- couchpotato/static/scripts/page/about.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/couchpotato/static/scripts/page/about.js b/couchpotato/static/scripts/page/about.js index 0b34679..0a96667 100644 --- a/couchpotato/static/scripts/page/about.js +++ b/couchpotato/static/scripts/page/about.js @@ -100,7 +100,12 @@ var AboutSettingTab = new Class({ new Element('li[text=No speed or download limits]'), new Element('li[text=Free SSL Encrypted connections]') ) - ) + ), + new Element('div.donate', { + 'html': + 'Or support me via:' + + '' + }) ); }, From 6e2380f203669f4abb46202d93ef74e0a8bf1cc2 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Aug 2012 16:35:50 +0200 Subject: [PATCH 28/73] NMA fix Thanks @Mochaka --- couchpotato/core/notifications/notifymyandroid/main.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/notifications/notifymyandroid/main.py b/couchpotato/core/notifications/notifymyandroid/main.py index a6a316d..7ac5936 100644 --- a/couchpotato/core/notifications/notifymyandroid/main.py +++ b/couchpotato/core/notifications/notifymyandroid/main.py @@ -15,11 +15,16 @@ class NotifyMyAndroid(Notification): nma.addkey(keys) nma.developerkey(self.conf('dev_key')) - # hacky fix for the event type + # hacky fix for the event type # as it seems to be part of the message now self.event = message.split(' ')[0] - - response = nma.push(self.default_title, self.event , message, self.conf('priority'), batch_mode = len(keys) > 1) + response = nma.push( + application = self.default_title, + event = self.event, + description = message, + priority = self.conf('priority'), + batch_mode = len(keys) > 1 + ) for key in keys: if not response[str(key)]['code'] == u'200': From 7ee04e3d18113fb95471d27960e645e510779e6e Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Aug 2012 17:41:16 +0200 Subject: [PATCH 29/73] nzbrus: Found some dvdrips in the misc category so adding to lookup --- couchpotato/core/providers/nzb/nzbsrus/__init__.py | 39 ++++++++ couchpotato/core/providers/nzb/nzbsrus/main.py | 111 +++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 couchpotato/core/providers/nzb/nzbsrus/__init__.py create mode 100644 couchpotato/core/providers/nzb/nzbsrus/main.py diff --git a/couchpotato/core/providers/nzb/nzbsrus/__init__.py b/couchpotato/core/providers/nzb/nzbsrus/__init__.py new file mode 100644 index 0000000..96322e1 --- /dev/null +++ b/couchpotato/core/providers/nzb/nzbsrus/__init__.py @@ -0,0 +1,39 @@ +from .main import Nzbsrus + +def start(): + return Nzbsrus() + +config = [{ + 'name': 'nzbsrus', + 'groups': [ + { + 'tab': 'searcher', + 'subtab': 'providers', + 'name': 'nzbsrus', + 'label': 'Nzbsrus', + 'wizard': True, + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + }, + { + 'name': 'userid', + 'label': 'User ID', + }, + { + 'name': 'api_key', + 'default': '', + 'label': 'Api Key', + }, + { + 'name': 'english_only', + 'default': 1, + 'type': 'bool', + 'label': 'English only', + 'description': 'Only search for English spoken movies on Nzbsrus', + }, + ], + }, + ], +}] diff --git a/couchpotato/core/providers/nzb/nzbsrus/main.py b/couchpotato/core/providers/nzb/nzbsrus/main.py new file mode 100644 index 0000000..2583691 --- /dev/null +++ b/couchpotato/core/providers/nzb/nzbsrus/main.py @@ -0,0 +1,111 @@ +from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.encoding import tryUrlencode +from couchpotato.core.helpers.rss import RSS +from couchpotato.core.logger import CPLog +from couchpotato.core.providers.nzb.base import NZBProvider +from couchpotato.environment import Env +import time +import xml.etree.ElementTree as XMLTree + +log = CPLog(__name__) + +# See the api http://www.nzbsrus.com/NzbsrusAPI.pdf +class Nzbsrus(NZBProvider, RSS): + + urls = { + 'download': 'https://www.nzbsrus.com/nzbdownload_rss.php/%s', + 'detail': 'https://www.nzbsrus.com/nzbdetails.php?id=%s', + 'search': 'https://www.nzbsrus.com/api.php?extended=1&xml=1&listname={date,grabs}', + } + + cat_ids = [ + ([90,45,51], ['720p', '1080p','brrip','bd50','dvdr']), + ([48,51], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), + + + + ] + cat_backup_id = 240 + + def search(self, movie, quality): + + results = [] + + if self.isDisabled(): + return results + + cat_id_string = '&'.join(['c%s=1' % x for x in self.getCatId(quality.get('identifier'))]) + + arguments = tryUrlencode({ + 'searchtext': 'imdb:'+movie['library']['identifier'][2:], + 'uid': self.conf('userid'), + 'key': self.conf('api_key'), + 'age': Env.setting('retention', section = 'nzb'), + + }) + # check for english_only + if self.conf('english_only'): + arguments += "&lang0=1&lang3=1&lang1=1" + + url = "%s&%s&%s" % (self.urls['search'], arguments ,cat_id_string) + + cache_key = 'nzbsrus_1.%s.%s' % (movie['library'].get('identifier'), cat_id_string) + single_cat = True + + data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()}) + if data: + try: + try: + data = XMLTree.fromstring(data) + nzbs = self.getElements(data, 'results/result') + except Exception, e: + log.debug('%s, %s', (self.getName(), e)) + return results + + for nzb in nzbs: + + title = self.getTextElement(nzb, "name") + if 'error' in title.lower(): continue + + id = self.getTextElement(nzb, "id") + size = int(round(int(self.getTextElement(nzb, "size")) / 1048576)) + age = int(round(( time.time() - int(self.getTextElement(nzb, "postdate")) ) / 86400 )) + + + new = { + 'id': id, + 'type': 'nzb', + 'provider': self.getName(), + 'name': title, + 'age': age, + 'size': size, + 'url': self.urls['download'] % id + self.getApiExt() + self.getTextElement(nzb, "key"), + 'download': self.download, + 'detail_url': self.urls['detail'] % id, + 'description': self.getTextElement(nzb, "addtext"), + 'check_nzb': True, + } + + 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: + new['score'] = fireEvent('score.calculate', new, movie, single = True) + results.append(new) + self.found(new) + + return results + except SyntaxError: + log.error('Failed to parse XML response from Nzbsrus.com') + + return results + + def download(self, url = '', nzb_id = ''): + return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()}) + + def getApiExt(self): + return '/%s/' % (self.conf('userid')) + + def isEnabled(self): + return NZBProvider.isEnabled(self) and self.conf('userid') and self.conf('api_key') From 3f35cba818f211f50b8e5c481872a5f8db2e5cad Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Aug 2012 17:57:56 +0200 Subject: [PATCH 30/73] NZBsRus cleanup --- couchpotato/core/providers/nzb/nzbsrus/__init__.py | 1 + couchpotato/core/providers/nzb/nzbsrus/main.py | 21 +++++++-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/couchpotato/core/providers/nzb/nzbsrus/__init__.py b/couchpotato/core/providers/nzb/nzbsrus/__init__.py index 96322e1..70f3e3f 100644 --- a/couchpotato/core/providers/nzb/nzbsrus/__init__.py +++ b/couchpotato/core/providers/nzb/nzbsrus/__init__.py @@ -11,6 +11,7 @@ config = [{ 'subtab': 'providers', 'name': 'nzbsrus', 'label': 'Nzbsrus', + 'description': 'See NZBsRus', 'wizard': True, 'options': [ { diff --git a/couchpotato/core/providers/nzb/nzbsrus/main.py b/couchpotato/core/providers/nzb/nzbsrus/main.py index 2583691..20a51d5 100644 --- a/couchpotato/core/providers/nzb/nzbsrus/main.py +++ b/couchpotato/core/providers/nzb/nzbsrus/main.py @@ -9,7 +9,6 @@ import xml.etree.ElementTree as XMLTree log = CPLog(__name__) -# See the api http://www.nzbsrus.com/NzbsrusAPI.pdf class Nzbsrus(NZBProvider, RSS): urls = { @@ -19,11 +18,8 @@ class Nzbsrus(NZBProvider, RSS): } cat_ids = [ - ([90,45,51], ['720p', '1080p','brrip','bd50','dvdr']), - ([48,51], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), - - - + ([90, 45, 51], ['720p', '1080p', 'brrip', 'bd50', 'dvdr']), + ([48, 51], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), ] cat_backup_id = 240 @@ -37,17 +33,18 @@ class Nzbsrus(NZBProvider, RSS): cat_id_string = '&'.join(['c%s=1' % x for x in self.getCatId(quality.get('identifier'))]) arguments = tryUrlencode({ - 'searchtext': 'imdb:'+movie['library']['identifier'][2:], + 'searchtext': 'imdb:' + movie['library']['identifier'][2:], 'uid': self.conf('userid'), 'key': self.conf('api_key'), 'age': Env.setting('retention', section = 'nzb'), }) + # check for english_only if self.conf('english_only'): arguments += "&lang0=1&lang3=1&lang1=1" - url = "%s&%s&%s" % (self.urls['search'], arguments ,cat_id_string) + url = "%s&%s&%s" % (self.urls['search'], arguments , cat_id_string) cache_key = 'nzbsrus_1.%s.%s' % (movie['library'].get('identifier'), cat_id_string) single_cat = True @@ -69,9 +66,8 @@ class Nzbsrus(NZBProvider, RSS): id = self.getTextElement(nzb, "id") size = int(round(int(self.getTextElement(nzb, "size")) / 1048576)) - age = int(round(( time.time() - int(self.getTextElement(nzb, "postdate")) ) / 86400 )) + age = int(round((time.time() - int(self.getTextElement(nzb, "postdate"))) / 86400)) - new = { 'id': id, 'type': 'nzb', @@ -88,7 +84,7 @@ class Nzbsrus(NZBProvider, RSS): is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = True, single_category = single_cat, single = True) + imdb_results = True, single = True) if is_correct_movie: new['score'] = fireEvent('score.calculate', new, movie, single = True) @@ -106,6 +102,3 @@ class Nzbsrus(NZBProvider, RSS): def getApiExt(self): return '/%s/' % (self.conf('userid')) - - def isEnabled(self): - return NZBProvider.isEnabled(self) and self.conf('userid') and self.conf('api_key') From e33c6b3a5fd40430f9df1396082c36b539279b0c Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Aug 2012 22:11:13 +0200 Subject: [PATCH 31/73] Added urls to providers --- couchpotato/core/providers/nzb/mysterbin/__init__.py | 2 +- couchpotato/core/providers/nzb/newzbin/__init__.py | 1 + couchpotato/core/providers/nzb/nzbclub/__init__.py | 2 +- couchpotato/core/providers/nzb/nzbindex/__init__.py | 2 +- couchpotato/core/providers/nzb/nzbmatrix/__init__.py | 1 + couchpotato/core/providers/torrent/kickasstorrents/__init__.py | 1 + couchpotato/core/providers/torrent/publichd/__init__.py | 4 ++-- couchpotato/core/providers/torrent/sceneaccess/__init__.py | 1 + couchpotato/core/providers/torrent/scenehd/__init__.py | 1 + couchpotato/core/providers/torrent/thepiratebay/__init__.py | 2 +- couchpotato/core/providers/torrent/torrentleech/__init__.py | 1 + 11 files changed, 12 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/providers/nzb/mysterbin/__init__.py b/couchpotato/core/providers/nzb/mysterbin/__init__.py index 0c75955..3f0d1d3 100644 --- a/couchpotato/core/providers/nzb/mysterbin/__init__.py +++ b/couchpotato/core/providers/nzb/mysterbin/__init__.py @@ -10,7 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'Mysterbin', - 'description': '', + 'description': 'Free provider, less accurate. See Mysterbin', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/newzbin/__init__.py b/couchpotato/core/providers/nzb/newzbin/__init__.py index 4ebd849..06b2548 100644 --- a/couchpotato/core/providers/nzb/newzbin/__init__.py +++ b/couchpotato/core/providers/nzb/newzbin/__init__.py @@ -10,6 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'newzbin', + 'description': 'See Newzbin', 'wizard': True, 'options': [ { diff --git a/couchpotato/core/providers/nzb/nzbclub/__init__.py b/couchpotato/core/providers/nzb/nzbclub/__init__.py index 9c14e10..e3387a3 100644 --- a/couchpotato/core/providers/nzb/nzbclub/__init__.py +++ b/couchpotato/core/providers/nzb/nzbclub/__init__.py @@ -10,7 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'NZBClub', - 'description': '', + 'description': 'Free provider, less accurate. See NZBClub', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/nzbindex/__init__.py b/couchpotato/core/providers/nzb/nzbindex/__init__.py index 8a3261b..59b2730 100644 --- a/couchpotato/core/providers/nzb/nzbindex/__init__.py +++ b/couchpotato/core/providers/nzb/nzbindex/__init__.py @@ -10,7 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'nzbindex', - 'description': 'Free provider, but less accurate.', + 'description': 'Free provider, less accurate. See NZBIndex', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/nzb/nzbmatrix/__init__.py b/couchpotato/core/providers/nzb/nzbmatrix/__init__.py index 82b6ef6..8fc6b40 100644 --- a/couchpotato/core/providers/nzb/nzbmatrix/__init__.py +++ b/couchpotato/core/providers/nzb/nzbmatrix/__init__.py @@ -11,6 +11,7 @@ config = [{ 'subtab': 'providers', 'name': 'nzbmatrix', 'label': 'NZBMatrix', + 'description': 'See NZBMatrix', 'wizard': True, 'options': [ { diff --git a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py index 6514643..2401f95 100644 --- a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py +++ b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py @@ -10,6 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'KickAssTorrents', + 'description': 'See KickAssTorrents', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/publichd/__init__.py b/couchpotato/core/providers/torrent/publichd/__init__.py index c28781e..94d0825 100644 --- a/couchpotato/core/providers/torrent/publichd/__init__.py +++ b/couchpotato/core/providers/torrent/publichd/__init__.py @@ -10,7 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'PublicHD', - 'description': 'Public Torrent site with only HD content.', + 'description': 'Public Torrent site with only HD content. See PublicHD', 'options': [ { 'name': 'enabled', @@ -20,4 +20,4 @@ config = [{ ], }, ], -}] \ No newline at end of file +}] diff --git a/couchpotato/core/providers/torrent/sceneaccess/__init__.py b/couchpotato/core/providers/torrent/sceneaccess/__init__.py index 3a12863..28b7ca3 100644 --- a/couchpotato/core/providers/torrent/sceneaccess/__init__.py +++ b/couchpotato/core/providers/torrent/sceneaccess/__init__.py @@ -10,6 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'SceneAccess', + 'description': 'See SceneAccess', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/scenehd/__init__.py b/couchpotato/core/providers/torrent/scenehd/__init__.py index d4b7a0b..c9f18be 100644 --- a/couchpotato/core/providers/torrent/scenehd/__init__.py +++ b/couchpotato/core/providers/torrent/scenehd/__init__.py @@ -10,6 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'SceneHD', + 'description': 'See SceneHD', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/thepiratebay/__init__.py b/couchpotato/core/providers/torrent/thepiratebay/__init__.py index 8aa4911..2f8872e 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/__init__.py +++ b/couchpotato/core/providers/torrent/thepiratebay/__init__.py @@ -9,7 +9,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'ThePirateBay', - 'description': 'The world\'s largest bittorrent tracker.', + 'description': 'The world\'s largest bittorrent tracker. See ThePirateBay', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/torrentleech/__init__.py b/couchpotato/core/providers/torrent/torrentleech/__init__.py index 19627e1..482dfda 100644 --- a/couchpotato/core/providers/torrent/torrentleech/__init__.py +++ b/couchpotato/core/providers/torrent/torrentleech/__init__.py @@ -10,6 +10,7 @@ config = [{ 'tab': 'searcher', 'subtab': 'providers', 'name': 'TorrentLeech', + 'description': 'See TorrentLeech', 'options': [ { 'name': 'enabled', From d817179747bbd3cb7a5cc7e3c8ab6a82384d7494 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Aug 2012 22:46:32 +0200 Subject: [PATCH 32/73] De-referer external urls. --- couchpotato/core/plugins/movie/static/movie.js | 11 +---------- couchpotato/static/scripts/couchpotato.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 2182b88..dbb28aa 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -323,19 +323,10 @@ var IMDBAction = new Class({ self.el = new Element('a.imdb', { 'title': 'Go to the IMDB page of ' + self.movie.getTitle(), - 'events': { - 'click': self.gotoIMDB.bind(self) - } + 'href': 'http://www.imdb.com/title/'+self.id+'/' }); if(!self.id) self.disable(); - }, - - gotoIMDB: function(e){ - var self = this; - (e).preventDefault(); - - window.open('http://www.imdb.com/title/'+self.id+'/'); } }); diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index b99e6c7..9e66e34 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -29,6 +29,7 @@ var CouchPotato = new Class({ History.addEvent('change', self.openPage.bind(self)); self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self)); + self.c.addEvent('click:relay(a[href^=http])', self.openDerefered.bind(self)); }, getOption: function(name){ @@ -269,6 +270,17 @@ var CouchPotato = new Class({ createUrl: function(action, params){ return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '') + }, + + openDerefered: function(e, el){ + (e).stop(); + + var url = 'http://www.dereferer.org/?' + el.get('href'); + + if(el.get('target') == '_blank' || (e.meta && Browser.Platform.mac) || (e.control && !Browser.Platform.mac)) + window.open(url); + else + window.location = url; } }); From bdfb073bca972f7933b4cda246941cfe29fc830b Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 14:14:57 +0200 Subject: [PATCH 33/73] Catch filetime error. fix #741 --- couchpotato/core/plugins/scanner/main.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index 7e47ed9..0bc4768 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -303,7 +303,15 @@ class Scanner(Plugin): break if file_too_new: - log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time.ctime(file_time[0]), identifier)) + try: + time_string = time.ctime(file_time[0]) + except: + try: + time_string = time.ctime(file_time[1]) + except: + time_string = 'unknown' + + log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time_string, identifier)) # Delete the unsorted list del group['unsorted_files'] From b599bc384bb2e4675ec7d2dce48138416493365c Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 14:23:14 +0200 Subject: [PATCH 34/73] Add updater type to about page --- couchpotato/static/scripts/page/about.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchpotato/static/scripts/page/about.js b/couchpotato/static/scripts/page/about.js index 0a96667..8ac38ba 100644 --- a/couchpotato/static/scripts/page/about.js +++ b/couchpotato/static/scripts/page/about.js @@ -58,6 +58,8 @@ var AboutSettingTab = new Class({ } } }), + new Element('dt[text=Updater]'), + self.updater_type = new Element('dd.updater'), new Element('dt[text=ID]'), new Element('dd', {'text': App.getOption('pid')}), new Element('dt[text=Directories]'), @@ -115,6 +117,7 @@ var AboutSettingTab = new Class({ var self = this; var date = new Date(json.version.date * 1000); self.version_text.set('text', json.version.hash + ' ('+date.toUTCString()+')'); + self.updater_type.set('text', json.version.type); } }); From ada4710a0a51abebe5621dbcf2abcad0d07d968b Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 14:31:38 +0200 Subject: [PATCH 35/73] Correctly select edit quality. fix #736 --- couchpotato/static/scripts/page/wanted.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 2ce6c14..3ef10d7 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -73,20 +73,23 @@ window.addEvent('domready', function(){ new Element('option', { 'text': alt.title }).inject(self.title_select); - + if(alt['default']) self.title_select.set('value', alt.title); }); Quality.getActiveProfiles().each(function(profile){ + + var profile_id = profile.id ? profile.id : profile.data.id; + new Element('option', { - 'value': profile.id ? profile.id : profile.data.id, + 'value': profile_id, 'text': profile.label ? profile.label : profile.data.label }).inject(self.profile_select); - if(self.movie.profile) - self.profile_select.set('value', profile.id ? profile.id : profile.data.id); + if(self.movie.profile && self.movie.profile.data.id == profile_id) + self.profile_select.set('value', profile_id); }); } From cf86948c138bf1e266d095b01c3c60f99bf55130 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 15:42:26 +0200 Subject: [PATCH 36/73] Remove empty folder when found better quality. fix #735 --- couchpotato/core/plugins/renamer/main.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index a2882ea..9a443ed 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -327,6 +327,11 @@ class Renamer(Plugin): try: if os.path.isfile(src): os.remove(src) + + parent_dir = os.path.normpath(os.path.dirname(src)) + if os.path.isdir(parent_dir) and destination != parent_dir: + self.deleteEmptyFolder(parent_dir, show_error = False) + except: log.error('Failed removing %s: %s', (src, traceback.format_exc())) self.tagDir(group, 'failed_remove') @@ -464,8 +469,9 @@ class Renamer(Plugin): def replaceDoubles(self, string): return string.replace(' ', ' ').replace(' .', '.') - def deleteEmptyFolder(self, folder): + def deleteEmptyFolder(self, folder, show_error = True): + loge = log.error if show_error else log.debug for root, dirs, files in os.walk(folder): for dir_name in dirs: @@ -474,9 +480,9 @@ class Renamer(Plugin): try: os.rmdir(full_path) except: - log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc())) + loge('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc())) try: os.rmdir(folder) except: - log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) + loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) From 4275be005902ad64d45d6541b9c25415521af119 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 17:06:49 +0200 Subject: [PATCH 37/73] Add some tooltips to quality. closes #725 --- couchpotato/core/plugins/movie/static/movie.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index dbb28aa..4ddac68 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -140,8 +140,10 @@ var Movie = new Class({ self.profile.getTypes().each(function(type){ var q = self.addQuality(type.quality_id || type.get('quality_id')); - if(type.finish == true || type.get('finish')) + if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){ q.addClass('finish'); + q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.') + } }); @@ -153,8 +155,11 @@ var Movie = new Class({ if(!q && (status.identifier == 'snatched' || status.identifier == 'done')) var q = self.addQuality(release.quality_id) - if (status && q) + + if (status && q && !q.hasClass(status.identifier)){ q.addClass(status.identifier); + q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label) + } }); @@ -175,7 +180,8 @@ var Movie = new Class({ var q = Quality.getQuality(quality_id); return new Element('span', { 'text': q.label, - 'class': 'q_'+q.identifier + ' q_id' + q.id + 'class': 'q_'+q.identifier + ' q_id' + q.id, + 'title': '' }).inject(self.quality); }, From 442444a95a408cff8106b7e212eee8a7c45f2578 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 17:36:10 +0200 Subject: [PATCH 38/73] Update readme. closes #717 --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2eb0f2f..b0e4222 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,12 @@ Once a movie is found, it will send it to SABnzbd or download the torrent to a s ## Running from Source -CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed also. +CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed also. -Windows: +Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for more details: -* Install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/) +* Install [Python 2.7](http://www.python.org/download/releases/2.7.3/) +* Then install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/) * If you come and ask on the forums 'why directory selection no work?', I will kill a kitten, also this is because you need PyWin32 * Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files. * Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`. From d64150d6a7fd2a250c59d5ad7fbb8595399015d8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 22:43:00 +0200 Subject: [PATCH 39/73] Magnet link improvements --- couchpotato/core/downloaders/base.py | 14 ++++++++++++++ couchpotato/core/downloaders/blackhole/main.py | 14 +++++++++++--- couchpotato/core/plugins/release/main.py | 4 +++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index d5f1c42..c910edd 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -4,6 +4,8 @@ from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env import os +import re +import traceback log = CPLog(__name__) @@ -45,6 +47,18 @@ class Downloader(Plugin): return is_correct + def magnetToTorrent(self, magnet_link): + torrent_hash = re.findall('urn:btih:([\w]{40})', magnet_link)[0] + url = 'http://torrage.com/torrent/%s.torrent' % torrent_hash + + try: + filedata = self.urlopen(url) + return filedata + except: + log.error('Failed converting magnet url to torrent: %s, %s', (url, traceback.format_exc())) + + return False + def isDisabled(self, manual): return not self.isEnabled(manual) diff --git a/couchpotato/core/downloaders/blackhole/main.py b/couchpotato/core/downloaders/blackhole/main.py index a3f0361..4713adf 100644 --- a/couchpotato/core/downloaders/blackhole/main.py +++ b/couchpotato/core/downloaders/blackhole/main.py @@ -8,7 +8,7 @@ log = CPLog(__name__) class Blackhole(Downloader): - type = ['nzb', 'torrent'] + type = ['nzb', 'torrent', 'torrent_magnet'] def download(self, data = {}, movie = {}, manual = False, filedata = None): if self.isDisabled(manual) or (not self.isCorrectType(data.get('type')) or (not self.conf('use_for') in ['both', data.get('type')])): @@ -20,8 +20,16 @@ class Blackhole(Downloader): else: try: if not filedata or len(filedata) < 50: - log.error('No nzb/torrent available!') - return False + try: + if data.get('type') == 'torrent_magnet': + filedata = self.magnetToTorrent(data.get('url')) + data['type'] = 'torrent' + except: + log.error('Failed download torrent via magnet url: %s', traceback.format_exc()) + + if not filedata or len(filedata) < 50: + log.error('No nzb/torrent available: %s', data.get('url')) + return False fullPath = os.path.join(directory, self.createFileName(data, filedata, movie)) diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index 0abc065..9046034 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -170,7 +170,9 @@ class Release(Plugin): # Get matching provider provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True) - item['download'] = provider.download + + if item['type'] != 'torrent_magnet': + item['download'] = provider.download success = fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({ 'profile': {'types': {'quality': {}}}, From b8c6393409321eda8fe17a75a3f003acbe545342 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 22:43:14 +0200 Subject: [PATCH 40/73] Quality and filesize fix in release list. --- couchpotato/core/plugins/movie/static/movie.js | 4 ++-- couchpotato/core/plugins/searcher/main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 4ddac68..f8af5c0 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -417,8 +417,8 @@ var ReleaseAction = new Class({ }).adopt( new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}), new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}), - new Element('span.quality', {'text': quality.label || 'n/a'}), - new Element('span.size', {'text': (self.get(release, 'size'))}), + new Element('span.quality', {'text': quality.get('label') || 'n/a'}), + new Element('span.size', {'text': release.info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}), new Element('span.age', {'text': self.get(release, 'age')}), new Element('span.score', {'text': self.get(release, 'score')}), new Element('span.provider', {'text': self.get(release, 'provider')}), diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 01358ee..afabeae 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -150,7 +150,7 @@ class Searcher(Plugin): for info in nzb: try: - if not isinstance(nzb[info], (str, unicode, int, long)): + if not isinstance(nzb[info], (str, unicode, int, long, float)): continue rls_info = ReleaseInfo( From 5df95d4132f1a585aac7f19f1bbcf970f531788d Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 22:44:11 +0200 Subject: [PATCH 41/73] Fixed spelling in please wait message. --- couchpotato/static/scripts/couchpotato.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 9e66e34..61ee45b 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -188,7 +188,7 @@ var CouchPotato = new Class({ restart: function(message, title){ var self = this; - self.blockPage(message || 'Restarting... please wait. If this takes to long, something must have gone wrong.', title); + self.blockPage(message || 'Restarting... please wait. If this takes too long, something must have gone wrong.', title); Api.request('app.restart'); self.checkAvailable(1000); }, @@ -217,7 +217,7 @@ var CouchPotato = new Class({ Updater.check(onComplete) - self.blockPage('Please wait. If this takes to long, something must have gone wrong.', 'Checking for updates'); + self.blockPage('Please wait. If this takes too long, something must have gone wrong.', 'Checking for updates'); self.checkAvailable(3000); }, From 9a0a829802fcfc940dff2e3e0f8205f317bfcdd1 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 26 Aug 2012 22:59:40 +0200 Subject: [PATCH 42/73] Show info link again. fix #706 --- couchpotato/core/plugins/movie/static/movie.js | 8 ++------ couchpotato/core/providers/torrent/kickasstorrents/main.py | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index f8af5c0..17234b9 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -397,10 +397,6 @@ var ReleaseAction = new Class({ quality = Quality.getProfile(release.quality_id) || {}, info = release.info; - try { - var details_url = info.filter(function(item){ return item.identifier == 'detail_url' }).pick().value; - } catch(e){} - if( status.identifier == 'ignored' || status.identifier == 'failed'){ self.last_release = release; } @@ -422,8 +418,8 @@ var ReleaseAction = new Class({ new Element('span.age', {'text': self.get(release, 'age')}), new Element('span.score', {'text': self.get(release, 'score')}), new Element('span.provider', {'text': self.get(release, 'provider')}), - details_url ? new Element('a.info.icon', { - 'href': details_url, + release.info['detail_url'] ? new Element('a.info.icon', { + 'href': release.info['detail_url'], 'target': '_blank' }) : null, new Element('a.download.icon', { diff --git a/couchpotato/core/providers/torrent/kickasstorrents/main.py b/couchpotato/core/providers/torrent/kickasstorrents/main.py index 84a679f..6c157a6 100644 --- a/couchpotato/core/providers/torrent/kickasstorrents/main.py +++ b/couchpotato/core/providers/torrent/kickasstorrents/main.py @@ -13,7 +13,7 @@ class KickAssTorrents(TorrentProvider): urls = { 'test': 'http://kat.ph/', - 'detail': 'http://kat.ph/%s-t%s.html', + 'detail': 'http://kat.ph/%s', 'search': 'http://kat.ph/i%s/', } @@ -74,6 +74,7 @@ class KickAssTorrents(TorrentProvider): new['id'] = temp.get('id')[-8:] new['name'] = link.text new['url'] = td.find('a', 'imagnet')['href'] + new['detail_url'] = self.urls['detail'] % link['href'][1:] new['score'] = 20 if td.find('a', 'iverif') else 0 elif column_name is 'size': new['size'] = self.parseSize(td.text) From a6c4763d6d63ac0776153539ff3129d03bfe2cf9 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 27 Aug 2012 22:45:35 +0200 Subject: [PATCH 43/73] Update Userscript with @grant --- couchpotato/core/plugins/userscript/main.py | 2 +- couchpotato/core/plugins/userscript/template.js | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/couchpotato/core/plugins/userscript/main.py b/couchpotato/core/plugins/userscript/main.py index 9c5f05f..e5999ab 100644 --- a/couchpotato/core/plugins/userscript/main.py +++ b/couchpotato/core/plugins/userscript/main.py @@ -15,7 +15,7 @@ log = CPLog(__name__) class Userscript(Plugin): - version = 2 + version = 3 def __init__(self): addApiView('userscript.get//', self.getUserScript, static = True) diff --git a/couchpotato/core/plugins/userscript/template.js b/couchpotato/core/plugins/userscript/template.js index 8b07272..c30fad5 100644 --- a/couchpotato/core/plugins/userscript/template.js +++ b/couchpotato/core/plugins/userscript/template.js @@ -1,6 +1,7 @@ // ==UserScript== // @name CouchPotato UserScript // @description Add movies like a real CouchPotato +// @grant none // @version {{version}} // @match {{host}}* @@ -44,21 +45,19 @@ function create() { return A; } -if (typeof GM_addStyle == 'undefined'){ - GM_addStyle = function(css) { - var head = document.getElementsByTagName('head')[0], - style = document.createElement('style'); - if (!head) - return; +var addStyle = function(css) { + var head = document.getElementsByTagName('head')[0], + style = document.createElement('style'); + if (!head) + return; - style.type = 'text/css'; - style.textContent = css; - head.appendChild(style); - } + style.type = 'text/css'; + style.textContent = css; + head.appendChild(style); } // Styles -GM_addStyle('\ +addStyle('\ #cp_popup { font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -moz-border-radius: 6px 0px 0px 6px; -webkit-border-radius: 6px 0px 0px 6px; border-radius: 6px 0px 0px 6px; -moz-box-shadow: 0 0 20px rgba(0,0,0,0.5); -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.5); box-shadow: 0 0 20px rgba(0,0,0,0.5); position:fixed; z-index:9999; bottom:0; right:0; font-size:15px; margin: 20px 0; display: block; background:#4E5969; } \ #cp_popup.opened { width: 492px; } \ #cp_popup a#add_to { cursor:pointer; text-align:center; text-decoration:none; color: #000; display:block; padding:5px 0 5px 5px; } \ From 2d5481b89e30be4a66f3f4834b740fedf05e1c4e Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 27 Aug 2012 23:04:04 +0200 Subject: [PATCH 44/73] Remove unused host in javascript api --- couchpotato/templates/_desktop.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index 0c228dc..80a095e 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -43,7 +43,7 @@ - +