diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index 1946963..092bd40 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -15,6 +15,7 @@ log = CPLog(__name__) views = {} template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates')) + class BaseHandler(RequestHandler): def get_current_user(self): diff --git a/couchpotato/core/_base/_core.py b/couchpotato/core/_base/_core.py index 0a98103..1ca7d87 100644 --- a/couchpotato/core/_base/_core.py +++ b/couchpotato/core/_base/_core.py @@ -189,7 +189,7 @@ class Core(Plugin): def signalHandler(self): if Env.get('daemonized'): return - def signal_handler(signal, frame): + def signal_handler(*args, **kwargs): fireEvent('app.shutdown', single = True) signal.signal(signal.SIGINT, signal_handler) diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py index 593593a..603c57d 100644 --- a/couchpotato/core/_base/clientscript.py +++ b/couchpotato/core/_base/clientscript.py @@ -57,13 +57,12 @@ class ClientScript(Plugin): ], } - urls = {'style': {}, 'script': {}} minified = {'style': {}, 'script': {}} paths = {'style': {}, 'script': {}} comment = { - 'style': '/*** %s:%d ***/\n', - 'script': '// %s:%d\n' + 'style': '/*** %s:%d ***/\n', + 'script': '// %s:%d\n' } html = { @@ -95,7 +94,6 @@ class ClientScript(Plugin): else: self.registerStyle(core_url, file_path, position = 'front') - def minify(self): # Create cache dir @@ -129,7 +127,7 @@ class ClientScript(Plugin): data = cssmin(data) data = data.replace('../images/', '../static/images/') data = data.replace('../fonts/', '../static/fonts/') - data = data.replace('../../static/', '../static/') # Replace inside plugins + data = data.replace('../../static/', '../static/') # Replace inside plugins raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data}) @@ -192,6 +190,7 @@ class ClientScript(Plugin): prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow'] prefix_tags = ['ms', 'moz', 'webkit'] + def prefix(self, data): trimmed_data = re.sub('(\t|\n|\r)+', '', data) diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index cba91b7..cde17eb 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -164,14 +164,14 @@ class BaseUpdater(Plugin): def info(self): - version = self.getVersion() + current_version = self.getVersion() return { 'last_check': self.last_check, 'update_version': self.update_version, - 'version': version, + 'version': current_version, 'repo_name': '%s/%s' % (self.repo_user, self.repo_name), - 'branch': version.get('branch', self.branch), + 'branch': current_version.get('branch', self.branch), } def getVersion(self): diff --git a/couchpotato/core/_base/updater/static/updater.js b/couchpotato/core/_base/updater/static/updater.js index be436ed..331e070 100644 --- a/couchpotato/core/_base/updater/static/updater.js +++ b/couchpotato/core/_base/updater/static/updater.js @@ -5,7 +5,7 @@ var UpdaterBase = new Class({ initialize: function(){ var self = this; - App.addEvent('load', self.info.bind(self, 2000)) + App.addEvent('load', self.info.bind(self, 2000)); App.addEvent('unload', function(){ if(self.timer) clearTimeout(self.timer); @@ -66,7 +66,7 @@ var UpdaterBase = new Class({ var changelog = 'https://github.com/'+data.repo_name+'/compare/'+data.version.hash+'...'+data.branch; if(data.update_version.changelog) - changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash + changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash; self.message = new Element('div.message.update').adopt( new Element('span', { diff --git a/couchpotato/core/database.py b/couchpotato/core/database.py index 19fde90..532befa 100644 --- a/couchpotato/core/database.py +++ b/couchpotato/core/database.py @@ -21,6 +21,8 @@ class Database(object): def __init__(self): addApiView('database.list_documents', self.listDocuments) + addApiView('database.reindex', self.reindex) + addApiView('database.compact', self.compact) addApiView('database.document.update', self.updateDocument) addApiView('database.document.delete', self.deleteDocument) @@ -114,9 +116,36 @@ class Database(object): results[key] = [] results[key].append(document) - return results + def reindex(self, **kwargs): + + success = True + try: + db = self.getDB() + db.reindex() + except: + log.error('Failed index: %s', traceback.format_exc()) + success = False + + return { + 'success': success + } + + def compact(self, **kwargs): + + success = True + try: + db = self.getDB() + db.compact() + except: + log.error('Failed compact: %s', traceback.format_exc()) + success = False + + return { + 'success': success + } + def migrate(self): from couchpotato import Env @@ -181,7 +210,7 @@ class Database(object): migrate_data[ml][p[0]] = [migrate_data[ml][p[0]]] migrate_data[ml][p[0]].append(columns) - c.close() + conn.close() log.info('Getting data took %s', time.time() - migrate_start) @@ -297,6 +326,11 @@ class Database(object): releaseinfos = migrate_data['releaseinfo'] for x in releaseinfos: info = releaseinfos[x] + + # Skip if release doesn't exist for this info + if not migrate_data['release'].get(info.get('release_id')): + continue + if not migrate_data['release'][info.get('release_id')].get('info'): migrate_data['release'][info.get('release_id')]['info'] = {} @@ -438,7 +472,6 @@ class Database(object): log.info('Total migration took %s', time.time() - migrate_start) log.info('=' * 30) - # rename old database log.info('Renaming old database to %s ', old_db + '.old') os.rename(old_db, old_db + '.old') diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 8042c4a..dfbff9f 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -24,17 +24,21 @@ class Downloader(Provider): ] torrent_trackers = [ - 'http://tracker.publicbt.com/announce', 'udp://tracker.istole.it:80/announce', - 'udp://fr33domtracker.h33t.com:3310/announce', 'http://tracker.istole.it/announce', - 'http://tracker.ccc.de/announce', + 'udp://fr33domtracker.h33t.com:3310/announce', + 'http://tracker.publicbt.com/announce', 'udp://tracker.publicbt.com:80/announce', + 'http://tracker.ccc.de/announce', 'udp://tracker.ccc.de:80/announce', 'http://exodus.desync.com/announce', 'http://exodus.desync.com:6969/announce', 'http://tracker.publichd.eu/announce', + 'udp://tracker.publichd.eu:80/announce', 'http://tracker.openbittorrent.com/announce', + 'udp://tracker.openbittorrent.com/announce', + 'udp://tracker.openbittorrent.com:80/announce', + 'udp://open.demonii.com:1337/announce', ] def __init__(self): @@ -184,6 +188,7 @@ class Downloader(Provider): def pause(self, release_download, pause): return + class ReleaseDownloadList(list): provider = None diff --git a/couchpotato/core/downloaders/nzbget.py b/couchpotato/core/downloaders/nzbget.py index 115e0d2..cfa7598 100644 --- a/couchpotato/core/downloaders/nzbget.py +++ b/couchpotato/core/downloaders/nzbget.py @@ -146,7 +146,7 @@ class NZBGet(Downloader): 'timeleft': timeleft, }) - for nzb in queue: # 'Parameters' is not passed in rpc.postqueue + for nzb in queue: # 'Parameters' is not passed in rpc.postqueue if nzb['NZBID'] in ids: log.debug('Found %s in NZBGet postprocessing queue', nzb['NZBFilename']) release_downloads.append({ diff --git a/couchpotato/core/downloaders/qbittorrent_.py b/couchpotato/core/downloaders/qbittorrent_.py index 558222d..0f88beb 100644 --- a/couchpotato/core/downloaders/qbittorrent_.py +++ b/couchpotato/core/downloaders/qbittorrent_.py @@ -14,6 +14,7 @@ log = CPLog(__name__) autoload = 'qBittorrent' + class qBittorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] @@ -107,25 +108,24 @@ class qBittorrent(Downloader): for torrent in torrents: if torrent.hash in ids: torrent.update_general() # get extra info + torrent_filelist = torrent.get_files() + torrent_files = [] - t_files = torrent.get_files() + torrent_dir = os.path.join(torrent.save_path, torrent.name) - check_dir = os.path.join(torrent.save_path, torrent.name) - if os.path.isdir(check_dir): - torrent.save_path = check_dir + if os.path.isdir(torrent_dir): + torrent.save_path = torrent_dir - if len(t_files) > 1 and os.path.isdir(torrent.save_path): # multi file torrent + if len(torrent_filelist) > 1 and os.path.isdir(torrent_dir): # multi file torrent, path.isdir check makes sure we're not in the root download folder for root, _, files in os.walk(torrent.save_path): for f in files: - p = os.path.join(root, f) - if os.path.isfile(p): - torrent_files.append(sp(p)) + torrent_files.append(sp(os.path.join(root, f))) else: # multi or single file placed directly in torrent.save_path - for f in t_files: - p = os.path.join(torrent.save_path, f.name) - if os.path.isfile(p): - torrent_files.append(sp(p)) + for f in torrent_filelist: + file_path = os.path.join(torrent.save_path, f.name) + if os.path.isfile(file_path): + torrent_files.append(sp(file_path)) release_downloads.append({ 'id': torrent.hash, diff --git a/couchpotato/core/downloaders/rtorrent_.py b/couchpotato/core/downloaders/rtorrent_.py index 98946b2..ab3ee9a 100644 --- a/couchpotato/core/downloaders/rtorrent_.py +++ b/couchpotato/core/downloaders/rtorrent_.py @@ -78,10 +78,10 @@ class rTorrent(Downloader): self.error_msg = '' try: - self.rt._verify_conn() + self.rt._verify_conn() except AssertionError as e: - self.error_msg = e.message - self.rt = None + self.error_msg = e.message + self.rt = None return self.rt @@ -94,44 +94,6 @@ class rTorrent(Downloader): return False - def updateProviderGroup(self, name, data): - if data.get('seed_time'): - log.info('seeding time ignored, not supported') - - if not name: - return False - - if not self.connect(): - return False - - views = self.rt.get_views() - - if name not in views: - self.rt.create_group(name) - - group = self.rt.get_group(name) - - try: - if data.get('seed_ratio'): - ratio = int(float(data.get('seed_ratio')) * 100) - log.debug('Updating provider ratio to %s, group name: %s', (ratio, name)) - - # Explicitly set all group options to ensure it is setup correctly - group.set_upload('1M') - group.set_min(ratio) - group.set_max(ratio) - group.set_command('d.stop') - group.enable() - else: - # Reset group action and disable it - group.set_command() - group.disable() - except MethodError as err: - log.error('Unable to set group options: %s', err.msg) - return False - - return True - def download(self, data = None, media = None, filedata = None): if not media: media = {} @@ -142,10 +104,6 @@ class rTorrent(Downloader): if not self.connect(): return False - group_name = 'cp_' + data.get('provider').lower() - if not self.updateProviderGroup(group_name, data): - return False - torrent_params = {} if self.conf('label'): torrent_params['label'] = self.conf('label') @@ -186,9 +144,6 @@ class rTorrent(Downloader): if self.conf('directory'): torrent.set_directory(self.conf('directory')) - # Set Ratio Group - torrent.set_visible(group_name) - # Start torrent if not self.conf('paused', default = 0): torrent.start() diff --git a/couchpotato/core/downloaders/transmission.py b/couchpotato/core/downloaders/transmission.py index dd11166..5c25dcf 100644 --- a/couchpotato/core/downloaders/transmission.py +++ b/couchpotato/core/downloaders/transmission.py @@ -160,6 +160,7 @@ class Transmission(Downloader): log.debug('Requesting Transmission to remove the torrent %s%s.', (release_download['name'], ' and cleanup the downloaded files' if delete_files else '')) return self.trpc.remove_torrent(release_download['id'], delete_files) + class TransmissionRPC(object): """TransmissionRPC lite library""" diff --git a/couchpotato/core/downloaders/utorrent.py b/couchpotato/core/downloaders/utorrent.py index 671dd9e..83df48e 100644 --- a/couchpotato/core/downloaders/utorrent.py +++ b/couchpotato/core/downloaders/utorrent.py @@ -29,14 +29,14 @@ class uTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] utorrent_api = None status_flags = { - 'STARTED' : 1, - 'CHECKING' : 2, - 'CHECK-START' : 4, - 'CHECKED' : 8, - 'ERROR' : 16, - 'PAUSED' : 32, - 'QUEUED' : 64, - 'LOADED' : 128 + 'STARTED': 1, + 'CHECKING': 2, + 'CHECK-START': 4, + 'CHECKED': 8, + 'ERROR': 16, + 'PAUSED': 32, + 'QUEUED': 64, + 'LOADED': 128 } def connect(self): diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 4cad884..4c0add1 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -2,7 +2,7 @@ from urllib import unquote import re from couchpotato.core.helpers.encoding import toUnicode -from couchpotato.core.helpers.variable import natsortKey, tryInt +from couchpotato.core.helpers.variable import natsortKey def getParams(params): @@ -44,6 +44,7 @@ def getParams(params): return dictToList(temp) non_decimal = re.compile(r'[^\d.]+') + def dictToList(params): if type(params) is dict: diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 34e66e9..459d0e9 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -216,6 +216,7 @@ def tryFloat(s): return float(s) except: return 0 + def natsortKey(string_): """See http://www.codinghorror.com/blog/archives/001018.html""" return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)] @@ -247,9 +248,6 @@ def getTitle(media_dict): except: log.error('Could not get title for %s', getIdentifier(media_dict)) return None - - log.error('Could not get title for %s', getIdentifier(media_dict)) - return None except: log.error('Could not get title for library item: %s', media_dict) return None @@ -296,8 +294,11 @@ def isSubFolder(sub_folder, base_folder): # Returns True if sub_folder is the same as or inside base_folder return base_folder and sub_folder and ss(os.path.normpath(base_folder).rstrip(os.path.sep) + os.path.sep) in ss(os.path.normpath(sub_folder).rstrip(os.path.sep) + os.path.sep) + # From SABNZBD re_password = [re.compile(r'(.+){{([^{}]+)}}$'), re.compile(r'(.+)\s+password\s*=\s*(.+)$', re.I)] + + def scanForPassword(name): m = None for reg in re_password: diff --git a/couchpotato/core/loader.py b/couchpotato/core/loader.py index 6d8d14a..11df37b 100644 --- a/couchpotato/core/loader.py +++ b/couchpotato/core/loader.py @@ -12,19 +12,22 @@ log = CPLog(__name__) class Loader(object): - plugins = {} - providers = {} - modules = {} + + def __init__(self): + self.plugins = {} + self.providers = {} + self.modules = {} + self.paths = {} def preload(self, root = ''): core = os.path.join(root, 'couchpotato', 'core') - self.paths = { + self.paths.update({ 'core': (0, 'couchpotato.core._base', os.path.join(core, '_base')), 'plugin': (1, 'couchpotato.core.plugins', os.path.join(core, 'plugins')), 'notifications': (20, 'couchpotato.core.notifications', os.path.join(core, 'notifications')), 'downloaders': (20, 'couchpotato.core.downloaders', os.path.join(core, 'downloaders')), - } + }) # Add media to loader self.addPath(root, ['couchpotato', 'core', 'media'], 25, recursive = True) diff --git a/couchpotato/core/media/__init__.py b/couchpotato/core/media/__init__.py index 8e704de..c491c01 100644 --- a/couchpotato/core/media/__init__.py +++ b/couchpotato/core/media/__init__.py @@ -5,6 +5,7 @@ from couchpotato import get_db, CPLog from couchpotato.core.event import addEvent, fireEvent, fireEventAsync from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.plugins.base import Plugin +import six log = CPLog(__name__) diff --git a/couchpotato/core/media/_base/library/__init__.py b/couchpotato/core/media/_base/library/__init__.py index 3e1babe..a404f81 100644 --- a/couchpotato/core/media/_base/library/__init__.py +++ b/couchpotato/core/media/_base/library/__init__.py @@ -1,5 +1,6 @@ from .main import Library + def autoload(): return Library() diff --git a/couchpotato/core/media/_base/matcher/__init__.py b/couchpotato/core/media/_base/matcher/__init__.py index 1e4cda3..c8b1e82 100644 --- a/couchpotato/core/media/_base/matcher/__init__.py +++ b/couchpotato/core/media/_base/matcher/__init__.py @@ -1,5 +1,6 @@ from .main import Matcher + def autoload(): return Matcher() diff --git a/couchpotato/core/media/_base/media/index.py b/couchpotato/core/media/_base/media/index.py index 42e6990..b3cd82f 100644 --- a/couchpotato/core/media/_base/media/index.py +++ b/couchpotato/core/media/_base/media/index.py @@ -99,7 +99,7 @@ from couchpotato.core.helpers.encoding import simplifyString""" class TitleIndex(TreeBasedIndex): - _version = 1 + _version = 2 custom_header = """from CodernityDB.tree_index import TreeBasedIndex from string import ascii_letters @@ -113,14 +113,14 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString""" return self.simplify(key) def make_key_value(self, data): - if data.get('_t') == 'media' and data.get('title') is not None: + if data.get('_t') == 'media' and data.get('title') is not None and len(data.get('title')) > 0: return self.simplify(data['title']), None def simplify(self, title): title = toUnicode(title) - nr_prefix = '' if title[0] in ascii_letters else '#' + nr_prefix = '' if title and len(title) > 0 and title[0] in ascii_letters else '#' title = simplifyString(title) for prefix in ['the ']: @@ -132,7 +132,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString""" class StartsWithIndex(TreeBasedIndex): - _version = 1 + _version = 2 custom_header = """from CodernityDB.tree_index import TreeBasedIndex from string import ascii_letters @@ -158,4 +158,4 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString""" title = title[len(prefix):] break - return str(title[0] if title[0] in ascii_letters else '#').lower() + return str(title[0] if title and len(title) > 0 and title[0] in ascii_letters else '#').lower() diff --git a/couchpotato/core/media/_base/media/main.py b/couchpotato/core/media/_base/media/main.py index 43a6d40..62e8a16 100644 --- a/couchpotato/core/media/_base/media/main.py +++ b/couchpotato/core/media/_base/media/main.py @@ -132,10 +132,10 @@ class MediaPlugin(MediaBase): if media: # Attach category - if media.get('category_id'): - media['category'] = db.get('id', media.get('category_id')) + try: media['category'] = db.get('id', media.get('category_id')) + except: pass - media['releases'] = list(fireEvent('release.for_media', media['_id'], single = True)) + media['releases'] = fireEvent('release.for_media', media['_id'], single = True) return media @@ -222,7 +222,6 @@ class MediaPlugin(MediaBase): del filter_by['media_status'] del filter_by['release_status'] - # Filter by combining ids for x in filter_by: media_ids = [n for n in media_ids if n in filter_by[x]] @@ -281,7 +280,7 @@ class MediaPlugin(MediaBase): for media_type in fireEvent('media.types', merge = True): def tempList(*args, **kwargs): - return self.listView(types = media_type, *args, **kwargs) + return self.listView(types = media_type, **kwargs) addApiView('%s.list' % media_type, tempList) def availableChars(self, types = None, status = None, release_status = None): @@ -350,7 +349,7 @@ class MediaPlugin(MediaBase): for media_type in fireEvent('media.types', merge = True): def tempChar(*args, **kwargs): - return self.charView(types = media_type, *args, **kwargs) + return self.charView(types = media_type, **kwargs) addApiView('%s.available_chars' % media_type, tempChar) def delete(self, media_id, delete_from = None): @@ -366,7 +365,7 @@ class MediaPlugin(MediaBase): deleted = True else: - media_releases = list(fireEvent('release.for_media', media['_id'], single = True)) + media_releases = fireEvent('release.for_media', media['_id'], single = True) total_releases = len(media_releases) total_deleted = 0 @@ -382,9 +381,8 @@ class MediaPlugin(MediaBase): if release.get('status') == 'done': db.delete(release) total_deleted += 1 - new_media_status = 'active' - if total_releases == total_deleted: + if (total_releases == total_deleted and media['status'] != 'active') or (delete_from == 'wanted' and media['status'] == 'active'): db.delete(media) deleted = True elif new_media_status: @@ -432,7 +430,7 @@ class MediaPlugin(MediaBase): move_to_wanted = True profile = db.get('id', m['profile_id']) - media_releases = list(fireEvent('release.for_media', m['_id'], single = True)) + media_releases = fireEvent('release.for_media', m['_id'], single = True) for q_identifier in profile['qualities']: index = profile['qualities'].index(q_identifier) diff --git a/couchpotato/core/media/_base/providers/base.py b/couchpotato/core/media/_base/providers/base.py index 13f0f6e..4476bc4 100644 --- a/couchpotato/core/media/_base/providers/base.py +++ b/couchpotato/core/media/_base/providers/base.py @@ -244,10 +244,16 @@ class YarrProvider(Provider): return 0 - def getCatId(self, identifier): + def getCatId(self, quality = None): + if not quality: quality = {} + identifier = quality.get('identifier') + + want_3d = False + if quality.get('custom'): + want_3d = quality['custom'].get('3d') for ids, qualities in self.cat_ids: - if identifier in qualities: + if identifier in qualities or (want_3d and '3d' in qualities): return ids if self.cat_backup_id: diff --git a/couchpotato/core/media/_base/providers/nzb/binsearch.py b/couchpotato/core/media/_base/providers/nzb/binsearch.py index 90e403d..90778af 100644 --- a/couchpotato/core/media/_base/providers/nzb/binsearch.py +++ b/couchpotato/core/media/_base/providers/nzb/binsearch.py @@ -18,7 +18,7 @@ class Base(NZBProvider): 'search': 'https://www.binsearch.info/index.php?%s', } - http_time_between_calls = 4 # Seconds + http_time_between_calls = 4 # Seconds def _search(self, media, quality, results): @@ -28,7 +28,7 @@ class Base(NZBProvider): try: html = BeautifulSoup(data) - main_table = html.find('table', attrs = {'id':'r2'}) + main_table = html.find('table', attrs = {'id': 'r2'}) if not main_table: return @@ -36,11 +36,11 @@ class Base(NZBProvider): items = main_table.find_all('tr') for row in items: - title = row.find('span', attrs = {'class':'s'}) + title = row.find('span', attrs = {'class': 's'}) if not title: continue - nzb_id = row.find('input', attrs = {'type':'checkbox'})['name'] + nzb_id = row.find('input', attrs = {'type': 'checkbox'})['name'] info = row.find('span', attrs = {'class':'d'}) size_match = re.search('size:.(?P[0-9\.]+.[GMB]+)', info.text) diff --git a/couchpotato/core/media/_base/providers/nzb/nzbclub.py b/couchpotato/core/media/_base/providers/nzb/nzbclub.py index 6ca6671..06ae7fe 100644 --- a/couchpotato/core/media/_base/providers/nzb/nzbclub.py +++ b/couchpotato/core/media/_base/providers/nzb/nzbclub.py @@ -18,7 +18,7 @@ class Base(NZBProvider, RSS): 'search': 'https://www.nzbclub.com/nzbfeeds.aspx?%s', } - http_time_between_calls = 4 #seconds + http_time_between_calls = 4 # seconds def _search(self, media, quality, results): @@ -55,7 +55,7 @@ class Base(NZBProvider, RSS): def getMoreInfo(self, item): full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000) html = BeautifulSoup(full_description) - nfo_pre = html.find('pre', attrs = {'class':'nfo'}) + nfo_pre = html.find('pre', attrs = {'class': 'nfo'}) description = toUnicode(nfo_pre.text) if nfo_pre else '' item['description'] = description diff --git a/couchpotato/core/media/_base/providers/nzb/nzbindex.py b/couchpotato/core/media/_base/providers/nzb/nzbindex.py index 8baa59a..ddcce8a 100644 --- a/couchpotato/core/media/_base/providers/nzb/nzbindex.py +++ b/couchpotato/core/media/_base/providers/nzb/nzbindex.py @@ -91,7 +91,7 @@ class Base(NZBProvider, RSS): nfo_url = re.search('href=\"(?P.+)\" ', item['description']).group('nfo') full_description = self.getCache('nzbindex.%s' % item['id'], url = nfo_url, cache_timeout = 25920000) html = BeautifulSoup(full_description) - item['description'] = toUnicode(html.find('pre', attrs = {'id':'nfo0'}).text) + item['description'] = toUnicode(html.find('pre', attrs = {'id': 'nfo0'}).text) except: pass diff --git a/couchpotato/core/media/_base/providers/nzb/omgwtfnzbs.py b/couchpotato/core/media/_base/providers/nzb/omgwtfnzbs.py index ab9efbd..acd12cf 100644 --- a/couchpotato/core/media/_base/providers/nzb/omgwtfnzbs.py +++ b/couchpotato/core/media/_base/providers/nzb/omgwtfnzbs.py @@ -20,7 +20,7 @@ class Base(NZBProvider, RSS): 'detail_url': 'https://omgwtfnzbs.org/details.php?id=%s', } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds cat_ids = [ ([15], ['dvdrip']), @@ -42,7 +42,7 @@ class Base(NZBProvider, RSS): q = '%s %s' % (title, movie['info']['year']) params = tryUrlencode({ 'search': q, - 'catid': ','.join([str(x) for x in self.getCatId(quality['identifier'])]), + 'catid': ','.join([str(x) for x in self.getCatId(quality)]), 'user': self.conf('username', default = ''), 'api': self.conf('api_key', default = ''), }) diff --git a/couchpotato/core/media/_base/providers/torrent/bithdtv.py b/couchpotato/core/media/_base/providers/torrent/bithdtv.py index b55bfff..94aafcd 100644 --- a/couchpotato/core/media/_base/providers/torrent/bithdtv.py +++ b/couchpotato/core/media/_base/providers/torrent/bithdtv.py @@ -21,7 +21,7 @@ class Base(TorrentProvider): } # Searches for movies only - BiT-HDTV's subcategory and resolution search filters appear to be broken - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds def _search(self, media, quality, results): @@ -40,7 +40,7 @@ class Base(TorrentProvider): html = BeautifulSoup(data) try: - result_table = html.find('table', attrs = {'width' : '750', 'class' : ''}) + result_table = html.find('table', attrs = {'width': '750', 'class': ''}) if result_table is None: return @@ -74,7 +74,7 @@ class Base(TorrentProvider): def getMoreInfo(self, item): full_description = self.getCache('bithdtv.%s' % item['id'], item['detail_url'], cache_timeout = 25920000) html = BeautifulSoup(full_description) - nfo_pre = html.find('table', attrs = {'class':'detail'}) + nfo_pre = html.find('table', attrs = {'class': 'detail'}) description = toUnicode(nfo_pre.text) if nfo_pre else '' item['description'] = description diff --git a/couchpotato/core/media/_base/providers/torrent/bitsoup.py b/couchpotato/core/media/_base/providers/torrent/bitsoup.py index d841c0f..9aeb624 100644 --- a/couchpotato/core/media/_base/providers/torrent/bitsoup.py +++ b/couchpotato/core/media/_base/providers/torrent/bitsoup.py @@ -20,7 +20,7 @@ class Base(TorrentProvider): 'baseurl': 'https://www.bitsoup.me/%s', } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds def _searchOnTitle(self, title, movie, quality, results): @@ -30,7 +30,7 @@ class Base(TorrentProvider): }) url = "%s&%s" % (self.urls['search'], arguments) - url = self.urls['search'] % self.buildUrl(media, quality) + url = self.urls['search'] % self.buildUrl(movie, quality) data = self.getHTMLData(url) if data: @@ -74,7 +74,6 @@ class Base(TorrentProvider): except: log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) - def getLoginParams(self): return { 'username': self.conf('username'), @@ -82,7 +81,6 @@ class Base(TorrentProvider): 'ssl': 'yes', } - def loginSuccess(self, output): return 'logout.php' in output.lower() diff --git a/couchpotato/core/media/_base/providers/torrent/hdbits.py b/couchpotato/core/media/_base/providers/torrent/hdbits.py index c2e85ac..6cf8d57 100644 --- a/couchpotato/core/media/_base/providers/torrent/hdbits.py +++ b/couchpotato/core/media/_base/providers/torrent/hdbits.py @@ -19,7 +19,7 @@ class Base(TorrentProvider): 'api': 'https://hdbits.org/api/torrents' } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds def _post_query(self, **params): @@ -56,7 +56,7 @@ class Base(TorrentProvider): 'name': result['name'], 'url': self.urls['download'] % (result['id'], self.conf('passkey')), 'detail_url': self.urls['detail'] % result['id'], - 'size': tryInt(result['size'])/1024/1024, + 'size': tryInt(result['size']) / 1024 / 1024, 'seeders': tryInt(result['seeders']), 'leechers': tryInt(result['leechers']) }) diff --git a/couchpotato/core/media/_base/providers/torrent/ilovetorrents.py b/couchpotato/core/media/_base/providers/torrent/ilovetorrents.py index d9db53b..56b7dac 100644 --- a/couchpotato/core/media/_base/providers/torrent/ilovetorrents.py +++ b/couchpotato/core/media/_base/providers/torrent/ilovetorrents.py @@ -23,9 +23,9 @@ class Base(TorrentProvider): } cat_ids = [ - (['41'], ['720p', '1080p', 'brrip']), - (['19'], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), - (['20'], ['dvdr']) + (['41'], ['720p', '1080p', 'brrip']), + (['19'], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), + (['20'], ['dvdr']) ] cat_backup_id = 200 @@ -36,7 +36,7 @@ class Base(TorrentProvider): page = 0 total_pages = 1 - cats = self.getCatId(quality['identifier']) + cats = self.getCatId(quality) while page < total_pages: @@ -79,7 +79,7 @@ class Base(TorrentProvider): return confirmed + trusted + vip + moderated id = re.search('id=(?P\d+)&', link).group('id') - url = self.urls['download'] % (download) + url = self.urls['download'] % download fileSize = self.parseSize(result.select('td.rowhead')[5].text) results.append({ @@ -113,7 +113,7 @@ class Base(TorrentProvider): try: full_description = self.getHTMLData(item['detail_url']) html = BeautifulSoup(full_description) - nfo_pre = html.find('td', attrs = {'class':'main'}).findAll('table')[1] + nfo_pre = html.find('td', attrs = {'class': 'main'}).findAll('table')[1] description = toUnicode(nfo_pre.text) if nfo_pre else '' except: log.error('Failed getting more info for %s', item['name']) diff --git a/couchpotato/core/media/_base/providers/torrent/iptorrents.py b/couchpotato/core/media/_base/providers/torrent/iptorrents.py index 4b9bd79..0433d3e 100644 --- a/couchpotato/core/media/_base/providers/torrent/iptorrents.py +++ b/couchpotato/core/media/_base/providers/torrent/iptorrents.py @@ -18,21 +18,21 @@ class Base(TorrentProvider): 'base_url': 'https://www.iptorrents.com', 'login': 'https://www.iptorrents.com/torrents/', 'login_check': 'https://www.iptorrents.com/inbox.php', - 'search': 'https://www.iptorrents.com/torrents/?l%d=1%s&q=%s&qf=ti&p=%d', + 'search': 'https://www.iptorrents.com/torrents/?%s%%s&q=%s&qf=ti&p=%%d', } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds cat_backup_id = None def buildUrl(self, title, media, quality): - return self._buildUrl(title.replace(':', ''), quality['identifier']) + return self._buildUrl(title.replace(':', ''), quality) - def _buildUrl(self, query, quality_identifier): + def _buildUrl(self, query, quality): - cat_ids = self.getCatId(quality_identifier) + cat_ids = self.getCatId(quality) if not cat_ids: - log.warning('Unable to find category ids for identifier "%s"', quality_identifier) + log.warning('Unable to find category ids for identifier "%s"', quality.get('identifier')) return None return self.urls['search'] % ("&".join(("l%d=" % x) for x in cat_ids), tryUrlencode(query).replace('%', '%%')) @@ -53,14 +53,14 @@ class Base(TorrentProvider): html = BeautifulSoup(data) try: - page_nav = html.find('span', attrs = {'class' : 'page_nav'}) + page_nav = html.find('span', attrs = {'class': 'page_nav'}) if page_nav: next_link = page_nav.find("a", text = "Next") if next_link: final_page_link = next_link.previous_sibling.previous_sibling pages = int(final_page_link.string) - result_table = html.find('table', attrs = {'class' : 'torrents'}) + result_table = html.find('table', attrs = {'class': 'torrents'}) if not result_table or 'nothing found!' in data.lower(): return @@ -80,8 +80,8 @@ class Base(TorrentProvider): torrent_download_url = self.urls['base_url'] + (result.find_all('td')[3].find('a'))['href'].replace(' ', '.') torrent_details_url = self.urls['base_url'] + torrent['href'] torrent_size = self.parseSize(result.find_all('td')[5].string) - torrent_seeders = tryInt(result.find('td', attrs = {'class' : 'ac t_seeders'}).string) - torrent_leechers = tryInt(result.find('td', attrs = {'class' : 'ac t_leechers'}).string) + torrent_seeders = tryInt(result.find('td', attrs = {'class': 'ac t_seeders'}).string) + torrent_leechers = tryInt(result.find('td', attrs = {'class': 'ac t_leechers'}).string) results.append({ 'id': torrent_id, diff --git a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py index b347ccb..534dc92 100644 --- a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py +++ b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py @@ -26,7 +26,7 @@ class Base(TorrentMagnetProvider): (['dvd'], ['dvdr']), ] - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds cat_backup_id = None proxy_list = [ @@ -42,7 +42,7 @@ class Base(TorrentMagnetProvider): if data: - cat_ids = self.getCatId(quality['identifier']) + cat_ids = self.getCatId(quality) table_order = ['name', 'size', None, 'age', 'seeds', 'leechers'] try: diff --git a/couchpotato/core/media/_base/providers/torrent/passthepopcorn.py b/couchpotato/core/media/_base/providers/torrent/passthepopcorn.py index 5368ad3..80e2348 100644 --- a/couchpotato/core/media/_base/providers/torrent/passthepopcorn.py +++ b/couchpotato/core/media/_base/providers/torrent/passthepopcorn.py @@ -18,12 +18,12 @@ log = CPLog(__name__) class Base(TorrentProvider): urls = { - 'domain': 'https://tls.passthepopcorn.me', - 'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s', - 'torrent': 'https://tls.passthepopcorn.me/torrents.php', - 'login': 'https://tls.passthepopcorn.me/ajax.php?action=login', - 'login_check': 'https://tls.passthepopcorn.me/ajax.php?action=login', - 'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d' + 'domain': 'https://tls.passthepopcorn.me', + 'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s', + 'torrent': 'https://tls.passthepopcorn.me/torrents.php', + 'login': 'https://tls.passthepopcorn.me/ajax.php?action=login', + 'login_check': 'https://tls.passthepopcorn.me/ajax.php?action=login', + 'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d' } http_time_between_calls = 2 @@ -136,23 +136,23 @@ class Base(TorrentProvider): def htmlToUnicode(self, text): def fixup(m): - text = m.group(0) - if text[:2] == "&#": + txt = m.group(0) + if txt[:2] == "&#": # character reference try: - if text[:3] == "&#x": - return unichr(int(text[3:-1], 16)) + if txt[:3] == "&#x": + return unichr(int(txt[3:-1], 16)) else: - return unichr(int(text[2:-1])) + return unichr(int(txt[2:-1])) except ValueError: pass else: # named entity try: - text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) + txt = unichr(htmlentitydefs.name2codepoint[txt[1:-1]]) except KeyError: pass - return text # leave as is + return txt # leave as is return re.sub("&#?\w+;", fixup, six.u('%s') % text) def unicodeToASCII(self, text): @@ -164,11 +164,11 @@ class Base(TorrentProvider): def getLoginParams(self): return { - 'username': self.conf('username'), - 'password': self.conf('password'), - 'passkey': self.conf('passkey'), - 'keeplogged': '1', - 'login': 'Login' + 'username': self.conf('username'), + 'password': self.conf('password'), + 'passkey': self.conf('passkey'), + 'keeplogged': '1', + 'login': 'Login' } def loginSuccess(self, output): diff --git a/couchpotato/core/media/_base/providers/torrent/publichd.py b/couchpotato/core/media/_base/providers/torrent/publichd.py index c17b655..1090be2 100644 --- a/couchpotato/core/media/_base/providers/torrent/publichd.py +++ b/couchpotato/core/media/_base/providers/torrent/publichd.py @@ -34,7 +34,7 @@ class Base(TorrentMagnetProvider): query = self.buildUrl(media) params = tryUrlencode({ - 'page':'torrents', + 'page': 'torrents', 'search': query, 'active': 1, }) diff --git a/couchpotato/core/media/_base/providers/torrent/sceneaccess.py b/couchpotato/core/media/_base/providers/torrent/sceneaccess.py index 9a27b05..2622e31 100644 --- a/couchpotato/core/media/_base/providers/torrent/sceneaccess.py +++ b/couchpotato/core/media/_base/providers/torrent/sceneaccess.py @@ -22,7 +22,7 @@ class Base(TorrentProvider): 'download': 'https://www.sceneaccess.eu/%s', } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds def _search(self, media, quality, results): @@ -33,16 +33,16 @@ class Base(TorrentProvider): html = BeautifulSoup(data) try: - resultsTable = html.find('table', attrs = {'id' : 'torrents-table'}) + resultsTable = html.find('table', attrs = {'id': 'torrents-table'}) if resultsTable is None: return - entries = resultsTable.find_all('tr', attrs = {'class' : 'tt_row'}) + entries = resultsTable.find_all('tr', attrs = {'class': 'tt_row'}) for result in entries: - link = result.find('td', attrs = {'class' : 'ttr_name'}).find('a') - url = result.find('td', attrs = {'class' : 'td_dl'}).find('a') - leechers = result.find('td', attrs = {'class' : 'ttr_leechers'}).find('a') + link = result.find('td', attrs = {'class': 'ttr_name'}).find('a') + url = result.find('td', attrs = {'class': 'td_dl'}).find('a') + leechers = result.find('td', attrs = {'class': 'ttr_leechers'}).find('a') torrent_id = link['href'].replace('details?id=', '') results.append({ @@ -50,8 +50,8 @@ class Base(TorrentProvider): 'name': link['title'], 'url': self.urls['download'] % url['href'], 'detail_url': self.urls['detail'] % torrent_id, - 'size': self.parseSize(result.find('td', attrs = {'class' : 'ttr_size'}).contents[0]), - 'seeders': tryInt(result.find('td', attrs = {'class' : 'ttr_seeders'}).find('a').string), + 'size': self.parseSize(result.find('td', attrs = {'class': 'ttr_size'}).contents[0]), + 'seeders': tryInt(result.find('td', attrs = {'class': 'ttr_seeders'}).find('a').string), 'leechers': tryInt(leechers.string) if leechers else 0, 'get_more_info': self.getMoreInfo, }) @@ -62,7 +62,7 @@ class Base(TorrentProvider): def getMoreInfo(self, item): full_description = self.getCache('sceneaccess.%s' % item['id'], item['detail_url'], cache_timeout = 25920000) html = BeautifulSoup(full_description) - nfo_pre = html.find('div', attrs = {'id':'details_table'}) + nfo_pre = html.find('div', attrs = {'id': 'details_table'}) description = toUnicode(nfo_pre.text) if nfo_pre else '' item['description'] = description diff --git a/couchpotato/core/media/_base/providers/torrent/thepiratebay.py b/couchpotato/core/media/_base/providers/torrent/thepiratebay.py index 4d81961..d401e6b 100644 --- a/couchpotato/core/media/_base/providers/torrent/thepiratebay.py +++ b/couchpotato/core/media/_base/providers/torrent/thepiratebay.py @@ -39,7 +39,7 @@ class Base(TorrentMagnetProvider): page = 0 total_pages = 1 - cats = self.getCatId(quality['identifier']) + cats = self.getCatId(quality) base_search_url = self.urls['search'] % self.getDomain() @@ -108,7 +108,7 @@ class Base(TorrentMagnetProvider): def getMoreInfo(self, item): full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000) html = BeautifulSoup(full_description) - nfo_pre = html.find('div', attrs = {'class':'nfo'}) + nfo_pre = html.find('div', attrs = {'class': 'nfo'}) description = toUnicode(nfo_pre.text) if nfo_pre else '' item['description'] = description diff --git a/couchpotato/core/media/_base/providers/torrent/torrentbytes.py b/couchpotato/core/media/_base/providers/torrent/torrentbytes.py index afb66eb..85a4fda 100644 --- a/couchpotato/core/media/_base/providers/torrent/torrentbytes.py +++ b/couchpotato/core/media/_base/providers/torrent/torrentbytes.py @@ -31,19 +31,19 @@ class Base(TorrentProvider): ([20], ['dvdr']), ] - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds cat_backup_id = None def _searchOnTitle(self, title, movie, quality, results): - url = self.urls['search'] % (tryUrlencode('%s %s' % (title.replace(':', ''), movie['info']['year'])), self.getCatId(quality['identifier'])[0]) + url = self.urls['search'] % (tryUrlencode('%s %s' % (title.replace(':', ''), movie['info']['year'])), self.getCatId(quality)[0]) data = self.getHTMLData(url) if data: html = BeautifulSoup(data) try: - result_table = html.find('table', attrs = {'border' : '1'}) + result_table = html.find('table', attrs = {'border': '1'}) if not result_table: return @@ -52,7 +52,7 @@ class Base(TorrentProvider): for result in entries[1:]: cells = result.find_all('td') - link = cells[1].find('a', attrs = {'class' : 'index'}) + link = cells[1].find('a', attrs = {'class': 'index'}) full_id = link['href'].replace('details.php?id=', '') torrent_id = full_id[:6] diff --git a/couchpotato/core/media/_base/providers/torrent/torrentday.py b/couchpotato/core/media/_base/providers/torrent/torrentday.py index dfb605b..bcc0593 100644 --- a/couchpotato/core/media/_base/providers/torrent/torrentday.py +++ b/couchpotato/core/media/_base/providers/torrent/torrentday.py @@ -16,7 +16,7 @@ class Base(TorrentProvider): 'download': 'http://www.td.af/download.php/%s/%s', } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds def _search(self, media, quality, results): diff --git a/couchpotato/core/media/_base/providers/torrent/torrentleech.py b/couchpotato/core/media/_base/providers/torrent/torrentleech.py index 208520d..fe42798 100644 --- a/couchpotato/core/media/_base/providers/torrent/torrentleech.py +++ b/couchpotato/core/media/_base/providers/torrent/torrentleech.py @@ -21,7 +21,7 @@ class Base(TorrentProvider): 'download': 'http://www.torrentleech.org%s', } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds cat_backup_id = None def _search(self, media, quality, results): @@ -34,7 +34,7 @@ class Base(TorrentProvider): html = BeautifulSoup(data) try: - result_table = html.find('table', attrs = {'id' : 'torrenttable'}) + result_table = html.find('table', attrs = {'id': 'torrenttable'}) if not result_table: return @@ -42,9 +42,9 @@ class Base(TorrentProvider): for result in entries[1:]: - link = result.find('td', attrs = {'class' : 'name'}).find('a') - url = result.find('td', attrs = {'class' : 'quickdownload'}).find('a') - details = result.find('td', attrs = {'class' : 'name'}).find('a') + link = result.find('td', attrs = {'class': 'name'}).find('a') + url = result.find('td', attrs = {'class': 'quickdownload'}).find('a') + details = result.find('td', attrs = {'class': 'name'}).find('a') results.append({ 'id': link['href'].replace('/torrent/', ''), @@ -52,8 +52,8 @@ class Base(TorrentProvider): 'url': self.urls['download'] % url['href'], 'detail_url': self.urls['download'] % details['href'], 'size': self.parseSize(result.find_all('td')[4].string), - 'seeders': tryInt(result.find('td', attrs = {'class' : 'seeders'}).string), - 'leechers': tryInt(result.find('td', attrs = {'class' : 'leechers'}).string), + 'seeders': tryInt(result.find('td', attrs = {'class': 'seeders'}).string), + 'leechers': tryInt(result.find('td', attrs = {'class': 'leechers'}).string), }) except: diff --git a/couchpotato/core/media/_base/providers/torrent/torrentshack.py b/couchpotato/core/media/_base/providers/torrent/torrentshack.py index 6c9d4cc..2a285b2 100644 --- a/couchpotato/core/media/_base/providers/torrent/torrentshack.py +++ b/couchpotato/core/media/_base/providers/torrent/torrentshack.py @@ -21,7 +21,7 @@ class Base(TorrentProvider): 'download': 'https://torrentshack.net/%s', } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # Seconds def _search(self, media, quality, results): @@ -32,16 +32,16 @@ class Base(TorrentProvider): html = BeautifulSoup(data) try: - result_table = html.find('table', attrs = {'id' : 'torrent_table'}) + result_table = html.find('table', attrs = {'id': 'torrent_table'}) if not result_table: return - entries = result_table.find_all('tr', attrs = {'class' : 'torrent'}) + entries = result_table.find_all('tr', attrs = {'class': 'torrent'}) for result in entries: - link = result.find('span', attrs = {'class' : 'torrent_name_link'}).parent - url = result.find('td', attrs = {'class' : 'torrent_td'}).find('a') + link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent + url = result.find('td', attrs = {'class': 'torrent_td'}).find('a') results.append({ 'id': link['href'].replace('torrents.php?torrentid=', ''), diff --git a/couchpotato/core/media/_base/providers/torrent/torrentz.py b/couchpotato/core/media/_base/providers/torrent/torrentz.py new file mode 100644 index 0000000..3937f64 --- /dev/null +++ b/couchpotato/core/media/_base/providers/torrent/torrentz.py @@ -0,0 +1,128 @@ +import re +import traceback + +from couchpotato.core.helpers.encoding import tryUrlencode +from couchpotato.core.helpers.rss import RSS +from couchpotato.core.helpers.variable import tryInt, splitString +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider +import six + + +log = CPLog(__name__) + + +class Base(TorrentMagnetProvider, RSS): + + urls = { + 'detail': 'https://torrentz.eu/%s', + 'search': 'https://torrentz.eu/feed?q=%s', + 'verified_search': 'https://torrentz.eu/feed_verified?q=%s' + } + + http_time_between_calls = 0 + + def _search(self, media, quality, results): + + search_url = self.urls['verified_search'] if self.conf('verified_only') else self.urls['search'] + + # Create search parameters + search_params = self.buildUrl(media) + + smin = quality.get('size_min') + smax = quality.get('size_max') + if smin and smax: + search_params += ' size %sm - %sm' % (smin, smax) + + min_seeds = tryInt(self.conf('minimal_seeds')) + if min_seeds: + search_params += ' seed > %s' % (min_seeds - 1) + + rss_data = self.getRSSData(search_url % search_params) + + if rss_data: + try: + + for result in rss_data: + + name = self.getTextElement(result, 'title') + detail_url = self.getTextElement(result, 'link') + description = self.getTextElement(result, 'description') + + magnet = splitString(detail_url, '/')[-1] + magnet_url = 'magnet:?xt=urn:btih:%s&dn=%s&tr=%s' % (magnet.upper(), tryUrlencode(name), tryUrlencode('udp://tracker.openbittorrent.com/announce')) + + reg = re.search('Size: (?P\d+) MB Seeds: (?P[\d,]+) Peers: (?P[\d,]+)', six.text_type(description)) + size = reg.group('size') + seeds = reg.group('seeds').replace(',', '') + peers = reg.group('peers').replace(',', '') + + results.append({ + 'id': magnet, + 'name': six.text_type(name), + 'url': magnet_url, + 'detail_url': detail_url, + 'size': tryInt(size), + 'seeders': tryInt(seeds), + 'leechers': tryInt(peers), + }) + + except: + log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) + + +config = [{ + 'name': 'torrentz', + 'groups': [ + { + 'tab': 'searcher', + 'list': 'torrent_providers', + 'name': 'Torrentz', + 'description': 'Torrentz is a free, fast and powerful meta-search engine. Torrentz', + 'wizard': True, + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': False + }, + { + 'name': 'verified_only', + 'type': 'bool', + 'default': True, + 'advanced': True, + 'description': 'Only search verified releases', + }, + { + 'name': 'minimal_seeds', + 'type': 'int', + 'default': 1, + 'advanced': True, + 'description': 'Only return releases with minimal X seeds', + }, + { + 'name': 'seed_ratio', + 'label': 'Seed ratio', + 'type': 'float', + 'default': 1, + 'description': 'Will not be (re)moved until this seed ratio is met.', + }, + { + 'name': 'seed_time', + 'label': 'Seed time', + 'type': 'int', + 'default': 40, + 'description': 'Will not be (re)moved until this seed time (in hours) is met.', + }, + { + 'name': 'extra_score', + 'advanced': True, + 'label': 'Extra Score', + 'type': 'int', + 'default': 0, + 'description': 'Starting score for each release found via this provider.', + } + ], + } + ] +}] diff --git a/couchpotato/core/media/_base/providers/torrent/yify.py b/couchpotato/core/media/_base/providers/torrent/yify.py index fe7a9b4..a694100 100644 --- a/couchpotato/core/media/_base/providers/torrent/yify.py +++ b/couchpotato/core/media/_base/providers/torrent/yify.py @@ -16,7 +16,7 @@ class Base(TorrentProvider): 'detail': '%s/api/movie.json?id=%s' } - http_time_between_calls = 1 #seconds + http_time_between_calls = 1 # seconds proxy_list = [ 'http://yify.unlocktorrent.com', @@ -81,7 +81,7 @@ config = [{ { 'name': 'enabled', 'type': 'enabler', - 'default': 0 + 'default': False }, { 'name': 'domain', diff --git a/couchpotato/core/media/_base/search/static/search.css b/couchpotato/core/media/_base/search/static/search.css index 57210d6..2defe24 100644 --- a/couchpotato/core/media/_base/search/static/search.css +++ b/couchpotato/core/media/_base/search/static/search.css @@ -6,12 +6,11 @@ top: 0; text-align: right; height: 100%; - border-bottom: 4px solid transparent; transition: all .4s cubic-bezier(0.9,0,0.1,1); position: absolute; z-index: 20; - border: 1px solid transparent; - border-width: 0 0 4px; + border: 0 solid transparent; + border-bottom-width: 4px; } .search_form:hover { border-color: #047792; @@ -22,19 +21,19 @@ right: 44px; } } - + .search_form.focused, .search_form.shown { border-color: #04bce6; } - + .search_form .input { height: 100%; overflow: hidden; width: 45px; transition: all .4s cubic-bezier(0.9,0,0.1,1); } - + .search_form.focused .input, .search_form.shown .input { width: 380px; @@ -49,7 +48,6 @@ color: #FFF; font-size: 25px; height: 100%; - padding: 10px; width: 100%; opacity: 0; padding: 0 40px 0 10px; @@ -59,23 +57,23 @@ .search_form.shown .input input { opacity: 1; } - + .search_form input::-ms-clear { width : 0; height: 0; } - + @media all and (max-width: 480px) { .search_form .input input { font-size: 15px; } - + .search_form.focused .input, .search_form.shown .input { width: 277px; } } - + .search_form .input a { position: absolute; top: 0; @@ -89,7 +87,7 @@ font-size: 15px; color: #FFF; } - + .search_form .input a:after { content: "\e03e"; } @@ -97,7 +95,7 @@ .search_form.shown.filled .input a:after { content: "\e04e"; } - + @media all and (max-width: 480px) { .search_form .input a { line-height: 44px; @@ -167,13 +165,13 @@ .media_result .options select[name=title] { width: 170px; } .media_result .options select[name=profile] { width: 90px; } .media_result .options select[name=category] { width: 80px; } - + @media all and (max-width: 480px) { - + .media_result .options select[name=title] { width: 90px; } .media_result .options select[name=profile] { width: 50px; } .media_result .options select[name=category] { width: 50px; } - + } .media_result .options .button { @@ -227,14 +225,14 @@ right: 7px; vertical-align: middle; } - + .media_result .info h2 { margin: 0; font-weight: normal; font-size: 20px; padding: 0; } - + .search_form .info h2 { position: absolute; width: 100%; @@ -247,12 +245,12 @@ overflow: hidden; white-space: nowrap; } - + .search_form .info h2 .title { position: absolute; width: 88%; } - + .media_result .info h2 .year { padding: 0 5px; text-align: center; @@ -260,14 +258,14 @@ width: 12%; right: 0; } - + @media all and (max-width: 480px) { - + .search_form .info h2 .year { font-size: 12px; margin-top: 7px; } - + } .search_form .mask, @@ -277,4 +275,4 @@ width: 100%; left: 0; top: 0; -} \ No newline at end of file +} diff --git a/couchpotato/core/media/_base/search/static/search.js b/couchpotato/core/media/_base/search/static/search.js index 470dcf0..1892ad1 100644 --- a/couchpotato/core/media/_base/search/static/search.js +++ b/couchpotato/core/media/_base/search/static/search.js @@ -16,7 +16,7 @@ Block.Search = new Class({ 'keyup': self.keyup.bind(self), 'focus': function(){ if(focus_timer) clearTimeout(focus_timer); - self.el.addClass('focused') + self.el.addClass('focused'); if(this.get('value')) self.hideResults(false) }, @@ -57,17 +57,17 @@ Block.Search = new Class({ (e).preventDefault(); if(self.last_q === ''){ - self.input.blur() + self.input.blur(); self.last_q = null; } else { self.last_q = ''; self.input.set('value', ''); - self.input.focus() + self.input.focus(); - self.media = {} - self.results.empty() + self.media = {}; + self.results.empty(); self.el.removeClass('filled') } @@ -92,16 +92,16 @@ Block.Search = new Class({ self.hidden = bool; }, - keyup: function(e){ + keyup: function(){ var self = this; - self.el[self.q() ? 'addClass' : 'removeClass']('filled') + self.el[self.q() ? 'addClass' : 'removeClass']('filled'); if(self.q() != self.last_q){ if(self.api_request && self.api_request.isRunning()) self.api_request.cancel(); - if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer) + if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer); self.autocomplete_timer = self.autocomplete.delay(300, self) } @@ -111,7 +111,7 @@ Block.Search = new Class({ var self = this; if(!self.q()){ - self.hideResults(true) + self.hideResults(true); return } @@ -139,7 +139,7 @@ Block.Search = new Class({ }) } else - self.fill(q, cache) + self.fill(q, cache); self.last_q = q; @@ -148,31 +148,31 @@ Block.Search = new Class({ fill: function(q, json){ var self = this; - self.cache[q] = json + self.cache[q] = json; - self.media = {} - self.results.empty() - - Object.each(json, function(media, type){ + self.media = {}; + self.results.empty(); + + Object.each(json, function(media){ if(typeOf(media) == 'array'){ Object.each(media, function(m){ - + var m = new Block.Search[m.type.capitalize() + 'Item'](m); - $(m).inject(self.results) - self.media[m.imdb || 'r-'+Math.floor(Math.random()*10000)] = m - + $(m).inject(self.results); + self.media[m.imdb || 'r-'+Math.floor(Math.random()*10000)] = m; + if(q == m.imdb) m.showOptions() - + }); } - }) + }); // Calculate result heights var w = window.getSize(), rc = self.result_container.getCoordinates(); - self.results.setStyle('max-height', (w.y - rc.top - 50) + 'px') + self.results.setStyle('max-height', (w.y - rc.top - 50) + 'px'); self.mask.fade('out') }, @@ -185,4 +185,4 @@ Block.Search = new Class({ return this.input.get('value').trim(); } -}); \ No newline at end of file +}); diff --git a/couchpotato/core/media/_base/searcher/main.py b/couchpotato/core/media/_base/searcher/main.py index 5a28043..fc07ded 100644 --- a/couchpotato/core/media/_base/searcher/main.py +++ b/couchpotato/core/media/_base/searcher/main.py @@ -14,9 +14,11 @@ log = CPLog(__name__) class Searcher(SearcherBase): + # noinspection PyMissingConstructor def __init__(self): addEvent('searcher.protocols', self.getSearchProtocols) addEvent('searcher.contains_other_quality', self.containsOtherQuality) + addEvent('searcher.correct_3d', self.correct3D) addEvent('searcher.correct_year', self.correctYear) addEvent('searcher.correct_name', self.correctName) addEvent('searcher.correct_words', self.correctWords) @@ -123,6 +125,17 @@ class Searcher(SearcherBase): return not (found.get(preferred_quality['identifier']) and len(found) == 1) + def correct3D(self, nzb, preferred_quality = None): + if not preferred_quality: preferred_quality = {} + if not preferred_quality.get('custom'): return + + threed = preferred_quality['custom'].get('3d') + + # Try guessing via quality tags + guess = fireEvent('quality.guess', [nzb.get('name')], single = True) + + return threed == guess.get('is_3d') + def correctYear(self, haystack, year, year_range): if not isinstance(haystack, (list, tuple, set)): @@ -183,7 +196,7 @@ class Searcher(SearcherBase): req = splitString(req_set, '&') req_match += len(list(set(rel_words) & set(req))) == len(req) - if len(required_words) > 0 and req_match == 0: + if len(required_words) > 0 and req_match == 0: log.info2('Wrong: Required word missing: %s', rel_name) return False diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index ce40595..1e8a38f 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -26,7 +26,7 @@ var MovieList = new Class({ self.filter = self.options.filter || { 'starts_with': null, 'search': null - } + }; self.el = new Element('div.movies').adopt( self.title = self.options.title ? new Element('h2', { @@ -52,7 +52,7 @@ var MovieList = new Class({ self.getMovies(); - App.on('movie.added', self.movieAdded.bind(self)) + App.on('movie.added', self.movieAdded.bind(self)); App.on('movie.deleted', self.movieDeleted.bind(self)) }, @@ -96,7 +96,7 @@ var MovieList = new Class({ if(self.options.load_more) self.scrollspy = new ScrollSpy({ min: function(){ - var c = self.load_more.getCoordinates() + var c = self.load_more.getCoordinates(); return c.top - window.document.getSize().y - 300 }, onEnter: self.loadMore.bind(self) @@ -179,7 +179,7 @@ var MovieList = new Class({ m.fireEvent('injected'); - self.movies.include(m) + self.movies.include(m); self.movies_added[movie._id] = true; }, @@ -187,7 +187,7 @@ var MovieList = new Class({ var self = this; var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - self.el.addClass('with_navigation') + self.el.addClass('with_navigation'); self.navigation = new Element('div.alph_nav').adopt( self.mass_edit_form = new Element('div.mass_edit_form').adopt( @@ -242,7 +242,7 @@ var MovieList = new Class({ this.addClass(a); el.inject(el.getParent(), 'top'); - el.getSiblings().hide() + el.getSiblings().hide(); setTimeout(function(){ el.getSiblings().setStyle('display', null); }, 100) @@ -286,7 +286,7 @@ var MovieList = new Class({ 'status': self.options.status }, self.filter), 'onSuccess': function(json){ - available_chars = json.chars + available_chars = json.chars; available_chars.each(function(c){ self.letters[c.capitalize()].addClass('available') @@ -300,7 +300,7 @@ var MovieList = new Class({ self.navigation_alpha = new Element('ul.numbers', { 'events': { 'click:relay(li.available)': function(e, el){ - self.activateLetter(el.get('data-letter')) + self.activateLetter(el.get('data-letter')); self.getMovies(true) } } @@ -318,7 +318,7 @@ var MovieList = new Class({ // All self.letters['all'] = new Element('li.letter_all.available.active', { - 'text': 'ALL', + 'text': 'ALL' }).inject(self.navigation_alpha); // Chars @@ -334,7 +334,7 @@ var MovieList = new Class({ if (self.options.menu.length > 0) self.options.menu.each(function(menu_item){ self.navigation_menu.addLink(menu_item); - }) + }); else self.navigation_menu.hide(); @@ -347,15 +347,15 @@ var MovieList = new Class({ movies = self.movies.length; self.movies.each(function(movie){ selected += movie.isSelected() ? 1 : 0 - }) + }); var indeterminate = selected > 0 && selected < movies, checked = selected == movies && selected > 0; - self.mass_edit_select.set('indeterminate', indeterminate) + self.mass_edit_select.set('indeterminate', indeterminate); - self.mass_edit_select_class[checked ? 'check' : 'uncheck']() - self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate') + self.mass_edit_select_class[checked ? 'check' : 'uncheck'](); + self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate'); self.mass_edit_selected.set('text', selected); }, @@ -371,7 +371,7 @@ var MovieList = new Class({ 'events': { 'click': function(e){ (e).preventDefault(); - this.set('text', 'Deleting..') + this.set('text', 'Deleting..'); Api.request('media.delete', { 'data': { 'id': ids.join(','), @@ -383,7 +383,7 @@ var MovieList = new Class({ var erase_movies = []; self.movies.each(function(movie){ if (movie.isSelected()){ - $(movie).destroy() + $(movie).destroy(); erase_movies.include(movie); } }); @@ -410,7 +410,7 @@ var MovieList = new Class({ changeQualitySelected: function(){ var self = this; - var ids = self.getSelectedMovies() + var ids = self.getSelectedMovies(); Api.request('movie.edit', { 'data': { @@ -423,11 +423,11 @@ var MovieList = new Class({ refreshSelected: function(){ var self = this; - var ids = self.getSelectedMovies() + var ids = self.getSelectedMovies(); Api.request('media.refresh', { 'data': { - 'id': ids.join(','), + 'id': ids.join(',') } }); }, @@ -435,7 +435,7 @@ var MovieList = new Class({ getSelectedMovies: function(){ var self = this; - var ids = [] + var ids = []; self.movies.each(function(movie){ if (movie.isSelected()) ids.include(movie.get('_id')) @@ -459,11 +459,11 @@ var MovieList = new Class({ reset: function(){ var self = this; - self.movies = [] + self.movies = []; if(self.mass_edit_select) - self.calculateSelected() + self.calculateSelected(); if(self.navigation_alpha) - self.navigation_alpha.getElements('.active').removeClass('active') + self.navigation_alpha.getElements('.active').removeClass('active'); self.offset = 0; if(self.scrollspy){ @@ -475,7 +475,7 @@ var MovieList = new Class({ activateLetter: function(letter){ var self = this; - self.reset() + self.reset(); self.letters[letter || 'all'].addClass('active'); self.filter.starts_with = letter; @@ -487,7 +487,7 @@ var MovieList = new Class({ self.el .removeClass(self.current_view+'_list') - .addClass(new_view+'_list') + .addClass(new_view+'_list'); self.current_view = new_view; Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000}); @@ -504,9 +504,9 @@ var MovieList = new Class({ if(self.search_timer) clearTimeout(self.search_timer); self.search_timer = (function(){ var search_value = self.navigation_search_input.get('value'); - if (search_value == self.last_search_value) return + if (search_value == self.last_search_value) return; - self.reset() + self.reset(); self.activateLetter(); self.filter.search = search_value; @@ -563,7 +563,7 @@ var MovieList = new Class({ if(self.loader_first){ var lf = self.loader_first; - self.loader_first.addClass('hide') + self.loader_first.addClass('hide'); self.loader_first = null; setTimeout(function(){ lf.destroy(); @@ -603,10 +603,10 @@ var MovieList = new Class({ var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined); if(self.title) - self.title[is_empty ? 'hide' : 'show']() + self.title[is_empty ? 'hide' : 'show'](); if(self.description) - self.description.setStyle('display', [is_empty ? 'none' : '']) + self.description.setStyle('display', [is_empty ? 'none' : '']); if(is_empty && self.options.on_empty_element){ self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, 'after'); diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 5dcaed7..4d9dccc 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -60,22 +60,6 @@ var MovieAction = new Class({ '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(){ @@ -122,7 +106,7 @@ MA.Release = new Class({ }); if(!self.movie.data.releases || self.movie.data.releases.length == 0) - self.el.hide() + self.el.hide(); else self.showHelper(); @@ -164,7 +148,7 @@ MA.Release = new Class({ new Element('span.age', {'text': 'Age'}), new Element('span.score', {'text': 'Score'}), new Element('span.provider', {'text': 'Provider'}) - ).inject(self.release_container) + ).inject(self.release_container); if(self.movie.data.releases) self.movie.data.releases.each(function(release){ @@ -186,13 +170,13 @@ MA.Release = new Class({ } // Create release - var item = new Element('div', { + release['el'] = new Element('div', { 'class': 'item '+release.status, 'id': 'release_'+release._id }).adopt( new Element('span.name', {'text': release_name, 'title': release_name}), new Element('span.status', {'text': release.status, 'class': 'release_status '+release.status}), - new Element('span.quality', {'text': quality.label || 'n/a'}), + new Element('span.quality', {'text': quality.label + (release.is_3d ? ' 3D' : '') || 'n/a'}), new Element('span.size', {'text': 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')}), @@ -219,7 +203,6 @@ MA.Release = new Class({ } }) ).inject(self.release_container); - release['el'] = item; if(release.status == 'ignored' || release.status == 'failed' || release.status == 'snatched'){ if(!self.last_release || (self.last_release && self.last_release.status != 'snatched' && release.status == 'snatched')) @@ -242,13 +225,13 @@ MA.Release = new Class({ status_el.set('text', new_status); if(!q && (new_status == 'snatched' || new_status == 'seeding' || new_status == 'done')) - var q = self.addQuality(release.quality_id); + q = self.addQuality(release.quality_id); if(q && !q.hasClass(new_status)) { q.removeClass(release.status).addClass(new_status); q.set('title', q.get('title').replace(release.status, new_status)); } - } + }; App.on('release.update_status', update_handle); @@ -391,12 +374,11 @@ MA.Release = new Class({ }, ignore: function(release){ - var self = this; Api.request('release.ignore', { 'data': { 'id': release._id - }, + } }) }, @@ -456,7 +438,7 @@ MA.Trailer = new Class({ watch: function(offset){ var self = this; - var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18' + var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'; var url = data_url.substitute({ 'title': encodeURI(self.getTitle()), 'year': self.get('year'), @@ -515,7 +497,7 @@ MA.Trailer = new Class({ } } - } + }; self.player.addEventListener('onStateChange', change_quality); } @@ -532,7 +514,7 @@ MA.Trailer = new Class({ $(self.movie).setStyle('height', null); setTimeout(function(){ - self.container.destroy() + self.container.destroy(); self.close_button.destroy(); }, 1800) } @@ -661,7 +643,7 @@ MA.Edit = new Class({ self.movie.slide('out'); } -}) +}); MA.Refresh = new Class({ @@ -847,7 +829,7 @@ MA.Files = new Class({ new Element('div.item.head').adopt( new Element('span.name', {'text': 'File'}), new Element('span.type', {'text': 'Type'}) - ).inject(self.files_container) + ).inject(self.files_container); if(self.movie.data.releases) Array.each(self.movie.data.releases, function(release){ diff --git a/couchpotato/core/media/movie/_base/static/movie.css b/couchpotato/core/media/movie/_base/static/movie.css index d579d54..6718dc3 100644 --- a/couchpotato/core/media/movie/_base/static/movie.css +++ b/couchpotato/core/media/movie/_base/static/movie.css @@ -398,7 +398,6 @@ .movies .data .quality span { padding: 2px 3px; - font-weight: bold; opacity: 0.5; font-size: 10px; height: 16px; @@ -451,7 +450,6 @@ right: 20px; line-height: 0; top: 0; - display: block; width: auto; opacity: 0; display: none; @@ -833,7 +831,6 @@ } .movies .alph_nav .search input { - padding: 6px 5px; width: 100%; height: 44px; display: inline-block; @@ -841,7 +838,6 @@ background: none; color: #444; font-size: 14px; - padding: 10px; padding: 0 10px 0 30px; border-bottom: 1px solid rgba(0,0,0,.08); } @@ -1043,7 +1039,6 @@ } .movies .progress > div .percentage { - font-weight: bold; display: inline-block; text-transform: uppercase; font-weight: normal; diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index 2b0c7b7..25d5e08 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -23,7 +23,7 @@ var Movie = new Class({ addEvents: function(){ var self = this; - self.global_events = {} + self.global_events = {}; // Do refresh with new data self.global_events['movie.update'] = function(notification){ @@ -32,7 +32,7 @@ var Movie = new Class({ self.busy(false); self.removeView(); self.update.delay(2000, self, notification); - } + }; App.on('movie.update', self.global_events['movie.update']); // Add spinner on load / search @@ -40,20 +40,20 @@ var Movie = new Class({ self.global_events[listener] = function(notification){ if(notification.data && (self.data._id == notification.data._id || (typeOf(notification.data._id) == 'array' && notification.data._id.indexOf(self.data._id) > -1))) self.busy(true); - } + }; App.on(listener, self.global_events[listener]); - }) + }); // Remove spinner self.global_events['movie.searcher.ended'] = function(notification){ if(notification.data && self.data._id == notification.data._id) self.busy(false) - } + }; App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']); // Reload when releases have updated self.global_events['release.update_status'] = function(notification){ - var data = notification.data + var data = notification.data; if(data && self.data._id == data.movie_id){ if(!self.data.releases) @@ -62,7 +62,7 @@ var Movie = new Class({ self.data.releases.push({'quality': data.quality, 'status': data.status}); self.updateReleases(); } - } + }; App.on('release.update_status', self.global_events['release.update_status']); @@ -73,7 +73,7 @@ var Movie = new Class({ self.el.destroy(); delete self.list.movies_added[self.get('id')]; - self.list.movies.erase(self) + self.list.movies.erase(self); self.list.checkIfEmpty(); @@ -117,18 +117,6 @@ var Movie = new Class({ }).inject(self.el, 'top').fade('hide'); }, - positionMask: function(){ - var self = this, - s = self.el.getSize() - - return self.mask.setStyles({ - 'width': s.x, - 'height': s.y - }).position({ - 'relativeTo': self.el - }) - }, - update: function(notification){ var self = this; @@ -197,7 +185,7 @@ var Movie = new Class({ if(self.profile.data) self.profile.getTypes().each(function(type){ - var q = self.addQuality(type.get('quality')); + var q = self.addQuality(type.get('quality'), type.get('3d')); 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.') @@ -209,7 +197,7 @@ var Movie = new Class({ self.updateReleases(); Object.each(self.options.actions, function(action, key){ - self.action[key.toLowerCase()] = action = new self.options.actions[key](self) + self.action[key.toLowerCase()] = action = new self.options.actions[key](self); if(action.el) self.actions.adopt(action) }); @@ -222,11 +210,11 @@ var Movie = new Class({ self.data.releases.each(function(release){ - var q = self.quality.getElement('.q_'+ release.quality), + var q = self.quality.getElement('.q_'+ release.quality+(release.is_3d ? '.is_3d' : ':not(.is_3d)')), status = release.status; if(!q && (status == 'snatched' || status == 'seeding' || status == 'done')) - var q = self.addQuality(release.quality) + q = self.addQuality(release.quality, release.is_3d || false); if (q && !q.hasClass(status)){ q.addClass(status); @@ -236,13 +224,13 @@ var Movie = new Class({ }); }, - addQuality: function(quality){ + addQuality: function(quality, is_3d){ var self = this; var q = Quality.getQuality(quality); return new Element('span', { - 'text': q.label, - 'class': 'q_'+q.identifier, + 'text': q.label + (is_3d ? ' 3D' : ''), + 'class': 'q_'+q.identifier + (is_3d ? ' is_3d' : ''), 'title': '' }).inject(self.quality); @@ -252,9 +240,9 @@ var Movie = new Class({ var self = this; if(self.data.title) - return self.getUnprefixedTitle(self.data.title) + return self.getUnprefixedTitle(self.data.title); else if(self.data.info.titles.length > 0) - return self.getUnprefixedTitle(self.data.info.titles[0]) + return self.getUnprefixedTitle(self.data.info.titles[0]); return 'Unknown movie' }, @@ -275,12 +263,12 @@ var Movie = new Class({ self.el.addEvent('outerClick', function(){ self.removeView(); self.slide('out') - }) + }); el.show(); self.data_container.addClass('hide_right'); } else { - self.el.removeEvents('outerClick') + self.el.removeEvents('outerClick'); setTimeout(function(){ if(self.el) @@ -297,7 +285,7 @@ var Movie = new Class({ if(self.el) self.el .removeClass(self.view+'_view') - .addClass(new_view+'_view') + .addClass(new_view+'_view'); self.view = new_view; }, diff --git a/couchpotato/core/media/movie/_base/static/search.js b/couchpotato/core/media/movie/_base/static/search.js index cc83739..3b42676 100644 --- a/couchpotato/core/media/movie/_base/static/search.js +++ b/couchpotato/core/media/movie/_base/static/search.js @@ -41,7 +41,7 @@ Block.Search.MovieItem = new Class({ ) ) ) - ) + ); if(info.titles) info.titles.each(function(title){ @@ -132,19 +132,19 @@ Block.Search.MovieItem = new Class({ if(!self.options_el.hasClass('set')){ - if(self.info.in_library){ + if(info.in_library){ var in_library = []; - (self.info.in_library.releases || []).each(function(release){ + (info.in_library.releases || []).each(function(release){ in_library.include(release.quality) }); } self.options_el.grab( new Element('div', { - 'class': self.info.in_wanted && self.info.in_wanted.profile_id || in_library ? 'in_library_wanted' : '' + 'class': info.in_wanted && info.in_wanted.profile_id || in_library ? 'in_library_wanted' : '' }).adopt( - self.info.in_wanted && self.info.in_wanted.profile_id ? new Element('span.in_wanted', { - 'text': 'Already in wanted list: ' + Quality.getProfile(self.info.in_wanted.profile_id).get('label') + info.in_wanted && info.in_wanted.profile_id ? new Element('span.in_wanted', { + 'text': 'Already in wanted list: ' + Quality.getProfile(info.in_wanted.profile_id).get('label') }) : (in_library ? new Element('span.in_library', { 'text': 'Already in library: ' + in_library.join(', ') }) : null), @@ -172,7 +172,7 @@ Block.Search.MovieItem = new Class({ new Element('option', { 'text': alt.title }).inject(self.title_select) - }) + }); // Fill categories @@ -215,9 +215,9 @@ Block.Search.MovieItem = new Class({ loadingMask: function(){ var self = this; - self.mask = new Element('div.mask').inject(self.el).fade('hide') + self.mask = new Element('div.mask').inject(self.el).fade('hide'); - createSpinner(self.mask) + createSpinner(self.mask); self.mask.fade('in') }, diff --git a/couchpotato/core/media/movie/charts/__init__.py b/couchpotato/core/media/movie/charts/__init__.py new file mode 100644 index 0000000..9c67375 --- /dev/null +++ b/couchpotato/core/media/movie/charts/__init__.py @@ -0,0 +1,34 @@ +from .main import Charts + + +def autoload(): + return Charts() + + +config = [{ + 'name': 'charts', + 'groups': [ + { + 'label': 'Charts', + 'description': 'Displays selected charts on the home page', + 'type': 'list', + 'name': 'charts_providers', + 'tab': 'display', + 'options': [ + { + 'name': 'max_items', + 'default': 5, + 'type': 'int', + 'description': 'Maximum number of items displayed from each chart.', + }, + { + 'name': 'update_interval', + 'default': 12, + 'type': 'int', + 'advanced': True, + 'description': '(hours)', + }, + ], + }, + ], +}] diff --git a/couchpotato/core/media/movie/charts/main.py b/couchpotato/core/media/movie/charts/main.py new file mode 100644 index 0000000..7100275 --- /dev/null +++ b/couchpotato/core/media/movie/charts/main.py @@ -0,0 +1,58 @@ +import time + +from couchpotato import tryInt +from couchpotato.core.logger import CPLog +from couchpotato.api import addApiView +from couchpotato.core.event import addEvent,fireEvent +from couchpotato.core.plugins.base import Plugin + + +log = CPLog(__name__) + + +class Charts(Plugin): + + update_in_progress = False + + def __init__(self): + addApiView('charts.view', self.automationView) + addEvent('app.load', self.setCrons) + + def setCrons(self): + fireEvent('schedule.interval', 'charts.update_cache', self.updateViewCache, hours = self.conf('update_interval', default = 12)) + + def automationView(self, force_update = False, **kwargs): + + if force_update: + charts = self.updateViewCache() + else: + charts = self.getCache('charts_cached') + if not charts: + charts = self.updateViewCache() + + return { + 'success': True, + 'count': len(charts), + 'charts': charts + } + + + def updateViewCache(self): + + if self.update_in_progress: + while self.update_in_progress: + time.sleep(1) + catched_charts = self.getCache('charts_cached') + if catched_charts: + return catched_charts + + try: + self.update_in_progress = True + charts = fireEvent('automation.get_chart_list', merge = True) + self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12))) + except: + log.error('Failed refreshing charts') + + self.update_in_progress = False + + return charts diff --git a/couchpotato/core/media/movie/charts/static/charts.css b/couchpotato/core/media/movie/charts/static/charts.css new file mode 100644 index 0000000..610ac15 --- /dev/null +++ b/couchpotato/core/media/movie/charts/static/charts.css @@ -0,0 +1,261 @@ +.charts { + clear: both; + margin-bottom: 30px; +} + +.charts > h2 { + height: 40px; +} + +.charts .chart { + display: inline-block; + width: 50%; + vertical-align: top; +} + +.charts .refresh { + clear:both; + position: relative; +} + + .charts .refresh .refreshing { + display: block; + padding: 20px; + font-size: 20px; + text-align:center; + } + + .charts .refresh a { + text-align: center; + padding: 0; + display: none; + width: 30px; + height: 30px; + position: absolute; + right: 10px; + top: -40px; + opacity: .7; + } + + .charts .refresh a:hover { + opacity: 1; + } + +.charts p.no_charts_enabled { + padding: 0.7em 1em; + display: none; +} + +.charts .chart h3 a { + color: #fff; +} + + +.charts .chart .media_result { + display: inline-block; + width: 100%; + height: 150px; +} + +@media all and (max-width: 960px) { + .charts .chart { + width: 50%; + } +} + +@media all and (max-width: 600px) { + .charts .chart { + width: 100%; + } +} + +.charts .chart .media_result .data { + left: 150px; + background: #4e5969; + border: none; +} + + .charts .chart .media_result .data .info { + top: 10px; + left: 15px; + right: 15px; + bottom: 10px; + overflow: hidden; + } + + .charts .chart .media_result .data .info h2 { + white-space: normal; + max-height: 120px; + font-size: 18px; + line-height: 18px; + } + + .charts .chart .media_result .data .info .rating, + .charts .chart .media_result .data .info .genres, + .charts .chart .media_result .data .info .year { + position: static; + display: block; + padding: 0; + opacity: .6; + } + + .charts .chart .media_result .data .info .year { + margin: 10px 0 0; + } + + .charts .chart .media_result .data .info .rating { + font-size: 20px; + float: right; + margin-top: -20px; + } + .charts .chart .media_result .data .info .rating:before { + content: "\e031"; + font-family: 'Elusive-Icons'; + font-size: 14px; + margin: 0 5px 0 0; + vertical-align: bottom; + } + + .charts .chart .media_result .data .info .genres { + font-size: 11px; + font-style: italic; + text-align: right; + } + + .charts .chart .media_result .data .info .plot { + display: block; + font-size: 11px; + overflow: hidden; + text-align: justify; + height: 100%; + z-index: 2; + top: 64px; + position: absolute; + background: #4e5969; + cursor: pointer; + transition: all .4s ease-in-out; + padding: 0 3px 10px 0; + } + .charts .chart .media_result .data:before { + bottom: 0; + content: ''; + display: block; + height: 10px; + right: 0; + left: 0; + bottom: 10px; + position: absolute; + background: linear-gradient( + 0deg, + rgba(78, 89, 105, 1) 0%, + rgba(78, 89, 105, 0) 100% + ); + z-index: 3; + pointer-events: none; + } + + .charts .chart .media_result .data .info .plot.full { + top: 0; + overflow: auto; + } + +.charts .chart .media_result .data { + cursor: default; +} + +.charts .chart .media_result .options { + left: 150px; +} + .charts .chart .media_result .options select[name=title] { width: 100%; } + .charts .chart .media_result .options select[name=profile] { width: 100%; } + .charts .chart .media_result .options select[name=category] { width: 100%; } + + .charts .chart .media_result .button { + position: absolute; + margin: 2px 0 0 0; + right: 15px; + bottom: 15px; + } + + +.charts .chart .media_result .thumbnail { + width: 100px; + position: absolute; + left: 50px; +} + +.charts .chart .media_result .chart_number { + color: white; + position: absolute; + top: 0; + padding: 10px; + font: bold 2em/1em Helvetica, Sans-Serif; + width: 50px; + height: 100%; + text-align: center; + border-left: 8px solid transparent; +} + + .charts .chart .media_result.chart_in_wanted .chart_number { + border-color: rgba(0, 255, 40, 0.3); + } + + .charts .chart .media_result.chart_in_library .chart_number { + border-color: rgba(0, 202, 32, 0.3); + } + + +.charts .chart .media_result .actions { + position: absolute; + top: 10px; + right: 10px; + display: none; + width: 90px; +} + .charts .chart .media_result:hover .actions { + display: block; + } + .charts .chart .media_result:hover h2 .title { + opacity: 0; + } + .charts .chart .media_result .data.open .actions { + display: none; + } + + .charts .chart .media_result .actions a { + margin-left: 10px; + vertical-align: middle; + } + + +.toggle_menu { + height: 50px; +} + +.toggle_menu a { + display: block; + width: 50%; + float: left; + color: rgba(255,255,255,.6); + border-bottom: 1px solid rgba(255, 255, 255, 0.0666667); +} + +.toggle_menu a:hover { + border-color: #047792; + border-width: 4px; + color: #fff; +} + +.toggle_menu a.active { + border-bottom: 4px solid #04bce6; + color: #fff; +} + +.toggle_menu a:last-child { + float: right; +} + +.toggle_menu h2 { + height: 40px; +} + diff --git a/couchpotato/core/media/movie/charts/static/charts.js b/couchpotato/core/media/movie/charts/static/charts.js new file mode 100644 index 0000000..00033f4 --- /dev/null +++ b/couchpotato/core/media/movie/charts/static/charts.js @@ -0,0 +1,170 @@ +var Charts = new Class({ + + Implements: [Options, Events], + + initialize: function(options){ + var self = this; + self.setOptions(options); + + self.create(); + }, + + create: function(){ + var self = this; + + self.el = new Element('div.charts').adopt( + self.el_no_charts_enabled = new Element('p.no_charts_enabled', { + 'html': 'Hey, it looks like you have no charts enabled at the moment. If you\'d like some great movie suggestions you can go to settings and turn on some charts of your choice.' + }), + self.el_refresh_container = new Element('div.refresh').adopt( + self.el_refresh_link = new Element('a.refresh.icon2', { + 'href': '#', + 'events': { + 'click': function(e) { + e.preventDefault(); + self.el.getChildren('div.chart').destroy(); + self.el_refreshing_text.show(); + self.el_refresh_link.hide(); + self.api_request = Api.request('charts.view', { + 'data': { 'force_update': 1 }, + 'onComplete': self.fill.bind(self) + }); + } + } + }), + self.el_refreshing_text = new Element('span.refreshing', { + 'text': 'Refreshing charts...' + }) + ) + ); + + if( Cookie.read('suggestions_charts_menu_selected') === 'charts') + self.el.show(); + else + self.el.hide(); + + self.api_request = Api.request('charts.view', { + 'onComplete': self.fill.bind(self) + }); + + self.fireEvent.delay(0, self, 'created'); + + }, + + fill: function(json){ + + var self = this; + + self.el_refreshing_text.hide(); + self.el_refresh_link.show(); + + if(!json || json.count == 0){ + self.el_no_charts_enabled.show(); + self.el_refresh_link.show(); + self.el_refreshing_text.hide(); + } + else { + self.el_no_charts_enabled.hide(); + + json.charts.sort(function(a, b) { + return a.order - b.order; + }); + + Object.each(json.charts, function(chart){ + + var c = new Element('div.chart').grab( + new Element('h3').grab( new Element('a', { + 'text': chart.name, + 'href': chart.url + })) + ); + + var it = 1; + + Object.each(chart.list, function(movie){ + + var m = new Block.Search.MovieItem(movie, { + 'onAdded': function(){ + self.afterAdded(m, movie) + } + }); + + var in_database_class = movie.in_wanted ? 'chart_in_wanted' : (movie.in_library ? 'chart_in_library' : ''), + in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : ''); + + m.el + .addClass(in_database_class) + .grab( + new Element('div.chart_number', { + 'text': it++, + 'title': in_database_title + }) + ); + + m.data_container.grab( + new Element('div.actions').adopt( + new Element('a.add.icon2', { + 'title': 'Add movie with your default quality', + 'data-add': movie.imdb, + 'events': { + 'click': m.showOptions.bind(m) + } + }), + $(new MA.IMDB(m)), + $(new MA.Trailer(m, { + 'height': 150 + })) + ) + ); + m.data_container.removeEvents('click'); + + var plot = false; + if(m.info.plot && m.info.plot.length > 0) + plot = m.info.plot; + + // Add rating + m.info_container.adopt( + m.rating = m.info.rating && m.info.rating.imdb && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', { + 'text': parseFloat(m.info.rating.imdb[0]), + 'title': parseInt(m.info.rating.imdb[1]) + ' votes' + }) : null, + m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', { + 'text': m.info.genres.slice(0, 3).join(', ') + }) : null, + m.plot = plot ? new Element('span.plot', { + 'text': plot, + 'events': { + 'click': function(){ + this.toggleClass('full') + } + } + }) : null + ); + + $(m).inject(c); + + }); + + c.inject(self.el); + + }); + + } + + self.fireEvent('loaded'); + + }, + + afterAdded: function(m){ + + $(m).getElement('div.chart_number') + .addClass('chart_in_wanted') + .set('title', 'Movie in wanted list'); + + }, + + toElement: function(){ + return this.el; + } + +}); diff --git a/couchpotato/core/media/movie/providers/automation/base.py b/couchpotato/core/media/movie/providers/automation/base.py index 5b22865..1a8d981 100644 --- a/couchpotato/core/media/movie/providers/automation/base.py +++ b/couchpotato/core/media/movie/providers/automation/base.py @@ -13,6 +13,7 @@ log = CPLog(__name__) class Automation(AutomationBase): enabled_option = 'automation_enabled' + chart_enabled_option = 'chart_display_enabled' http_time_between_calls = 2 interval = 1800 @@ -20,6 +21,7 @@ class Automation(AutomationBase): def __init__(self): addEvent('automation.get_movies', self._getMovies) + addEvent('automation.get_chart_list', self._getChartList) def _getMovies(self): @@ -34,6 +36,13 @@ class Automation(AutomationBase): return self.getIMDBids() + def _getChartList(self): + + if not (self.conf(self.chart_enabled_option) or self.conf(self.chart_enabled_option) is None): + return + + return self.getChartList() + def search(self, name, year = None, imdb_only = False): prop_name = 'automation.cached.%s.%s' % (name, year) @@ -94,5 +103,9 @@ class Automation(AutomationBase): def getIMDBids(self): return [] + def getChartList(self): + # Example return: [ {'name': 'Display name of list', 'url': 'http://example.com/', 'order': 1, 'list': []} ] + return + def canCheck(self): return time.time() > self.last_checked + self.interval diff --git a/couchpotato/core/media/movie/providers/automation/bluray.py b/couchpotato/core/media/movie/providers/automation/bluray.py index b411900..55114fd 100644 --- a/couchpotato/core/media/movie/providers/automation/bluray.py +++ b/couchpotato/core/media/movie/providers/automation/bluray.py @@ -14,6 +14,8 @@ class Bluray(Automation, RSS): interval = 1800 rss_url = 'http://www.blu-ray.com/rss/newreleasesfeed.xml' backlog_url = 'http://www.blu-ray.com/movies/movies.php?show=newreleases&page=%s' + display_url = 'http://www.blu-ray.com/movies/movies.php?show=newreleases' + chart_order = 1 def getIMDBids(self): @@ -62,7 +64,7 @@ class Bluray(Automation, RSS): name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip() year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip() - if not name.find('/') == -1: # make sure it is not a double movie release + if not name.find('/') == -1: # make sure it is not a double movie release continue if tryInt(year) < self.getMinimal('year'): @@ -77,6 +79,32 @@ class Bluray(Automation, RSS): return movies + def getChartList(self): + # Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id) + movie_list = {'name': 'Blu-ray.com - New Releases', 'url': self.display_url, 'order': self.chart_order, 'list': []} + max_items = int(self.conf('max_items', section='charts', default=5)) + rss_movies = self.getRSSData(self.rss_url) + + for movie in rss_movies: + name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip() + year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip() + + if not name.find('/') == -1: # make sure it is not a double movie release + continue + + movie = self.search(name, year) + + if movie: + movie_list['list'].append( movie ) + if len(movie_list['list']) >= max_items: + break + + if not movie_list['list']: + return + + return [ movie_list ] + + config = [{ 'name': 'bluray', 'groups': [ @@ -101,5 +129,19 @@ config = [{ }, ], }, + { + 'tab': 'display', + 'list': 'charts_providers', + 'name': 'bluray_charts_display', + 'label': 'Blu-ray.com', + 'description': 'Display new releases from Blu-ray.com', + 'options': [ + { + 'name': 'chart_display_enabled', + 'default': True, + 'type': 'enabler', + }, + ], + }, ], }] diff --git a/couchpotato/core/media/movie/providers/automation/goodfilms.py b/couchpotato/core/media/movie/providers/automation/goodfilms.py index 2524f66..483cf02 100644 --- a/couchpotato/core/media/movie/providers/automation/goodfilms.py +++ b/couchpotato/core/media/movie/providers/automation/goodfilms.py @@ -46,7 +46,10 @@ class Goodfilms(Automation): break for movie in this_watch_list: - movies.append({ 'title': movie['data-film-title'], 'year': movie['data-film-year'] }) + movies.append({ + 'title': movie['data-film-title'], + 'year': movie['data-film-year'] + }) if not 'next page' in data.lower(): break diff --git a/couchpotato/core/media/movie/providers/automation/imdb.py b/couchpotato/core/media/movie/providers/automation/imdb.py index 10150f2..0f2e294 100644 --- a/couchpotato/core/media/movie/providers/automation/imdb.py +++ b/couchpotato/core/media/movie/providers/automation/imdb.py @@ -105,6 +105,16 @@ class IMDBAutomation(IMDBBase): 'top250': 'http://www.imdb.com/chart/top', 'boxoffice': 'http://www.imdb.com/chart/', } + chart_names = { + 'theater': 'IMDB - Movies in Theaters', + 'top250': 'IMDB - Top 250 Movies', + 'boxoffice': 'IMDB - Box Office', + } + chart_order = { + 'theater': 2, + 'top250': 4, + 'boxoffice': 3, + } first_table = ['boxoffice'] @@ -144,6 +154,46 @@ class IMDBAutomation(IMDBBase): return movies + def getChartList(self): + # Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id) + movie_lists = [] + max_items = int(self.conf('max_items', section='charts', default=5)) + + for url in self.chart_urls: + if self.conf('chart_display_%s' % url): + movie_list = {'name': self.chart_names[url], 'url': self.chart_urls[url], 'order': self.chart_order[url], 'list': []} + data = self.getHTMLData(self.chart_urls[url]) + if data: + html = BeautifulSoup(data) + + try: + result_div = html.find('div', attrs = {'id': 'main'}) + + try: + if url in self.first_table: + table = result_div.find('table') + result_div = table if table else result_div + except: + pass + + imdb_ids = getImdb(str(result_div), multiple = True) + + for imdb_id in imdb_ids[0:max_items]: + info = self.getInfo(imdb_id) + movie_list['list'].append(info) + + if self.shuttingDown(): + break + except: + log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc())) + + if movie_list['list']: + movie_lists.append(movie_list) + + + return movie_lists + + config = [{ 'name': 'imdb', 'groups': [ @@ -206,5 +256,40 @@ config = [{ }, ], }, + { + 'tab': 'display', + 'list': 'charts_providers', + 'name': 'imdb_charts_display', + 'label': 'IMDB', + 'description': 'Display movies from IMDB Charts', + 'options': [ + { + 'name': 'chart_display_enabled', + 'default': True, + 'type': 'enabler', + }, + { + 'name': 'chart_display_theater', + 'type': 'bool', + 'label': 'In Theaters', + 'description': 'New Movies In-Theaters chart', + 'default': False, + }, + { + 'name': 'chart_display_top250', + 'type': 'bool', + 'label': 'TOP 250', + 'description': 'IMDB TOP 250 chart', + 'default': False, + }, + { + 'name': 'chart_display_boxoffice', + 'type': 'bool', + 'label': 'Box office TOP 10', + 'description': 'IMDB Box office TOP 10 chart', + 'default': True, + }, + ], + }, ], }] diff --git a/couchpotato/core/media/movie/providers/automation/letterboxd.py b/couchpotato/core/media/movie/providers/automation/letterboxd.py index fa914d8..e9fc874 100644 --- a/couchpotato/core/media/movie/providers/automation/letterboxd.py +++ b/couchpotato/core/media/movie/providers/automation/letterboxd.py @@ -50,7 +50,10 @@ class Letterboxd(Automation): for movie in soup.find_all('a', attrs = {'class': 'frame'}): match = removeEmpty(self.pattern.split(movie['title'])) - movies.append({'title': match[0], 'year': match[1] }) + movies.append({ + 'title': match[0], + 'year': match[1] + }) return movies diff --git a/couchpotato/core/media/movie/providers/info/_modifier.py b/couchpotato/core/media/movie/providers/info/_modifier.py index cea0f49..bf25727 100644 --- a/couchpotato/core/media/movie/providers/info/_modifier.py +++ b/couchpotato/core/media/movie/providers/info/_modifier.py @@ -104,6 +104,9 @@ class MovieResultModifier(Plugin): if media.get('status') == 'active': temp['in_wanted'] = media + try: temp['in_wanted']['profile'] = db.get('id', media['profile_id']) + except: temp['in_wanted']['profile'] = {'label': ''} + for release in fireEvent('release.for_media', media['_id'], single = True): if release.get('status') == 'done': if not temp['in_library']: diff --git a/couchpotato/core/media/movie/providers/info/themoviedb.py b/couchpotato/core/media/movie/providers/info/themoviedb.py index 9f5c5e8..b48822c 100644 --- a/couchpotato/core/media/movie/providers/info/themoviedb.py +++ b/couchpotato/core/media/movie/providers/info/themoviedb.py @@ -74,6 +74,7 @@ class TheMovieDb(MovieProvider): if not result: try: log.debug('Getting info: %s', cache_key) + # noinspection PyArgumentList movie = tmdb3.Movie(identifier) try: exists = movie.title is not None except: exists = False diff --git a/couchpotato/core/media/movie/providers/torrent/bithdtv.py b/couchpotato/core/media/movie/providers/torrent/bithdtv.py index 8b77199..e7b16af 100644 --- a/couchpotato/core/media/movie/providers/torrent/bithdtv.py +++ b/couchpotato/core/media/movie/providers/torrent/bithdtv.py @@ -14,6 +14,6 @@ class BiTHDTV(MovieProvider, Base): def buildUrl(self, media): query = tryUrlencode({ 'search': fireEvent('library.query', media, single = True), - 'cat': 7 # Movie cat + 'cat': 7 # Movie cat }) return query diff --git a/couchpotato/core/media/movie/providers/torrent/bitsoup.py b/couchpotato/core/media/movie/providers/torrent/bitsoup.py index b3ecf2e..c351ab9 100644 --- a/couchpotato/core/media/movie/providers/torrent/bitsoup.py +++ b/couchpotato/core/media/movie/providers/torrent/bitsoup.py @@ -11,6 +11,7 @@ autoload = 'Bitsoup' class Bitsoup(MovieProvider, Base): cat_ids = [ + ([17], ['3d']), ([41], ['720p', '1080p']), ([20], ['dvdr']), ([19], ['brrip', 'dvdrip']), @@ -23,6 +24,6 @@ class Bitsoup(MovieProvider, Base): fireEvent('library.query', media, include_year = False, single = True), media['info']['year'] ), - 'cat': self.getCatId(quality['identifier'])[0], + 'cat': self.getCatId(quality)[0], }) return query diff --git a/couchpotato/core/media/movie/providers/torrent/iptorrents.py b/couchpotato/core/media/movie/providers/torrent/iptorrents.py index 4f9d651..f06aadf 100644 --- a/couchpotato/core/media/movie/providers/torrent/iptorrents.py +++ b/couchpotato/core/media/movie/providers/torrent/iptorrents.py @@ -10,6 +10,7 @@ autoload = 'IPTorrents' class IPTorrents(MovieProvider, Base): cat_ids = [ + ([87], ['3d']), ([48], ['720p', '1080p', 'bd50']), ([72], ['cam', 'ts', 'tc', 'r5', 'scr']), ([7], ['dvdrip', 'brrip']), @@ -19,4 +20,4 @@ class IPTorrents(MovieProvider, Base): def buildUrl(self, title, media, quality): query = '%s %s' % (title.replace(':', ''), media['info']['year']) - return self._buildUrl(query, quality['identifier']) + return self._buildUrl(query, quality) diff --git a/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py b/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py index d1f378b..bbaea26 100644 --- a/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py +++ b/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py @@ -10,29 +10,29 @@ autoload = 'PassThePopcorn' class PassThePopcorn(MovieProvider, Base): quality_search_params = { - 'bd50': {'media': 'Blu-ray', 'format': 'BD50'}, - '1080p': {'resolution': '1080p'}, - '720p': {'resolution': '720p'}, - 'brrip': {'media': 'Blu-ray'}, - 'dvdr': {'resolution': 'anysd'}, - 'dvdrip': {'media': 'DVD'}, - 'scr': {'media': 'DVD-Screener'}, - 'r5': {'media': 'R5'}, - 'tc': {'media': 'TC'}, - 'ts': {'media': 'TS'}, - 'cam': {'media': 'CAM'} + 'bd50': {'media': 'Blu-ray', 'format': 'BD50'}, + '1080p': {'resolution': '1080p'}, + '720p': {'resolution': '720p'}, + 'brrip': {'media': 'Blu-ray'}, + 'dvdr': {'resolution': 'anysd'}, + 'dvdrip': {'media': 'DVD'}, + 'scr': {'media': 'DVD-Screener'}, + 'r5': {'media': 'R5'}, + 'tc': {'media': 'TC'}, + 'ts': {'media': 'TS'}, + 'cam': {'media': 'CAM'} } post_search_filters = { - 'bd50': {'Codec': ['BD50']}, - '1080p': {'Resolution': ['1080p']}, - '720p': {'Resolution': ['720p']}, - 'brrip': {'Source': ['Blu-ray'], 'Quality': ['High Definition'], 'Container': ['!ISO']}, - 'dvdr': {'Codec': ['DVD5', 'DVD9']}, - 'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']}, - 'scr': {'Source': ['DVD-Screener']}, - 'r5': {'Source': ['R5']}, - 'tc': {'Source': ['TC']}, - 'ts': {'Source': ['TS']}, - 'cam': {'Source': ['CAM']} + 'bd50': {'Codec': ['BD50']}, + '1080p': {'Resolution': ['1080p']}, + '720p': {'Resolution': ['720p']}, + 'brrip': {'Source': ['Blu-ray'], 'Quality': ['High Definition'], 'Container': ['!ISO']}, + 'dvdr': {'Codec': ['DVD5', 'DVD9']}, + 'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']}, + 'scr': {'Source': ['DVD-Screener']}, + 'r5': {'Source': ['R5']}, + 'tc': {'Source': ['TC']}, + 'ts': {'Source': ['TS']}, + 'cam': {'Source': ['CAM']} } diff --git a/couchpotato/core/media/movie/providers/torrent/sceneaccess.py b/couchpotato/core/media/movie/providers/torrent/sceneaccess.py index 2a77839..dee4b32 100644 --- a/couchpotato/core/media/movie/providers/torrent/sceneaccess.py +++ b/couchpotato/core/media/movie/providers/torrent/sceneaccess.py @@ -18,10 +18,8 @@ class SceneAccess(MovieProvider, Base): ] def buildUrl(self, media, quality): - url = self.urls['search'] % ( - self.getCatId(quality['identifier'])[0], - self.getCatId(quality['identifier'])[0] - ) + cat_id = self.getCatId(quality)[0] + url = self.urls['search'] % (cat_id, cat_id) arguments = tryUrlencode({ 'search': fireEvent('library.query', media, single = True), diff --git a/couchpotato/core/media/movie/providers/torrent/thepiratebay.py b/couchpotato/core/media/movie/providers/torrent/thepiratebay.py index 457e53d..f804d70 100644 --- a/couchpotato/core/media/movie/providers/torrent/thepiratebay.py +++ b/couchpotato/core/media/movie/providers/torrent/thepiratebay.py @@ -12,10 +12,11 @@ autoload = 'ThePirateBay' class ThePirateBay(MovieProvider, Base): cat_ids = [ - ([207], ['720p', '1080p']), - ([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), - ([201, 207], ['brrip']), - ([202], ['dvdr']) + ([209], ['3d']), + ([207], ['720p', '1080p']), + ([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), + ([201, 207], ['brrip']), + ([202], ['dvdr']) ] def buildUrl(self, media, page, cats): diff --git a/couchpotato/core/media/movie/providers/torrent/torrentday.py b/couchpotato/core/media/movie/providers/torrent/torrentday.py index 03fe3fe..872a7d1 100644 --- a/couchpotato/core/media/movie/providers/torrent/torrentday.py +++ b/couchpotato/core/media/movie/providers/torrent/torrentday.py @@ -16,5 +16,6 @@ class TorrentDay(MovieProvider, Base): ([3], ['dvdr']), ([5], ['bd50']), ] + def buildUrl(self, media): return fireEvent('library.query', media, single = True) diff --git a/couchpotato/core/media/movie/providers/torrent/torrentleech.py b/couchpotato/core/media/movie/providers/torrent/torrentleech.py index 07ace2b..9ab1580 100644 --- a/couchpotato/core/media/movie/providers/torrent/torrentleech.py +++ b/couchpotato/core/media/movie/providers/torrent/torrentleech.py @@ -24,5 +24,5 @@ class TorrentLeech(MovieProvider, Base): def buildUrl(self, media, quality): return ( tryUrlencode(fireEvent('library.query', media, single = True)), - self.getCatId(quality['identifier'])[0] + self.getCatId(quality)[0] ) diff --git a/couchpotato/core/media/movie/providers/torrent/torrentshack.py b/couchpotato/core/media/movie/providers/torrent/torrentshack.py index 24ac863..85e420d 100644 --- a/couchpotato/core/media/movie/providers/torrent/torrentshack.py +++ b/couchpotato/core/media/movie/providers/torrent/torrentshack.py @@ -31,6 +31,6 @@ class TorrentShack(MovieProvider, Base): def buildUrl(self, media, quality): query = (tryUrlencode(fireEvent('library.query', media, single = True)), - self.getCatId(quality['identifier'])[0], + self.getCatId(quality)[0], self.getSceneOnly()) return query diff --git a/couchpotato/core/media/movie/providers/torrent/torrentz.py b/couchpotato/core/media/movie/providers/torrent/torrentz.py new file mode 100644 index 0000000..742554c --- /dev/null +++ b/couchpotato/core/media/movie/providers/torrent/torrentz.py @@ -0,0 +1,15 @@ +from couchpotato.core.helpers.encoding import tryUrlencode +from couchpotato.core.logger import CPLog +from couchpotato.core.event import fireEvent +from couchpotato.core.media._base.providers.torrent.torrentz import Base +from couchpotato.core.media.movie.providers.base import MovieProvider + +log = CPLog(__name__) + +autoload = 'Torrentz' + + +class Torrentz(MovieProvider, Base): + + def buildUrl(self, media): + return tryUrlencode('"%s"' % fireEvent('library.query', media, single = True)) diff --git a/couchpotato/core/media/movie/providers/trailer/hdtrailers.py b/couchpotato/core/media/movie/providers/trailer/hdtrailers.py index 084c8ea..ad040a3 100644 --- a/couchpotato/core/media/movie/providers/trailer/hdtrailers.py +++ b/couchpotato/core/media/movie/providers/trailer/hdtrailers.py @@ -52,7 +52,7 @@ class HDTrailers(TrailerProvider): return result_data def findViaAlternative(self, group): - results = {'480p':[], '720p':[], '1080p':[]} + results = {'480p': [], '720p': [], '1080p': []} movie_name = getTitle(group) diff --git a/couchpotato/core/media/movie/providers/userscript/filmcentrum.py b/couchpotato/core/media/movie/providers/userscript/filmcentrum.py new file mode 100644 index 0000000..b2b15a9 --- /dev/null +++ b/couchpotato/core/media/movie/providers/userscript/filmcentrum.py @@ -0,0 +1,8 @@ +from couchpotato.core.media._base.providers.userscript.base import UserscriptBase + +autoload = 'FilmCentrum' + + +class FilmCentrum(UserscriptBase): + + includes = ['*://filmcentrum.nl/films/*'] diff --git a/couchpotato/core/media/movie/searcher.py b/couchpotato/core/media/movie/searcher.py index 682fa69..68a76ad 100644 --- a/couchpotato/core/media/movie/searcher.py +++ b/couchpotato/core/media/movie/searcher.py @@ -143,14 +143,17 @@ class MovieSearcher(SearcherBase, MovieTypeBase): ret = False + index = 0 for q_identifier in profile.get('qualities'): - index = profile['qualities'].index(q_identifier) quality_custom = { 'quality': q_identifier, 'finish': profile['finish'][index], - 'wait_for': profile['wait_for'][index] + 'wait_for': profile['wait_for'][index], + '3d': profile['3d'][index] if profile.get('3d') else False } + index += 1 + if not self.conf('always_search') and not self.couldBeReleased(q_identifier in pre_releases, release_dates, movie['info']['year']): too_early_to_search.append(q_identifier) continue @@ -168,6 +171,9 @@ class MovieSearcher(SearcherBase, MovieTypeBase): quality = fireEvent('quality.single', identifier = q_identifier, single = True) log.info('Search for %s in %s', (default_title, quality['label'])) + # Extend quality with profile customs + quality['custom'] = quality_custom + results = fireEvent('searcher.search', search_protocols, movie, quality, single = True) or [] if len(results) == 0: log.debug('Nothing found for %s in %s', (default_title, quality['label'])) @@ -221,13 +227,17 @@ class MovieSearcher(SearcherBase, MovieTypeBase): if not fireEvent('searcher.correct_words', nzb['name'], media, single = True): return False - preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True) + preferred_quality = quality if quality else fireEvent('quality.single', identifier = quality['identifier'], single = True) # Contains lower quality string if fireEvent('searcher.contains_other_quality', nzb, movie_year = media['info']['year'], preferred_quality = preferred_quality, single = True): log.info2('Wrong: %s, looking for %s', (nzb['name'], quality['label'])) return False + # Contains lower quality string + if not fireEvent('searcher.correct_3d', nzb, preferred_quality = preferred_quality, single = True): + log.info2('Wrong: %s, %slooking for %s in 3D', (nzb['name'], ('' if preferred_quality['custom'].get('3d') else 'NOT '), quality['label'])) + return False # File to small if nzb['size'] and tryInt(preferred_quality['size_min']) > tryInt(nzb['size']): @@ -239,7 +249,6 @@ class MovieSearcher(SearcherBase, MovieTypeBase): log.info2('Wrong: "%s" is too large to be %s. %sMB instead of the maximum of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_max'])) return False - # Provider specific functions get_more = nzb.get('get_more_info') if get_more: diff --git a/couchpotato/core/media/movie/suggestion/static/suggest.css b/couchpotato/core/media/movie/suggestion/static/suggest.css index d4ba734..8e74784 100644 --- a/couchpotato/core/media/movie/suggestion/static/suggest.css +++ b/couchpotato/core/media/movie/suggestion/static/suggest.css @@ -1,4 +1,7 @@ .suggestions { + clear: both; + padding-top: 10px; + margin-bottom: 30px; } .suggestions > h2 { @@ -91,7 +94,6 @@ padding: 0 3px 10px 0; } .suggestions .media_result .data:before { - bottom: 0; content: ''; display: block; height: 10px; @@ -107,7 +109,7 @@ z-index: 3; pointer-events: none; } - + .suggestions .media_result .data .info .plot.full { top: 0; overflow: auto; @@ -123,14 +125,14 @@ .suggestions .media_result .options select[name=title] { width: 100%; } .suggestions .media_result .options select[name=profile] { width: 100%; } .suggestions .media_result .options select[name=category] { width: 100%; } - - .suggestions .media_result .button { + + .suggestions .media_result .button { position: absolute; margin: 2px 0 0 0; right: 15px; bottom: 15px; } - + .suggestions .media_result .thumbnail { width: 100px; diff --git a/couchpotato/core/media/movie/suggestion/static/suggest.js b/couchpotato/core/media/movie/suggestion/static/suggest.js index c4e5630..494f045 100644 --- a/couchpotato/core/media/movie/suggestion/static/suggest.js +++ b/couchpotato/core/media/movie/suggestion/static/suggest.js @@ -42,11 +42,10 @@ var SuggestList = new Class({ } } - }).grab( - new Element('h2', { - 'text': 'You might like these' - }) - ); + }); + + var cookie_menu_select = Cookie.read('suggestions_charts_menu_selected'); + if( cookie_menu_select === 'suggestions' || cookie_menu_select === null ) self.el.show(); else self.el.hide(); self.api_request = Api.request('suggestion.view', { 'onComplete': self.fill.bind(self) @@ -116,7 +115,7 @@ var SuggestList = new Class({ } } }) : null - ) + ); $(m).inject(self.el); @@ -150,4 +149,4 @@ var SuggestList = new Class({ return this.el; } -}) +}); diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 1a506c1..0cd8e57 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -131,7 +131,7 @@ class CoreNotifier(Notification): for message in messages: if message.get('time') > last_check: - message['sticky'] = True # Always sticky core messages + message['sticky'] = True # Always sticky core messages message_type = 'core.message.important' if message.get('important') else 'core.message' fireEvent(message_type, message = message.get('message'), data = message) diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 2cd4fff..c999fc7 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -17,14 +17,14 @@ var NotificationBase = new Class({ App.addEvent('load', self.addTestButtons.bind(self)); // Notification bar - self.notifications = [] + self.notifications = []; App.addEvent('load', function(){ App.block.notification = new Block.Menu(self, { 'button_class': 'icon2.eye-open', 'class': 'notification_menu', 'onOpen': self.markAsRead.bind(self) - }) + }); $(App.block.notification).inject(App.getBlock('search'), 'after'); self.badge = new Element('div.badge').inject(App.block.notification, 'top').hide(); @@ -40,7 +40,7 @@ var NotificationBase = new Class({ var self = this; var added = new Date(); - added.setTime(result.added*1000) + added.setTime(result.added*1000); result.el = App.getBlock('notification').addLink( new Element('span.'+(result.read ? 'read' : '' )).adopt( @@ -51,7 +51,7 @@ var NotificationBase = new Class({ self.notifications.include(result); if((result.data.important !== undefined || result.data.sticky !== undefined) && !result.read){ - var sticky = true + var sticky = true; App.trigger('message', [result.message, sticky, result]) } else if(!result.read){ @@ -62,7 +62,7 @@ var NotificationBase = new Class({ setBadge: function(value){ var self = this; - self.badge.set('text', value) + self.badge.set('text', value); self.badge[value ? 'show' : 'hide']() }, @@ -73,9 +73,9 @@ var NotificationBase = new Class({ if(!force_ids) { var rn = self.notifications.filter(function(n){ return !n.read && n.data.important === undefined - }) + }); - var ids = [] + var ids = []; rn.each(function(n){ ids.include(n._id) }) @@ -106,7 +106,7 @@ var NotificationBase = new Class({ 'onSuccess': function(json){ self.processData(json, true) } - }).send() + }).send(); setInterval(function(){ @@ -141,7 +141,7 @@ var NotificationBase = new Class({ stopPoll: function(){ if(this.request) - this.request.cancel() + this.request.cancel(); this.stopped = true; }, @@ -154,7 +154,7 @@ var NotificationBase = new Class({ App.trigger(result._t || result.type, [result]); if(result.message && result.read === undefined && !init) self.showMessage(result.message); - }) + }); if(json.result.length > 0) self.last_id = json.result.getLast().message_id @@ -180,11 +180,11 @@ var NotificationBase = new Class({ }, 10); var hide_message = function(){ - new_message.addClass('hide') + new_message.addClass('hide'); setTimeout(function(){ new_message.destroy(); }, 1000); - } + }; if(sticky) new_message.grab( @@ -206,7 +206,7 @@ var NotificationBase = new Class({ addTestButtons: function(){ var self = this; - var setting_page = App.getPage('Settings') + var setting_page = App.getPage('Settings'); setting_page.addEvent('create', function(){ Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self)) }) diff --git a/couchpotato/core/notifications/email_.py b/couchpotato/core/notifications/email_.py index 938a361..a63eb3d 100644 --- a/couchpotato/core/notifications/email_.py +++ b/couchpotato/core/notifications/email_.py @@ -97,7 +97,8 @@ config = [{ 'name': 'smtp_server', 'label': 'SMTP server', }, - { 'name': 'smtp_port', + { + 'name': 'smtp_port', 'label': 'SMTP server port', 'default': '25', 'type': 'int', diff --git a/couchpotato/core/notifications/growl.py b/couchpotato/core/notifications/growl.py index 160e383..e60e7ef 100644 --- a/couchpotato/core/notifications/growl.py +++ b/couchpotato/core/notifications/growl.py @@ -19,6 +19,8 @@ class Growl(Notification): def __init__(self): super(Growl, self).__init__() + self.growl = None + if self.isEnabled(): addEvent('app.load', self.register) diff --git a/couchpotato/core/notifications/nmj.py b/couchpotato/core/notifications/nmj.py index 4b9b92b..7579caf 100644 --- a/couchpotato/core/notifications/nmj.py +++ b/couchpotato/core/notifications/nmj.py @@ -20,6 +20,7 @@ autoload = 'NMJ' class NMJ(Notification): + # noinspection PyMissingConstructor def __init__(self): addEvent('renamer.after', self.addToLibrary) addApiView(self.testNotifyName(), self.test) diff --git a/couchpotato/core/notifications/pushalot.py b/couchpotato/core/notifications/pushalot.py index f8fa187..fa781bc 100644 --- a/couchpotato/core/notifications/pushalot.py +++ b/couchpotato/core/notifications/pushalot.py @@ -30,7 +30,7 @@ class Pushalot(Notification): } headers = { - 'Content-type': 'application/x-www-form-urlencoded' + 'Content-type': 'application/x-www-form-urlencoded' } try: diff --git a/couchpotato/core/notifications/pushover.py b/couchpotato/core/notifications/pushover.py index 9559374..ea4b00a 100644 --- a/couchpotato/core/notifications/pushover.py +++ b/couchpotato/core/notifications/pushover.py @@ -25,6 +25,7 @@ class Pushover(Notification): 'token': self.app_token, 'message': toUnicode(message), 'priority': self.conf('priority'), + 'sound': self.conf('sound'), } if data and data.get('identifier'): @@ -33,10 +34,9 @@ class Pushover(Notification): 'url_title': toUnicode('%s on IMDb' % getTitle(data)), }) - http_handler.request('POST', - "/1/messages.json", - headers = {'Content-type': 'application/x-www-form-urlencoded'}, - body = tryUrlencode(api_data) + http_handler.request('POST', '/1/messages.json', + headers = {'Content-type': 'application/x-www-form-urlencoded'}, + body = tryUrlencode(api_data) ) response = http_handler.getresponse() @@ -83,6 +83,11 @@ config = [{ 'advanced': True, 'description': 'Also send message when movie is snatched.', }, + { + 'name': 'sound', + 'advanced': True, + 'description': 'Define custom sound for Pushover alert.' + }, ], } ], diff --git a/couchpotato/core/notifications/twitter/static/twitter.js b/couchpotato/core/notifications/twitter/static/twitter.js index 2c4e6e3..c817fe0 100644 --- a/couchpotato/core/notifications/twitter/static/twitter.js +++ b/couchpotato/core/notifications/twitter/static/twitter.js @@ -59,7 +59,7 @@ var TwitterNotification = new Class({ ).inject(fieldset.getElement('.test_button'), 'before'); }) - }, + } }); diff --git a/couchpotato/core/notifications/xbmc.py b/couchpotato/core/notifications/xbmc.py index 682801c..28439a7 100644 --- a/couchpotato/core/notifications/xbmc.py +++ b/couchpotato/core/notifications/xbmc.py @@ -89,7 +89,7 @@ class XBMC(Notification): self.use_json_notifications[host] = False # send the text message - resp = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message}) + resp = self.notifyXBMCnoJSON(host, {'title': self.default_title, 'message': message}) for r in resp: if r.get('result') and r['result'] == 'OK': log.debug('Message delivered successfully!') diff --git a/couchpotato/core/plugins/automation.py b/couchpotato/core/plugins/automation.py index e17dc36..39d7c9e 100644 --- a/couchpotato/core/plugins/automation.py +++ b/couchpotato/core/plugins/automation.py @@ -35,9 +35,9 @@ class Automation(Plugin): prop_name = 'automation.added.%s' % imdb_id added = Env.prop(prop_name, default = False) if not added: - added_movie = fireEvent('movie.add', params = {'identifier': imdb_id}, force_readd = False, search_after = False, update_library = True, single = True) + added_movie = fireEvent('movie.add', params = {'identifier': imdb_id}, force_readd = False, search_after = False, update_after = True, single = True) if added_movie: - movie_ids.append(added_movie['id']) + movie_ids.append(added_movie['_id']) Env.prop(prop_name, True) for movie_id in movie_ids: diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index c5892f5..13eb7ca 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -43,8 +43,8 @@ class Plugin(object): http_failed_disabled = {} http_opener = requests.Session() - def __new__(typ, *args, **kwargs): - new_plugin = super(Plugin, typ).__new__(typ) + def __new__(cls, *args, **kwargs): + new_plugin = super(Plugin, cls).__new__(cls) new_plugin.registerPlugin() return new_plugin @@ -329,7 +329,7 @@ class Plugin(object): elif data.get('password'): tag += '{{%s}}' % data.get('password') - max_length = 127 - len(tag) # Some filesystems don't support 128+ long filenames + max_length = 127 - len(tag) # Some filesystems don't support 128+ long filenames return '%s%s' % (toSafeString(toUnicode(release_name)[:max_length]), tag) def createFileName(self, data, filedata, media): @@ -349,6 +349,7 @@ class Plugin(object): now = time.time() file_too_new = False + file_time = [] for cur_file in files: # File got removed while checking diff --git a/couchpotato/core/plugins/browser.py b/couchpotato/core/plugins/browser.py index 9492a2d..6880f3b 100644 --- a/couchpotato/core/plugins/browser.py +++ b/couchpotato/core/plugins/browser.py @@ -17,7 +17,8 @@ if os.name == 'nt': raise ImportError("Missing the win32file module, which is a part of the prerequisite \ pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/") else: - import win32file #@UnresolvedImport + # noinspection PyUnresolvedReferences + import win32file autoload = 'FileBrowser' diff --git a/couchpotato/core/plugins/category/static/category.css b/couchpotato/core/plugins/category/static/category.css index 0987c19..3218a79 100644 --- a/couchpotato/core/plugins/category/static/category.css +++ b/couchpotato/core/plugins/category/static/category.css @@ -22,7 +22,7 @@ .category > .delete:hover { opacity: 1; } - + .category .ctrlHolder:hover { background: none; } @@ -69,7 +69,7 @@ } #category_ordering li .handle { - background: url('../../static/profile_plugin/handle.png') center; + background: url('../../images/handle.png') center; width: 20px; float: right; } @@ -79,4 +79,4 @@ float: right; width: 250px; margin: 0; - } \ No newline at end of file + } diff --git a/couchpotato/core/plugins/category/static/category.js b/couchpotato/core/plugins/category/static/category.js index fedd284..b323491 100644 --- a/couchpotato/core/plugins/category/static/category.js +++ b/couchpotato/core/plugins/category/static/category.js @@ -9,7 +9,7 @@ var CategoryListBase = new Class({ setup: function(categories){ var self = this; - self.categories = [] + self.categories = []; Array.each(categories, self.createCategory.bind(self)); }, @@ -17,7 +17,7 @@ var CategoryListBase = new Class({ addSettings: function(){ var self = this; - self.settings = App.getPage('Settings') + self.settings = App.getPage('Settings'); self.settings.addEvent('create', function(){ var tab = self.settings.createSubTab('category', { 'label': 'Categories', @@ -31,7 +31,7 @@ var CategoryListBase = new Class({ self.createList(); self.createOrdering(); - }) + }); // Add categories in renamer self.settings.addEvent('create', function(){ @@ -97,9 +97,9 @@ var CategoryListBase = new Class({ createCategory: function(data){ var self = this; - var data = data || {'id': randomString()} - var category = new Category(data) - self.categories.include(category) + var data = data || {'id': randomString()}; + var category = new Category(data); + self.categories.include(category); return category; }, @@ -108,7 +108,7 @@ var CategoryListBase = new Class({ var self = this; var category_list; - var group = self.settings.createGroup({ + self.settings.createGroup({ 'label': 'Category ordering' }).adopt( new Element('.ctrlHolder#category_ordering').adopt( @@ -118,7 +118,7 @@ var CategoryListBase = new Class({ 'html': 'Change the order the categories are in the dropdown list.
First one will be default.' }) ) - ).inject(self.content) + ).inject(self.content); Array.each(self.categories, function(category){ new Element('li', {'data-id': category.data._id}).adopt( @@ -145,7 +145,7 @@ var CategoryListBase = new Class({ var ids = []; - self.category_sortable.list.getElements('li').each(function(el, nr){ + self.category_sortable.list.getElements('li').each(function(el){ ids.include(el.get('data-id')); }); @@ -157,7 +157,7 @@ var CategoryListBase = new Class({ } -}) +}); window.CategoryList = new CategoryListBase(); @@ -235,8 +235,6 @@ var Category = new Class({ if(self.save_timer) clearTimeout(self.save_timer); self.save_timer = (function(){ - var data = self.getData(); - Api.request('category.save', { 'data': self.getData(), 'useSpinner': true, @@ -257,7 +255,7 @@ var Category = new Class({ getData: function(){ var self = this; - var data = { + return { 'id' : self.data._id, 'label' : self.el.getElement('.category_label input').get('value'), 'required' : self.el.getElement('.category_required input').get('value'), @@ -265,8 +263,6 @@ var Category = new Class({ 'ignored' : self.el.getElement('.category_ignored input').get('value'), 'destination': self.data.destination } - - return data }, del: function(){ diff --git a/couchpotato/core/plugins/dashboard.py b/couchpotato/core/plugins/dashboard.py index fad68b0..7a26b2a 100644 --- a/couchpotato/core/plugins/dashboard.py +++ b/couchpotato/core/plugins/dashboard.py @@ -77,8 +77,8 @@ class Dashboard(Plugin): if coming_soon: # Don't list older movies - if ((not late and (media['info']['year'] >= now_year-1) and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or - (late and ((media['info']['year'] < now_year-1) or ((eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))))): + if ((not late and (media['info']['year'] >= now_year - 1) and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or + (late and (media['info']['year'] < now_year - 1 or (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200)))): medias.append(media) if len(medias) >= limit: diff --git a/couchpotato/core/plugins/file.py b/couchpotato/core/plugins/file.py index b2533c0..92d85c5 100644 --- a/couchpotato/core/plugins/file.py +++ b/couchpotato/core/plugins/file.py @@ -1,5 +1,4 @@ import os.path -import time import traceback from couchpotato import get_db @@ -64,7 +63,7 @@ class FileManager(Plugin): def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None): if not urlopen_kwargs: urlopen_kwargs = {} - if not dest: # to Cache + if not dest: # to Cache dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url))) if not overwrite and os.path.isfile(dest): diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py index ced069e..2565398 100644 --- a/couchpotato/core/plugins/log/main.py +++ b/couchpotato/core/plugins/log/main.py @@ -67,14 +67,14 @@ class Logging(Plugin): if x is nr: current_path = path - log = '' + log_content = '' if current_path: f = open(current_path, 'r') - log = f.read() + log_content = f.read() return { 'success': True, - 'log': toUnicode(log), + 'log': toUnicode(log_content), 'total': total, } diff --git a/couchpotato/core/plugins/log/static/log.css b/couchpotato/core/plugins/log/static/log.css index bcab6e2..b706835 100644 --- a/couchpotato/core/plugins/log/static/log.css +++ b/couchpotato/core/plugins/log/static/log.css @@ -50,7 +50,7 @@ overflow: hidden; line-height: 150%; font-size: 11px; - font-family: Lucida Console, Monaco, Nimbus Mono L; + font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif; } .page.log .container .error { diff --git a/couchpotato/core/plugins/log/static/log.js b/couchpotato/core/plugins/log/static/log.js index e450b07..c5effeb 100644 --- a/couchpotato/core/plugins/log/static/log.js +++ b/couchpotato/core/plugins/log/static/log.js @@ -42,7 +42,7 @@ Page.Log = new Class({ } } }).inject(nav); - }; + } new Element('li', { 'text': 'clear', @@ -63,7 +63,6 @@ Page.Log = new Class({ }, addColors: function(text){ - var self = this; text = text .replace(/&/g, '&') @@ -74,9 +73,9 @@ Page.Log = new Class({ .replace(/\u001b\[36m/gi, '') .replace(/\u001b\[33m/gi, '') .replace(/\u001b\[0m\n/gi, '
') - .replace(/\u001b\[0m/gi, '') + .replace(/\u001b\[0m/gi, ''); return '
' + text + '
'; } -}) +}); diff --git a/couchpotato/core/plugins/manage.py b/couchpotato/core/plugins/manage.py index 623ce6f..f6b0ddd 100644 --- a/couchpotato/core/plugins/manage.py +++ b/couchpotato/core/plugins/manage.py @@ -160,7 +160,7 @@ class Manage(Plugin): except: log.error('Failed updating library: %s', (traceback.format_exc())) - while True and not self.shuttingDown(): + while self.in_progress and len(self.in_progress) > 0 and not self.shuttingDown(): delete_me = {} @@ -171,14 +171,12 @@ class Manage(Plugin): for delete in delete_me: del self.in_progress[delete] - if len(self.in_progress) == 0: - break - time.sleep(1) fireEvent('notify.frontend', type = 'manage.updating', data = False) self.in_progress = False + # noinspection PyDefaultArgument def createAddToLibrary(self, folder, added_identifiers = []): def addToLibrary(group, total_found, to_go): @@ -219,7 +217,7 @@ class Manage(Plugin): pr = self.in_progress[folder] pr['to_go'] -= 1 - avg = (time.time() - pr['started'])/(pr['total'] - pr['to_go']) + avg = (time.time() - pr['started']) / (pr['total'] - pr['to_go']) pr['eta'] = tryInt(avg * pr['to_go']) diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py index 5d15c11..a7fdcd4 100644 --- a/couchpotato/core/plugins/profile/main.py +++ b/couchpotato/core/plugins/profile/main.py @@ -79,7 +79,8 @@ class ProfilePlugin(Plugin): 'core': kwargs.get('core', False), 'qualities': [], 'wait_for': [], - 'finish': [] + 'finish': [], + '3d': [] } # Update types @@ -88,6 +89,7 @@ class ProfilePlugin(Plugin): profile['qualities'].append(type.get('quality')) profile['wait_for'].append(tryInt(type.get('wait_for'))) profile['finish'].append((tryInt(type.get('finish')) == 1) if order > 0 else True) + profile['3d'].append(tryInt(type.get('3d'))) order += 1 id = kwargs.get('id') @@ -184,6 +186,14 @@ class ProfilePlugin(Plugin): }, { 'label': 'SD', 'qualities': ['dvdrip', 'dvdr'] + }, { + 'label': 'Prefer 3D HD', + 'qualities': ['1080p', '720p', '720p', '1080p'], + '3d': [True, True] + }, { + 'label': '3D HD', + 'qualities': ['1080p', '720p'], + '3d': [True, True] }] # Create default quality profile @@ -197,12 +207,15 @@ class ProfilePlugin(Plugin): 'order': order, 'qualities': profile.get('qualities'), 'finish': [], - 'wait_for': [] + 'wait_for': [], + '3d': [] } + threed = profile.get('3d', []) for q in profile.get('qualities'): pro['finish'].append(True) pro['wait_for'].append(0) + pro['3d'].append(threed.pop() if threed else False) db.insert(pro) order += 1 diff --git a/couchpotato/core/plugins/profile/static/profile.css b/couchpotato/core/plugins/profile/static/profile.css index d94576b..073fa57 100644 --- a/couchpotato/core/plugins/profile/static/profile.css +++ b/couchpotato/core/plugins/profile/static/profile.css @@ -22,18 +22,17 @@ .profile > .delete:hover { opacity: 1; } - + .profile .ctrlHolder:hover { background: none; } .profile .qualities { min-height: 80px; - padding-top: 0; } .profile .formHint { - width: 250px !important; + width: 210px !important; vertical-align: top !important; margin: 0 !important; padding-left: 3px !important; @@ -78,21 +77,55 @@ } .profile .quality_type select { - width: 186px; + width: 120px; margin-left: -1px; } - .profile .types li.is_empty .check, .profile .types li.is_empty .delete, .profile .types li.is_empty .handle { + .profile .types li.is_empty .check, + .profile .types li.is_empty .delete, + .profile .types li.is_empty .handle, + .profile .types li.is_empty .check_label { visibility: hidden; } + .profile .types .type label { + display: inline-block; + width: auto; + float: none; + text-transform: uppercase; + font-size: 11px; + font-weight: normal; + margin-right: 20px; + text-shadow: none; + vertical-align: bottom; + padding: 0; + height: 17px; + } + .profile .types .type label .check { + margin-right: 5px; + } + .profile .types .type label .check_label { + display: inline-block; + vertical-align: top; + height: 16px; + line-height: 13px; + } + + .profile .types .type .threed { + display: none; + } + + .profile .types .type.allow_3d .threed { + display: inline-block; + } + .profile .types .type .handle { - background: url('../../static/profile_plugin/handle.png') center; + background: url('../../images/handle.png') center; display: inline-block; height: 20px; width: 20px; - cursor: -webkit-grab; cursor: -moz-grab; + cursor: -webkit-grab; cursor: grab; margin: 0; } @@ -106,6 +139,9 @@ font-size: 13px; color: #fd5353; } + .profile .types .type:not(.allow_3d) .delete { + margin-left: 55px; + } .profile .types .type:hover:not(.is_empty) .delete { visibility: visible; @@ -144,7 +180,7 @@ } #profile_ordering li .handle { - background: url('../../static/profile_plugin/handle.png') center; + background: url('../../images/handle.png') center; width: 20px; float: right; } @@ -154,4 +190,4 @@ float: right; width: 250px; margin: 0; - } \ No newline at end of file + } diff --git a/couchpotato/core/plugins/profile/static/profile.js b/couchpotato/core/plugins/profile/static/profile.js index 1bd2edc..8441b6a 100644 --- a/couchpotato/core/plugins/profile/static/profile.js +++ b/couchpotato/core/plugins/profile/static/profile.js @@ -63,6 +63,7 @@ var Profile = new Class({ data.types.include({ 'quality': quality, 'finish': data.finish[nr] || false, + '3d': data['3d'] ? data['3d'][nr] || false : false, 'wait_for': data.wait_for[nr] || 0 }) }); @@ -99,7 +100,7 @@ var Profile = new Class({ 'onComplete': function(json){ if(json.success){ self.data = json.profile; - self.type_container.getElement('li:first-child input[type=checkbox]') + self.type_container.getElement('li:first-child input.finish[type=checkbox]') .set('checked', true) .getParent().addClass('checked'); } @@ -118,16 +119,17 @@ var Profile = new Class({ 'label' : self.el.getElement('.quality_label input').get('value'), 'wait_for' : self.el.getElement('.wait_for input').get('value'), 'types': [] - } + }; Array.each(self.type_container.getElements('.type'), function(type){ if(!type.hasClass('deleted') && type.getElement('select').get('value') != -1) data.types.include({ 'quality': type.getElement('select').get('value'), - 'finish': +type.getElement('input[type=checkbox]').checked, + 'finish': +type.getElement('input.finish[type=checkbox]').checked, + '3d': +type.getElement('input.3d[type=checkbox]').checked, 'wait_for': 0 }); - }) + }); return data }, @@ -240,6 +242,7 @@ Profile.Type = new Class({ self.addEvent('change', function(){ self.el[self.qualities.get('value') == '-1' ? 'addClass' : 'removeClass']('is_empty'); + self.el[Quality.getQuality(self.qualities.get('value')).allow_3d ? 'addClass': 'removeClass']('allow_3d'); self.deleted = self.qualities.get('value') == '-1'; }); @@ -250,24 +253,40 @@ Profile.Type = new Class({ var data = self.data; self.el = new Element('li.type').adopt( - new Element('span.quality_type').adopt( + new Element('span.quality_type').grab( self.fillQualities() ), - new Element('span.finish').adopt( - self.finish = new Element('input.inlay.finish[type=checkbox]', { - 'checked': data.finish !== undefined ? data.finish : 1, - 'events': { - 'change': function(e){ - if(self.el == self.el.getParent().getElement(':first-child')){ - self.finish_class.check(); - alert('Top quality always finishes the search') - return; + self.finish_container = new Element('label.finish').adopt( + new Element('span.finish').grab( + self.finish = new Element('input.inlay.finish[type=checkbox]', { + 'checked': data.finish !== undefined ? data.finish : 1, + 'events': { + 'change': function(){ + if(self.el == self.el.getParent().getElement(':first-child')){ + self.finish_class.check(); + alert('Top quality always finishes the search'); + return; + } + + self.fireEvent('change'); } - - self.fireEvent('change'); } - } - }) + }) + ), + new Element('span.check_label[text=finish]') + ), + self['3d_container'] = new Element('label.threed').adopt( + new Element('span.3d').grab( + self['3d'] = new Element('input.inlay.3d[type=checkbox]', { + 'checked': data['3d'] !== undefined ? data['3d'] : 0, + 'events': { + 'change': function(){ + self.fireEvent('change'); + } + } + }) + ), + new Element('span.check_label[text=3D]') ), new Element('span.delete.icon2', { 'events': { @@ -279,7 +298,11 @@ Profile.Type = new Class({ self.el[self.data.quality ? 'removeClass' : 'addClass']('is_empty'); + if(self.data.quality && Quality.getQuality(self.data.quality).allow_3d) + self.el.addClass('allow_3d'); + self.finish_class = new Form.Check(self.finish); + self['3d_class'] = new Form.Check(self['3d']); }, @@ -290,7 +313,7 @@ Profile.Type = new Class({ 'events': { 'change': self.fireEvent.bind(self, 'change') } - }).adopt( + }).grab( new Element('option', { 'text': '+ Add another quality', 'value': -1 @@ -300,7 +323,8 @@ Profile.Type = new Class({ Object.each(Quality.qualities, function(q){ new Element('option', { 'text': q.label, - 'value': q.identifier + 'value': q.identifier, + 'data-allow_3d': q.allow_3d }).inject(self.qualities) }); @@ -316,6 +340,7 @@ Profile.Type = new Class({ return { 'quality': self.qualities.get('value'), 'finish': +self.finish.checked, + '3d': +self['3d'].checked, 'wait_for': 0 } }, @@ -338,4 +363,4 @@ Profile.Type = new Class({ return this.el; } -}) +}); diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index e64b503..c27815e 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -21,9 +21,9 @@ class QualityPlugin(Plugin): } qualities = [ - {'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]}, - {'identifier': '1080p', 'hd': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts', 'x264', 'h264']}, - {'identifier': '720p', 'hd': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts'], 'tags': ['x264', 'h264']}, + {'identifier': 'bd50', 'hd': True, 'allow_3d': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]}, + {'identifier': '1080p', 'hd': True, 'allow_3d': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts', 'x264', 'h264']}, + {'identifier': '720p', 'hd': True, 'allow_3d': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts'], 'tags': ['x264', 'h264']}, {'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':[], 'tags': ['hdtv', 'hdrip', 'webdl', ('web', 'dl')]}, {'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': ['br2dvd'], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r')]}, {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': [], 'allow': [], 'ext':[], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]}, @@ -34,6 +34,11 @@ class QualityPlugin(Plugin): {'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': [], 'ext':[]} ] pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr'] + threed_tags = { + 'hsbs': [('half', 'sbs')], + 'fsbs': [('full', 'sbs')], + '3d': [], + } cached_qualities = None cached_order = None @@ -58,6 +63,7 @@ class QualityPlugin(Plugin): addEvent('app.test', self.doTest) + self.order = [] self.addOrder() def addOrder(self): @@ -185,14 +191,19 @@ class QualityPlugin(Plugin): # Start with 0 score = {} for quality in qualities: - score[quality.get('identifier')] = 0 + score[quality.get('identifier')] = { + 'score': 0, + '3d': {} + } for cur_file in files: words = re.split('\W+', cur_file.lower()) for quality in qualities: contains_score = self.containsTagScore(quality, words, cur_file) - self.calcScore(score, quality, contains_score) + threedscore = self.contains3D(quality, words, cur_file) if quality.get('allow_3d') else (0, None) + + self.calcScore(score, quality, contains_score, threedscore) # Try again with loose testing for quality in qualities: @@ -208,10 +219,13 @@ class QualityPlugin(Plugin): if not has_non_zero: return None - heighest_quality = max(score, key = score.get) + heighest_quality = max(score, key = lambda p: score[p]['score']) if heighest_quality: for quality in qualities: if quality.get('identifier') == heighest_quality: + quality['is_3d'] = False + if score[heighest_quality].get('3d'): + quality['is_3d'] = True return self.setCache(cache_key, quality) return None @@ -234,12 +248,12 @@ class QualityPlugin(Plugin): qualities = [qualities] if isinstance(qualities, (str, unicode)) else qualities for alt in qualities: - if (isinstance(alt, tuple)): + if isinstance(alt, tuple): if len(set(words) & set(alt)) == len(alt): log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file)) score += points.get(tag_type) - if (isinstance(alt, (str, unicode)) and ss(alt.lower()) in cur_file.lower()): + if isinstance(alt, (str, unicode)) and ss(alt.lower()) in cur_file.lower(): log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file)) score += points.get(tag_type) / 2 @@ -255,6 +269,23 @@ class QualityPlugin(Plugin): return score + def contains3D(self, quality, words, cur_file = ''): + cur_file = ss(cur_file) + + for key in self.threed_tags: + tags = self.threed_tags.get(key, []) + + for tag in tags: + if (isinstance(tag, tuple) and '.'.join(tag) in '.'.join(words)) or (isinstance(tag, (str, unicode)) and ss(tag.lower()) in cur_file.lower()): + log.debug('Found %s in %s', (tag, cur_file)) + return 1, key + + if list(set([key]) & set(words)): + log.debug('Found %s in %s', (tag, cur_file)) + return 1, key + + return 0, None + def guessLooseScore(self, quality, extra = None): score = 0 @@ -277,9 +308,16 @@ class QualityPlugin(Plugin): return score - def calcScore(self, score, quality, add_score): + def calcScore(self, score, quality, add_score, threedscore = (0, None)): + + score[quality['identifier']]['score'] += add_score + + threedscore, threedtag = threedscore + if threedscore and threedtag: + if threedscore not in score[quality['identifier']]['3d']: + score[quality['identifier']]['3d'][threedtag] = 0 - score[quality['identifier']] += add_score + score[quality['identifier']]['3d'][threedtag] += threedscore # Set order for allow calculation (and cache) if not self.cached_order: @@ -289,7 +327,7 @@ class QualityPlugin(Plugin): if add_score != 0: for allow in quality.get('allow', []): - score[allow] -= 40 if self.cached_order[allow] < self.cached_order[quality['identifier']] else 5 + score[allow]['score'] -= 40 if self.cached_order[allow] < self.cached_order[quality['identifier']] else 5 def doTest(self): diff --git a/couchpotato/core/plugins/quality/static/quality.js b/couchpotato/core/plugins/quality/static/quality.js index 613096f..d103908 100644 --- a/couchpotato/core/plugins/quality/static/quality.js +++ b/couchpotato/core/plugins/quality/static/quality.js @@ -8,7 +8,7 @@ var QualityBase = new Class({ self.qualities = data.qualities; - self.profiles = [] + self.profiles = []; Array.each(data.profiles, self.createProfilesClass.bind(self)); App.addEvent('load', self.addSettings.bind(self)) @@ -37,7 +37,7 @@ var QualityBase = new Class({ addSettings: function(){ var self = this; - self.settings = App.getPage('Settings') + self.settings = App.getPage('Settings'); self.settings.addEvent('create', function(){ var tab = self.settings.createSubTab('profile', { 'label': 'Quality', @@ -91,9 +91,9 @@ var QualityBase = new Class({ createProfilesClass: function(data){ var self = this; - var data = data || {'id': randomString()} - var profile = new Profile(data) - self.profiles.include(profile) + var data = data || {'id': randomString()}; + var profile = new Profile(data); + self.profiles.include(profile); return profile; }, @@ -102,7 +102,7 @@ var QualityBase = new Class({ var self = this; var profile_list; - var group = self.settings.createGroup({ + self.settings.createGroup({ 'label': 'Profile Defaults', 'description': '(Needs refresh \'' +(App.isMac() ? 'CMD+R' : 'F5')+ '\' after editing)' }).adopt( @@ -113,7 +113,7 @@ var QualityBase = new Class({ 'html': 'Change the order the profiles are in the dropdown list. Uncheck to hide it completely.
First one will be default.' }) ) - ).inject(self.content) + ).inject(self.content); Array.each(self.profiles, function(profile){ var check; @@ -175,14 +175,14 @@ var QualityBase = new Class({ 'description': 'Edit the minimal and maximum sizes (in MB) for each quality.', 'advanced': true, 'name': 'sizes' - }).inject(self.content) + }).inject(self.content); new Element('div.item.head.ctrlHolder').adopt( new Element('span.label', {'text': 'Quality'}), new Element('span.min', {'text': 'Min'}), new Element('span.max', {'text': 'Max'}) - ).inject(group) + ).inject(group); Array.each(self.qualities, function(quality){ new Element('div.ctrlHolder.item').adopt( diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index a80dfc0..72a0a50 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -343,6 +343,10 @@ class Release(Plugin): found_releases = [] + is_3d = False + try: is_3d = quality['custom']['3d'] + except: pass + for rel in search_results: rel_identifier = md5(rel['url']) @@ -353,6 +357,7 @@ class Release(Plugin): 'identifier': rel_identifier, 'media_id': media.get('_id'), 'quality': quality.get('identifier'), + 'is_3d': is_3d, 'status': rel.get('status', 'available'), 'last_edit': int(time.time()), 'info': {} @@ -438,6 +443,17 @@ class Release(Plugin): def forMedia(self, media_id): db = get_db() + raw_releases = list(db.get_many('release', media_id, with_doc = True)) + + releases = [] + for r in raw_releases: + releases.append(r['doc']) + + releases = sorted(releases, key = lambda k: k.get('info', {}).get('score', 0), reverse = True) + + # Sort based on preferred search method + download_preference = self.conf('preferred_method', section = 'searcher') + if download_preference != 'both': + releases = sorted(releases, key = lambda k: k.get('info', {}).get('protocol', '')[:3], reverse = (download_preference == 'torrent')) - for release in db.get_many('release', media_id, with_doc = True): - yield release['doc'] + return releases diff --git a/couchpotato/core/plugins/renamer.py b/couchpotato/core/plugins/renamer.py index 4eca783..3830b97 100644 --- a/couchpotato/core/plugins/renamer.py +++ b/couchpotato/core/plugins/renamer.py @@ -148,7 +148,7 @@ class Renamer(Plugin): log.debug('The provided media folder %s does not exist. Trying to find it in the \'from\' folder.', media_folder) # Update to the from folder - if len(release_download.get('files'), []) == 1: + if len(release_download.get('files', [])) == 1: new_media_folder = from_folder else: new_media_folder = os.path.join(from_folder, os.path.basename(media_folder)) @@ -190,6 +190,8 @@ class Renamer(Plugin): release_files = release_download.get('files', []) if release_files: + files = release_files + # If there is only one file in the torrent, the downloader did not create a subfolder if len(release_files) == 1: folder = media_folder @@ -204,7 +206,8 @@ class Renamer(Plugin): db = get_db() # Extend the download info with info stored in the downloaded release - release_download = self.extendReleaseDownload(release_download) + if release_download: + release_download = self.extendReleaseDownload(release_download) # Unpack any archives extr_files = None @@ -248,7 +251,7 @@ class Renamer(Plugin): 'profile_id': None }, search_after = False, status = 'done', single = True) else: - group['media'] = fireEvent('movie.update_info', identifier = getIdentifier(group['media']), single = True) + group['media'] = fireEvent('movie.update_info', media_id = group['media'].get('_id'), single = True) if not group['media'] or not group['media'].get('_id'): log.error('Could not rename, no library item to work with: %s', group_identifier) @@ -503,7 +506,7 @@ class Renamer(Plugin): fireEvent('release.update_status', release['_id'], status = 'downloaded', single = True) # Remove leftover files - if not remove_leftovers: # Don't remove anything + if not remove_leftovers: # Don't remove anything break log.debug('Removing leftover files') @@ -528,8 +531,8 @@ class Renamer(Plugin): parent_dir = os.path.dirname(src) if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and \ - not isSubFolder(destination, parent_dir) and not isSubFolder(media_folder, parent_dir) and \ - not isSubFolder(parent_dir, base_folder): + not isSubFolder(destination, parent_dir) and not isSubFolder(media_folder, parent_dir) and \ + not isSubFolder(parent_dir, base_folder): delete_folders.append(parent_dir) @@ -577,11 +580,11 @@ class Renamer(Plugin): # Remove matching releases for release in remove_releases: - log.debug('Removing release %s', release.identifier) + log.debug('Removing release %s', release.get('identifier')) try: db.delete(release) except: - log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc())) + log.error('Failed removing %s: %s', (release, traceback.format_exc())) if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(release_download): if media_folder: @@ -652,13 +655,13 @@ Remove it if you want it to be renamed (again, or at least let it try again) tag_files = release_download.get('files', []) # Tag all files in release folder - else: + elif release_download['folder']: for root, folders, names in scandir.walk(release_download['folder']): tag_files.extend([os.path.join(root, name) for name in names]) for filename in tag_files: - # Dont tag .ignore files + # Don't tag .ignore files if os.path.splitext(filename)[1] == '.ignore': continue @@ -682,19 +685,20 @@ Remove it if you want it to be renamed (again, or at least let it try again) return False elif isinstance(release_download, dict): + + folder = release_download['folder'] + if not os.path.isdir(folder): + return False + # Untag download_files if they are known if release_download.get('files'): tag_files = release_download.get('files', []) # Untag all files in release folder else: - for root, folders, names in scandir.walk(release_download['folder']): + for root, folders, names in scandir.walk(folder): tag_files.extend([sp(os.path.join(root, name)) for name in names if not os.path.splitext(name)[1] == '.ignore']) - folder = release_download['folder'] - if not os.path.isdir(folder): - return False - if not folder: return False @@ -729,7 +733,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) # Find tag on all files in release folder else: - for root, folders, names in scandir.walk(release_download['folder']): + for root, folders, names in scandir.walk(folder): tag_files.extend([sp(os.path.join(root, name)) for name in names if not os.path.splitext(name)[1] == '.ignore']) # Find all .ignore files in folder @@ -867,7 +871,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) release_downloads = fireEvent('download.status', download_ids, merge = True) if download_ids else [] if len(no_status_support) > 0: - log.debug('Download status functionality is not implemented for one of the active downloaders: %s', no_status_support) + log.debug('Download status functionality is not implemented for one of the active downloaders: %s', list(set(no_status_support))) if not release_downloads: if fire_scan: @@ -1016,7 +1020,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) if release_download['pause'] and self.conf('file_action') == 'link': fireEvent('download.pause', release_download = release_download, pause = False, single = True) if release_download['process_complete']: - #First make sure the files were succesfully processed + # First make sure the files were successfully processed if not self.hastagRelease(release_download = release_download, tag = 'failed_rename'): # Remove the seeding tag if it exists self.untagRelease(release_download = release_download, tag = 'renamed_already') diff --git a/couchpotato/core/plugins/scanner.py b/couchpotato/core/plugins/scanner.py index 65ae257..6b7c7b0 100644 --- a/couchpotato/core/plugins/scanner.py +++ b/couchpotato/core/plugins/scanner.py @@ -28,7 +28,7 @@ class Scanner(Plugin): ignored_in_path = [os.path.sep + 'extracted' + os.path.sep, 'extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo', - 'thumbs.db', 'ehthumbs.db', 'desktop.ini'] #unpacking, smb-crap, hidden files + 'thumbs.db', 'ehthumbs.db', 'desktop.ini'] # unpacking, smb-crap, hidden files ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate'] extensions = { 'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'], @@ -85,7 +85,7 @@ class Scanner(Plugin): 'hdtv': ['hdtv'] } - clean = '[ _\,\.\(\)\[\]\-]?(extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip' \ + clean = '[ _\,\.\(\)\[\]\-]?(3d|hsbs|sbs|extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip' \ '|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)' multipart_regex = [ '[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1 @@ -129,7 +129,7 @@ class Scanner(Plugin): check_file_date = True try: files = [] - for root, dirs, walk_files in scandir.walk(folder): + for root, dirs, walk_files in scandir.walk(folder, followlinks=True): files.extend([sp(os.path.join(root, filename)) for filename in walk_files]) # Break if CP wants to shut down @@ -589,7 +589,7 @@ class Scanner(Plugin): if len(movie) > 0: imdb_id = movie[0].get('imdb') - log.debug('Found movie via search: %s', cur_file) + log.debug('Found movie via search: %s', identifier) if imdb_id: break else: log.debug('Identifier to short to use for search: %s', identifier) @@ -597,7 +597,7 @@ class Scanner(Plugin): if imdb_id: try: db = get_db() - return db.get('media', imdb_id, with_doc = True)['doc'] + return db.get('media', 'imdb-%s' % imdb_id, with_doc = True)['doc'] except: log.debug('Movie "%s" not in library, just getting info', imdb_id) return { @@ -835,6 +835,7 @@ class Scanner(Plugin): cleaned = ' '.join(re.split('\W+', simplifyString(release_name))) cleaned = re.sub(self.clean, ' ', cleaned) + year = None for year_str in [file_name, release_name, cleaned]: if not year_str: continue year = self.findYear(year_str) @@ -843,7 +844,7 @@ class Scanner(Plugin): cp_guess = {} - if year: # Split name on year + if year: # Split name on year try: movie_name = cleaned.rsplit(year, 1).pop(0).strip() if movie_name: diff --git a/couchpotato/core/plugins/userscript/bookmark.js b/couchpotato/core/plugins/userscript/bookmark.js_tmpl similarity index 95% rename from couchpotato/core/plugins/userscript/bookmark.js rename to couchpotato/core/plugins/userscript/bookmark.js_tmpl index cdba3b4..cc04baf 100644 --- a/couchpotato/core/plugins/userscript/bookmark.js +++ b/couchpotato/core/plugins/userscript/bookmark.js_tmpl @@ -32,7 +32,7 @@ var isCorrectUrl = function() { } var addUserscript = function() { // Add window param - document.body.setAttribute('cp_auto_open', true) + document.body.setAttribute('cp_auto_open', 'true') // Load userscript var e = document.createElement('script'); diff --git a/couchpotato/core/plugins/userscript/main.py b/couchpotato/core/plugins/userscript/main.py index f857fb9..4ca8ed3 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 = 4 + version = 5 def __init__(self): addApiView('userscript.get/(.*)/(.*)', self.getUserScript, static = True) @@ -35,7 +35,7 @@ class Userscript(Plugin): 'host': host, } - return self.renderTemplate(__file__, 'bookmark.js', **params) + return self.renderTemplate(__file__, 'bookmark.js_tmpl', **params) def getIncludes(self, **kwargs): @@ -60,7 +60,7 @@ class Userscript(Plugin): 'host': '%s://%s' % (self.request.protocol, self.request.headers.get('X-Forwarded-Host') or self.request.headers.get('host')), } - script = klass.renderTemplate(__file__, 'template.js', **params) + script = klass.renderTemplate(__file__, 'template.js_tmpl', **params) klass.createFile(os.path.join(Env.get('cache_dir'), 'couchpotato.user.js'), script) self.redirect(Env.get('api_base') + 'file.cache/couchpotato.user.js') diff --git a/couchpotato/core/plugins/userscript/static/userscript.js b/couchpotato/core/plugins/userscript/static/userscript.js index 11daa06..3a7fd9d 100644 --- a/couchpotato/core/plugins/userscript/static/userscript.js +++ b/couchpotato/core/plugins/userscript/static/userscript.js @@ -12,7 +12,7 @@ Page.Userscript = new Class({ } }, - indexAction: function(param){ + indexAction: function(){ var self = this; self.el.adopt( @@ -60,21 +60,21 @@ var UserscriptSettingTab = new Class({ addSettings: function(){ var self = this; - self.settings = App.getPage('Settings') + self.settings = App.getPage('Settings'); self.settings.addEvent('create', function(){ var host_url = window.location.protocol + '//' + window.location.host; self.settings.createGroup({ 'name': 'userscript', - 'label': 'Install the bookmarklet or userscript', + 'label': 'Install the browser extension or bookmarklet', 'description': 'Easily add movies via imdb.com, appletrailers and more' }).inject(self.settings.tabs.automation.content, 'top').adopt( new Element('a.userscript.button', { - 'text': 'Install userscript', - 'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js', + 'text': 'Install extension', + 'href': 'https://couchpota.to/extension/', 'target': '_blank' - }), + }), new Element('span.or[text=or]'), new Element('span.bookmarklet').adopt( new Element('a.button.green', { @@ -86,7 +86,7 @@ var UserscriptSettingTab = new Class({ 'target': '', 'events': { 'click': function(e){ - (e).stop() + (e).stop(); alert('Drag it to your bookmark ;)') } } @@ -96,7 +96,7 @@ var UserscriptSettingTab = new Class({ }) ) ).setStyles({ - 'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')" + 'background-image': "url('https://couchpota.to/media/images/userscript.gif')" }); }); diff --git a/couchpotato/core/plugins/userscript/static/userscript.png b/couchpotato/core/plugins/userscript/static/userscript.png deleted file mode 100644 index c8e7657..0000000 Binary files a/couchpotato/core/plugins/userscript/static/userscript.png and /dev/null differ diff --git a/couchpotato/core/plugins/userscript/template.js b/couchpotato/core/plugins/userscript/template.js_tmpl similarity index 98% rename from couchpotato/core/plugins/userscript/template.js rename to couchpotato/core/plugins/userscript/template.js_tmpl index 202f5dc..5a32da3 100644 --- a/couchpotato/core/plugins/userscript/template.js +++ b/couchpotato/core/plugins/userscript/template.js_tmpl @@ -25,13 +25,16 @@ var version = {{version}}, host = '{{host}}', api = '{{api}}'; -function create() { +var create = function() { + var A, B; switch (arguments.length) { case 1: - var A = document.createTextNode(arguments[0]); + A = document.createTextNode(arguments[0]); break; default: - var A = document.createElement(arguments[0]), B = arguments[1]; + A = document.createElement(arguments[0]); + B = arguments[1]; + for ( var b in B) { if (b.indexOf("on") == 0){ A.addEventListener(b.substring(2), B[b], false); diff --git a/couchpotato/core/plugins/wizard/static/wizard.css b/couchpotato/core/plugins/wizard/static/wizard.css index c27c1d8..9af32ed 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.css +++ b/couchpotato/core/plugins/wizard/static/wizard.css @@ -5,10 +5,9 @@ .page.wizard h1 { padding: 10px 0; - margin: 0 5px; display: block; font-size: 30px; - margin-top: 80px; + margin: 80px 5px 0; } .page.wizard .description { @@ -52,7 +51,7 @@ font-weight: normal; border-bottom: 4px solid transparent; } - + .page.wizard .tabs li:hover a { border-color: #047792; } .page.wizard .tabs li.done a { border-color: #04bce6; } diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index b4857ab..b43e398 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -35,11 +35,11 @@ Page.Wizard = new Class({ }, 'automation': { 'title': 'Easily add movies to your wanted list!', - 'description': 'You can easily add movies from your favorite movie site, like IMDB, Rotten Tomatoes, Apple Trailers and more. Just install the userscript or drag the bookmarklet to your browsers bookmarks.' + + 'description': 'You can easily add movies from your favorite movie site, like IMDB, Rotten Tomatoes, Apple Trailers and more. Just install the extension or drag the bookmarklet to your bookmarks.' + '
Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)', 'content': function(){ return App.createUserscriptButtons().setStyles({ - 'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')" + 'background-image': "url('https://couchpota.to/media/images/userscript.gif')" }) } }, @@ -111,10 +111,10 @@ Page.Wizard = new Class({ var form = self.el.getElement('.uniForm'); var tabs = self.el.getElement('.tabs'); - self.groups.each(function(group, nr){ + self.groups.each(function(group){ if(self.headers[group]){ - group_container = new Element('.wgroup_'+group, { + var group_container = new Element('.wgroup_'+group, { 'styles': { 'opacity': 0.2 }, @@ -129,7 +129,7 @@ Page.Wizard = new Class({ }) } - var content = self.headers[group].content + var content = self.headers[group].content; group_container.adopt( new Element('h1', { 'text': self.headers[group].title @@ -144,7 +144,7 @@ Page.Wizard = new Class({ var tab_navigation = tabs.getElement('.t_'+group); if(!tab_navigation && self.headers[group] && self.headers[group].include){ - tab_navigation = [] + tab_navigation = []; self.headers[group].include.each(function(inc){ tab_navigation.include(tabs.getElement('.t_'+inc)); }) @@ -157,7 +157,7 @@ Page.Wizard = new Class({ self.headers[group].include.each(function(inc){ self.el.getElement('.tab_'+inc).inject(group_container); - }) + }); new Element('li.t_'+group).adopt( new Element('a', { @@ -215,9 +215,9 @@ Page.Wizard = new Class({ self.groups.each(function(groups2, nr2){ var t2 = self.el.getElement('.t_'+groups2); t2[nr2 > nr ? 'removeClass' : 'addClass' ]('done'); - }) + }); g.tween('opacity', 1); - } + }; if(nr == 0) func(); @@ -241,4 +241,4 @@ Page.Wizard = new Class({ } -}); \ No newline at end of file +}); diff --git a/couchpotato/core/settings.py b/couchpotato/core/settings.py index be2a0d0..16c827c 100644 --- a/couchpotato/core/settings.py +++ b/couchpotato/core/settings.py @@ -56,6 +56,10 @@ class Settings(object): addEvent('database.setup', self.databaseSetup) + self.file = None + self.p = None + self.log = None + def setFile(self, config_file): self.file = config_file @@ -195,7 +199,6 @@ class Settings(object): def getOptions(self): return self.options - def view(self, **kwargs): return { 'options': self.getOptions(), @@ -254,6 +257,7 @@ class Settings(object): 'value': toUnicode(value), }) + class PropertyIndex(HashIndex): _version = 1 diff --git a/couchpotato/runner.py b/couchpotato/runner.py index f55d11e..dcdac5c 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -100,14 +100,14 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En if not os.path.isdir(backup_path): os.makedirs(backup_path) for root, dirs, files in scandir.walk(backup_path): - for file in files: - ints = re.findall('\d+', file) + for backup_file in files: + ints = re.findall('\d+', backup_file) # Delete non zip files if len(ints) != 1: - os.remove(os.path.join(backup_path, file)) + os.remove(os.path.join(backup_path, backup_file)) else: - existing_backups.append((int(ints[0]), file)) + existing_backups.append((int(ints[0]), backup_file)) # Remove all but the last 5 for eb in existing_backups[:-backup_count]: @@ -117,8 +117,8 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En new_backup = toUnicode(os.path.join(backup_path, '%s.tar.gz' % int(time.time()))) zipf = tarfile.open(new_backup, 'w:gz') for root, dirs, files in scandir.walk(db_path): - for file in files: - zipf.add(os.path.join(root, file), arcname = 'database/%s' % os.path.join(root[len(db_path)+1:], file)) + for zfilename in files: + zipf.add(os.path.join(root, zfilename), arcname = 'database/%s' % os.path.join(root[len(db_path) + 1:], zfilename)) zipf.close() # Open last @@ -178,6 +178,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En logger.addHandler(hdlr2) # Start logging & enable colors + # noinspection PyUnresolvedReferences import color_logs from couchpotato.core.logger import CPLog log = CPLog(__name__) @@ -212,7 +213,8 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En } # Load the app - application = Application([], + application = Application( + [], log_function = lambda x: None, debug = config['use_reloader'], gzip = True, diff --git a/couchpotato/core/plugins/profile/static/handle.png b/couchpotato/static/images/handle.png similarity index 100% rename from couchpotato/core/plugins/profile/static/handle.png rename to couchpotato/static/images/handle.png diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 0333228..27ae9c0 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -317,8 +317,8 @@ return new Element('div.group_userscript').adopt( new Element('a.userscript.button', { - 'text': 'Install userscript', - 'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js', + 'text': 'Install extension', + 'href': 'https://couchpota.to/extension/', 'target': '_blank' }), new Element('span.or[text=or]'), @@ -369,7 +369,7 @@ // Create parallel callback var callbacks = []; - self.global_events[name].each(function(handle, nr){ + self.global_events[name].each(function(handle){ callbacks.push(function(callback){ var results = handle.apply(handle, args || []); diff --git a/couchpotato/static/scripts/misc/downloaders.js b/couchpotato/static/scripts/misc/downloaders.js index 5127275..c3611a2 100644 --- a/couchpotato/static/scripts/misc/downloaders.js +++ b/couchpotato/static/scripts/misc/downloaders.js @@ -68,7 +68,7 @@ var DownloadersBase = new Class({ testButtonName: function(fieldset){ var name = String(fieldset.getElement('h2').innerHTML).substring(0,String(fieldset.getElement('h2').innerHTML).indexOf(" 0 ? ', ' + new Date ().increment('second', folder_progress.eta).timeDiffInWords().replace('from now', 'to go') : '') }), - new Element('span.percentage', {'text': folder_progress.total ? (((folder_progress.total-folder_progress.to_go)/folder_progress.total)*100).round() + '%' : '0%'}) + new Element('span.percentage', {'text': folder_progress.total ? Math.round(((folder_progress.total-folder_progress.to_go)/folder_progress.total)*100) + '%' : '0%'}) ).inject(self.progress_container) }); diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 74f3c7f..0915548 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -370,7 +370,7 @@ var OptionBase = new Class({ createTooltip(self.options.description[1]).inject(hint, 'top'); } else { - var hint = new Element('p.formHint', { + new Element('p.formHint', { 'html': self.options.description || '' }).inject(self.el) } @@ -1312,8 +1312,9 @@ Option.Combined = new Class({ var head = new Element('div.head').inject(self.combined_list); Object.each(self.inputs, function(input, name){ + var _in = input.getNext(); self.labels[name] = input.getPrevious().get('text'); - self.descriptions[name] = (_in = input.getNext()) ? _in.get('text') : ''; + self.descriptions[name] = _in ? _in.get('text') : ''; new Element('abbr', { 'class': name, @@ -1465,4 +1466,4 @@ var createTooltip = function(description){ ); return tip; -} +}; diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 6a3d4cb..ca57f95 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -73,7 +73,7 @@ Page.Wanted = new Class({ } else { var progress = json.movie; - self.manual_search.set('text', 'Searching.. (' + (((progress.total-progress.to_go)/progress.total)*100).round() + '%)'); + self.manual_search.set('text', 'Searching.. (' + Math.round(((progress.total-progress.to_go)/progress.total)*100) + '%)'); } } }); @@ -87,7 +87,7 @@ Page.Wanted = new Class({ var self = this; var options = { 'name': 'Scan_folder' - } + }; if(!self.folder_browser){ self.folder_browser = new Option['Directory']("Scan", "folder", "", options); @@ -96,9 +96,9 @@ Page.Wanted = new Class({ var folder = self.folder_browser.getValue(); Api.request('renamer.scan', { 'data': { - 'base_folder': folder, - }, - }); + 'base_folder': folder + } + }); }; self.folder_browser.inject(self.el, 'top'); diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 0ade518..5d2ed6b 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -111,7 +111,7 @@ body > .spinner, .mask{ width: 100%; padding: 200px; } - + @media all and (max-width: 480px) { body > .mask { padding: 20px; @@ -185,9 +185,9 @@ body > .spinner, .mask{ .icon2.completed:before { content: "\e070"; } .icon2.info:before { content: "\e089"; } .icon2.attention:before { content: "\e009"; } -.icon2.readd:before { - display: inline-block; - content: "\e04b"; +.icon2.readd:before { + display: inline-block; + content: "\e04b"; transform: scale(-1, 1); } .icon2.imdb:before { @@ -200,8 +200,8 @@ body > .spinner, .mask{ position: relative; top: -3px; } -.icon2.menu:before { - content: "\e076\00a0 \e076\00a0 \e076\00a0"; +.icon2.menu:before { + content: "\e076\00a0 \e076\00a0 \e076\00a0"; line-height: 6px; transform: scaleX(2); width: 20px; @@ -213,7 +213,7 @@ body > .spinner, .mask{ margin-left: 5px; } @media screen and (-webkit-min-device-pixel-ratio:0) { - .icon2.menu:before { + .icon2.menu:before { margin-top: -7px; } } @@ -229,7 +229,7 @@ body > .spinner, .mask{ box-shadow: 0 0 10px rgba(0,0,0,.1); transition: all .4s ease-in-out; } - + @media all and (max-width: 480px) { .header { height: 44px; @@ -253,7 +253,7 @@ body > .spinner, .mask{ left: 0; bottom: 0; } - + .header .foldout { width: 44px; height: 100%; @@ -264,7 +264,7 @@ body > .spinner, .mask{ line-height: 42px; color: #FFF; } - + .header .logo { display: inline-block; font-size: 3em; @@ -274,36 +274,36 @@ body > .spinner, .mask{ color: #FFF; font-weight: normal; vertical-align: top; - font-family: Lobster; + font-family: Lobster, sans-serif; } - + @media all and (max-width: 480px) { .header .foldout { display: inline-block; } - + .header .logo { padding-top: 7px; border: 0; font-size: 1.7em; } } - + @media all and (min-width: 481px) and (max-width: 640px) { - + .header .logo { display: none; } - + } - + .header .navigation ul { display: inline-block; margin: 0; padding: 0; height: 100%; } - + .header .navigation li { color: #fff; display: inline-block; @@ -317,7 +317,7 @@ body > .spinner, .mask{ .header .navigation li:first-child { border: none; } - + .header .navigation li a { display: block; padding: 15px; @@ -327,13 +327,13 @@ body > .spinner, .mask{ border-width: 0 0 4px 0; font-weight: normal; } - + .header .navigation li:hover a { border-color: #047792; } .header .navigation li.active a { border-color: #04bce6; } - + .header .navigation li.disabled { color: #e5e5e5; } .header .navigation li a { color: #fff; } - + .header .navigation .backtotop { opacity: 0; display: block; @@ -349,24 +349,24 @@ body > .spinner, .mask{ font-weight: normal; } .header:hover .navigation .backtotop { color: #fff; } - + @media all and (max-width: 480px) { - + body { position: absolute; width: 100%; transition: all .5s cubic-bezier(0.9,0,0.1,1); left: 0; } - + .menu_shown body { left: 240px; } - + .header .navigation { height: 100%; } - + .menu_shown .header .navigation .overlay { position: fixed; right: 0; @@ -374,7 +374,7 @@ body > .spinner, .mask{ bottom: 0; left: 240px; } - + .header .navigation ul { width: 240px; position: fixed; @@ -382,11 +382,11 @@ body > .spinner, .mask{ background: rgba(0,0,0,.5); transition: all .5s cubic-bezier(0.9,0,0.1,1); } - + .menu_shown .header .navigation ul { left: 0; } - + .header .navigation ul li { display: block; text-align: left; @@ -397,7 +397,7 @@ body > .spinner, .mask{ border-width: 0 4px 0 0; padding: 5px 20px; } - + .header .navigation ul li.separator { background-color: rgba(255,255,255, .07); height: 5px; @@ -410,31 +410,31 @@ body > .spinner, .mask{ height: 100%; border-left: 1px solid rgba(255,255,255,.07); } - + @media all and (max-width: 480px) { .header .more_menu { display: none; } } - + .header .more_menu .button { height: 100%; line-height: 66px; text-align: center; padding: 0; } - + .header .more_menu .wrapper { width: 200px; margin-left: -106px; margin-top: 22px; } - + @media all and (max-width: 480px) { .header .more_menu .button { line-height: 44px; } - + .header .more_menu .wrapper { margin-top: 0; } @@ -454,12 +454,12 @@ body > .spinner, .mask{ top: 0; right: 0; } - + .header .notification_menu { right: 60px; display: block; } - + @media all and (max-width: 480px) { .header .notification_menu { right: 0; @@ -685,15 +685,15 @@ body > .spinner, .mask{ border-bottom: 4px solid transparent; border-radius: 0; } - + .more_menu .button:hover { border-color: #047792; } - + .more_menu.show .button { border-color: #04bce6; } - + .more_menu .wrapper { display: none; top: 0; @@ -706,23 +706,23 @@ body > .spinner, .mask{ color: #444; background: #fff; } - + .more_menu.show .wrapper { display: block; top: 44px; } - + .more_menu ul { padding: 0; margin: 0; list-style: none; } - + .more_menu .wrapper li { width: 100%; height: auto; } - + .more_menu .wrapper li a { display: block; border-bottom: 1px solid rgba(255,255,255,0.2); @@ -735,21 +735,21 @@ body > .spinner, .mask{ } .more_menu .wrapper li:first-child a { padding-top: 5px; } .more_menu .wrapper li:last-child a { padding-bottom: 5px; } - + .more_menu .wrapper li .separator { border-bottom: 1px solid rgba(0,0,0,.1); display: block; height: 1px; margin: 5px 0; } - + .more_menu .wrapper li:last-child a { border: none; } .more_menu .wrapper li a:hover { background: rgba(0,0,0,0.05); } - + .messages { position: fixed; right: 0; @@ -823,7 +823,7 @@ body > .spinner, .mask{ margin: -200px 0 0 -200px; } @media all and (max-width: 480px) { - + .login form { padding: 0; height: 300px; @@ -833,9 +833,9 @@ body > .spinner, .mask{ top: 10px; margin: 0; } - + } - + .page.login .ctrlHolder { padding: 0; margin: 0 0 20px; @@ -843,31 +843,31 @@ body > .spinner, .mask{ .page.login .ctrlHolder:hover { background: none; } - + .page.login input[type=text], .page.login input[type=password] { width: 100% !important; font-size: 25px; padding: 14px !important; } - + .page.login .remember_me { font-size: 15px; float: left; width: 150px; padding: 20px 0; } - + .page.login .remember_me .check { margin: 5px 5px 0 0; } - + .page.login .button { font-size: 25px; padding: 20px; float: right; } - + /* Fonts */ @font-face { font-family: 'Elusive-Icons'; @@ -936,4 +936,4 @@ body > .spinner, .mask{ url('../fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg'); font-weight: normal; font-style: normal; -} \ No newline at end of file +} diff --git a/couchpotato/static/style/settings.css b/couchpotato/static/style/settings.css index 5bb226f..1f77b8e 100644 --- a/couchpotato/static/style/settings.css +++ b/couchpotato/static/style/settings.css @@ -698,12 +698,18 @@ } .group_userscript { - background: center bottom no-repeat; - min-height: 360px; + background: 5px 75px no-repeat; + min-height: 460px; font-size: 20px; font-weight: normal; } + .settings .group_userscript { + background-position: center 120px; + background-size: auto 70%; + min-height: 360px; + } + .group_userscript h2 .hint { display: block; margin: 0 !important; @@ -741,7 +747,7 @@ .tooltip { position: absolute; - right: 0px; + right: 0; width: 30px; height: 30px; } diff --git a/couchpotato/templates/database.html b/couchpotato/templates/database.html index b12ad23..e512538 100644 --- a/couchpotato/templates/database.html +++ b/couchpotato/templates/database.html @@ -26,7 +26,7 @@ ) ); - documents.each(function(doc, nr){ + documents.each(function(doc){ new Element('tr.document').adopt( new Element('td.id', {'text': doc['_id']}), new Element('td._rev', {'text': doc['_rev']}), diff --git a/libs/unrar2/__init__.py b/libs/unrar2/__init__.py index b5c9a4d..fe27cfe 100644 --- a/libs/unrar2/__init__.py +++ b/libs/unrar2/__init__.py @@ -33,7 +33,7 @@ similar to the C interface provided by UnRAR. There is also a higher level interface which makes some common operations easier. """ -__version__ = '0.99.2' +__version__ = '0.99.3' try: WindowsError diff --git a/libs/unrar2/unix.py b/libs/unrar2/unix.py index 21f384c..9ebab40 100644 --- a/libs/unrar2/unix.py +++ b/libs/unrar2/unix.py @@ -33,6 +33,7 @@ from rar_exceptions import * class UnpackerNotInstalled(Exception): pass rar_executable_cached = None +rar_executable_version = None def call_unrar(params): "Calls rar/unrar command line executable, returns stdout pipe" @@ -59,10 +60,10 @@ def call_unrar(params): class RarFileImplementation(object): def init(self, password = None): + global rar_executable_version self.password = password - stdoutdata, stderrdata = self.call('v', []).communicate() for line in stderrdata.splitlines(): @@ -73,18 +74,42 @@ class RarFileImplementation(object): accum = [] source = iter(stdoutdata.splitlines()) line = '' - while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')): - if line.strip().endswith('is not RAR archive'): - raise InvalidRARArchive + while not (line.startswith('UNRAR')): line = source.next() - while not line.startswith('Pathname/Comment'): - accum.append(line.rstrip('\n')) + signature = line + # The code below is mighty flaky + # and will probably crash on localized versions of RAR + # but I see no safe way to rewrite it using a CLI tool + if signature.startswith("UNRAR 4"): + rar_executable_version = 4 + while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')): + if line.strip().endswith('is not RAR archive'): + raise InvalidRARArchive + line = source.next() + while not line.startswith('Pathname/Comment'): + accum.append(line.rstrip('\n')) + line = source.next() + if len(accum): + accum[0] = accum[0][9:] # strip out "Comment:" part + self.comment = '\n'.join(accum[:-1]) + else: + self.comment = None + elif signature.startswith("UNRAR 5"): + rar_executable_version = 5 line = source.next() - if len(accum): - accum[0] = accum[0][9:] - self.comment = '\n'.join(accum[:-1]) + while not line.startswith('Archive:'): + if line.strip().endswith('is not RAR archive'): + raise InvalidRARArchive + accum.append(line.rstrip('\n')) + line = source.next() + if len(accum): + self.comment = '\n'.join(accum[:-1]).strip() + else: + self.comment = None else: - self.comment = None + raise UnpackerNotInstalled("Unsupported RAR version, expected 4.x or 5.x, found: " + + signature.split(" ")[1]) + def escaped_password(self): return '-' if self.password == None else self.password @@ -97,7 +122,8 @@ class RarFileImplementation(object): def infoiter(self): - stdoutdata, stderrdata = self.call('v', ['c-']).communicate() + command = "v" if rar_executable_version == 4 else "l" + stdoutdata, stderrdata = self.call(command, ['c-']).communicate() for line in stderrdata.splitlines(): if line.strip().startswith("Cannot open"): @@ -106,31 +132,48 @@ class RarFileImplementation(object): accum = [] source = iter(stdoutdata.splitlines()) line = '' - while not line.startswith('--------------'): + while not line.startswith('-----------'): if line.strip().endswith('is not RAR archive'): raise InvalidRARArchive - if line.find("CRC failed") >= 0: + if line.startswith("CRC failed") or line.startswith("Checksum error"): raise IncorrectRARPassword line = source.next() line = source.next() i = 0 re_spaces = re.compile(r"\s+") - while not line.startswith('--------------'): - accum.append(line) - if len(accum) == 2: + if rar_executable_version == 4: + while not line.startswith('-----------'): + accum.append(line) + if len(accum) == 2: + data = {} + data['index'] = i + # asterisks mark password-encrypted files + data['filename'] = accum[0].strip().lstrip("*") # asterisks marks password-encrypted files + fields = re_spaces.split(accum[1].strip()) + data['size'] = int(fields[0]) + attr = fields[5] + data['isdir'] = 'd' in attr.lower() + data['datetime'] = time.strptime(fields[3] + " " + fields[4], '%d-%m-%y %H:%M') + data['comment'] = None + yield data + accum = [] + i += 1 + line = source.next() + elif rar_executable_version == 5: + while not line.startswith('-----------'): + fields = line.strip().lstrip("*").split() data = {} data['index'] = i - data['filename'] = accum[0].strip() - info = re_spaces.split(accum[1].strip()) - data['size'] = int(info[0]) - attr = info[5] + data['filename'] = " ".join(fields[4:]) + data['size'] = int(fields[1]) + attr = fields[0] data['isdir'] = 'd' in attr.lower() - data['datetime'] = time.strptime(info[3] + " " + info[4], '%d-%m-%y %H:%M') + data['datetime'] = time.strptime(fields[2] + " " + fields[3], '%d-%m-%y %H:%M') data['comment'] = None yield data - accum = [] i += 1 - line = source.next() + line = source.next() + def read_files(self, checker): res = [] @@ -151,7 +194,7 @@ class RarFileImplementation(object): if overwrite: options.append('o+') else: - options.append('o-') + options.append('o-') if not path.endswith(os.sep): path += os.sep names = [] @@ -165,7 +208,7 @@ class RarFileImplementation(object): names.append(path) proc = self.call(command, options, names) stdoutdata, stderrdata = proc.communicate() - if stderrdata.find("CRC failed") >= 0: + if stderrdata.find("CRC failed") >= 0 or stderrdata.find("Checksum error") >= 0: raise IncorrectRARPassword return res