diff --git a/README.md b/README.md index b0e4222d..91223f18 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Linux (ubuntu / debian): * 'cd' to the folder of your choosing. * Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git` * Then do `python CouchPotatoServer/CouchPotato.py` to start -* To run on boot copy the init script. `cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato` -* Change the paths inside the init script. `nano /etc/init.d/couchpotato` -* Make it executable. `chmod +x /etc/init.d/couchpotato` +* To run on boot copy the init script. `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato` +* Change the paths inside the init script. `sudo nano /etc/init.d/couchpotato` +* Make it executable. `sudo chmod +x /etc/init.d/couchpotato` * Add it to defaults. `sudo update-rc.d couchpotato defaults` diff --git a/couchpotato/core/_base/_core/__init__.py b/couchpotato/core/_base/_core/__init__.py index 2aca8e2b..8d702f18 100644 --- a/couchpotato/core/_base/_core/__init__.py +++ b/couchpotato/core/_base/_core/__init__.py @@ -27,6 +27,7 @@ config = [{ 'name': 'host', 'advanced': True, 'default': '0.0.0.0', + 'hidden': True, 'label': 'IP', 'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.', }, diff --git a/couchpotato/core/_base/updater/__init__.py b/couchpotato/core/_base/updater/__init__.py index 7aae0b2e..a304f9e7 100644 --- a/couchpotato/core/_base/updater/__init__.py +++ b/couchpotato/core/_base/updater/__init__.py @@ -1,4 +1,6 @@ from .main import Updater +from couchpotato.environment import Env +import os def start(): return Updater() @@ -33,6 +35,7 @@ config = [{ { 'name': 'git_command', 'default': 'git', + 'hidden': not os.path.isdir(os.path.join(Env.get('app_dir'), '.git')), 'advanced': True }, ], diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 6fa6a915..776976d8 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -1,10 +1,7 @@ from base64 import b32decode, b16encode from couchpotato.core.event import addEvent -from couchpotato.core.helpers.encoding import toSafeString from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.environment import Env -import os import random import re @@ -23,29 +20,17 @@ class Downloader(Plugin): def __init__(self): addEvent('download', self.download) - addEvent('download.status', self.getDownloadStatus) + addEvent('download.status', self.getAllDownloadStatus) + addEvent('download.remove_failed', self.removeFailed) def download(self, data = {}, movie = {}, manual = False, filedata = None): pass - def getDownloadStatus(self, data = {}, movie = {}): + def getAllDownloadStatus(self): return False - def createNzbName(self, data, movie): - tag = self.cpTag(movie) - return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag) - - def createFileName(self, data, filedata, movie): - name = os.path.join(self.createNzbName(data, movie)) - if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '' not in filedata: - return '%s.%s' % (name, 'rar') - return '%s.%s' % (name, data.get('type')) - - def cpTag(self, movie): - if Env.setting('enabled', 'renamer'): - return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else '' - - return '' + def removeFailed(self, name = {}, nzo_id = {}): + return False def isCorrectType(self, item_type): is_correct = item_type in self.type diff --git a/couchpotato/core/downloaders/nzbget/__init__.py b/couchpotato/core/downloaders/nzbget/__init__.py index 5015437e..8b68c95f 100644 --- a/couchpotato/core/downloaders/nzbget/__init__.py +++ b/couchpotato/core/downloaders/nzbget/__init__.py @@ -11,7 +11,6 @@ config = [{ 'name': 'nzbget', 'label': 'NZBGet', 'description': 'Send NZBs to your NZBGet installation.', - 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/downloaders/pneumatic/__init__.py b/couchpotato/core/downloaders/pneumatic/__init__.py index dedcde19..004821c5 100644 --- a/couchpotato/core/downloaders/pneumatic/__init__.py +++ b/couchpotato/core/downloaders/pneumatic/__init__.py @@ -12,7 +12,6 @@ config = [{ 'name': 'pneumatic', 'label': 'Pneumatic', 'description': 'Download the .strm file to a specific folder.', - 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 0151cc47..eaf91b56 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -1,6 +1,6 @@ from couchpotato.core.downloaders.base import Downloader from couchpotato.core.helpers.encoding import tryUrlencode -from couchpotato.core.helpers.variable import cleanHost +from couchpotato.core.helpers.variable import cleanHost, mergeDicts from couchpotato.core.logger import CPLog import json import traceback @@ -63,106 +63,92 @@ class Sabnzbd(Downloader): log.error("Unknown error: " + result[:40]) return False - def getDownloadStatus(self, data = {}, movie = {}): - if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): - return + def getAllDownloadStatus(self): + if self.isDisabled(manual = False): + return False - nzbname = self.createNzbName(data, movie) - log.info('Checking download status of "%s" at SABnzbd.', nzbname) + log.debug('Checking SABnzbd download status.') # Go through Queue - params = { - 'apikey': self.conf('api_key'), - 'mode': 'queue', - 'output': 'json' - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - try: - sab = self.urlopen(url, timeout = 60, show_error = False) + queue = self.call({ + 'mode': 'queue', + }) except: - log.error('Failed checking status: %s', traceback.format_exc()) + log.error('Failed getting queue: %s', traceback.format_exc()) return False - try: - history = json.loads(sab) - except: - log.debug("Result text from SAB: " + sab[:40]) - log.error('Failed parsing json status: %s', traceback.format_exc()) - return False - - try: - for slot in history['queue']['slots']: - log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft'])) - if slot['filename'] == nzbname: - return slot['status'].lower() - except: - log.debug('No items in queue: %s', (traceback.format_exc())) - # Go through history items - params = { - 'apikey': self.conf('api_key'), - 'mode': 'history', - 'limit': 15, - 'output': 'json' - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + try: + history = self.call({ + 'mode': 'history', + 'limit': 15, + }) + except: + log.error('Failed getting history json: %s', traceback.format_exc()) + return False + + statuses = [] + + # Get busy releases + for item in queue.get('slots', []): + statuses.append({ + 'id': item['nzo_id'], + 'name': item['filename'], + 'status': 'busy', + 'original_status': item['status'], + 'timeleft': item['timeleft'] if not queue['paused'] else -1, + }) + + # Get old releases + for item in history.get('slots', []): + + status = 'busy' + if item['status'] == 'Failed' or (item['status'] == 'Completed' and item['fail_message'].strip()): + status = 'failed' + elif item['status'] == 'Completed': + status = 'completed' + + statuses.append({ + 'id': item['nzo_id'], + 'name': item['name'], + 'status': status, + 'original_status': item['status'], + 'timeleft': 0, + }) + + return statuses + + def removeFailed(self, item): + + if not self.conf('delete_failed', default = True): + return False + + log.info('%s failed downloading, deleting...', item['name']) try: - sab = self.urlopen(url, timeout = 60, show_error = False) + self.call({ + 'mode': 'history', + 'name': 'delete', + 'del_files': '1', + 'value': item['id'] + }, use_json = False) except: - log.error('Failed getting history: %s', traceback.format_exc()) - return + log.error('Failed deleting: %s', traceback.format_exc()) + return False - try: - history = json.loads(sab) - except: - log.debug("Result text from SAB: " + sab[:40]) - log.error('Failed parsing history json: %s', traceback.format_exc()) - return + return True - try: - for slot in history['history']['slots']: - log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) - if slot['name'] == nzbname: - # Note: if post process even if failed is on in SabNZBd, it will complete with a fail message - if slot['status'] == 'Failed' or (slot['status'] == 'Completed' and slot['fail_message'].strip()): + def call(self, params, use_json = True): - # Delete failed download - if self.conf('delete_failed', default = True): + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(mergeDicts(params, { + 'apikey': self.conf('api_key'), + 'output': 'json' + })) - log.info('%s failed downloading, deleting...', slot['name']) - params = { - 'apikey': self.conf('api_key'), - 'mode': 'history', - 'name': 'delete', - 'del_files': '1', - 'value': slot['nzo_id'] - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + data = self.urlopen(url, timeout = 60, show_error = False) + if use_json: + return json.loads(data)[params['mode']] + else: + return data - try: - sab = self.urlopen(url, timeout = 60, show_error = False) - except: - log.error('Failed deleting: %s', traceback.format_exc()) - return False - - result = sab.strip() - if not result: - log.error("SABnzbd didn't return anything.") - - log.debug("Result text from SAB: " + result[:40]) - if result == "ok": - log.info('SabNZBd deleted failed release %s successfully.', slot['name']) - elif result == "Missing authentication": - log.error("Incorrect username/password or API?.") - else: - log.error("Unknown error: " + result[:40]) - - return 'failed' - else: - return slot['status'].lower() - except: - log.debug('No items in history: %s', (traceback.format_exc())) - - return 'not_found' diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index 8319150a..3a5b9009 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -46,49 +46,30 @@ def fireEvent(name, *args, **kwargs): #log.debug('Firing event %s', name) try: - # Fire after event - is_after_event = False - try: - del kwargs['is_after_event'] - is_after_event = True - except: pass + options = { + 'is_after_event': False, # Fire after event + 'on_complete': False, # onComplete event + 'single': False, # Return single handler + 'merge': False, # Merge items + 'in_order': False, # Fire them in specific order, waits for the other to finish + } - # onComplete event - on_complete = False - try: - on_complete = kwargs['on_complete'] - del kwargs['on_complete'] - except: pass - - # Return single handler - single = False - try: - del kwargs['single'] - single = True - except: pass - - # Merge items - merge = False - try: - del kwargs['merge'] - merge = True - except: pass - - # Merge items - in_order = False - try: - del kwargs['in_order'] - in_order = True - except: pass + # Do options + for x in options: + try: + val = kwargs[x] + del kwargs[x] + options[x] = val + except: pass e = events[name] - if not in_order: e.lock.acquire() + if not options['in_order']: e.lock.acquire() e.asynchronous = False - e.in_order = in_order + e.in_order = options['in_order'] result = e(*args, **kwargs) - if not in_order: e.lock.release() + if not options['in_order']: e.lock.release() - if single and not merge: + if options['single'] and not options['merge']: results = None # Loop over results, stop when first not None result is found. @@ -112,7 +93,7 @@ def fireEvent(name, *args, **kwargs): errorHandler(r[1]) # Merge - if merge and len(results) > 0: + if options['merge'] and len(results) > 0: # Dict if type(results[0]) == dict: merged = {} @@ -133,11 +114,11 @@ def fireEvent(name, *args, **kwargs): log.debug('Return modified results for %s', name) results = modified_results - if not is_after_event: + if not options['is_after_event']: fireEvent('%s.after' % name, is_after_event = True) - if on_complete: - on_complete() + if options['on_complete']: + options['on_complete']() return results except KeyError, e: diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 5312177c..2578d2c2 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -118,8 +118,16 @@ def getTitle(library_dict): try: return library_dict['titles'][0]['title'] except: - log.error('Could not get title for %s', library_dict['identifier']) - return None + try: + for title in library_dict.titles: + if title.default: + return title.title + except: + log.error('Could not get title for %s', library_dict.identifier) + return None + + log.error('Could not get title for %s', library_dict['identifier']) + return None except: log.error('Could not get title for library item: %s', library_dict) return None diff --git a/couchpotato/core/logger.py b/couchpotato/core/logger.py index 8d8c9f59..7a357b36 100644 --- a/couchpotato/core/logger.py +++ b/couchpotato/core/logger.py @@ -5,7 +5,7 @@ import traceback class CPLog(object): context = '' - replace_private = ['api', 'apikey', 'api_key', 'password', 'username', 'h'] + replace_private = ['api', 'apikey', 'api_key', 'password', 'username', 'h', 'uid', 'key'] def __init__(self, context = ''): if context.endswith('.main'): diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index e0af50de..33f90d5a 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -79,7 +79,6 @@ class CoreNotifier(Notification): q.update({Notif.read: True}) db.commit() - #db.close() return jsonified({ 'success': True @@ -107,7 +106,6 @@ class CoreNotifier(Notification): ndict['type'] = 'notification' notifications.append(ndict) - #db.close() return jsonified({ 'success': True, 'empty': len(notifications) == 0, @@ -133,7 +131,6 @@ class CoreNotifier(Notification): self.frontend(type = listener, data = data) - #db.close() return True def frontend(self, type = 'notification', data = {}, message = None): diff --git a/couchpotato/core/plugins/automation/__init__.py b/couchpotato/core/plugins/automation/__init__.py index 75e0d28f..b7b1ab28 100644 --- a/couchpotato/core/plugins/automation/__init__.py +++ b/couchpotato/core/plugins/automation/__init__.py @@ -5,13 +5,12 @@ def start(): config = [{ 'name': 'automation', - 'order': 30, + 'order': 101, 'groups': [ { 'tab': 'automation', 'name': 'automation', - 'label': 'Automation', - 'description': 'Minimal movie requirements', + 'label': 'Minimal movie requirements', 'options': [ { 'name': 'year', diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 40189ef1..8a1e0767 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -1,7 +1,8 @@ from StringIO import StringIO from couchpotato import addView from couchpotato.core.event import fireEvent, addEvent -from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss +from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss, \ + toSafeString from couchpotato.core.helpers.variable import getExt from couchpotato.core.logger import CPLog from couchpotato.environment import Env @@ -245,6 +246,22 @@ class Plugin(object): Env.get('cache').set(cache_key, value, timeout) return value + def createNzbName(self, data, movie): + tag = self.cpTag(movie) + return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag) + + def createFileName(self, data, filedata, movie): + name = os.path.join(self.createNzbName(data, movie)) + if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '' not in filedata: + return '%s.%s' % (name, 'rar') + return '%s.%s' % (name, data.get('type')) + + def cpTag(self, movie): + if Env.setting('enabled', 'renamer'): + return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else '' + + return '' + def isDisabled(self): return not self.isEnabled() diff --git a/couchpotato/core/plugins/browser/main.py b/couchpotato/core/plugins/browser/main.py index aa11b220..b84284b1 100644 --- a/couchpotato/core/plugins/browser/main.py +++ b/couchpotato/core/plugins/browser/main.py @@ -27,6 +27,8 @@ class FileBrowser(Plugin): }, 'return': {'type': 'object', 'example': """{ 'is_root': bool, //is top most folder + 'parent': string, //parent folder of requested path + 'home': string, //user home folder 'empty': bool, //directory is empty 'dirs': array, //directory names }"""} @@ -64,14 +66,35 @@ class FileBrowser(Plugin): path = getParam('path', '/') + # Set proper home dir for some systems + try: + import pwd + os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir + except: + pass + + home = os.path.expanduser('~') + + if not path: + path = home + try: dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True)) except: dirs = [] + parent = os.path.dirname(path.rstrip(os.path.sep)) + if parent == path.rstrip(os.path.sep): + parent = '/' + elif parent != '/' and parent[-2:] != ':\\': + parent += os.path.sep + return jsonified({ - 'is_root': path == '/' or not path, + 'is_root': path == '/', 'empty': len(dirs) == 0, + 'parent': parent, + 'home': home + os.path.sep, + 'platform': os.name, 'dirs': dirs, }) diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py index 40427e44..0658911f 100644 --- a/couchpotato/core/plugins/file/main.py +++ b/couchpotato/core/plugins/file/main.py @@ -109,7 +109,6 @@ class FileManager(Plugin): db.commit() type_dict = ft.to_dict() - #db.close() return type_dict def getTypes(self): @@ -122,5 +121,4 @@ class FileManager(Plugin): for type_object in results: types.append(type_object.to_dict()) - #db.close() return types diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index 95dda2ff..aa1611dd 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -53,7 +53,6 @@ class LibraryPlugin(Plugin): library_dict = l.to_dict(self.default_dict) - #db.close() return library_dict def update(self, identifier, default_title = '', force = False): diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index a1016066..d86b97c9 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -431,9 +431,11 @@ class MoviePlugin(Plugin): movie = db.query(Movie).filter_by(id = movie_id).first() if movie: + deleted = False if delete_from == 'all': db.delete(movie) db.commit() + deleted = True else: done_status = fireEvent('status.get', 'done', single = True) @@ -456,6 +458,7 @@ class MoviePlugin(Plugin): if total_releases == total_deleted: db.delete(movie) db.commit() + deleted = True elif new_movie_status: new_status = fireEvent('status.get', new_movie_status, single = True) movie.profile_id = None @@ -464,6 +467,9 @@ class MoviePlugin(Plugin): else: fireEvent('movie.restatus', movie.id, single = True) + if deleted: + fireEvent('notify.frontend', type = 'movie.deleted', data = movie.to_dict()) + #db.close() return True diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index 38fc7109..51b0f4c5 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -35,6 +35,17 @@ var MovieList = new Class({ if(options.add_new) App.addEvent('movie.added', self.movieAdded.bind(self)) + + App.addEvent('movie.deleted', self.movieDeleted.bind(self)) + }, + + movieDeleted: function(notification){ + var self = this; + + if(!self.movies_added[notification.data.id]) + self.movies_added[notification.data.id].destroy(); + + self.checkIfEmpty(); }, movieAdded: function(notification){ @@ -43,6 +54,8 @@ var MovieList = new Class({ if(!self.movies_added[notification.data.id]) self.createMovie(notification.data, 'top'); + + self.checkIfEmpty(); }, create: function(){ @@ -86,18 +99,18 @@ var MovieList = new Class({ Object.each(movies, function(movie){ self.createMovie(movie); }); - + self.setCounter(total); }, - + setCounter: function(count){ var self = this; - + if(!self.navigation_counter) return; - + self.navigation_counter.set('text', (count || 0)); - + }, createMovie: function(movie, inject_at){ @@ -309,6 +322,8 @@ var MovieList = new Class({ erase_movies.each(function(movie){ self.movies.erase(movie); + + movie.destroy() }); self.calculateSelected(); @@ -458,6 +473,8 @@ var MovieList = new Class({ self.addMovies(json.movies, json.total); self.load_more.set('text', 'load more movies'); if(self.scrollspy) self.scrollspy.start(); + + self.checkIfEmpty() } }); }, @@ -475,6 +492,28 @@ var MovieList = new Class({ }, + checkIfEmpty: function(){ + var self = this; + + var is_empty = self.movies.length == 0; + + if(is_empty && self.options.on_empty_element){ + self.el.grab(self.options.on_empty_element); + + if(self.navigation) + self.navigation.hide(); + + self.empty_element = self.options.on_empty_element; + } + else if(self.empty_element){ + self.empty_element.destroy(); + + if(self.navigation) + self.navigation.show(); + } + + }, + toElement: function(){ return this.el; } diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index f809c579..0979ccf0 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -534,5 +534,5 @@ } .movies .alph_nav .more_menu > a { - background-position: center -157px; + background-position: center -158px; } diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index abaea65c..b10b5b1b 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -16,14 +16,41 @@ var Movie = new Class({ self.profile = Quality.getProfile(data.profile_id) || {}; self.parent(self, options); - App.addEvent('movie.update.'+data.id, self.update.bind(self)); + self.addEvents(); + }, + + addEvents: function(){ + var self = this; + + App.addEvent('movie.update.'+self.data.id, self.update.bind(self)); ['movie.busy', 'searcher.started'].each(function(listener){ - App.addEvent(listener+'.'+data.id, function(notification){ + App.addEvent(listener+'.'+self.data.id, function(notification){ if(notification.data) self.busy(true) }); }) + + App.addEvent('searcher.ended.'+self.data.id, function(notification){ + if(notification.data) + self.busy(false) + }); + }, + + destroy: function(){ + var self = this; + + self.el.destroy(); + delete self.list.movies_added[self.get('id')]; + self.list.movies.erase(self) + + self.list.checkIfEmpty(); + + // Remove events + App.removeEvents('movie.update.'+self.data.id); + ['movie.busy', 'searcher.started'].each(function(listener){ + App.removeEvents(listener+'.'+self.data.id); + }) }, busy: function(set_busy){ @@ -359,7 +386,7 @@ var ReleaseAction = new Class({ var status = Status.get(release.status_id); - if((status.identifier == 'ignored' || status.identifier == 'failed') || (!self.next_release && status.identifier == 'available')){ + if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){ self.hide_on_click = false; self.show(); buttons_done = true; @@ -397,19 +424,11 @@ var ReleaseAction = new Class({ var status = Status.get(release.status_id), quality = Quality.getProfile(release.quality_id) || {}, info = release.info; - - if( status.identifier == 'ignored' || status.identifier == 'failed'){ - self.last_release = release; - } - else if(!self.next_release && status.identifier == 'available'){ - self.next_release = release; - } + release.status = status; // Create release new Element('div', { - 'class': 'item '+status.identifier + - (self.next_release && self.next_release.id == release.id ? ' next_release' : '') + - (self.last_release && self.last_release.id == release.id ? ' last_release' : ''), + 'class': 'item '+status.identifier, 'id': 'release_'+release.id }).adopt( new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}), @@ -442,11 +461,27 @@ var ReleaseAction = new Class({ } }) ).inject(self.release_container) + + if(status.identifier == 'ignored' || status.identifier == 'failed' || status.identifier == 'snatched'){ + if(!self.last_release || (self.last_release && self.last_release.status.identifier != 'snatched' && status.identifier == 'snatched')) + self.last_release = release; + } + else if(!self.next_release && status.identifier == 'available'){ + self.next_release = release; + } }); + if(self.last_release){ + self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release'); + } + + if(self.next_release){ + self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release'); + } + self.trynext_container.adopt( new Element('span.or', { - 'text': 'Download' + 'text': 'This movie is snatched, if anything went wrong, download' }), self.last_release ? new Element('a.button.orange', { 'text': 'the same release again', @@ -455,7 +490,7 @@ var ReleaseAction = new Class({ } }) : null, self.next_release && self.last_release ? new Element('span.or', { - 'text': 'or' + 'text': ',' }) : null, self.next_release ? [new Element('a.button.green', { 'text': self.last_release ? 'another release' : 'the best release', diff --git a/couchpotato/core/plugins/movie/static/search.js b/couchpotato/core/plugins/movie/static/search.js index 6c6c94e9..2115ef4d 100644 --- a/couchpotato/core/plugins/movie/static/search.js +++ b/couchpotato/core/plugins/movie/static/search.js @@ -324,7 +324,7 @@ Block.Search.Item = new Class({ var self = this; if(!self.options.hasClass('set')){ - + if(self.info.in_library){ var in_library = []; self.info.in_library.releases.each(function(release){ @@ -339,7 +339,7 @@ Block.Search.Item = new Class({ 'height': null, 'width': null }) : null, - self.info.in_wanted ? new Element('span.in_wanted', { + self.info.in_wanted && self.info.in_wanted.profile ? new Element('span.in_wanted', { 'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label }) : (in_library ? new Element('span.in_library', { 'text': 'Already in library: ' + in_library.join(', ') diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py index 76fb37c5..4caa54f7 100644 --- a/couchpotato/core/plugins/profile/main.py +++ b/couchpotato/core/plugins/profile/main.py @@ -47,7 +47,6 @@ class ProfilePlugin(Plugin): for profile in profiles: temp.append(profile.to_dict(self.to_dict)) - #db.close() return temp def save(self): @@ -84,7 +83,6 @@ class ProfilePlugin(Plugin): profile_dict = p.to_dict(self.to_dict) - #db.close() return jsonified({ 'success': True, 'profile': profile_dict @@ -95,7 +93,6 @@ class ProfilePlugin(Plugin): db = get_session() default = db.query(Profile).first() default_dict = default.to_dict(self.to_dict) - #db.close() return default_dict @@ -113,7 +110,6 @@ class ProfilePlugin(Plugin): order += 1 db.commit() - #db.close() return jsonified({ 'success': True @@ -137,8 +133,6 @@ class ProfilePlugin(Plugin): except Exception, e: message = log.error('Failed deleting Profile: %s', e) - #db.close() - return jsonified({ 'success': success, 'message': message @@ -181,10 +175,10 @@ class ProfilePlugin(Plugin): ) p.types.append(profile_type) - db.commit() quality_order += 1 order += 1 - #db.close() + db.commit() + return True diff --git a/couchpotato/core/plugins/profile/static/profile.js b/couchpotato/core/plugins/profile/static/profile.js index 470fda8f..3bb44989 100644 --- a/couchpotato/core/plugins/profile/static/profile.js +++ b/couchpotato/core/plugins/profile/static/profile.js @@ -86,7 +86,10 @@ var Profile = new Class({ }, 'onComplete': function(json){ if(json.success){ - self.data = json.profile + self.data = json.profile; + self.type_container.getElement('li:first-child input[type=checkbox]') + .set('checked', true) + .getParent().addClass('checked'); } } }); @@ -239,9 +242,17 @@ Profile.Type = new Class({ ), new Element('span.finish').adopt( self.finish = new Element('input.inlay.finish[type=checkbox]', { - 'checked': data.finish, + 'checked': data.finish !== undefined ? data.finish : 1, 'events': { - 'change': self.fireEvent.bind(self, 'change') + '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.fireEvent('change'); + } } }) ), @@ -255,7 +266,7 @@ Profile.Type = new Class({ self.el[self.data.quality_id > 0 ? 'removeClass' : 'addClass']('is_empty'); - new Form.Check(self.finish); + self.finish_class = new Form.Check(self.finish); }, diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index 84ac80a8..7964fb18 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -9,6 +9,7 @@ from couchpotato.core.plugins.base import Plugin from couchpotato.core.settings.model import Quality, Profile, ProfileType import os.path import re +import time log = CPLog(__name__) @@ -68,7 +69,6 @@ class QualityPlugin(Plugin): q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict()) temp.append(q) - #db.close() return temp def single(self, identifier = ''): @@ -80,7 +80,6 @@ class QualityPlugin(Plugin): if quality: quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict()) - #db.close() return quality_dict def getQuality(self, identifier): @@ -100,7 +99,6 @@ class QualityPlugin(Plugin): setattr(quality, params.get('value_type'), params.get('value')) db.commit() - #db.close() return jsonified({ 'success': True }) @@ -113,46 +111,48 @@ class QualityPlugin(Plugin): for q in self.qualities: # Create quality - quality = db.query(Quality).filter_by(identifier = q.get('identifier')).first() + qual = db.query(Quality).filter_by(identifier = q.get('identifier')).first() - if not quality: + if not qual: log.info('Creating quality: %s', q.get('label')) - quality = Quality() - db.add(quality) + qual = Quality() + qual.order = order + qual.identifier = q.get('identifier') + qual.label = toUnicode(q.get('label')) + qual.size_min, qual.size_max = q.get('size') - quality.order = order - quality.identifier = q.get('identifier') - quality.label = toUnicode(q.get('label')) - quality.size_min, quality.size_max = q.get('size') + db.add(qual) # Create single quality profile - profile = db.query(Profile).filter( + prof = db.query(Profile).filter( Profile.core == True ).filter( - Profile.types.any(quality = quality) + Profile.types.any(quality = qual) ).all() - if not profile: + if not prof: log.info('Creating profile: %s', q.get('label')) - profile = Profile( + prof = Profile( core = True, - label = toUnicode(quality.label), + label = toUnicode(qual.label), order = order ) - db.add(profile) + db.add(prof) profile_type = ProfileType( - quality = quality, - profile = profile, + quality = qual, + profile = prof, finish = True, order = 0 ) - profile.types.append(profile_type) + prof.types.append(profile_type) order += 1 - db.commit() - #db.close() + db.commit() + + time.sleep(0.3) # Wait a moment + return True def guess(self, files, extra = {}): diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index 9046034c..8f408543 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -88,8 +88,6 @@ class Release(Plugin): fireEvent('movie.restatus', movie.id) - #db.close() - return True @@ -108,7 +106,6 @@ class Release(Plugin): release_id = getParam('id') - #db.close() return jsonified({ 'success': self.delete(release_id) }) @@ -152,7 +149,6 @@ class Release(Plugin): rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id') db.commit() - #db.close() return jsonified({ 'success': True }) @@ -161,6 +157,7 @@ class Release(Plugin): db = get_session() id = getParam('id') + status_snatched = fireEvent('status.add', 'snatched', single = True) rel = db.query(Relea).filter_by(id = id).first() if rel: @@ -181,14 +178,16 @@ class Release(Plugin): 'files': {} }), manual = True, single = True) - #db.close() + if success: + rel.status_id = status_snatched.get('id') + db.commit() + return jsonified({ 'success': success }) else: log.error('Couldn\'t find release with id: %s', id) - #db.close() return jsonified({ 'success': False }) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index a696265c..993575ca 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -20,6 +20,7 @@ log = CPLog(__name__) class Renamer(Plugin): renaming_started = False + checking_snatched = False def __init__(self): @@ -33,6 +34,7 @@ class Renamer(Plugin): addEvent('app.load', self.scan) fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every')) + fireEvent('schedule.interval', 'renamer.check_snatched_forced', self.scan, hours = 2) def scanView(self): @@ -386,7 +388,6 @@ class Renamer(Plugin): if self.shuttingDown(): break - #db.close() self.renaming_started = False def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''): @@ -495,6 +496,11 @@ class Renamer(Plugin): loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) def checkSnatched(self): + if self.checking_snatched: + log.debug('Already checking snatched') + + self.checking_snatched = True + snatched_status = fireEvent('status.get', 'snatched', single = True) ignored_status = fireEvent('status.get', 'ignored', single = True) failed_status = fireEvent('status.get', 'failed', single = True) @@ -504,57 +510,74 @@ class Renamer(Plugin): db = get_session() rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all() - if rels: - log.debug('Checking status snatched releases...') - scan_required = False - for rel in rels: - - # Get current selected title - default_title = '' - for title in rel.movie.library.titles: - if title.default: default_title = title.title - - # Check if movie has already completed and is manage tab (legacy db correction) - if rel.movie.status_id == done_status.get('id'): - log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title) - rel.status_id = ignored_status.get('id') - db.commit() - continue - - item = {} - for info in rel.info: - item[info.identifier] = info.value - - movie_dict = fireEvent('movie.get', rel.movie_id, single = True) - - # check status - downloadstatus = fireEvent('download.status', data = item, movie = movie_dict, single = True) - if not downloadstatus: + if rels: + self.checking_snatched = True + log.debug('Checking status snatched releases...') + # get queue and history (once) from SABnzbd + statuses = fireEvent('download.status', merge = True) + if not statuses: log.debug('Download status functionality is not implemented for active downloaders.') scan_required = True else: - log.debug('Download status: %s' , downloadstatus) + try: + for rel in rels: + rel_dict = rel.to_dict({'info': {}}) - if downloadstatus == 'failed': - if self.conf('next_on_failed'): - fireEvent('searcher.try_next_release', movie_id = rel.movie_id) - else: - rel.status_id = failed_status.get('id') - db.commit() + # Get current selected title + default_title = getTitle(rel.movie.library) - log.info('Download of %s failed.', item['name']) + # Check if movie has already completed and is manage tab (legacy db correction) + if rel.movie.status_id == done_status.get('id'): + log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title) + rel.status_id = ignored_status.get('id') + db.commit() + continue - elif downloadstatus == 'completed': - log.info('Download of %s completed!', item['name']) - scan_required = True + movie_dict = fireEvent('movie.get', rel.movie_id, single = True) - elif downloadstatus == 'not_found': - log.info('%s not found in downloaders', item['name']) - rel.status_id = ignored_status.get('id') - db.commit() + # check status + nzbname = self.createNzbName(rel_dict['info'], movie_dict) + + found = False + for item in statuses: + if item['name'] == nzbname: + + timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft'] + log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft)) + + if item['status'] == 'busy': + pass + elif item['status'] == 'failed': + fireEvent('download.remove_failed', item, single = True) + + if self.conf('next_on_failed'): + fireEvent('searcher.try_next_release', movie_id = rel.movie_id) + else: + rel.status_id = failed_status.get('id') + db.commit() + elif item['status'] == 'completed': + log.info('Download of %s completed!', item['name']) + scan_required = True + + found = True + break + + if not found: + log.info('%s not found in downloaders', nzbname) + rel.status_id = ignored_status.get('id') + db.commit() + + if self.conf('next_on_failed'): + fireEvent('searcher.try_next_release', movie_id = rel.movie_id) + + except: + log.error('Failed checking for release in downloader: %s', traceback.format_exc()) - # Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd if scan_required: fireEvent('renamer.scan') + + self.checking_snatched = False + + return True diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index b22b6265..4c639e2d 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -542,7 +542,6 @@ class Scanner(Plugin): break except: pass - #db.close() # Search based on OpenSubtitleHash if not imdb_id and not group['is_dvd']: diff --git a/couchpotato/core/plugins/score/scores.py b/couchpotato/core/plugins/score/scores.py index 385e3a95..8afa129c 100644 --- a/couchpotato/core/plugins/score/scores.py +++ b/couchpotato/core/plugins/score/scores.py @@ -10,7 +10,7 @@ name_scores = [ # Video 'x264:1', 'h264:1', # Audio - 'DTS:4', 'AC3:2', + 'dts:4', 'ac3:2', # Quality '720p:10', '1080p:10', 'bluray:10', 'dvd:1', 'dvdrip:1', 'brrip:1', 'bdrip:1', 'bd50:1', 'bd25:1', # Language / Subs diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 567b84ea..66a2e87e 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -1,6 +1,6 @@ from couchpotato import get_session from couchpotato.api import addApiView -from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.event import addEvent, fireEvent, fireEventAsync from couchpotato.core.helpers.encoding import simplifyString, toUnicode from couchpotato.core.helpers.request import jsonified, getParam from couchpotato.core.helpers.variable import md5, getTitle @@ -36,9 +36,38 @@ class Searcher(Plugin): }, }) + addApiView('searcher.full_search', self.allMoviesView, docs = { + 'desc': 'Starts a full search for all wanted movies', + }) + + addApiView('searcher.progress', self.getProgress, docs = { + 'desc': 'Get the progress of current full search', + 'return': {'type': 'object', 'example': """{ + 'progress': False || object, total & to_go, +}"""}, + }) + # Schedule cronjob fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) + def allMoviesView(self): + + in_progress = self.in_progress + if not in_progress: + fireEventAsync('searcher.all') + fireEvent('notify.frontend', type = 'searcher.started', data = True, message = 'Full search started') + else: + fireEvent('notify.frontend', type = 'searcher.already_started', data = True, message = 'Full search already in progress') + + return jsonified({ + 'success': not in_progress + }) + + def getProgress(self): + + return jsonified({ + 'progress': self.in_progress + }) def allMovies(self): @@ -54,6 +83,11 @@ class Searcher(Plugin): Movie.status.has(identifier = 'active') ).all() + self.in_progress = { + 'total': len(movies), + 'to_go': len(movies), + } + for movie in movies: movie_dict = movie.to_dict({ 'profile': {'types': {'quality': {}}}, @@ -65,15 +99,17 @@ class Searcher(Plugin): try: self.single(movie_dict) except IndexError: + log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc())) fireEvent('library.update', movie_dict['library']['identifier'], force = True) except: log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc())) + self.in_progress['to_go'] -= 1 + # Break if CP wants to shut down if self.shuttingDown(): break - #db.close() self.in_progress = False def single(self, movie): @@ -192,7 +228,6 @@ class Searcher(Plugin): fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True) - #db.close() return ret def download(self, data, movie, manual = False): @@ -243,7 +278,6 @@ class Searcher(Plugin): except Exception, e: log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc())) - #db.close() return True log.info('Tried to download, but none of the downloaders are enabled') @@ -254,7 +288,7 @@ class Searcher(Plugin): imdb_results = kwargs.get('imdb_results', False) retention = Env.setting('retention', section = 'nzb') - if nzb.get('seeds') is None and retention < nzb.get('age', 0): + if nzb.get('seeds') is None and 0 < retention < nzb.get('age', 0): log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s', (nzb['age'], retention, nzb['name'])) return False @@ -354,9 +388,11 @@ class Searcher(Plugin): year_name = fireEvent('scanner.name_year', name, single = True) if movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None): if size > 3000: # Assume dvdr - return 'dvdr' == preferred_quality['identifier'] + log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', (size)) + found['dvdr'] = True else: # Assume dvdrip - return 'dvdrip' == preferred_quality['identifier'] + log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', (size)) + found['dvdrip'] = True # Allow other qualities for allowed in preferred_quality.get('allow'): @@ -410,6 +446,11 @@ class Searcher(Plugin): if not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0): return True else: + + # For movies before 1972 + if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0: + return True + if wanted_quality in pre_releases: # Prerelease 1 week before theaters if dates.get('theater') - 604800 < now: diff --git a/couchpotato/core/plugins/subtitle/__init__.py b/couchpotato/core/plugins/subtitle/__init__.py index 858ce9d6..686d385e 100644 --- a/couchpotato/core/plugins/subtitle/__init__.py +++ b/couchpotato/core/plugins/subtitle/__init__.py @@ -8,9 +8,9 @@ config = [{ 'groups': [ { 'tab': 'renamer', - 'subtab': 'subtitles', 'name': 'subtitle', - 'label': 'Download subtitles after rename', + 'label': 'Download subtitles', + 'description': 'after rename', 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/plugins/subtitle/main.py b/couchpotato/core/plugins/subtitle/main.py index 9ff39f47..3a66a8bc 100644 --- a/couchpotato/core/plugins/subtitle/main.py +++ b/couchpotato/core/plugins/subtitle/main.py @@ -40,8 +40,6 @@ class Subtitle(Plugin): # get subtitles for those files subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services) - #db.close() - def searchSingle(self, group): if self.isDisabled(): return diff --git a/couchpotato/core/plugins/trailer/__init__.py b/couchpotato/core/plugins/trailer/__init__.py index 033df088..f3aa59de 100644 --- a/couchpotato/core/plugins/trailer/__init__.py +++ b/couchpotato/core/plugins/trailer/__init__.py @@ -8,9 +8,9 @@ config = [{ 'groups': [ { 'tab': 'renamer', - 'subtab': 'trailer', 'name': 'trailer', - 'label': 'Download trailer after rename', + 'label': 'Download trailer', + 'description': 'after rename', 'options': [ { 'name': 'enabled', @@ -24,12 +24,6 @@ config = [{ 'type': 'dropdown', 'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')], }, - { - 'name': 'automatic', - 'default': False, - 'type': 'bool', - 'description': 'Automaticly search & download for movies in library', - }, ], }, ], diff --git a/couchpotato/core/plugins/wizard/static/wizard.css b/couchpotato/core/plugins/wizard/static/wizard.css index d1aa99c8..a24f2b9e 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.css +++ b/couchpotato/core/plugins/wizard/static/wizard.css @@ -14,12 +14,13 @@ .page.wizard .tab_wrapper { background: #5c697b; - padding: 18px 0; - font-size: 23px; + padding: 10px 0; + font-size: 18px; position: fixed; top: 0; margin: 0; width: 100%; + min-width: 960px; left: 0; z-index: 2; box-shadow: 0 0 50px rgba(0,0,0,0.55); @@ -36,7 +37,7 @@ display: inline-block; } .page.wizard .tabs li a { - padding: 20px 30px; + padding: 20px 10px; } .page.wizard .tab_wrapper .pointer { @@ -45,7 +46,7 @@ border-top: 10px solid #5c697b; display: block; position: absolute; - top: 60px; + top: 44px; } .page.wizard .tab_content { @@ -58,11 +59,25 @@ .page.wizard .wgroup_finish { height: 300px; } + .page.wizard .wgroup_finish h1 { + text-align: center; + } + .page.wizard .wgroup_finish .wizard_support, + .page.wizard .wgroup_finish .description { + font-size: 25px; + line-height: 120%; + margin: 20px 0; + text-align: center; + } .page.wizard .button.green { padding: 20px; font-size: 25px; - margin: 10px 30px; + margin: 10px 30px 80px; display: block; text-align: center; - } \ No newline at end of file + } + +.page.wizard .tab_nzb_providers { + margin: 20px 0 0 0; +} diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index a4438cba..5d087add 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -1,214 +1,266 @@ -Page.Wizard = new Class({ - - Extends: Page.Settings, - - name: 'wizard', - has_tab: false, - wizard_only: true, - - headers: { - 'welcome': { - 'title': 'Welcome to the new CouchPotato', - 'description': 'To get started, fill in each of the following settings as much as your can.
Maybe first start with importing your movies from the previous CouchPotato', - 'content': new Element('div', { - 'styles': { - 'margin': '0 0 0 30px' - } - }).adopt( - new Element('div', { - 'html': 'Select the data.db. It should be in your CouchPotato root directory.' - }), - self.import_iframe = new Element('iframe', { - 'styles': { - 'height': 40, - 'width': 300, - 'border': 0, - 'overflow': 'hidden' - } - }) - ), - 'event': function(){ - self.import_iframe.set('src', Api.createUrl('v1.import')) - } - }, - 'general': { - 'title': 'General', - 'description': 'If you want to access CP from outside your local network, you better secure it a bit with a username & password.' - }, - 'downloaders': { - 'title': 'What download apps are you using?', - 'description': 'If you don\'t have any of these listed, you have to use Blackhole. Or drop me a line, maybe I\'ll support your download app.' - }, - 'providers': { - 'title': 'Are you registered at any of these sites?', - 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more.' - }, - 'renamer': { - 'title': 'Move & rename the movies after downloading?', - 'description': '' - }, - 'finish': { - 'title': 'Finish Up', - 'description': 'Are you done? Did you fill in everything as much as possible? Yes, ok gogogo!', - 'content': new Element('div').adopt( - new Element('a.button.green', { - 'text': 'I\'m ready to start the awesomeness, wow this button is big and green!', - 'events': { - 'click': function(e){ - (e).preventDefault(); - Api.request('settings.save', { - 'data': { - 'section': 'core', - 'name': 'show_wizard', - 'value': 0 - }, - 'useSpinner': true, - 'spinnerOptions': { - 'target': self.el - }, - 'onComplete': function(){ - window.location = App.createUrl(); - } - }); - } - } - }) - ) - } - }, - groups: ['welcome', 'general', 'downloaders', 'searcher', 'providers', 'renamer', 'finish'], - - open: function(action, params){ - var self = this; - - if(!self.initialized){ - App.fireEvent('unload'); - App.getBlock('header').hide(); - - self.parent(action, params); - - self.addEvent('create', function(){ - self.order(); - }); - - self.initialized = true; - - self.scroll = new Fx.Scroll(document.body, { - 'transition': 'quint:in:out' - }); - } - else - (function(){ - var sc = self.el.getElement('.wgroup_'+action); - self.scroll.start(0, sc.getCoordinates().top-80); - }).delay(1) - }, - - order: function(){ - var self = this; - - var form = self.el.getElement('.uniForm'); - var tabs = self.el.getElement('.tabs'); - - self.groups.each(function(group){ - if(self.headers[group]){ - group_container = new Element('.wgroup_'+group, { - 'styles': { - 'opacity': 0.2 - }, - 'tween': { - 'duration': 350 - } - }); - group_container.adopt( - new Element('h1', { - 'text': self.headers[group].title - }), - self.headers[group].description ? new Element('span.description', { - 'html': self.headers[group].description - }) : null, - self.headers[group].content ? self.headers[group].content : null - ).inject(form); - } - - var tab_navigation = tabs.getElement('.t_'+group); - if(tab_navigation && group_container){ - tab_navigation.inject(tabs); // Tab navigation - self.el.getElement('.tab_'+group).inject(group_container); // Tab content - if(self.headers[group]){ - var a = tab_navigation.getElement('a'); - a.set('text', (self.headers[group].label || group).capitalize()); - var url_split = a.get('href').split('wizard')[1].split('/'); - if(url_split.length > 3) - a.set('href', a.get('href').replace(url_split[url_split.length-3]+'/', '')); - - } - } - else { - new Element('li.t_'+group).adopt( - new Element('a', { - 'href': App.createUrl('wizard/'+group), - 'text': (self.headers[group].label || group).capitalize() - }) - ).inject(tabs); - } - - if(self.headers[group] && self.headers[group].event) - self.headers[group].event.call() - }); - - // Remove toggle - self.el.getElement('.advanced_toggle').destroy(); - - // Hide retention - self.el.getElement('.tab_searcher').hide(); - self.el.getElement('.t_searcher').hide(); - - // Add pointer - new Element('.tab_wrapper').wraps(tabs).adopt( - self.pointer = new Element('.pointer', { - 'tween': { - 'transition': 'quint:in:out' - } - }) - ); - - // Add nav - var minimum = self.el.getSize().y-window.getSize().y; - self.groups.each(function(group, nr){ - - var g = self.el.getElement('.wgroup_'+group); - if(!g || !g.isVisible()) return; - var t = self.el.getElement('.t_'+group); - if(!t) return; - - var func = function(){ - var ct = t.getCoordinates(); - self.pointer.tween('left', ct.left+(ct.width/2)-(self.pointer.getWidth()/2)); - g.tween('opacity', 1); - } - - if(nr == 0) - func(); - - - var ss = new ScrollSpy( { - min: function(){ - var c = g.getCoordinates(); - var top = c.top-(window.getSize().y/2); - return top > minimum ? minimum : top - }, - max: function(){ - var c = g.getCoordinates(); - return c.top+(c.height/2) - }, - onEnter: func, - onLeave: function(){ - g.tween('opacity', 0.2) - } - }); - }); - - } - +Page.Wizard = new Class({ + + Extends: Page.Settings, + + name: 'wizard', + has_tab: false, + wizard_only: true, + + headers: { + 'welcome': { + 'title': 'Welcome to the new CouchPotato', + 'description': 'To get started, fill in each of the following settings as much as your can.
Maybe first start with importing your movies from the previous CouchPotato', + 'content': new Element('div', { + 'styles': { + 'margin': '0 0 0 30px' + } + }).adopt( + new Element('div', { + 'html': 'Select the data.db. It should be in your CouchPotato root directory.' + }), + self.import_iframe = new Element('iframe', { + 'styles': { + 'height': 40, + 'width': 300, + 'border': 0, + 'overflow': 'hidden' + } + }) + ), + 'event': function(){ + self.import_iframe.set('src', Api.createUrl('v1.import')) + } + }, + 'general': { + 'title': 'General', + 'description': 'If you want to access CP from outside your local network, you better secure it a bit with a username & password.' + }, + 'downloaders': { + 'title': 'What download apps are you using?', + 'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use Blackhole.' + }, + 'providers': { + 'title': 'Are you registered at any of these sites?', + 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.', + 'include': ['nzb_providers', 'torrent_providers'] + }, + 'renamer': { + 'title': 'Move & rename the movies after downloading?', + 'description': 'The coolest part of CP is that it can move and organize your downloaded movies automagically. Check settings and you can even download trailers, subtitles and other data when it has finished downloading. It\'s awesome!' + }, + '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.' + + '
Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)', + 'content': function(){ + return App.createUserscriptButtons().setStyles({ + 'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')" + }) + } + }, + 'finish': { + 'title': 'Finishing Up', + 'description': 'Are you done? Did you fill in everything as much as possible?' + + '
Be sure to check the settings to see what more CP can do!

' + + '
After you\'ve used CP for a while, and you like it (which of course you will), consider supporting CP. Maybe even by writing some code.
Or by getting a subscription at Usenet Server or Newshosting.
', + 'content': new Element('div').adopt( + new Element('a.button.green', { + 'styles': { + 'margin-top': 20 + }, + 'text': 'I\'m ready to start the awesomeness, wow this button is big and green!', + 'events': { + 'click': function(e){ + (e).preventDefault(); + Api.request('settings.save', { + 'data': { + 'section': 'core', + 'name': 'show_wizard', + 'value': 0 + }, + 'useSpinner': true, + 'spinnerOptions': { + 'target': self.el + }, + 'onComplete': function(){ + window.location = App.createUrl(); + } + }); + } + } + }) + ) + } + }, + groups: ['welcome', 'general', 'downloaders', 'searcher', 'providers', 'renamer', 'automation', 'finish'], + + open: function(action, params){ + var self = this; + + if(!self.initialized){ + App.fireEvent('unload'); + App.getBlock('header').hide(); + + self.parent(action, params); + + self.addEvent('create', function(){ + self.order(); + }); + + self.initialized = true; + + self.scroll = new Fx.Scroll(document.body, { + 'transition': 'quint:in:out' + }); + } + else + (function(){ + var sc = self.el.getElement('.wgroup_'+action); + self.scroll.start(0, sc.getCoordinates().top-80); + }).delay(1) + }, + + order: function(){ + var self = this; + + var form = self.el.getElement('.uniForm'); + var tabs = self.el.getElement('.tabs'); + + self.groups.each(function(group, nr){ + + if(self.headers[group]){ + group_container = new Element('.wgroup_'+group, { + 'styles': { + 'opacity': 0.2 + }, + 'tween': { + 'duration': 350 + } + }); + + if(self.headers[group].include){ + self.headers[group].include.each(function(inc){ + group_container.addClass('wgroup_'+inc); + }) + } + + var content = self.headers[group].content + group_container.adopt( + new Element('h1', { + 'text': self.headers[group].title + }), + self.headers[group].description ? new Element('span.description', { + 'html': self.headers[group].description + }) : null, + content ? (typeOf(content) == 'function' ? content() : content) : null + ).inject(form); + } + + var tab_navigation = tabs.getElement('.t_'+group); + + if(!tab_navigation && self.headers[group] && self.headers[group].include){ + tab_navigation = [] + self.headers[group].include.each(function(inc){ + tab_navigation.include(tabs.getElement('.t_'+inc)); + }) + } + + if(tab_navigation && group_container){ + tabs.adopt(tab_navigation); // Tab navigation + + if(self.headers[group] && self.headers[group].include){ + + self.headers[group].include.each(function(inc){ + self.el.getElement('.tab_'+inc).inject(group_container); + }) + + new Element('li.t_'+group).adopt( + new Element('a', { + 'href': App.createUrl('wizard/'+group), + 'text': (self.headers[group].label || group).capitalize() + }) + ).inject(tabs); + + } + else + self.el.getElement('.tab_'+group).inject(group_container); // Tab content + + if(tab_navigation.getElement && self.headers[group]){ + var a = tab_navigation.getElement('a'); + a.set('text', (self.headers[group].label || group).capitalize()); + var url_split = a.get('href').split('wizard')[1].split('/'); + if(url_split.length > 3) + a.set('href', a.get('href').replace(url_split[url_split.length-3]+'/', '')); + + } + } + else { + new Element('li.t_'+group).adopt( + new Element('a', { + 'href': App.createUrl('wizard/'+group), + 'text': (self.headers[group].label || group).capitalize() + }) + ).inject(tabs); + } + + if(self.headers[group] && self.headers[group].event) + self.headers[group].event.call() + }); + + // Remove toggle + self.el.getElement('.advanced_toggle').destroy(); + + // Hide retention + self.el.getElement('.tab_searcher').hide(); + self.el.getElement('.t_searcher').hide(); + self.el.getElement('.t_nzb_providers').hide(); + self.el.getElement('.t_torrent_providers').hide(); + + // Add pointer + new Element('.tab_wrapper').wraps(tabs).adopt( + self.pointer = new Element('.pointer', { + 'tween': { + 'transition': 'quint:in:out' + } + }) + ); + + // Add nav + var minimum = self.el.getSize().y-window.getSize().y; + self.groups.each(function(group, nr){ + + var g = self.el.getElement('.wgroup_'+group); + if(!g || !g.isVisible()) return; + var t = self.el.getElement('.t_'+group); + if(!t) return; + + var func = function(){ + var ct = t.getCoordinates(); + self.pointer.tween('left', ct.left+(ct.width/2)-(self.pointer.getWidth()/2)); + g.tween('opacity', 1); + } + + if(nr == 0) + func(); + + + var ss = new ScrollSpy( { + min: function(){ + var c = g.getCoordinates(); + var top = c.top-(window.getSize().y/2); + return top > minimum ? minimum : top + }, + max: function(){ + var c = g.getCoordinates(); + return c.top+(c.height/2) + }, + onEnter: func, + onLeave: function(){ + g.tween('opacity', 0.2) + } + }); + }); + + } + }); \ No newline at end of file diff --git a/couchpotato/core/providers/movie/_modifier/main.py b/couchpotato/core/providers/movie/_modifier/main.py index 5af1659e..7346480e 100644 --- a/couchpotato/core/providers/movie/_modifier/main.py +++ b/couchpotato/core/providers/movie/_modifier/main.py @@ -70,7 +70,6 @@ class MovieResultModifier(Plugin): except: log.error('Tried getting more info on searched movies: %s', traceback.format_exc()) - #db.close() return temp def checkLibrary(self, result): diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 8e890484..5d6a35ba 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -94,7 +94,6 @@ class CouchPotatoApi(MovieProvider): db = get_session() active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all() movies = [x.library.identifier for x in active_movies] - #db.close() suggestions = self.suggest(movies, ignore) diff --git a/couchpotato/core/providers/nzb/mysterbin/__init__.py b/couchpotato/core/providers/nzb/mysterbin/__init__.py index 3f0d1d33..b34f72fc 100644 --- a/couchpotato/core/providers/nzb/mysterbin/__init__.py +++ b/couchpotato/core/providers/nzb/mysterbin/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'nzb_providers', 'name': 'Mysterbin', 'description': 'Free provider, less accurate. See Mysterbin', 'options': [ diff --git a/couchpotato/core/providers/nzb/newzbin/__init__.py b/couchpotato/core/providers/nzb/newzbin/__init__.py index 06b25486..11292339 100644 --- a/couchpotato/core/providers/nzb/newzbin/__init__.py +++ b/couchpotato/core/providers/nzb/newzbin/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'nzb_providers', 'name': 'newzbin', 'description': 'See Newzbin', 'wizard': True, diff --git a/couchpotato/core/providers/nzb/newznab/__init__.py b/couchpotato/core/providers/nzb/newznab/__init__.py index e54db343..712f03e5 100644 --- a/couchpotato/core/providers/nzb/newznab/__init__.py +++ b/couchpotato/core/providers/nzb/newznab/__init__.py @@ -8,8 +8,9 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'nzb_providers', 'name': 'newznab', + 'order': 10, 'description': 'Enable multiple NewzNab providers such as NZB.su and nzbs.org', 'wizard': True, 'options': [ diff --git a/couchpotato/core/providers/nzb/nzbclub/__init__.py b/couchpotato/core/providers/nzb/nzbclub/__init__.py index e3387a3e..fc7b7ef2 100644 --- a/couchpotato/core/providers/nzb/nzbclub/__init__.py +++ b/couchpotato/core/providers/nzb/nzbclub/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'nzb_providers', 'name': 'NZBClub', 'description': 'Free provider, less accurate. See NZBClub', 'options': [ diff --git a/couchpotato/core/providers/nzb/nzbindex/__init__.py b/couchpotato/core/providers/nzb/nzbindex/__init__.py index 59b2730d..5bf5cd4d 100644 --- a/couchpotato/core/providers/nzb/nzbindex/__init__.py +++ b/couchpotato/core/providers/nzb/nzbindex/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'nzb_providers', 'name': 'nzbindex', 'description': 'Free provider, less accurate. See NZBIndex', 'options': [ diff --git a/couchpotato/core/providers/nzb/nzbmatrix/__init__.py b/couchpotato/core/providers/nzb/nzbmatrix/__init__.py index 8fc6b408..c3a5bfaa 100644 --- a/couchpotato/core/providers/nzb/nzbmatrix/__init__.py +++ b/couchpotato/core/providers/nzb/nzbmatrix/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'nzb_providers', 'name': 'nzbmatrix', 'label': 'NZBMatrix', 'description': 'See NZBMatrix', diff --git a/couchpotato/core/providers/nzb/nzbsrus/__init__.py b/couchpotato/core/providers/nzb/nzbsrus/__init__.py index 70f3e3f0..cd4d6691 100644 --- a/couchpotato/core/providers/nzb/nzbsrus/__init__.py +++ b/couchpotato/core/providers/nzb/nzbsrus/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'nzb_providers', 'name': 'nzbsrus', 'label': 'Nzbsrus', 'description': 'See NZBsRus', diff --git a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py index 2401f95a..ffa3934c 100644 --- a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py +++ b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py @@ -8,9 +8,10 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'torrent_providers', 'name': 'KickAssTorrents', 'description': 'See KickAssTorrents', + 'wizard': True, 'options': [ { 'name': 'enabled', diff --git a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py index 67bf6665..44d98cb7 100644 --- a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py +++ b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py @@ -5,32 +5,34 @@ def start(): config = [{ 'name': 'passthepopcorn', - 'groups': [{ - 'tab': 'searcher', - 'subtab': 'providers', - 'name': 'PassThePopcorn', - 'description': 'See PassThePopcorn.me', - 'options': [ - { - 'name': 'enabled', - 'type': 'enabler', - 'default': False - }, - { - 'name': 'domain', - 'advanced': True, - 'label': 'Proxy server', - 'description': 'Domain for requests (HTTPS only!), keep empty to use default (tls.passthepopcorn.me).', - }, - { - 'name': 'username', - 'default': '', - }, - { - 'name': 'password', - 'default': '', - 'type': 'password', - } - ], - }] + 'groups': [ + { + 'tab': 'searcher', + 'subtab': 'torrent_providers', + 'name': 'PassThePopcorn', + 'description': 'See PassThePopcorn.me', + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': False + }, + { + 'name': 'domain', + 'advanced': True, + 'label': 'Proxy server', + 'description': 'Domain for requests (HTTPS only!), keep empty to use default (tls.passthepopcorn.me).', + }, + { + 'name': 'username', + 'default': '', + }, + { + 'name': 'password', + 'default': '', + 'type': 'password', + } + ], +} + ] }] diff --git a/couchpotato/core/providers/torrent/publichd/__init__.py b/couchpotato/core/providers/torrent/publichd/__init__.py index 94d0825e..edffeba8 100644 --- a/couchpotato/core/providers/torrent/publichd/__init__.py +++ b/couchpotato/core/providers/torrent/publichd/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'torrent_providers', 'name': 'PublicHD', 'description': 'Public Torrent site with only HD content. See PublicHD', 'options': [ diff --git a/couchpotato/core/providers/torrent/sceneaccess/__init__.py b/couchpotato/core/providers/torrent/sceneaccess/__init__.py index 28b7ca33..e59f89b1 100644 --- a/couchpotato/core/providers/torrent/sceneaccess/__init__.py +++ b/couchpotato/core/providers/torrent/sceneaccess/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'torrent_providers', 'name': 'SceneAccess', 'description': 'See SceneAccess', 'options': [ diff --git a/couchpotato/core/providers/torrent/scenehd/__init__.py b/couchpotato/core/providers/torrent/scenehd/__init__.py index c9f18be8..a3d03130 100644 --- a/couchpotato/core/providers/torrent/scenehd/__init__.py +++ b/couchpotato/core/providers/torrent/scenehd/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'torrent_providers', 'name': 'SceneHD', 'description': 'See SceneHD', 'options': [ diff --git a/couchpotato/core/providers/torrent/thepiratebay/__init__.py b/couchpotato/core/providers/torrent/thepiratebay/__init__.py index 2f8872e4..9c56eb8f 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/__init__.py +++ b/couchpotato/core/providers/torrent/thepiratebay/__init__.py @@ -5,23 +5,26 @@ def start(): config = [{ 'name': 'thepiratebay', - 'groups': [{ - 'tab': 'searcher', - 'subtab': 'providers', - 'name': 'ThePirateBay', - 'description': 'The world\'s largest bittorrent tracker. See ThePirateBay', - 'options': [ - { - 'name': 'enabled', - 'type': 'enabler', - 'default': False - }, - { - 'name': 'domain', - 'advanced': True, - 'label': 'Proxy server', - 'description': 'Domain for requests, keep empty to let CouchPotato pick.', - } - ], - }] + 'groups': [ + { + 'tab': 'searcher', + 'subtab': 'torrent_providers', + 'name': 'ThePirateBay', + 'description': 'The world\'s largest bittorrent tracker. See ThePirateBay', + 'wizard': True, + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': False + }, + { + 'name': 'domain', + 'advanced': True, + 'label': 'Proxy server', + 'description': 'Domain for requests, keep empty to let CouchPotato pick.', + } + ], + } + ] }] diff --git a/couchpotato/core/providers/torrent/torrentleech/__init__.py b/couchpotato/core/providers/torrent/torrentleech/__init__.py index 482dfda7..b808a005 100644 --- a/couchpotato/core/providers/torrent/torrentleech/__init__.py +++ b/couchpotato/core/providers/torrent/torrentleech/__init__.py @@ -8,7 +8,7 @@ config = [{ 'groups': [ { 'tab': 'searcher', - 'subtab': 'providers', + 'subtab': 'torrent_providers', 'name': 'TorrentLeech', 'description': 'See TorrentLeech', 'options': [ diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py index c530a275..366a101c 100644 --- a/couchpotato/core/settings/__init__.py +++ b/couchpotato/core/settings/__init__.py @@ -204,7 +204,6 @@ class Settings(object): except: pass - #db.close() return prop def setProperty(self, identifier, value = ''): @@ -221,4 +220,3 @@ class Settings(object): p.value = toUnicode(value) db.commit() - #db.close() diff --git a/couchpotato/runner.py b/couchpotato/runner.py index 31a8ce32..569ff57e 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -170,18 +170,17 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En log.warning('%s %s %s line:%s', (category, message, filename, lineno)) warnings.showwarning = customwarn + # Check if database exists + db = Env.get('db_path') + db_exists = os.path.isfile(db_path) # Load configs & plugins loader = Env.get('loader') loader.preload(root = base_path) loader.run() - # Load migrations - initialize = True - db = Env.get('db_path') - if os.path.isfile(db_path): - initialize = False + if db_exists: from migrate.versioning.api import version_control, db_version, version, upgrade repo = os.path.join(base_path, 'couchpotato', 'core', 'migration') @@ -201,7 +200,8 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En from couchpotato.core.settings.model import setup setup() - if initialize: + # Fill database with needed stuff + if not db_exists: fireEvent('app.initialize', in_order = True) # Create app diff --git a/couchpotato/static/images/emptylist.png b/couchpotato/static/images/emptylist.png new file mode 100644 index 00000000..08db6537 Binary files /dev/null and b/couchpotato/static/images/emptylist.png differ diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 61ee45bd..a94f5d4e 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -281,6 +281,47 @@ var CouchPotato = new Class({ window.open(url); else window.location = url; + }, + + createUserscriptButtons: function(){ + + var userscript = false; + try { + if(Components.interfaces.gmIGreasemonkeyService) + userscript = true + } + catch(e){ + userscript = Browser.chrome === true; + } + + var host_url = window.location.protocol + '//' + window.location.host; + + return new Element('div.group_userscript').adopt( + (userscript ? [new Element('a.userscript.button', { + 'text': 'Install userscript', + 'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js', + 'target': '_self' + }), new Element('span.or[text=or]')] : null), + new Element('span.bookmarklet').adopt( + new Element('a.button.orange', { + 'text': '+CouchPotato', + 'href': "javascript:void((function(){var e=document.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','" + + host_url + Api.createUrl('userscript.bookmark') + + "?host="+ encodeURI(host_url + Api.createUrl('userscript.get')+randomString()+'/') + + "&r='+Math.random()*99999999);document.body.appendChild(e)})());", + 'target': '', + 'events': { + 'click': function(e){ + (e).stop() + alert('Drag it to your bookmark ;)') + } + } + }), + new Element('span', { + 'text': '⇽ Drag this to your bookmarks' + }) + ) + ); } }); diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 576ad44c..f4afc84d 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -211,7 +211,7 @@ Page.Settings = new Class({ if(self.tabs[tab_name] && self.tabs[tab_name].tab) return self.tabs[tab_name].tab - var label = (tab.label || tab.name || tab_name).capitalize() + var label = tab.label || (tab.name || tab_name).capitalize() var tab_el = new Element('li.t_'+tab_name).adopt( new Element('a', { 'href': App.createUrl(self.name+'/'+tab_name), @@ -244,7 +244,7 @@ Page.Settings = new Class({ if(!parent_tab.subtabs_el) parent_tab.subtabs_el = new Element('ul.subtabs').inject(parent_tab.tab); - var label = (tab.label || tab.name || tab_name).capitalize() + var label = tab.label || (tab.name || tab_name.replace('_', ' ')).capitalize() var tab_el = new Element('li.t_'+tab_name).adopt( new Element('a', { 'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name), @@ -274,7 +274,7 @@ Page.Settings = new Class({ 'class': (group.advanced ? 'inlineLabels advanced' : 'inlineLabels') + ' group_' + (group.name || '') + ' subtab_' + (group.subtab || '') }).adopt( new Element('h2', { - 'text': (group.label || group.name).capitalize() + 'text': group.label || (group.name).capitalize() }).adopt( new Element('span.hint', { 'html': group.description || '' @@ -635,7 +635,7 @@ Option.Directory = new Class({ ), self.dir_list = new Element('ul', { 'events': { - 'click:relay(li)': function(e, el){ + 'click:relay(li:not(.empty))': function(e, el){ (e).preventDefault(); self.selectDirectory(el.get('data-value')) }, @@ -701,12 +701,24 @@ Option.Directory = new Class({ fillBrowser: function(json){ var self = this; - var v = self.input.get('text'); + self.data = json; + + var v = self.getValue(); var previous_dir = self.getParentDir(); + if(v == '') + self.input.set('text', json.home); + if(previous_dir != v && previous_dir.length >= 1 && !json.is_root){ + + var prev_dirname = self.getCurrentDirname(previous_dir); + if(previous_dir == json.home) + prev_dirname = 'Home'; + else if (previous_dir == '/' && json.platform == 'nt') + prev_dirname = 'Computer'; + self.back_button.set('data-value', previous_dir) - self.back_button.set('html', '« '+self.getCurrentDirname(previous_dir)) + self.back_button.set('html', '« '+prev_dirname) self.back_button.show() } else { @@ -719,23 +731,24 @@ Option.Directory = new Class({ else self.cached[v] = json; - setTimeout(function(){ - self.dir_list.empty(); + self.dir_list.empty(); + if(json.dirs.length > 0) json.dirs.each(function(dir){ - if(dir.indexOf(v) != -1){ - new Element('li', { - 'data-value': dir, - 'text': self.getCurrentDirname(dir) - }).inject(self.dir_list) - } + new Element('li', { + 'data-value': dir, + 'text': self.getCurrentDirname(dir) + }).inject(self.dir_list) }); - }, 50); + else + new Element('li.empty', { + 'text': 'Selected folder is empty' + }).inject(self.dir_list) }, getDirs: function(){ var self = this; - var c = self.input.get('text'); + var c = self.getValue(); if(self.cached[c] && self.use_cache){ self.fillBrowser() @@ -754,7 +767,10 @@ Option.Directory = new Class({ getParentDir: function(dir){ var self = this; - var v = dir || self.input.get('text'); + if(!dir && self.data && self.data.parent) + return self.data.parent; + + var v = dir || self.getValue(); var sep = Api.getOption('path_sep'); var dirs = v.split(sep); if(dirs.pop() == '') @@ -941,7 +957,7 @@ Option.Choice = new Class({ mtches.append([value == matchsplit ? match : matchsplit]); }); }); - + if(mtches.length == 0 && value != '') mtches.include(value); diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 3ef10d7a..599e7814 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -10,16 +10,73 @@ Page.Wanted = new Class({ if(!self.wanted){ + self.manual_search = new Element('a', { + 'title': 'Force a search for the full wanted list', + 'text': 'Search all wanted', + 'events':{ + 'click': self.doFullSearch.bind(self, true) + } + }); + + // See if userscript can be installed + + // Wanted movies self.wanted = new MovieList({ 'identifier': 'wanted', 'status': 'active', 'actions': MovieActions, - 'add_new': true + 'add_new': true, + 'menu': [self.manual_search], + 'on_empty_element': App.createUserscriptButtons().setStyles({ + 'background-image': "url('"+Api.createUrl('static/images/emptylist.png')+"')", + 'height': 750, + 'width': 800, + 'padding-top': 260, + 'margin-top': -50 + }) }); $(self.wanted).inject(self.el); + + // Check if search is in progress + self.startProgressInterval(); } + }, + + doFullSearch: function(full){ + var self = this; + + if(!self.search_in_progress){ + + Api.request('searcher.full_search'); + self.startProgressInterval(); + + } + + }, + + startProgressInterval: function(){ + var self = this; + + var start_text = self.manual_search.get('text'); + self.progress_interval = setInterval(function(){ + Api.request('searcher.progress', { + 'onComplete': function(json){ + self.search_in_progress = true; + if(!json.progress){ + clearInterval(self.progress_interval); + self.search_in_progress = false; + self.manual_search.set('text', start_text); + } + else { + var progress = json.progress; + self.manual_search.set('text', 'Searching.. (' + (((progress.total-progress.to_go)/progress.total)*100).round() + '%)'); + } + } + }) + }, 1000); + } }); @@ -222,7 +279,7 @@ window.addEvent('domready', function(){ movie.set('tween', { 'duration': 300, 'onComplete': function(){ - movie.destroy(); + self.movie.destroy() } }); movie.tween('height', 0); diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 183374b9..e187593e 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -376,22 +376,12 @@ body > .spinner, .mask{ font-weight: bold; } - .select .list:before { - content: ' '; - height: 0; - position: absolute; - width: 0; - border: 6px solid transparent; - border-bottom-color: #282d34; - margin: -11px 0 0 70px; - } - .select .list { display: none; background: #282d34; border: 1px solid #1f242b; position: absolute; - margin: 30px 0 0 0; + margin: 28px 0 0 0; box-shadow: 0 20px 20px -10px rgba(0,0,0,0.4); border-radius:3px; z-index: 3; diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/page/settings.css index 686c3b21..f17e6183 100644 --- a/couchpotato/static/style/page/settings.css +++ b/couchpotato/static/style/page/settings.css @@ -235,6 +235,19 @@ .page .directory_list li:hover { background-color: #515c68; } + + .page .directory_list li.empty { + background: none; + height: 100px; + text-align: center; + font-style: italic; + border: none; + line-height: 100px; + cursor: default; + color: #BBB; + text-shadow: none; + font-size: 12px; + } .page .directory_list .actions { clear: both; @@ -319,12 +332,11 @@ .page .tag_input > ul { list-style: none; - padding: 3px 0; border-radius: 3px; cursor: text; width: 30%; margin: 0 !important; - height: 27px; + min-height: 27px; line-height: 0; display: inline-block; } @@ -339,7 +351,7 @@ min-width: 2px; font-size: 12px; padding: 0; - margin: 0 !important; + margin: 4px 0 0 !important; border-width: 0; background: 0; line-height: 20px; @@ -400,7 +412,7 @@ margin: -9px 0 0 -16px; border-radius: 30px 30px 0 0; cursor: pointer; - background: url('../../images/icon.delete.png') no-repeat center 2px, -webkit-linear-gradient( + background: url('../../images/icon.delete.png') no-repeat center 2px, linear-gradient( 270deg, #5b9bd1 0%, #5b9bd1 100%