From b350f1e798540dc67c0bf7ef077dc6ff8a206136 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 22 May 2012 00:14:09 +0200 Subject: [PATCH 01/47] OnComplete handler for events and async re-use --- couchpotato/core/event.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index 82f81e21..e8fe5b9b 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -1,6 +1,7 @@ from axl.axel import Event from couchpotato.core.helpers.variable import mergeDicts, natcmp from couchpotato.core.logger import CPLog +import thread import threading import traceback @@ -53,6 +54,13 @@ def fireEvent(name, *args, **kwargs): is_after_event = True except: pass + # onComplete event + on_complete = False + try: + on_complete = kwargs['on_complete'] + del kwargs['on_complete'] + except: pass + # Return single handler single = False try: @@ -129,24 +137,23 @@ def fireEvent(name, *args, **kwargs): if not is_after_event: fireEvent('%s.after' % name, is_after_event = True) + if on_complete: + on_complete() + return results except KeyError, e: pass except Exception: log.error('%s: %s' % (name, traceback.format_exc())) -def fireEventAsync(name, *args, **kwargs): - #log.debug('Async "%s": %s, %s' % (name, args, kwargs)) +def fireEventAsync(*args, **kwargs): try: - e = events[name] - e.lock.acquire() - e.asynchronous = True - e.error_handler = errorHandler - e(*args, **kwargs) - e.lock.release() + my_thread = threading.Thread(target = fireEvent, args = args, kwargs = kwargs) + my_thread.setDaemon(True) + my_thread.start() return True except Exception, e: - log.error('%s: %s' % (name, e)) + log.error('%s: %s' % (args[0], e)) def errorHandler(error): etype, value, tb = error From 06db2d98502642253e25fe39f2e16515970c96dd Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 22 May 2012 00:14:46 +0200 Subject: [PATCH 02/47] Single movie UI update --- couchpotato/core/notifications/core/main.py | 9 +- .../notifications/core/static/notification.js | 2 +- couchpotato/core/plugins/library/main.py | 2 +- couchpotato/core/plugins/movie/main.py | 12 +- .../core/plugins/movie/static/movie.js | 139 +++++++++++++----- couchpotato/core/plugins/searcher/main.py | 14 +- couchpotato/static/scripts/page/wanted.js | 2 +- 7 files changed, 124 insertions(+), 56 deletions(-) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 10a1401d..5419049e 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/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 from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.request import jsonified, getParam from couchpotato.core.helpers.variable import tryInt @@ -48,13 +48,6 @@ class CoreNotifier(Notification): addApiView('notification.listener', self.listener) - self.registerEvents() - - def registerEvents(self): - - # Library update, frontend refresh - addEvent('library.update_finish', lambda data: fireEvent('notify.frontend', type = 'library.update', data = data)) - def markAsRead(self): ids = [x.strip() for x in getParam('ids').split(',')] diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 371b95ed..e35a394c 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -88,7 +88,7 @@ var NotificationBase = new Class({ self.request = Api.request('notification.listener', { 'initialDelay': 100, - 'delay': 3000, + 'delay': 1500, 'data': {'init':true}, 'onSuccess': self.processData.bind(self) }) diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index 347d8580..0e4fc8cc 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -127,7 +127,7 @@ class LibraryPlugin(Plugin): library_dict = library.to_dict(self.default_dict) - fireEvent('library.update_finish', data = library_dict) + fireEvent('notify.frontend', type = 'library.update.%s' % identifier, data = library_dict) #db.close() return library_dict diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 2c5ec731..60aa7adc 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -247,8 +247,16 @@ class MoviePlugin(Plugin): if title.default: default_title = title.title if movie: - fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True) - fireEventAsync('searcher.single', movie.to_dict(self.default_dict)) + def notifyFront(): + movie = db.query(Movie).filter_by(id = id).first() + fireEvent('notify.frontend', type = 'movie.update.%s' % movie.id, data = movie.to_dict(self.default_dict)) + + def afterUpdate(): + movie = db.query(Movie).filter_by(id = id).first() + fireEventAsync('searcher.single', movie.to_dict(self.default_dict), on_complete = notifyFront) + + fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = afterUpdate) + #db.close() return jsonified({ diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 39b12eed..9b8fa22b 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -11,53 +11,114 @@ var Movie = new Class({ self.view = options.view || 'thumbs'; self.list = list; + self.el = new Element('div.movie.inlay'); + self.profile = Quality.getProfile(data.profile_id) || {}; self.parent(self, options); + + App.addEvent('movie.update.'+data.id, self.update.bind(self)); + App.addEvent('searcher.started.'+data.id, self.searching.bind(self)); + App.addEvent('searcher.ended.'+data.id, self.searching.bind(self)); + }, + + searching: function(notification){ + var self = this; + + if(notification && notification.type.indexOf('ended') > -1){ + if(self.spinner){ + self.mask.fade('out'); + setTimeout(function(){ + self.mask.destroy(); + self.spinner.el.destroy(); + self.spinner = null; + self.mask = null; + }, 400); + } + } + else if(!self.spinner) { + self.createMask(); + self.spinner = createSpinner(self.mask); + self.positionMask(); + self.mask.fade('in'); + } + }, + + createMask: function(){ + var self = this; + self.mask = new Element('div.mask', { + 'styles': { + 'z-index': '1' + } + }).inject(self.el, 'top').fade('hide'); + self.positionMask(); + }, + + 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; + + self.data = notification.data; + self.container.destroy(); + self.profile = Quality.getProfile(self.data.profile_id) || {}; + self.create(); }, create: function(){ var self = this; - self.el = new Element('div.movie.inlay').adopt( - self.select_checkbox = new Element('input[type=checkbox].inlay', { - 'events': { - 'change': function(){ - self.fireEvent('select') - } - } - }), - self.thumbnail = File.Select.single('poster', self.data.library.files), - self.data_container = new Element('div.data.inlay.light', { - 'tween': { - duration: 400, - transition: 'quint:in:out', - onComplete: self.fireEvent.bind(self, 'slideEnd') - } - }).adopt( - self.info_container = new Element('div.info').adopt( - self.title = new Element('div.title', { - 'text': self.getTitle() || 'n/a' - }), - self.year = new Element('div.year', { - 'text': self.data.library.year || 'n/a' - }), - self.rating = new Element('div.rating.icon', { - 'text': self.data.library.rating - }), - self.description = new Element('div.description', { - 'text': self.data.library.plot - }), - self.quality = new Element('div.quality', { - 'events': { - 'click': function(e){ - var releases = self.el.getElement('.actions .releases'); - if(releases) - releases.fireEvent('click', [e]) - } + self.el.adopt( + self.container = new Element('div').adopt( + self.select_checkbox = new Element('input[type=checkbox].inlay', { + 'events': { + 'change': function(){ + self.fireEvent('select') } - }) - ), - self.actions = new Element('div.actions') + } + }), + self.thumbnail = File.Select.single('poster', self.data.library.files), + self.data_container = new Element('div.data.inlay.light', { + 'tween': { + duration: 400, + transition: 'quint:in:out', + onComplete: self.fireEvent.bind(self, 'slideEnd') + } + }).adopt( + self.info_container = new Element('div.info').adopt( + self.title = new Element('div.title', { + 'text': self.getTitle() || 'n/a' + }), + self.year = new Element('div.year', { + 'text': self.data.library.year || 'n/a' + }), + self.rating = new Element('div.rating.icon', { + 'text': self.data.library.rating + }), + self.description = new Element('div.description', { + 'text': self.data.library.plot + }), + self.quality = new Element('div.quality', { + 'events': { + 'click': function(e){ + var releases = self.el.getElement('.actions .releases'); + if(releases) + releases.fireEvent('click', [e]) + } + } + }) + ), + self.actions = new Element('div.actions') + ) ) ); diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 03d9261f..93eb55ed 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -83,6 +83,9 @@ class Searcher(Plugin): if not default_title: return + fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True) + + ret = False for quality_type in movie['profile']['types']: if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases): log.info('To early to search for %s, %s' % (quality_type['quality']['identifier'], default_title)) @@ -107,7 +110,7 @@ class Searcher(Plugin): # Check if movie isn't deleted while searching if not db.query(Movie).filter_by(id = movie.get('id')).first(): - return + break # Add them to this movie releases list for nzb in sorted_results: @@ -144,7 +147,8 @@ class Searcher(Plugin): for nzb in sorted_results: downloaded = self.download(data = nzb, movie = movie) if downloaded is True: - return True + ret = True + break elif downloaded != 'try_next': break else: @@ -153,11 +157,13 @@ class Searcher(Plugin): break # Break if CP wants to shut down - if self.shuttingDown(): + if self.shuttingDown() or ret: break + fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True) + #db.close() - return False + return ret def download(self, data, movie, manual = False): diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 1f811c69..f8858e72 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -17,7 +17,6 @@ Page.Wanted = new Class({ 'actions': MovieActions }); $(self.wanted).inject(self.el); - App.addEvent('library.update', self.wanted.update.bind(self.wanted)); } } @@ -131,6 +130,7 @@ window.addEvent('domready', function(){ var self = this; (e).preventDefault(); + self.movie.searching(); Api.request('movie.refresh', { 'data': { 'id': self.movie.get('id') From f874e9c4e3e7a74ee96de2622eb966e807312b8a Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 21 May 2012 10:59:05 +0200 Subject: [PATCH 03/47] Always return application/json --- couchpotato/core/helpers/request.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 7ff4bb29..16a2f73d 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -70,9 +70,8 @@ def jsonify(mimetype, *args, **kwargs): return getattr(current_app, 'response_class')(content, mimetype = mimetype) def jsonified(*args, **kwargs): - from couchpotato.environment import Env callback = getParam('callback_func', None) if callback: return padded_jsonify(callback, *args, **kwargs) else: - return jsonify('text/javascript' if Env.doDebug() else 'application/json', *args, **kwargs) + return jsonify('application/json', *args, **kwargs) From 30fa8a7531b3d2ad3d7198c27653928aa4407d10 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 22 May 2012 20:59:26 +0200 Subject: [PATCH 04/47] Cleanup --- couchpotato/core/event.py | 1 - 1 file changed, 1 deletion(-) diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index e8fe5b9b..b7bed808 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -1,7 +1,6 @@ from axl.axel import Event from couchpotato.core.helpers.variable import mergeDicts, natcmp from couchpotato.core.logger import CPLog -import thread import threading import traceback From 4774deaa80d86c20ce7beb133ccf51fc7d04dbdc Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 22 May 2012 21:28:20 +0200 Subject: [PATCH 05/47] Don't hide options on slidein --- couchpotato/core/plugins/movie/static/movie.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 9b8fa22b..57b58dc7 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -78,7 +78,7 @@ var Movie = new Class({ var self = this; self.el.adopt( - self.container = new Element('div').adopt( + self.container = new Element('div.movie_container').adopt( self.select_checkbox = new Element('input[type=checkbox].inlay', { 'events': { 'change': function(){ @@ -211,7 +211,7 @@ var Movie = new Class({ self.el.removeEvents('outerClick') self.addEvent('slideEnd:once', function(){ - self.el.getElements('> :not(.data):not(.poster)').hide(); + self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide(); }); self.data_container.tween('right', -840, 0); From d362827a8badcfa4e8a1cf6fc88b593ceac874b8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 22 May 2012 21:52:06 +0200 Subject: [PATCH 06/47] Update on edit --- couchpotato/core/plugins/movie/main.py | 31 +++++++++++++++-------- couchpotato/static/scripts/page/wanted.js | 8 +++++- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 60aa7adc..de307799 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -247,15 +247,7 @@ class MoviePlugin(Plugin): if title.default: default_title = title.title if movie: - def notifyFront(): - movie = db.query(Movie).filter_by(id = id).first() - fireEvent('notify.frontend', type = 'movie.update.%s' % movie.id, data = movie.to_dict(self.default_dict)) - - def afterUpdate(): - movie = db.query(Movie).filter_by(id = id).first() - fireEventAsync('searcher.single', movie.to_dict(self.default_dict), on_complete = notifyFront) - - fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = afterUpdate) + fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id)) #db.close() @@ -377,7 +369,7 @@ class MoviePlugin(Plugin): fireEvent('movie.restatus', m.id) movie_dict = m.to_dict(self.default_dict) - fireEventAsync('searcher.single', movie_dict) + fireEventAsync('searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id)) #db.close() return jsonified({ @@ -466,3 +458,22 @@ class MoviePlugin(Plugin): #db.close() return True + + def createOnComplete(self, movie_id): + + def onComplete(): + db = get_session() + movie = db.query(Movie).filter_by(id = movie_id).first() + fireEventAsync('searcher.single', movie.to_dict(self.default_dict), on_complete = self.createNotifyFront(movie_id)) + + return onComplete + + + def createNotifyFront(self, movie_id): + + def notifyFront(): + db = get_session() + movie = db.query(Movie).filter_by(id = movie_id).first() + fireEvent('notify.frontend', type = 'movie.update.%s' % movie.id, data = movie.to_dict(self.default_dict)) + + return notifyFront diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index f8858e72..9566058e 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -72,14 +72,20 @@ window.addEvent('domready', function(){ new Element('option', { 'text': alt.title }).inject(self.title_select); + + if(alt['default']) + self.title_select.set('value', alt.title); }); + Quality.getActiveProfiles().each(function(profile){ new Element('option', { 'value': profile.id ? profile.id : profile.data.id, 'text': profile.label ? profile.label : profile.data.label }).inject(self.profile_select); - self.profile_select.set('value', (self.movie.profile || {})['id']); + + if(self.movie.profile) + self.profile_select.set('value', self.movie.profile.data.id); }); } From a1ac73a9a1be603e3ef58864a46ab5f4ab7ec6dd Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 22 May 2012 22:07:35 +0200 Subject: [PATCH 07/47] Add m4v extension. fix #332 --- couchpotato/core/plugins/scanner/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index e287fd33..bbf25239 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -27,7 +27,7 @@ class Scanner(Plugin): ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #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'], + 'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'], 'movie_extra': ['mds'], 'dvd': ['vts_*', 'vob'], 'nfo': ['nfo', 'txt', 'tag'], From 75b734db7230fe8203b95fe6cab09fef47febfe7 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 22 May 2012 23:12:24 +0200 Subject: [PATCH 08/47] No error when trailers aren't found. fix #327 --- couchpotato/core/plugins/trailer/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/trailer/main.py b/couchpotato/core/plugins/trailer/main.py index 8f8e4ab2..7c6d5d3f 100644 --- a/couchpotato/core/plugins/trailer/main.py +++ b/couchpotato/core/plugins/trailer/main.py @@ -1,5 +1,5 @@ from couchpotato.core.event import addEvent, fireEvent -from couchpotato.core.helpers.variable import getExt +from couchpotato.core.helpers.variable import getExt, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin import os @@ -17,6 +17,9 @@ class Trailer(Plugin): if self.isDisabled() or len(group['files']['trailer']) > 0: return trailers = fireEvent('trailer.search', group = group, merge = True) + if not trailers or trailers == []: + log.info('No trailers found for: %s' % getTitle(group['library'])) + return for trailer in trailers.get(self.conf('quality'), []): destination = '%s-trailer.%s' % (self.getRootName(group), getExt(trailer)) From 11958334ac01ffac6aba1bc2896a362483edc94f Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 23 May 2012 23:00:27 +0200 Subject: [PATCH 09/47] Add new movie to top of wanted list. closes #301 --- couchpotato/core/plugins/library/main.py | 4 +- couchpotato/core/plugins/movie/main.py | 21 ++++++-- couchpotato/core/plugins/movie/static/list.js | 52 +++++++++++++------ .../core/plugins/movie/static/movie.js | 6 ++- couchpotato/static/scripts/page/wanted.js | 3 +- 5 files changed, 59 insertions(+), 27 deletions(-) diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index 0e4fc8cc..a7960d7c 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -138,8 +138,8 @@ class LibraryPlugin(Plugin): library = db.query(Library).filter_by(identifier = identifier).first() if not library.info: - self.update(identifier) - dates = library.get('info', {}).get('release_dates') + library_dict = self.update(identifier) + dates = library_dict.get('info', {}).get('release_dates') else: dates = library.info.get('release_date') diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index de307799..70ee39a5 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -287,7 +287,7 @@ class MoviePlugin(Plugin): db = get_session() m = db.query(Movie).filter_by(library_id = library.get('id')).first() - do_search = False + added = True if not m: m = Movie( library_id = library.get('id'), @@ -295,8 +295,13 @@ class MoviePlugin(Plugin): status_id = status_active.get('id'), ) db.add(m) - fireEvent('library.update', params.get('identifier'), default_title = params.get('title', '')) - do_search = True + db.commit() + + onComplete = None + if search_after: + onComplete = self.createOnComplete(m.id) + + fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete) elif force_readd: # Clean snatched history for release in m.releases: @@ -306,9 +311,11 @@ class MoviePlugin(Plugin): m.profile_id = params.get('profile_id', default_profile.get('id')) else: log.debug('Movie already exists, not updating: %s' % params) + added = False if force_readd: m.status_id = status_active.get('id') + do_search = True db.commit() @@ -321,8 +328,12 @@ class MoviePlugin(Plugin): movie_dict = m.to_dict(self.default_dict) - if (force_readd or do_search) and search_after: - fireEventAsync('searcher.single', movie_dict) + if do_search and search_after: + onComplete = self.createOnComplete(m.id) + onComplete() + + if added: + fireEvent('notify.frontend', type = 'movie.added', data = movie_dict) #db.close() return movie_dict diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index 23bdcf89..e8266acb 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -5,10 +5,12 @@ var MovieList = new Class({ options: { navigation: true, limit: 50, - menu: [] + menu: [], + add_new: false }, movies: [], + movies_added: [], letters: {}, filter: { 'startswith': null, @@ -30,6 +32,17 @@ var MovieList = new Class({ }) ); self.getMovies(); + + if(options.add_new) + App.addEvent('movie.added', self.movieAdded.bind(self)) + }, + + movieAdded: function(notification){ + var self = this; + window.scroll(0,0); + + if(self.movies_added[notification.data.id]) + self.createMovie(notification.data, 'top'); }, create: function(){ @@ -71,26 +84,31 @@ var MovieList = new Class({ } Object.each(movies, function(movie){ - - // Attach proper actions - var a = self.options.actions, - status = Status.get(movie.status_id); - var actions = a[status.identifier.capitalize()] || a.Wanted || {}; - - var m = new Movie(self, { - 'actions': actions, - 'view': self.current_view, - 'onSelect': self.calculateSelected.bind(self) - }, movie); - $(m).inject(self.movie_list); - m.fireEvent('injected'); - - self.movies.include(m) - + self.createMovie(movie); }); }, + createMovie: function(movie, inject_at){ + var self = this; + + // Attach proper actions + var a = self.options.actions, + status = Status.get(movie.status_id); + var actions = a[status.identifier.capitalize()] || a.Wanted || {}; + + var m = new Movie(self, { + 'actions': actions, + 'view': self.current_view, + 'onSelect': self.calculateSelected.bind(self) + }, movie); + $(m).inject(self.movie_list, inject_at || 'bottom'); + m.fireEvent('injected'); + + self.movies.include(m) + self.movies_added.include(movie.id); + }, + createNavigation: function(){ var self = this; var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'; diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 57b58dc7..c93a98ea 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -28,8 +28,10 @@ var Movie = new Class({ if(self.spinner){ self.mask.fade('out'); setTimeout(function(){ - self.mask.destroy(); - self.spinner.el.destroy(); + if(self.mask) + self.mask.destroy(); + if(self.spinner) + self.spinner.el.destroy(); self.spinner = null; self.mask = null; }, 400); diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 9566058e..2ec49e2b 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -14,7 +14,8 @@ Page.Wanted = new Class({ self.wanted = new MovieList({ 'identifier': 'wanted', 'status': 'active', - 'actions': MovieActions + 'actions': MovieActions, + 'add_new': true }); $(self.wanted).inject(self.el); } From 556752fd3f4068b2764cc219478f9e404ef624f3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 24 May 2012 22:58:09 +0200 Subject: [PATCH 10/47] Original filename --- couchpotato/core/plugins/renamer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py index d7e78511..27566d17 100644 --- a/couchpotato/core/plugins/renamer/__init__.py +++ b/couchpotato/core/plugins/renamer/__init__.py @@ -17,7 +17,7 @@ rename_options = { 'audio': 'Audio (DTS)', 'group': 'Releasegroup name', 'source': 'Source media (Bluray)', - 'filename': 'Original filename', + 'original': 'Original filename', 'original_folder': 'Original foldername', 'imdb_id': 'IMDB id (tt0123456)', }, From 7604a36cd79acc0e670e398dc96d029908a9732d Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 24 May 2012 23:48:39 +0200 Subject: [PATCH 11/47] Explain imdb watchlist. fix #342 --- couchpotato/core/providers/automation/imdb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/automation/imdb/__init__.py b/couchpotato/core/providers/automation/imdb/__init__.py index 338a8b62..142ebab4 100644 --- a/couchpotato/core/providers/automation/imdb/__init__.py +++ b/couchpotato/core/providers/automation/imdb/__init__.py @@ -10,7 +10,7 @@ config = [{ 'tab': 'automation', 'name': 'imdb_automation', 'label': 'IMDB', - 'description': 'From any public IMDB watchlists', + 'description': 'From any public IMDB watchlists. Url should end with export?list_id=XXXXX&author_id=XXXXX', 'options': [ { 'name': 'automation_enabled', From 91bae8c1b5a1f998160663e9394bbe9bb9455f46 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 16:11:54 +0200 Subject: [PATCH 12/47] Don't search twice. --- couchpotato/core/plugins/movie/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 70ee39a5..f8415d1e 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -302,6 +302,7 @@ class MoviePlugin(Plugin): onComplete = self.createOnComplete(m.id) fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete) + do_search = False elif force_readd: # Clean snatched history for release in m.releases: From 84eecaac6180bbc98b933e83e1ba69dad3365954 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 17:21:40 +0200 Subject: [PATCH 13/47] Movies didn't add --- couchpotato/core/plugins/movie/static/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index e8266acb..800f1711 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -41,7 +41,7 @@ var MovieList = new Class({ var self = this; window.scroll(0,0); - if(self.movies_added[notification.data.id]) + if(!self.movies_added[notification.data.id]) self.createMovie(notification.data, 'top'); }, From bc47b5fad559831e28770dc71d71e9d0e69b6db7 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 20:18:03 +0200 Subject: [PATCH 14/47] IMDB automation crashed on second watchlist. fix #322 --- .../core/providers/automation/imdb/main.py | 9 +++++++++ couchpotato/static/images/imdb_watchlist.png | Bin 0 -> 32721 bytes couchpotato/static/style/page/settings.css | 7 ++++++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 couchpotato/static/images/imdb_watchlist.png diff --git a/couchpotato/core/providers/automation/imdb/main.py b/couchpotato/core/providers/automation/imdb/main.py index 02fde26b..248db2d2 100644 --- a/couchpotato/core/providers/automation/imdb/main.py +++ b/couchpotato/core/providers/automation/imdb/main.py @@ -23,7 +23,14 @@ class IMDB(Automation): movies = [] headers = {} + enablers = self.conf('automation_urls_use').split(',') + + index = -1 for csv_url in self.conf('automation_urls').split(','): + index += 1 + if not enablers[index]: + continue + prop_name = 'automation.imdb.last_update.%s' % md5(csv_url) last_update = float(Env.prop(prop_name, default = 0)) @@ -36,6 +43,8 @@ class IMDB(Automation): for column in csv_reader.next(): headers[column] = nr nr += 1 + else: + csv_reader.next() for row in csv_reader: created = int(time.mktime(parse(row[headers['created']]).timetuple())) diff --git a/couchpotato/static/images/imdb_watchlist.png b/couchpotato/static/images/imdb_watchlist.png new file mode 100644 index 0000000000000000000000000000000000000000..b16fd0940abf5d140eba24c8c0d8e5b7d432e488 GIT binary patch literal 32721 zcmaI7V_;-W*Df5}b~4d)Y))+3?%1|%Yhv5BolNXWGO_JU{N;Y|p6|zbPyg6mRlQWJ zu3cKIBNXJs5#jLQKtMncB_%|ZKtMp1zshScP+w2{p#0gdFI;C)4QFLLQ)f4zqX~$h zv7Mm_iKGqC%tXlqXzbxIZo&fs0#0tBqT#F|E5mJMXG0JChlbwW#{P>N1cZm*-5zLU zW#UX?Xkunz%S(FI*-J`dVa!Xa&L+z!YcFhKZXx06Xrk;Xr()!3WyEDn%Fjo_Mt5ol=V;>=6>W$C}WU}OJ3Vr`xNej!ngE^c998V>tpDpp1#>%RJ128H zdlF$~HhLx!S)h@H?LRI5;*gc)mb7(p2HF~#NQ&^1e#xM>urTIk6JlXuVPX;C5*6ZP zViIK%5@ZtQV&h_AWM*U(WMmioFRzH5k&BIqt@D3*jsFj?=>N+54;XChzch=OI9j-x z7>haD*^vCJ)7%#SyDdWhtGxg48vpOM2>-9V3}41D{4=!wzoGtb(-(gJDgF;|zbgL& zeG}U+ygPmY`(3#269mLtSW-k##eMBu7g9h)6Z>P65kWX8Z#5u$do5zM{&CH!)vBug zP^&kaW@N3}iq@6Jpyp3xKm-_6{(T=p*Jn>!ItMJE?D)q^S~~XAAFr*>E#9LIuNm<< zRzy;)K-r2`O}yX67S?JfZ>+7jowdH3!OvI+KT_PTHv6ZhE}}MQ*w`?ssHhaEQzYc% zmaVB%B6&xw3&IPqA`AMA0Pqq8;iPa3Xc57GXlZFllEyU*OxU4B(!~mGIlqV)NYf%o zrSS49pcGIgL{a-yFckR)qm9@VMI;-+u_^^H6h%-)A(a1lV$4lqizeG?rUjH16_LA= z=l&MSEeHIIA4-swi76o)&BVk6vRwzMT!s2Y{@=ppX%PHJhKK7(PS4M2155u+E*8Rc zvbVRlv%TGCSx^w3h>8lG_oW~&ft#BH&H% zuC<|eQGwWh2ynHijCR*oSHn}j{hOnF0D+rHtDMu4;j7M{*VXBRFaEySufo`dk%AT> zS)QBrNJqef6^h}aB?(7f*|XJQOu476r4`AL0?4J~_fG<#LXVt>`h~Y_<@H&7V@Es% z%6-1+Q>X)fG7Nkc<8!;vPe9qR(T}$^+=207R0o%+60msXPtMP0z zp;04di22=Vg~MwrH*oc5xLmk+=MOEzd6J%i_spjN$Nk8F@AJH3&PEZ>rRl_-O~&(X zUecFiO4MH!6yj5H*=J3`1b zd(oNf*Z&?{g;^gVuT|G|racIkzX8*lpcVYBVb2gMO7Bd(3(K8rwRQmO8&T<%e%oeN z=@t~BJQ2Kx{*Ox+$XF3ecQ4D7*uwhr4L+4MI>jRn3-R+!6{k1gJDrhemPHDO7Z1xc zIdRR@h(>j}IUGfx_D+2$K$(9u;mx5S@_KYLd>}qnw}mfoplNJre^_G98c9I5X*y)w zq*Hvq?h^1wZiE|7XGxG~ZWY}hQ~^tgt2dB=K_jDL#o0o8kd|_3ns1~*-qs0(xJUJy z^fnGDBFDbZJrIo)zD>U-Ds_e0zVA^0jPpjZSA~AOM-FD_;Jz|La1bw)X|a5l7DRD9 zp>DDDimV$`nO#-`s5O&!Jl2A!7s~6`edtgNPbM9~6yNn1mSL(~e_n9ggqoy1?U zZ_J572X0q80cd_gah2l%J9@BDpgpBLq=yyT#Cm{@cRbWzY{cR5Aq+E&fstTO*_r#O z8-Ruv6ntlG|ULZL#A3ZugnzSa?2Fo~TjLdgc+jH_QU(!&~+ z(}j-M7grg(JnpjZY0QM6MhAwF$3(&#J=AQ)8ioLUismo%;WI>kp_Pj>wN(%@!=SyP zHsgR-8kn-4!;!OR*XtG8r50sAI-kL>@K5_z30Q=5T^wf>4BPYGC^H~cnF1+4HMWm` zR1wd^8g?jTyF)+E)=Xte22Nn>6)D#fN--ju4G;+59)wRoc)`zgF$RtA@O-WxE*n2E zz>LY-e7GbZ6Wte->7X@5Lj_sGPO+mq4B1QnI5L`JR%f}|8&3f+Q0|8M zIL?1SosYmc*Jd5KCTjn^l|nOi(=`gI)BS*IvH^bMe}2YXidxYarpvhN=YYd;z%y;QPBSm^Vvq_Ud^L%oVam&q*AHT`OFS7tTtPIrbjFm%Y1r=I2Iu5y7|0maWB zp@+R19rS`pd}Q0nf-6=Vdt98Z1_dRbN(pj+6Bj@tft%nBQ`R4B^iLD<&&0vD24;?* zDczB=)fG5gC%l_sxq@h~WLOi)_a#IznP*woj9KM_BiBR@V9(Yfj8l3-eUr@&ywMkl z3x6Fd!EiV`F~sD(At1Au9E_Q8P9r*&M&6KoP{E@TMO2F}cBt)pC;m7NiQv{!cmWG z$@1b{y{94>wRdj}$<6t>*(9`Nbf{;uP?A>M#7~8aOx#!oX}^g&(6I$JKK@?UaiZ56 zNLSTQvt7r`_N%`Gf!gCrgrGcLWj|QB!%JSf2(VQ96TL{Sz%+vwKX`s)4sfn{AD=aZ z<49cRAg%pTS3=WI+^P>ZO4j>0P!JROmO7^afJ&5=SKK9; zs|pIVYD!h&mCa-a>l6~eFi%r5YrQRrMP;GtyN~?@&(2Q=NrA(6)#HlxsGI@HZss!OpA0ciFx!9_@I2==o0fa|&EK6xA&#VsdNjYTK~A+|f~ZlL(?WQIi< zHwoOZE;%JPQvV%?=7p39Z(2;e`=OUn;*qU=15Ds&L!x^UEhpUx(k|K<7HW7wv;4QW zemNVnnKni}Iw1rj_1{R}{X`k3kUSxa7Y6W8KpR;3*1ovV7mx8a&&o=RA$x}Ik+9QLd4w1i&hQNCUS zZW|tqf9)9d(MXcJi))ay8bk?I?w&-Ma)b$1JJQFKGFo_S7?X0HX8?^Wg$n-JcHZ2s=TsFX9ah|s(Hqo^jX z==#q6h7$~S$LwbNO$Cv0>{i|IeZ~2nDeE90*)b~=>dy#Ss|#lhI^DS?TYDV15?+cz%b#y5wCU);pW-{L}Xh?rx{>(DS@qK5gLY)5PRgQM^*Dx)F8YLBFTd=aS)Ff!5{@XW55!EIeCj?F9!sL!bHtJ%e! zbjJ?U`z4NDC!UBcrG<1x9zkat@^T(W9X7$+~7tEF(JG;~!KIF0CAHD)h z-cNN`wH-WZi8Vd9N@J=RfF)6gKU@6=>#neCF*rz8MuYJEQ9G~5>S{k`GR^-?E{ti; zwY5y(3KF*k6utE33dBA|t)6i@1S{u%*iR|rdPJ%S%iW$^R~=Yucht{gA>%6NF3J|x zcW#>|u&Ir%9Q*frO*y-lR%cAGS<%C3!wpv_&gaPI-Gmc|5-+j%0QS__-D*|#EZ{N5 z7gfk8F-?{|Ins0He7K+UT^2ah)PfxGCG|fSCcC{|KQb&#ebv%ma3wRb{L8MnQfxPg zV-r=DMQBH8t^e!OtgfA zG6#Guj(Dt{eWwa4t4~i46mzYH!qX5;3AdM_y%&R=u8o9`Gz*$cPX6)zmlSmNtG|BMxgdhrUpw9QC zgbUUF8R*OLN?{Y=bl}r31{s3XWZ{LQSnj0yR6@~Qn zi5*f9L##X7!hK4zOh;ru0u>R}LQRpXasL)LKPt@|6C4b&y8feYf|_LoR8jwAtxDoMzQqzAAi+KZ-ws3(FD4!ulTE+^M6pb9Udv`q z;I82QvIGm(yioWwc}Eq)q$o29#vpomLBr*$sNXRocVSXTkjW(Rc(G+;^aWIV4C6YyHJuAeEV9DzH!fS| zOD?zkn$^;Z#jkIGs6%h571_iP|A{7fpciJ#&J1BKO8;Uw`FGHR36=S`8m)HrSi&D; zYOXonv7AkxJg;c(9AzbUB#R%oOhxN%FlF)P@3DDJudE<2pa6vqg931c7Ot%X0Tpap}rYtHbi+)4DdF3F8(T`0pmg4+*fFb?q{xmWMDhnYsiX4 zfQ~j&0CCEEYRU_k@3#_N<_qOjJr<`vb?XN(i}@y^SG2JzoZJ%|UpqM8i#f;`JT{=; zQq3T!c@X7+{n{s-9Y!d>d$<9f$(%!p}bt6Io0)(rp&3oc;V$4qEnvlSMQt zT2!(`fFLK}zRmYO>Rk05lX?~f(B#@q#8 zQO1K}xomDuhc^V)3uSRO{Er4%mXc~lM|){qF7tXkAC8< z1ExwZ*+Ne!x_IM?;oZK`j<3E?ITLZOHJ87YHcpDoB;nBA{Y3GZVr2nUg;3TrG%~um zJ&<q!`;d9I1hG zbgB=N&g6HX(zsbVyIxL}8%jQn@NyW59WhXrhb36EX5;RiDPy}zNsg(QvoGFOJ9X=2(OJ2v zGk9fmwAU?Vy*0wRFu5p&iz+VVj?3jp{-gE&<>9QZwbe|MM)Nu{TBOl*N+a)LrIt8{ z$0KmLTDwu}8+hLU^w7QM2<5R^>JUUtkX8GKbzJZMtc`QUVeqK)0~d`Fdh-@<+2D~6w2k3^`-K; zyHlAQ86KA3<#Sk%FB+yNA$t6)a+U-D@O7UJghKuujcv zVOTn2Jd=-_$cBAiNy_M}1Cn+-hoF+vof#1(}b#;{_L7#}z^WOY?y;UB( zK(joZviEVc;oV=ia_U%Hp94XE+*O%z934ix15zHxO8*9A50Aa?UN)@$az?AJp*bY_ ze&}=ELHwvAxb=v!D<8t~(OuNK3#EV;s|oSpOuj$cjDx9}(8y5AKobb_mRn*|gs&cq zR_}H@W6o|#Y%WK}GrLosi0xvKIu91n)g0)o(B|D5xv+NA4K8Dg^~FVK?AgdbtL=}P z9r9e7Yq>w+MzuhZ!CRb`^Tfk7b3+Sv_t;^wMtaz3AK^L4Q10GaLf0w0@LMI=D$Z*# z!{JEGbGH%1?c?Kc#X>RQ-Qm=E54+9UvA>0yMM*>g5QcolOCzp>(HLSPWVx5A5wT_0 z0w73AP1JuRXXJUMcFf-iky`U8Xa?4@(BWyg^9;C5Y^yv@*@CAT6yLk{@SD8M7s%y? zw7;eM>6gL`Wi~w-Y*s&^fyM&G$G4|=`a$iS=7|HBL}FYcu1-a#v6=B)+e3 zmA9CX=k!s$-r*OcnXz=%^-18vDUT4ZsLn*-)nPv*ll2AU8&fm?ZN@B`YQ+<$+Z7ht zQZMAeUm8zV?byU8wSqm2g#!aH7m%QOvl*Sok8Uq|F_mxer~Lir z*RAR1$;_PJI`HqS9a*+Di04Xnus9r0z6tW7l69o8ho#Q)#J=p(PPlPCNak;Iub*Fe zT{a$m@Jp5h^4#P~QjKnBgQ@r4NQ}ef23InzCRz|QvT0nkc5Cc=7LNzB72Nq&?^d6) zg5Qj_vJ~mwJdM&2CvP)kTlm*p1RK3yiXzU)G~++j*4yy|M{YTvkQXI!5PkuRU}T${ z7scE7+Xsb|v;UUR;xEa<-}2h_Ky5Z_E}ZZY@CmDV8*U)7w^=I%NDho?X(nHAq3h9sv$2K4v$}-w zbJfcEeY}6tr!DIwtY?{O^rRgJ3u{ztghMyB6|mrlixjODq!#sk&kz3xP}Z*8v@e6} zsDB6YTDExG&k|HSOg5eSt(3ckz|()~0{tk;^{#q3J;&RWv5UfHcdj1ncnyB2cN6Zdu9=kRFO*nk9BHK3f{ zd=z|Mo95SfUAC)?zFJK7$enYg4J=1Jnav09?C~86N2T>={!peYp=|UFyvbxqTo~2=8NY@p;yjJq|dozXF3ctdL%lRIvW(e?`(L7RvKKrGef*bxN!%qgS zOXGpbN=RD|T@BX%2OT)Bsi3MdDP@jJce6<0ZV>n#(@Ah7-kpzZZiR+0UuShs6o1CP zw0_8maWS8%{30Fnf^U)OS%z4{JQ!W6ogjSfn#Q^rLB|Rqe5mL-xSx^|?NJz8+2Ap` zgbq!<8LuJYc{gZo4%KQa3B&@st&l;l9;eF{uO*F~A&W=q=xH%3FsA@vc>&%DkI)-E z4EgWw-)8C6mJ5R4E4m8whifgzvt4bb^08fzzh|O2^o@>@VM;7n^e=>0h^IaYC?p`1 z(dR4G8KQ~#i6G%H7(KmLYIG^5!m5(9N3Ve9OBqm<_l@hHi8D}TIzxJmmt1Q3i-Op; z7Osi0u`F({-&CAu6DwD-sRuZe=~1Q0Y}Q$~%_e!2pb6)|m_JOG^sgm$fHz!K#vIVBo4@DRssULh^#QW%T!b5Jn z+h)AF6`49}!lSl8+2jX-9wVN(1)O#$2hb-4bNw?G%ja{L!2}N&)r)+J)jX0Zu717P zK#fxMuecejYrSw^7S_mnc1bEF-0hT1nC#XfKvq38htI0sp%Z&Lp@*40K^dE-1wX*l zo6z=w%td1Z4|vLbPmh5Agm)~ewOWE1 zNwRm#iLfPHt@!Jay8?+v)YohKFa{;LHajdli=0es`9r0&o1}JpZas0b3@UY_p|8h?~X8f|~u44ZVf}BIP*S&>) zc$`iiXg+Gn{hEHFvjZV>0AX-IwKf|(X;+Dn-%e%3CMVrqtfoxS8+EYXmHjY!9&j}p z`q#u&TBZsmcvv?weSYsj2ifzWOrqkDax%FP;u*grN>|RM{$^VPLo5c*Cvk@2y+G*iI!uoI zC|iQfzfbti3=T|nfEcC!yIvbL2+G6V^GR!w{PN*54nqk zo+MYf@bk|qVO7#y6^tS!E-!tP4d|gV>+N0R;hyhski&9|W{=`YSC2JvbuWUss0+5~ z9{ZEt4kIR|oyO_~jTYd;Bt~C%U`+hJ@>=Cudl*QpjcXU zEtc0mLZKRPvi|;tE*YAhlk}d=rE-(KpMi?dmDAg&@Zii3V% zS$^j$z5fcpJI*uQlT7kv*^ve&E#k(A#R`3hg$#$K4$~6@3#H}v3L)PVplacI-+q2z zC}18W^)0G+ePC27K`hKzD~xP_=Kd= z?XEiFXt_OcYEnd%VbPSHdP(qB?7<{qGsCMmqQmog6e3yjQI7|OQtu{V6a(S#xytTQ zt0LABv(H+IXA5xZNis99diS->#O!B_CC*}dQVb$BCv)mGXJ2jd{9Q49u>^ZdAAUaZ$A;E1tz1qKV!;kWNA&p|ZmI=Q zLmTQLOE<#|e-_**;L(h;Q=K6>7Zqg*@xsi|{!Fa%k;n>w4G++qrfUY+wKl?lQ9&h@Lj+_U!q zO1_Ctz&BKq+QvCvRG((KU9Rr)A;m_c{SFNc@ly20Ksw51Ez5KQ;s#}y2Ly)06fgNj znU^B+cN%tb#AI#?+VfwK5m(C~L^p~zOE0`wy6?Nk^W$ZT5+AB(r_0N7f6Ge=+>BcC zy1GB+aI7y6!t;q+xBmoOiVeWveWkESYYWX-M8-BRU~iLCUfXe|-jR8%T9G54(2io2 z5Pa_XK?^gsoAAZp%F_@VmDx~f*uV`G$X#+%`f8EAVA!4G4=VWxm`uc1&I7D_Zcqt- z**6NE*z>be|7E$I?1{E1q`qAdG1BhU(a@&_`RLUy2(uzoxU(}nn{^D;qHs1tJ%-p@ zu97aiIb0(_AZ~}r{-F|t*{MJ9lLk{iqn4^tkhM|P*CEFOs$a#gpa(@JJtl%ZMHQ`D zSlbakyMk-)W)2f?Ezy4#|9aZ$=HZk&Jp#Ofd2Umpu-QaSz-0fTh-)n|`W`jC-?@Ar z#8|2lXin6Po$?n_RYi$aTH6pcp$t6ZBpt6fR$WNhg2v62K13lj%Xwdk5p)6VyvGfB zdJn8!!*!Rt05*;J7pwU&{P}NCl^Z&V`B=lLt9el!)7g^=>-?CC7L;wdhW6QtGgkoS*oozjEv(&e#L@7HAM+}K2cVc^`9hRS9$4vJ_q&KKHZ4E5y^ zP#3zzCW=lMb7^OT4Bwbu=#@cv8?0}OX-XfuNjKEe3rBWoxYteY)>HXIV zZ%IDth=mf#tCl^EZt3{op|7`9Nwq2BW~?a%*wBeQ0FUYNmM`cZ8-zo+dy>e%w^-bA z1|tx`Ng5jhF@Iy7dy&r;1%!=aS{&K>_$gLNohoTnBv+ux_LBjJbzf97w9KkN9)YUn}ziB$4eZkIQHK`~u< z5|oW3v$(e7Bf`f@SUw$9tO<|J_`NM={qY<0Wukkiqwvp`A`W5g+fXYB$`ig4LMEaM z$uekMRYg@Eo39?8+%!=ruMZa5@?+rlA~=aR55VS5uiEbf>2)Zz z9y}dm;&kK_5Z2xmKaJ0)YyUNy%A?)@Lg4y9lFHIVK^C`%NCAvFr0ERXCc5pL%6aOP za%ZH-mO9bu9)GfFd(8lGyLCF}MTpM$foKB2`w6xcUuon3ytBa|(1*8`@k|uq;lvIP zL#6he=NHyR569)j%Rk6lNo%%*>%Axbc`X-8a3@*^7!Ydjg6~S0Ag8F)ZuFhGuJnI?92#X782ND(_ty zT{IE4IhixpbnkQL9fyt99p8iRPhF4)mIJoNf_8)G+Ofxh1JBhZbc%H`KNTmx5G>)z z;(NxA@R`LIwN?}(t81UOgn&DN_=WZvzi$~!Rbe-kEqU#bbqBLPgaX#w4h)-6v_o}3 zWyU5%COTomDSLr*j>l?(wnEF=8qDGDK)#RKdWT1a=xh$csd0RdgH5aKvs><=o%@c> z<8DgACW@x45MgQF;+FAX-Xa2cM(1N=1fvO5JTo*IuIxLyi59#V@WPFN*CZyNJ#_yF zQ&B6j8|bfh@w+^oz(R(eH6eS-?5Z~aMYMvciGmd=2i|n12emGNBQtLMgC_LbJ*+o2 z=(|`;Fbya>dtYdui$Y%^6nxgvRaemN1#gzTh5sX=Ho7bdr{_DHQevR}KGBIzX3A0r z{$B+j1RQBvz$2Ho3Qh<U+v{qP0?6 z9K7R99T1#2`PFZInl`kM>fY%vnTpl2r1`lnp3@R691WGmn;%1O1Dg^%KHhYa-Ly&n%hPIhC6l;Z zeg~@7*<5<;imkh{;KTpVam%UP+Lyq~@6gc?Oya`Y1&aiY5@d+QQjw_(tp$)=3Hfwf z^42R^Xf~T6O%yVOQWuunoaKYa2sRPFCXzuINlBTM`>GlWAjxTVXve^H~?4Xs$nI`l|9k~dpwTv0u6!A0p3o3jn&m~Ra~ zmR|rJ^ZSJv3zM97!9e4Bk9APx`LwvUnYSZc@i)2I4$p%PKcvC}-s*fh2Y`+U2%u>j zGJhefO7;vqgQ+`(8K;&}f_yssE)iFr&*9SX%YmNWlBfO1<xF&&dTED#P@4>2%l1SFoy16hPongM#OK208tJ-?KzAMe? z#f?;%f-s{RL0n)HX^e;PeRi3DFBm<)q=#!j*!GSE^=qZT@D&tbx99); z&|I38CPXGh*z7R=+0n{en$jqUX${Q~F|Ck}s4#O%hf>G47DxzE&cC?yxqrjF{iqn@T*MEP=JQ^+V|l_p1JF4ggt`fbfQi0nM(n3R z9cB{(m1;+R9q{sNZl_ZFqn*^9+bx|z=dT^21(BRq<({Vn zsqUI<w^sPaOdgDezO_J$&`VnOM+~4nfch>6hO%oeU4~zBXSSylSV(tb5Zf}OlvD(2 z14|du9h}Zt^XEIzLb}ayiMSEFhZw&|P5;u}&DTBp5e6BTxf55e z8&M;je;Ay-x?%^J&0v#!B*(hQ8d}n2V|2B@=ff$c<#JW|L%B&r8OjZN;L{v# zKAmJoC>A?{jU-CT1ATN{!dzXGOGx$CMyOV_{e}=$JuI~d$)-`4aKsluJaGc5rlth@ znQ~{JoRu<6)gFW=Hpj5MN1r4GkS z;|@pphc6MKQ4`bNX?6~(NzRpPd&Q{H&{c9%a^lTGu^@1mV2(;>zoG z#ddlMD`CPE5BPMY8x1(KV_VCo4^Ib?(8BlmGgipqh*-TOW}&4%$cBwLj?fnAPdyPw zsww;Z{v@MZMU43f=9jhpFqXXwL>65;Ea-AOoPp|SqIr6pHUcs6BNokj!9vr3{yD!t zI5K*Sd6*F5yaG$H#XE}GOGYLAa|V=>ta*0HJX5oLS`Faq9}p*o^JXvmt1^?^2f?8f zdcg4x5Lf+$9=k4adP1 z;|2$7Ss9d6iRN(X$ttB{vP=wJP)jU(dyU6y7EHzsb-d6ErG>q`BqdKv9BD(obw53x z=AjxZ+#`O^D$}A55fVN7w8lZ4=xV^Uml{jb2i<5v#=R0Mq4&)5xJO zDi2m7&+aF2PxY< zW@r_hhy= zDPn|Ndu(UBQz&W7?aK;cYF#AcE2J_{QzBa|^XSxOS=od+GBFm$r+kk1p_))=zL37? zoio9Tc65XokX3Z*x!Sr&^(=Fg)ltPCb$;VZ~D_uXjEaA^KMZ*Z^*J5vjpC&F)tJsDEOI?N^IJNq@qT40`YS?`VW4B21Bsm zUAiPO%0h>!*t}6CyVFzl>|3ofUvFtPG&ZGsp4v%PnOY%4SC+ZC8jchN?Q&grw5%K} zz)W)5O(2LGa<jFi53RElaXKDA3RAgUzz%CH|0@F-YwMDb@82inpxtSTQa}5m0$~VF2RsnG_}c z&p}6Aj-fvR@EA4cka6>n&l-scE72IuFEv6JmFSF3h5T-GOb33iPTZcVih(ym6_-^) zxkQ$2PX0atLS_4gHZr**7o`}WH=GFC&XRkP_6>=Jo~Va<`|f#gSj%YriRBoK!m^K^ zpx+jIlP6Rj!{9a===NNSg_yw&r6?(^TvbJvBRGpCF%+3MEPDhB6EavJT;wH&me2*` zR~LkuOhi*q27RZueBmA(aZ50rj(FY*90!h$B^M9lpbvOr?(o~L9xr8goGPd=9IkHo z&LO?Ap9UJ{;1yo28HSRi%2s_K51XfwVt2B$0|b*yz$;8?vrN)vxXjdu$x7AF9jirD=Gezt!y7F7QxC#zxfd^FedQ|O zTvWWPZ#*;vf=^(l{D&4Fu4aG-Qo(HnD+RH5mhJ>$P2RT@JKZ7zv|^rG^Pgzr+9M@I+pQ zRPnFB)Zy3`wjX7_nt(|54t7r7g$#r^+!1tHWYKy+fj4p@J?U^2%N_3E6Y~eheuJt0 zdVMCNRJCUVTqfa)ST#@G@hToI!5{P_XlV0kw{ljP!2^Gzb|jq3W^C!w`w>b?ne_(I`i?X)m{-@dxp1mUyu!n$BhrpSj|UeMvG0m)qKfzFs%Dz+^JS+ zD&(w7s{q9_{k)69pBZr07b#&&^ze&i(J65Z2G{jJR$a3EBT@+3@9$-_wjr*~H9)%= z^hT4}ey%dd;izB-MV^hS)(_4ULT&erA*L~X!_C%iW(FqWGtDhk=0)4zI=%u5DWfbb zwOGJ)^;gePDSTqylyi{Zslby8jH_w-RTj%S@z11RiO$J*8?V%*C3UhWC|Vnnr59rH zio;(?l*^3O9Diuza_N7UQaK-eQiEF^bL9SwD;A?fF|oUwXiP0(;wtCLT&_0+&_cDs zt(HiTI&f9`(auV!Sg3@8Ul=?g1m~q)E?A3-kB%%F37M6BV7jvC$eRJjbVEIh2EUrP zkrs(ptTMDS*Qjp3tt&r^6aCD2;yiP)xI(~^B(~yBB>FrLl5yx%AS+wVa`SfFRJv>8 zSz6&F&vKP=&0};!(NH$sE45g*zYi!2OgZn1GJ+x<8N0{=QVad;R;FK=v-pYvI5n`p z&mKJ5ONk%X4hYBHEkrbJ3oEXrCl6r%?(sxNv2vuH1}XmSrux$4Y?pp)r$@i5Q?8;;#-S^SlK4 z0wRtf>X+kcNo_7~`j@P+^K5`T?XYqVchD-l_Ug#OTcsrDryg6)*=YFDe%?%b|n|wEMGB zCFb5XVX-L%c8*RpB?w0mZY(AU!4@&_bW;J%3~!evhcLYoWk0JG=&S2Ib7`nIcpBjR zUiCQAJYR=JAJ9JOwf+BK3-PzxMlY%EuHRj2)`90oHqE~fAkM!fH%AxA(&+cO4GXOUxv*QAbC3DF?51 zMrFFhNk3k#7(?YcZy)+usla~u5{H>!lP$QsR6oeF6*efr!&>w3vbhW|se5fif+ue2 z4URe7O$EnB50~hnt+s9k{*@O9$;|wb;Po)@&S`|m0hQ^c*;oFj#koWlg!YCvHI5+c z;Ag?2f+YwyRKg&_m37&bMY7I>ahL&2U-{cR^C?~)vpwf)C?opRn>Bgl>i2y+H`*-? z=s0BB{sx=I?bZu6B1&s9smJMjRYDz=NPBaNhU123hx&1_Do$JZucmh*>?+7}B>=#&1C~$>w8HjLx%jKr!MZ98XRZ?O#kL$#4~z!K69{by*eeIrsMD{)Xle z#jP5|+XN!YkB)(3Ura%{ptz;2{e8o->z+E}ux|yB>if-qDiRl_AU#($#Oo-L6=UYI zn1O+57}D44^hn#a@d>41aVqN}Zrh_0yXS$cB&#%h{mIxky6VehUvdrwhr z9wm&}$qOy0CExd*s_pR@?&Gf{7>zc*V=k4uq`*BE*UG>I+%hxV;L2O6115ls-pKlg z-R6F3U-6Xj2BNw=ld{3pe(s#fA^qR0bU7Jg_&1)<*b?PgXT*D}DxC9>+%;M;4Xtw| zs+;idKZmK4DP<w#4Z-nb`t`iXKMqNO&A>) z23cj)l(Y0AimIw-z3&CtZ8GEx{M@qJiQQ)G{C_S6LOx;@y6w;rqb1G@3Pe_iEeYf2 zSwa(cNiy-aX9el7z=+O?Rr}GUSsh8i2P7-Gk*6P9ElwT%RMuwkvR7V|X-r_8(G#K& z{Qm;QJvzdnRO0>3eONT|I$VEU19v+f1V+2!m%k|VnYRZ6Yf0fdwG}$m*ou#rV=$na zJ>LB{8}4-`!MXKBoSN7gZ%!^jk3rrz_wqC@%&Etk_Ow6?etvQas&-k37V|EkSxN3y zQ7U6_=FCh~Ei((1yUoDLZowG*$4nHF&17HB$v&&`(SHDz4f+k8JKjbuXCNWuBK)GO zVCV5^uzzzLEqcvD<3DR6WOZ}$0wyCQ@F;feKZ@Pt`Y|)+!=oxgO&!LySK5+niP$u_ zAcn1>@Xz(b$SX}(uVY4k+kzU!w!zMO419c~SUr3$I!yJ#o9&%Qxs?dLd=vqycD%9v z9)JJZ2Afo)u-E@*P_OReO-`G%O-Z9SCuO~S1gBHm@M$X%^6&_D?K{Gjy~p@vXrVZX ziA%a--Or6M$=g|rqwGS#1Z*4Tj^S%1*n0IK@>>dl$k2kIG6kHBFEF7p(z9E>fe z&WL;UTQ>PcFwIVSDfyp#W*Bl>4dWY+C+@874@dGV>G>f7q(Jovr?Gyl7kfq3!?zJ` z?}<^f_MzLsHrTar64^Tn*L-K8DD&j2Zie-TOZa8(4BQ-74^^DFciADIE#BSVilR+s zAosw1*fq9>)`!Z!?0uK~hgHLCkQe9!!nd!(?z_qY)|2zzT`~nPxp#uZlY?>bMy>}Vy%JR$H6k%*T%j+12GwXVxHv!W5 z$6lk$PYxNeW=)vkK+_4n@SWWjwxko1ot}$fVR5V`ZNpfrA_gj7L(wTQ^6QkKOgAinmE7o5UvG=0t8Gc{?X^QkHy*b zebHx)1oMvvqiZEcXlLUKK#$IRP)chhU%G(Flxsw9|CiEBvtr7fXy#`hy_9Q=UJDQu z7K~H>Oe5y^ZFtxY!A;WelM=ajuYEfFq`gG$j_N-H@h@H@PVpEg4iPAu-kJkaRxIJw zegOFl!0qQTn%hME(*=dgGoHPfxWB3m8zoFv8el~K8hlSIMb09OFSjjf739{-sc3yI zO^GL04q*PD1JSl^V=UXx_yW~~ToenMmi30ANmkyEt#G5F?(WLzFaEJ$hL*r=%fVh1*@_|V=PGOq!;WZ6)|nCA0m`#H&V)^vr_jr zb6=U)TJ))(pWBtHWbbUN4s;`tu=!luw;CD@@+I|f3!@w5(=o?&H}!3d9D~Hj`P%C! z!00hk2V1@CVr0D*X5EE8qv}q*b;Gga&QKKzx&{Z&U;uJKm zuiI=WW78KYf#yQKpE!33k#r_BOWvm`6~?QjV4%`z0YZwOXFkHULo3nQ^EPG-YK&Z! zW+7B*<{_D|@->5MS|_(&J}FIh;%L3qMZ5-g^4wKK>4ToWrbn6Z*;2Im8q)(s)4)rm zU(+)N_Ga8zBAANuMV4wZw@9UByhcdF&XEfq81nif>#$*RaOPTw;ED)5K1>dI6@jx; zL3nup?=FQPQ2-=2PVHn(>|%NBG;JzJx19)md6;Ov`_Zs)S6q2-q~Xze=s}staNI2{ zm=l5)bFX3d?EdhoQVZ^mdSGL0EQIW129XACzlB?Sh7!$iAj%Egg}Bu8qp9gfLL2JEe2x8EgJhLZX2MCn)b#(mHIB0I*VYMoqVQ@L|wwMfB^VzUVu(jhO!N* zx1Z(aI$XL*bP;Uou_;euA-6@tLN1I0p zTi-&nS?eYc01WvEB^{b)+okZE-vn<-Jv5ngH0^r$N=ilgz&L%{YIYKF04f{#OeuQ; zLibmpUBgxE9$ERe_-pD^4CQ@^!wx+`^1v4Otyy{e#$Pp!Xm*o+=8l(Wy8`Do+M`bI z_V{-ycf85KHCXpoCyifJ?f_WRoaygau`ga-h(*ELqfoJRIfMobfhF7codkfzH3rvG zE`kVSTq9w_cUyU2^!gF_x7RGxuRIGj^(JA%>fh04;4FB!IpZVUAJ#-uwMw-#aC$Pg zD9VqVdym;TS7}}wk&H=lYn-E^F0Oa;e+EyAowIOAf z(^dj?S_XTHiAX8c!Tw@ywk*QeAH4^@q#k~m!_>oLEbd%PY#QH63pEuSTCiCf*<@*K zR_XjSw;8*ye%{Okht51-sD21OQ^BE`?bWMSs9Ce-7c>2hi6TA@Dq2Nq$O(4FV!$e5 z5l1xE6jG&*c3&pQqB8CS;#HP#a^g_vV?+d`cFrV!7TKbqB*w)dg*;zNJ12HQf+*R{ zuKSw7p+(8QBDYN=m^V?6?7ncGED3N5^+6|B*KAd%)^>K{izSL#5q+kjh$4z8BAALI ziYTIpVEWz4mzZ-ZiYTIdTkTQ=(<~5?9z+yTe!O-mf~hFqirOI7fheMxFUl9h9Ayl` z!^08r;R6ztU)N-!P1B%e3>#H1v3uD9{QF<%r)DPGx>n3ltQa{v1LtO}?lP7SQA9A! z=JNRP97tr2aC37*E*BTr$SCZ*pZMi7qPT`mmJ)Pdd(X&0p7x{7*7VbU@DwFWFGPGo zWp4T1eRCdd)`e#9O64KI>DIEHWMHj>|PRj@||5R+BJ$U>)*% zGW-H{!+$W8BmL7$Ig%ZK)QHBXtDRCh6wjbdXvDOd6vnj6z<@O%y?)emg6;RFpNe3b zO~kQSb+oVme7cn-K(rYz^uUgx200mB8M}@L;OM6D_;_$O8cjM*05lg$mVAN@<7VKV zhJILuH9}sJB(EXY)1_l zg<->5bDI0s3xYG=Vd$K z_iX4I(1LSDVJ}rj)6ERf6~R=L%oV$u_&4+vdJ}LtwX_%9T`V!(_krepWT-)I2Q0)d zjl9sL`*f^q3w#WE!D@%Kf0;+X${)U`IadMw@gkP30S0XBMNHyY_|BRElwFFovpS)c zS1+tNI)b$v7xC0)%@lqeCfel!^B>tDQbz_x6Aj3jk)dEOS>S1YHAFx?XOZK5KWW+Sn7YlscWF}Sc} z8tZSG)}bs0uHi@2>0|_5Ie-O|2cccNCK_rG#=B}$(i_t~f%%J$vfvtYd?U`MdZNeA z#R%X$!tpa?XW%e6TX5!Z{-%rA^aEd+7>}nYGfm8RW3u#Gi@s`V4rMZXnlN+-OHtB? zp(xpfuB&dRNl`*a-@vA|fK|O}u!;%g;-21}Tb?EHy}F{61j{ntKa1$NXqJvIA6SSK#u?r`SJl6WsbwMKvaHQwn#I zMfuU#d;B;K9XiBr2YnBrK~bwuY7(>#54vgZYMdtQJP*zqijvESNTc-z`ESKhqU0o> z9fqR03n0H+5%gaDj7@Rg-^Y1>ttDE{e2i;{SE94WZOk0h7&$A=MyM+LK7H*oHPrAl z)0615G)PMKQxj(H`wQ6oB}E*~&qg8~0hJY~iZ!?!JpCSD|+0Bcll*a*2Ll%NSxW(_dJk*dGiJrcg)7%eH7dV)5K}-^Kgz7gmSHh zG2PXvO~TLB)c#0kw3|GIy!10sy6tRSdh`k}pWVZOO>1!eRkG>!LK;cXbw8X4IIf}R zICSDFqE&$YRY}Ik@+ci%IX21AaKNMOQuxnr#!-|^JPL&o4K?BUaf}N9(ZD;Rw5uar zU0vDD$*CZ^jbKOy@8F+9kI9YiV8WnD8p@I2h!FP1>MkJU%qq0&u^7)Z695u%WKlm1 zU3f}M38Hv_IX${#RY1tM=?0**1b~z=vw>1ibCU3lQup> zt#N(b2sCTh9L<_FW4BgKC*iq5!cw1DIcl|5hL%L`BoibcgS|vovz3tF#d31wDGWu4 z@1z>IJg+4t?0jZ$`^N!0$lrG8-qet?BTw~C7)+Agy7M%mX-mXznuC@#E1*abPYhdK z5kAKkqr5$L?uSA2z}ln10`k>(T->=G7oNp_MXglVhboNzP6WXrQffrD$) z$m68z(^STNqM%1s_-_=!G?8boT1?~$ED{tXu@eGOGl z%M&;w(|5fI;MJRYB?LE9tHv@DP`@`FRRmK}MESzt`PpEUL5(OBgy4 z3UD&B20t;D0AP%XWv7&k9rZ2a?vnl&Qf5v5y1-I=7@=~&SO?{F{?u?Y+y@{Cz>(StpJ#ut!U+eoynQQcgHo+(>LqQ?x6hh$3pHqI`m=X-l4%^g%v8@fXTlSn<>&Q*if#deJ#U9nun= zXY9q@3+v$}mB2zGLGw|&@J<+xj(&?Rf3}3g$_iE%78pI%2mhvU>+p?>v7o}jEI+{5 z2IVke$3xBUaShAke>=|N=-L60@JsjR`34#)wftxN^Zn>j(G$O~yC=Q^qKIHB%IAw? z@#<(H1ou3Jhx~upgCn|&l9>MOZ?a99W zC^mjM=8SBK<4ZbY?YStmBLTjn^PuOPqZq&XF#L~iK+ycPY2=DZBMMMF9-MuE`{BGR zf{Mq>|Fd@;@KqFB|G&58h6G4JL{t-H;(Lk$ zc0NUvry$a7G%1208c>0Ult4mq%if)DX4{>e-MtAWHzY9U_nTzz?(FQ8bN+MAnfBFk z+&f%3ZI zz285_#SviIn4vhRQ3&_HZyj4=9zD|G8NB@9**Lc6MR>(LdSuZ?O-HC4bJGlVbjWKD zUX5GKqeH$kj}A$0{2cc_4V?JMcX;@+f#}umLQE;1tH-GjPyGN@--*b%Pc+-NG|=$| zEZq7zZo6zao_vaJnfD=<6_s%+X)^>$*5IyQT`^S~O5=_~i>PD)NfJyYRTD*<_QAOL zcFdh%9$Ru|dklMd2@qe2Prrl?o{h{MaGXbv7;qNu$sHY{8rq_@1xVS(A}pss*S|;W z^asOX{ngTn@HB%Pz&^B%qrul)iY|r5b%TAI>if`=A&#?6{I7 z)n<|dP<4(P;GgH-gV?dhVoeaM>2E(hQG54H+BK;KII_1JbR<@h#ey`^a4!ycB>tG z)hCb8x_QEJ{K)6ejlk89e+9XaN|Mw}B{>@^Y#dv{f<^4ul9$=BB`}U2SvI8}&N=y3 zbo=-&JI;h3FVO$%M~7(qRQRxdymG|adOp++W0t;+P3_OYjc4`NZ$0$VOIUo>F#U)% zKfRP4QIcBrBF-H6qT|Wf`P zcNDJZJ^-zXzQGCmm0{k*H}UNeH{raa8yV3P>oNJQ6}an#k@OvQBZ(E0pTRio?|As; z9*|>Kl4^qFu_cu-t!vM*CFLkC-l>1b>(~-2PL+-xQDZ1BEul49h=TgEua>xX2jE zV@o8Nt(A*{eR3;|Q>9ZnLQxbHNL@~nq+KQHGnFJsk|YVHk|arzR7(s;Sq0Tk)-)Zt z$#D(UH>!TpC3oOw#(R?Lwp>autz$G~)DCmXkF^nql9o>EH}QZ21wQe(=dSnQ{Dv1w z@Nq2UvHug{J9cF5d!&NAaHu2RBVyUa^x*uCowitaLR>{aQotr0?`IR0F3s_hvdC6cish5y8hL z7+!*}_+WsKV8e&WMd0)T1TR7Ik`Lk`xW2;WCD8B?U}WgjeCpA+qY=s|-16-_h({+N z)~%>Ii{8qK-lF<4NH=UI6ORM|)$qP!`WR_3HQ)j}A-d;H{WhwiG3pW`R_d zZ)C+oW3U=MeQmGcAskp*_pNo9R&@H_{`)9s90Azg&^yWrHtw`!L-E#9T*72AuHx2i ziHCN|ffPyaL~6%jd4eML5sG7%KH|FY0kd!rSDqC>@b?6so$Y?^>`W9pJ$6UmC6y}& zLAqY;I_LA|0rXb+h?S}Wf9;s?&lNjwRvHR2t;pr;R}nx?am2AqRE7IX1ii~ukABt& z#5VAUmJyJgbO&>jCmy-CFt2w#!C>X%;Az zBAynf(XkSaVc^;nS>bwCUKh2}JG5gCt{kACNyPP0ygoLGFG{^EHsT{_=}e>*3*z4F z*}<&oGo7rwDR~h*dwl@Pni#H}XhV>hegHR&h@eX_ft#Mxu#xaN;^J}?Xb?c>Ry4gJ zE!Irp;}^B+$9R6?nNgg3n1+eZ#WA(mEDxuW5F+4-!o^YiEnP=WZQPES`Q?xtofPRJa4$b#NWT{XnIdgk$BXNb;FB)iJg}59`VskiNCAH z)9XRJ!CBBktrk-Jz}k8)!L;sCQsbo6S1IA7*;h+2;t_M@&_PE>ad&^?_J)-SeDEF6 z;D7+Sv<;$tvjB2K1j&>)LlF-POL3UU*7pMT_bNqfQgg~+B>F1Ul>1FWHFb?@5)|)| zaDZF@*3MGA3xl?>Lb=Rg-tbKh2B z+%vCC`kQb6cx2=rl zszP3LSxm$?E~h)28m+q%%r;3dtwyrU9>dJ{GHZArnitq94o%3(j6qz`-MIQ~ehk-t zWSWv|2qw=`p+soRMB3caHDHdZG+r|cD7ybh7rqEPaY@0JBm*`67?HFFYUL$B-y@os zKbnS?j%R==&$${NdMajn39No2UPpXObnc=}9&1@Fi4$U>(sr&!=efi98aT^fb}+&nx7M}(3zy_BBb&DR>=|6o-Z z=k_wz7ME{M;PK}P=EtF@X8~s-#lBASbW<3IMpQg8TE)D$SXTyM>A=G@)0E=jNC;rW z#bNY57`6)g^^61_nVGKrp2tUUNsj;;np!T|p1{?Q5&RJe;?_&TII_7VFd7!qckiAK zH0wy~#XO3uegArx&?|-4pG#m$JdEdVpb4#t;mS!yUJF`=F=}wY-7f15kZs;22Tz`o z!uua8=yp;NE%OZb&9rxucy6h?%$axwLg(z{IeuJVgjwD6y0}f1ULeFrm5C%V#YPDQ!26F)%3VV zyvF7R$(z<_Sj*n0ftcx7@mNp_!+7=z2BZXwzfa<$MHB!ELb&9bF#1#BOsNVAsW8`X zW&r8>wHkij2ILlmam&>Ko^8_8W#Oz=36D{{A+2(w(?6f zL&MbQ_Zk(ljaCSv`m=!j{h<^{cm6Mh)qfh_8`{%IkG*SYwM;X;XJi=d3V=m3l9*k_ z*CqEoOT)9itJh)r`lb;EkZxI<#Jkf|_(KigpM9fvu)C(ee?-41Zs^T`l$P;36`wEE zP_%{iT`cWT2yL4YtXZ1GCtqpUk_e!4?=ViL^{JN7`ufxOb}=o>iX!08a+Z$(*wzUR z^Njqlb{RX20%^X{F8{ZXnC%il|9m5l(GU%h5Y9h`wqHoY%3lc7U=V#zq;+VQmFVs6 zv@`olr+!T^WI}bOndU2L=GPfYs6;R)i9L9e?p+4Nl}U4d=Z!@v z#8Zlgxn;b6THgQ~(BF%ul;h^FSPZIdZi(X2J|UcY5*6a{#)eD`s6KiQx%yL}&35}7X;Db>CG-+n|d{yT7q}nJuW%crls8&nC_~&`$xML>l z`7H{ZBj={~feMeN*N?)DF7MMhjN_WnZ*PoY@U!fmidW}GF!Amn+8-Cf0bc-T9Bss& zIU$Az7dkh$AjL79b+g%4ii&kja&cul1@(diYgZ+4&x|16ItTcLs?4{{Grk?DZo}^$-(|P>fDE=N+(16zK zdP}oX1e4nuRaY!CR^v2FtBy3M8_DD?LpwFw%ia@~xU^flu1(jBkq^R|A)*^V@7dh> z;J)kBRf#!SfY&*VV;a)sB6fQG-nQwqcr9Gx@@u&=hwpAc9GkmTOhe zKnkPA@0H(cH2r|_*7g+El?Bl#CxE8;3R=;6Bk?5OSP+0}4_aFQ0JQBG!iC*~Xw!t6 z>kW(uabo1Rz}m$yl>T;*YsVzL4(Ax)vS^W_w*gCcf!Qw6JOfCLHrDXL>nZ%K=QCh; zNTsaq!244b&gZOHeKiNHPLI2D8Ykg=zr=f5FoAGHHgiOR zIp)d0F|XYIj%J#C-1}u*CR8>3lHLYEAg5k>@XE*&uLV&JyF zd5=|83I}3BecIq9%oulF9fx~FpSRbw!PJzxB73jbr8XOkQ}bTrY0 zkKJc31$NCw(b+qiIW4Ps0d(_{rEy>z!^Bio#W77MWqpIDagcGY9ZJfL@4fRW)tVTP z42Yl&)!J6iQL#d`^5-mXtFX&&eUAY|B9%7FRIc2X6tcW9&|tm%mc=oQ z`<~uy5XR_xf;h;E&!uhEoh4hbtz6x!#y3>cMbh$bid|1C)%^Bl>ULbis&y&+8l&H- z7s5%cv~=0@@+vsD8%sZl`)*F)whvQSw9OD^LhFjfv#f?h!fF#E@Y=csl+7Ea`J#E7 zpze}k`Aeou&6F7aJ)>@k-M?z7G@ls%6(^tQ*TQerSw{}uVtl8!htdA&dqzB)NA0>K zp7n0?f|Vc5XhWN?**DBIthBA?c6xkXta#Qp)U+Jf@+6i&rEuj?9BHr+H`7k2+0#m; zW?BVP-tRuURyE5EB%<37^Rv`Z8{ zn^30vErEY;F@bSI65nsiLC<{+mF>N00b9dT1Vu$tjg02*8^+y(3~i4+kI>lfOeVU< zX={zN|F)Qp4G^fNhH532GzSs%^eEUp|6~k}8b>jxbp)^85yv?X5Ulf9MQMRmkF$el z9SfMs5jC%Ark{H^fu*NM(DAS+UOqdC*Ow?5d`<|>Xq?{`rtl*L%=f+pI`jzOjNv(` z|8WW{Ne}}mh<^5b5}S51Ko4Q?V4!tb7(?3{ddNSzDR^meTGMRZDU5SVG|XR0uv+8X z4)poIKZVgQm(q5bNX_`%5Ux1K;Dt{n6CLF3fjqNQWxNmy7^Dg$;j)&JOPT(#Ky8 zy(zqLYY-idi{SC&jAy?tisQo9skTbf_+VlT&2po-^vEFko=Ssh_iN<@?|nhgdx(NI z$Ar;_mTAdC4K2C^(WPGwuKqfPXTC|`$AJ;FJ(SkxAq4-vw;U_tMjqLr1irYFxtYF?`exk`0h9>bOJNCT+Iq~M7kSHT zMVB8SiSwOYE40LWLVB!CdHw^x#8ZRMo-c}Xl^(ymS96Tn!e@?@ohL=xHN|OH0aYGb zQi)3Ck1Wv#gScZ$_#;b_$)tX4$ra;|aP-vf9yjx<9wPMgF4rr4p<8a&(s zlsLhhA~^OrG4e%CU)t&l4Nd6pZ;FU#K0{P%9eg;`>=GFJgMt)i+OdHbJx>niPLHJW zBgOEtz$&Ujg&)A(e|G8 zuQ^A~UEd_`_cP0elbe>S%y6iFjZ}hZ7Ad8rr=0k0Z3%d-7jY{U{90q; zugp4`#^<}nS2X;z251!0upwodlcGjR@3m>EU?){)7k#N>b;`LXr04o_m&V~y9-$mw zWw06A=R0Ywhc7R2$8pR}6kI8$n&Y=-->D%wrw_raEd({xYuTf?Q3~I)@byEub*e6) zb$JbmV=gnF*Gzk!*zv^po>Pa2t4;a$)9vN0^}6Z;-m<}`<>0dn)0rIkFzd~|>WbLB z63Fs!y9CoL3`VKqJsAsos?Vm6An)k}4RZ)KVOsk-TOo{#X-(Q-&l{B$rJxQtw-m8p>Ulm@I z_GmSOs|1GKYidc7w7cY0f@w8_fdmXmlJ-s+mS8G1FG-T5oJ!49f~X`(l4^(CN-&iq zNs=T9rcyiI(}|E>*{?~Gq}?a45=`qBvS3eK&hE8LSEp1F-W~fm-ktM@YW;!xLPod)!n=Vk32mb2Mrm9Hiw5j-(&Y! znL<7L;gSo7xPd68_-ec=mtXOjwWq8akI_ntB5WSMq$QI`AcCBb`*1ZDKS|RJRFI}v zkL=!pakt=6^%dMPygPRmy`;TOPNkV&=g1Q1a=d@nNjUd`53ywKYv>-NrZP2`2V6f1 z|FaLFD#yG@_oHJlV4BZ|BASHSkHpGSFHp4L zH;$BFS%m}W$~Mfxb$tTJq4^Ed^89=D?`ds#)tl(u`)+){@*CWAIE_=U9&$R}jUo!7 zizeNQAO+Ukh=S{0nMc3qO!HcrW>inJizCf4GtI?o=Hs5zjdDghU5)9#lI!?*_qB5W(LJF#}wOx%CbDH!(f-|);!V=%ZgaNqe| zFxq^7!xTLv|GptWwqet@ZTR!gKlM;5oJeS&2~_eOE;_IWp5J^U zrp%s!hb}0`#oZ6Y=NpZ4jg!e8m^pJ4j%wExvpSB#3y)n2EFFb|A`$%KyvK3Zn3pj8 zG+^xIS7Cm!nP|!PdYUh89*HTlrsKg2VtShY-C(4tsz#b!4m}D_?-+?OV{Sp4G z({LCK9ceh6hGS^xPD39W2GVdE4QJCZgocZ0xRQqJXtA7SOPSh7~mYOv73l*3+KAC>({R*>(;F!bLPy^-(jJ8X*+pw5J!ze<0H zdcHwoL{e=fi|3epGC5`P%H)>GFOy>?&rGhFd^0&`^3LRbSL9e~rghldXHp*S`3f%S z)y(uUCiQ=LW{ieykHM6*$YVQNI;FP z#I!Hzy=B8uXu1iS_kTjyjF+#YhHU&Nd`h2(dR&8Et=()COBw@Zv3Oa9CCTmiAeOJ+ zfTE%zJzUbMzO9NYoMsv?F}~A#Khv#{cMLuy3W|$)qrEq-yX>FlU_coA9s~q?3_{nI zIi^1E^p_>fGL6-nL1i@T2amb7<~9Du6Rj&x!0$Go(C zb~&B!+SVcr{Lj<4@4BaPL7(xM_R$)+D5eDc4I^2NwpEv>G zq!CY}xOH&ri2j_#$Ze?kWRA;Z1^(Y8X^Iy6HMKhdM;aclKQdtoI@c#Ac*W76Rcpsf zO3L{OhfmgL4$~uVeT*&*fTWpb{g$nK1;zXLZ8_>B!BkRRflgyEhKMKLD^7G31wF4N z&b;O+oO#7%xH7jrUYjrr&rvXKXeQAh%CSKCYE1hID7>T@qWP_H0@YHdPOpb!`t-$q z0r3>|*67cke>4e;{=5v`nxx%E{Ag)3oSV5!dWsQmMkG$`Qz8a%AJS<6PH5{fJHR|4 z-F0rd7oc1BB1Y_kI}bp=wt1u4D{F77Azy>a+2~N18J;r?eJvMAuiC6C!iK&``$A+JP95lk|civ4g zXA}+_^bo$LX{=qd6qEn+GG?!1Uv7j!x73H;pC+(4Ymea4M ze}iUz`!7IR=d6!KZ8qiMv*h8ij|^*Lz=pva|WX-+E4#R#K51%`0cyhpXAr|!9s z2IFV2<4DYX@qDK_O_}P|(~iRR=3LZA-u(#|bTG8Z<~{GjvMKd&&Pli8*pKhBUzxb# z0^@n#$FS)AJPJtn;iMy9G(qYHoc1u)c>i`P9>3rj+;sgZ7&oFb7VKDvF8RcTd8`cD zvKMj2fEOLl#?CKB+ja(q9o)psaei04z2+<2esK>B?fZ8-?y%REVTg^P1x{U+Ca}+8 zjqN$COo;UPtyYx0u6Xy?S-AD0?ikvKPjk7RrqOvdv;mrVN}F|}7Yh};{@M(<6a52& zaMgg0wEUw{OzWVbWY@h9pv@jS$c2y)!X6<~LCC4(|4hN_yJFt>d}qxZ5Kky&r;=L# z#S==|>7>?C&$VOo6BVV!rSxwY1qB?8#^>Y2yd%-*)zuh(VJmFgT#Ru3KFIf4!lTAe z8Z&geXnwxC2acDbG->qB`9cDv#l=X4a?vn9r&^CKVd<*$dn`ZEYElVGSYH2fn&l`i zrf)}cX&uVRQMJ{qpEqwFTC`|^P$*=F^fKmv{om?ztspg1Nd~|b6u56E4OMRKrtNy1 z!hQD1kVKS%WG0n4ANe`nkF0O=Kx$oArL*xYQx&OxmD0>b7P2I%PDz@izwC3V)7$Qa z3()ua8>p$?s>ZYe@mi9kJxP`&nEu7e_gbf2r@%Gm5R4f8galGad!_t($!k^RSB~aq zp@Af5NFbF|JNYr2ijM@-T>#3;5OZL&fvCM5Kq>>I)_n=Il4^n%nOrjY%!b@cGd~MJ z8;L|vT3U+2!a_$wR;FR<8mNd9^eZZlmuqBi)Lbj7H<`!k0XLDacJAD%%V%|(_EIyg zriSHB1}oI7R}Vk__@fS}Vxv|R_7shsFOOgO_GYbgR|R-?)KP5&`^w0WpG+}%T)up{ zE}!C*CpiGMn}tEtIIRZB&CNyq`t`A3!2+~x+ZGKPG|+p)z2(5YOxmcV>Z?~t70W1_ z3!Y3SQBqQ(11Yl>nS6>sT3sE81k=Bujf&=~7#Ntc|EpH5LRncE5{ZOkVn09OpQXkA zGh+As1g9h=E2p9M*eA zYn7A*p!^Cd_TO4rRh{O3b%U=}{)!lF?b@~2ym>RuI_oT*iT!>zZrm6xTehs!w~LF5 zQIMq>@!14wN!gP{ew7lxa*02f(OTWa%W(K zfZbzpEzPv9YNc#1(xpq6qF=v$I&fzCJ#*&FN{_5s^_UIHX4+C)tr;2Q!IYOfultpe z-zu5;FTebCO_MDflqv0+8qt=3`_}=L2T4&o<)86@DuQZtwSmq>!-fr$ty2LYiJP5NwCM?d z&7Osi{^COfC8I0#p%1y?|F7$nr{GYNoqMTeSeyYe2(l zG%ql%jETQD11uYjWuYob#*0FO=zao33xS?wxpUd^Zm5WOaoG)R$3j?qa>JCLF?fCj;vw)$Z9lxn(DfcwxsOKq6mm0n2Mn4HKOkY&{8w4;l||WhI)ai)i9P1Rh$-OUGqRH z0kvke$LcooCDp8%&yae56*d4Z{QU1dI%P4m|p(rXT<)i7S>hy}d-KvhMXfJn+d z;7K#TZd-?$Ony;Q^_#darX|6&1{yKb7IKz$Do*m}r&9R=sT_35Oxc{iw7W<1<$zVy z>#w5Kh+Zy$%B<0sU|Pe#>IbTpd2a!#7kJ8v`nz8+0HkN!i!v{U+Y?zEI2kR+$x0asaB90IH>t@@BudbzdeB zm4I5afR|06mQ*u9S`ny<0PCmKR#iKdV45W}pMTDq`W1nv1k;)YJPD?C8%+Jc)$6~P zHY&~hS_r5-VESpI;!FSupb|_aWfxd8fo63AsRYw3YN(LilPM+DPCoH#+ibMC1k)^lYGr`6vb!2dsv6)`#!`@Ax@XB=RpLl6ts{-= zxAT;w>H*Wgvf;G^)4dm5C78+vmS9?q^(( Date: Fri, 25 May 2012 20:21:35 +0200 Subject: [PATCH 15/47] Notify when wrong watchlist url is usud --- couchpotato/core/providers/automation/imdb/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/couchpotato/core/providers/automation/imdb/main.py b/couchpotato/core/providers/automation/imdb/main.py index 248db2d2..1f9c62c5 100644 --- a/couchpotato/core/providers/automation/imdb/main.py +++ b/couchpotato/core/providers/automation/imdb/main.py @@ -30,6 +30,9 @@ class IMDB(Automation): index += 1 if not enablers[index]: continue + elif 'author_id=' in csv_url: + log.error('This isn\'t the correct url.: %s' % csv_url) + continue prop_name = 'automation.imdb.last_update.%s' % md5(csv_url) last_update = float(Env.prop(prop_name, default = 0)) From 30256f4f36ae3e2395043111cec84164fc98b53c Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 20:45:12 +0200 Subject: [PATCH 16/47] Give some feedback while deleting movies. fix #314 --- couchpotato/core/plugins/movie/static/list.js | 1 + 1 file changed, 1 insertion(+) diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index 800f1711..db4abd47 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -278,6 +278,7 @@ var MovieList = new Class({ 'events': { 'click': function(e){ (e).preventDefault(); + this.set('text', 'Deleting..') Api.request('movie.delete', { 'data': { 'id': ids.join(','), From bd8bd14cc8d5be5cb11a215d7ae3618e387ec1aa Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 21:19:23 +0200 Subject: [PATCH 17/47] Add download handler to providers --- couchpotato/core/providers/nzb/mysterbin/main.py | 1 + couchpotato/core/providers/nzb/nzbindex/main.py | 1 + 2 files changed, 2 insertions(+) diff --git a/couchpotato/core/providers/nzb/mysterbin/main.py b/couchpotato/core/providers/nzb/mysterbin/main.py index 902c37c5..5e619792 100644 --- a/couchpotato/core/providers/nzb/mysterbin/main.py +++ b/couchpotato/core/providers/nzb/mysterbin/main.py @@ -81,6 +81,7 @@ class Mysterbin(NZBProvider): 'size': size, 'url': self.urls['download'] % myster_id, 'description': description, + 'download': self.download, 'check_nzb': False, } diff --git a/couchpotato/core/providers/nzb/nzbindex/main.py b/couchpotato/core/providers/nzb/nzbindex/main.py index 43364aca..fd53cdde 100644 --- a/couchpotato/core/providers/nzb/nzbindex/main.py +++ b/couchpotato/core/providers/nzb/nzbindex/main.py @@ -71,6 +71,7 @@ class NzbIndex(NZBProvider, RSS): 'id': nzbindex_id, 'type': 'nzb', 'provider': self.getName(), + 'download': self.download, 'name': self.getTextElement(nzb, "title"), 'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))), 'size': tryInt(enclosure['length']) / 1024 / 1024, From a3d812ece6f7fb37afe70053b8a4dd720bcd3299 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 22:10:52 +0200 Subject: [PATCH 18/47] Cleanup --- couchpotato/core/notifications/growl/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py index b98888e3..72ba2a56 100644 --- a/couchpotato/core/notifications/growl/main.py +++ b/couchpotato/core/notifications/growl/main.py @@ -1,9 +1,8 @@ -from couchpotato.core.event import fireEvent +from couchpotato.core.event import fireEvent, addEvent from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification from couchpotato.environment import Env from gntp import notifier -import logging import traceback log = CPLog(__name__) @@ -17,7 +16,7 @@ class Growl(Notification): super(Growl, self).__init__() if self.isEnabled(): - self.register() + addEvent('app.load', self.register) def register(self): if self.registered: return From 30ef0e45e0ebdec9b056587c0122b45fad0ede3b Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 22:23:18 +0200 Subject: [PATCH 19/47] Show correct diff. fix #319 --- couchpotato/core/_base/updater/static/updater.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/_base/updater/static/updater.js b/couchpotato/core/_base/updater/static/updater.js index bcbf48a8..54310d7c 100644 --- a/couchpotato/core/_base/updater/static/updater.js +++ b/couchpotato/core/_base/updater/static/updater.js @@ -52,7 +52,7 @@ var UpdaterBase = new Class({ createMessage: function(data){ var self = this; - var changelog = 'https://github.com/'+data.repo_name+'/compare/'+data.version.hash+'...'+data.update_version.hash; + 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 @@ -85,6 +85,7 @@ var UpdaterBase = new Class({ App.checkAvailable.delay(500, App); if(self.message) self.message.destroy(); + window.location.reload(); } } }); From 7fa06eade472fdc36d3882a0a214287b4c2f871c Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 23:40:54 +0200 Subject: [PATCH 20/47] Select folder, show root --- couchpotato/core/plugins/browser/main.py | 6 ++++-- couchpotato/static/scripts/page/settings.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/browser/main.py b/couchpotato/core/plugins/browser/main.py index 887edc30..90f2673c 100644 --- a/couchpotato/core/plugins/browser/main.py +++ b/couchpotato/core/plugins/browser/main.py @@ -62,13 +62,15 @@ class FileBrowser(Plugin): def view(self): + path = getParam('path', '/') + try: - dirs = self.getDirectories(path = getParam('path', '/'), show_hidden = getParam('show_hidden', True)) + dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True)) except: dirs = [] return jsonified({ - 'is_root': getParam('path', '/') == '/', + 'is_root': path == '/' or not path, 'empty': len(dirs) == 0, 'dirs': dirs, }) diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 20ca457e..39ef9465 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -702,7 +702,7 @@ Option.Directory = new Class({ var v = self.input.get('text'); var previous_dir = self.getParentDir(); - if(previous_dir != v && previous_dir.length > 1){ + if(previous_dir != v && previous_dir.length >= 1 && !json.is_root){ self.back_button.set('data-value', previous_dir) self.back_button.set('html', '« '+self.getCurrentDirname(previous_dir)) self.back_button.show() From f6afd4a0a26d038e2467321005fd01b2e1bb15e3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 25 May 2012 23:41:14 +0200 Subject: [PATCH 21/47] Add movie to list, fix. --- couchpotato/core/plugins/movie/main.py | 2 +- couchpotato/core/plugins/movie/static/list.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index f8415d1e..ce386d14 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -302,7 +302,7 @@ class MoviePlugin(Plugin): onComplete = self.createOnComplete(m.id) fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete) - do_search = False + search_after = False elif force_readd: # Clean snatched history for release in m.releases: diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index db4abd47..208c4e28 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -10,7 +10,7 @@ var MovieList = new Class({ }, movies: [], - movies_added: [], + movies_added: {}, letters: {}, filter: { 'startswith': null, @@ -106,7 +106,7 @@ var MovieList = new Class({ m.fireEvent('injected'); self.movies.include(m) - self.movies_added.include(movie.id); + self.movies_added[movie.id] = true; }, createNavigation: function(){ From 0a9c8abcad1d75f97779baaf0cc04db225627bdf Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 00:04:50 +0200 Subject: [PATCH 22/47] Move srt files. fix #274 --- couchpotato/core/plugins/renamer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 53310c47..89c33e5e 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -211,7 +211,7 @@ class Renamer(Plugin): if file_type is 'subtitle': # rename subtitles with or without language - #rename_files[current_file] = os.path.join(destination, final_folder_name, final_file_name) + rename_files[current_file] = os.path.join(destination, final_folder_name, final_file_name) sub_langs = group['subtitle_language'].get(current_file, []) rename_extras = self.getRenameExtras( From 5ae29bd2824f25f6894e257d9dce117e46317d26 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 00:10:39 +0200 Subject: [PATCH 23/47] Rename id to tmdb_id. fix #287 --- couchpotato/core/providers/movie/themoviedb/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/movie/themoviedb/main.py b/couchpotato/core/providers/movie/themoviedb/main.py index 387fcac4..b1a1b62b 100644 --- a/couchpotato/core/providers/movie/themoviedb/main.py +++ b/couchpotato/core/providers/movie/themoviedb/main.py @@ -151,7 +151,7 @@ class TheMovieDb(MovieProvider): movie_data = { 'via_tmdb': True, - 'id': int(movie.get('id', 0)), + 'tmdb_id': int(movie.get('id', 0)), 'titles': [toUnicode(movie.get('name'))], 'original_title': movie.get('original_name'), 'images': { From 2c3e53eb1f341ba93fce20f75c9756a9d7d8206e Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 10:01:34 +0200 Subject: [PATCH 24/47] Use CP quality check for newzbin. fix #348 --- couchpotato/core/providers/nzb/newzbin/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/couchpotato/core/providers/nzb/newzbin/main.py b/couchpotato/core/providers/nzb/newzbin/main.py index c6de2a29..9239e84f 100644 --- a/couchpotato/core/providers/nzb/newzbin/main.py +++ b/couchpotato/core/providers/nzb/newzbin/main.py @@ -61,7 +61,6 @@ class Newzbin(NZBProvider, RSS): url = "%s?%s" % (self.urls['search'], arguments) cache_key = str('newzbin.%s.%s.%s' % (movie['library']['identifier'], str(format_id), str(cat_id))) - single_cat = True data = self.getCache(cache_key) if not data: @@ -118,7 +117,7 @@ class Newzbin(NZBProvider, RSS): is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality, - imdb_results = True, single_category = single_cat, single = True) + imdb_results = True, single = True) if is_correct_movie: new['score'] = fireEvent('score.calculate', new, movie, single = True) results.append(new) From 98c8a47dd0abab808aa9331d26db24e6c49615a3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 11:01:55 +0200 Subject: [PATCH 25/47] IE9 fixes --- couchpotato/core/helpers/request.py | 11 ++++++++--- couchpotato/core/plugins/movie/static/search.js | 10 +++++++--- couchpotato/static/scripts/couchpotato.js | 4 ++-- couchpotato/static/style/main.css | 1 + 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 16a2f73d..07aa18e8 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -1,7 +1,7 @@ from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.variable import natcmp from flask.globals import current_app -from flask.helpers import json +from flask.helpers import json, make_response from libs.werkzeug.urls import url_decode from urllib import unquote import flask @@ -72,6 +72,11 @@ def jsonify(mimetype, *args, **kwargs): def jsonified(*args, **kwargs): callback = getParam('callback_func', None) if callback: - return padded_jsonify(callback, *args, **kwargs) + content = padded_jsonify(callback, *args, **kwargs) else: - return jsonify('application/json', *args, **kwargs) + content = jsonify('application/json', *args, **kwargs) + + response = make_response(content) + response.cache_control.no_cache = True + + return response diff --git a/couchpotato/core/plugins/movie/static/search.js b/couchpotato/core/plugins/movie/static/search.js index 438ba9aa..bfb3b0d4 100644 --- a/couchpotato/core/plugins/movie/static/search.js +++ b/couchpotato/core/plugins/movie/static/search.js @@ -221,7 +221,9 @@ Block.Search.Item = new Class({ } }).adopt( self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', { - 'src': info.images.poster[0] + 'src': info.images.poster[0], + 'height': null, + 'width': null }) : null, new Element('div.info').adopt( self.title = new Element('h2', { @@ -332,8 +334,10 @@ Block.Search.Item = new Class({ self.options.adopt( new Element('div').adopt( - self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', { - 'src': self.info.images.poster[0] + self.option_thumbnail = self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', { + 'src': self.info.images.poster[0], + 'height': null, + 'width': null }) : null, self.info.in_wanted ? new Element('span.in_wanted', { 'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index e2c23b82..c3a63397 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -24,8 +24,8 @@ var CouchPotato = new Class({ if(window.location.hash) History.handleInitialState(); - else - self.openPage(window.location.pathname); + + self.openPage(window.location.pathname); History.addEvent('change', self.openPage.bind(self)); self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self)); diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index d580368c..884d8724 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -437,6 +437,7 @@ body > .spinner, .mask{ border-radius:3px; border: 1px solid #252930; box-shadow: inset 0 1px 0px rgba(255,255,255,0.20), 0 0 3px rgba(0,0,0, 0.2); + background: rgb(55,62,74); background-image: -webkit-gradient( linear, left bottom, From fcd13fcb85838e5c0f6b1bbef56bef3e36ec3494 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 22:55:11 +0200 Subject: [PATCH 26/47] Nonblocking update listener --- couchpotato/api.py | 32 +++++++ couchpotato/core/_base/_core/main.py | 1 - couchpotato/core/notifications/core/main.py | 87 ++++++++++++++++--- .../notifications/core/static/notification.js | 59 +++++++++---- couchpotato/core/plugins/library/main.py | 3 - couchpotato/core/plugins/movie/main.py | 1 + .../core/plugins/movie/static/movie.js | 13 ++- couchpotato/environment.py | 1 - couchpotato/runner.py | 20 +++-- couchpotato/static/scripts/page/wanted.js | 1 - 10 files changed, 170 insertions(+), 48 deletions(-) diff --git a/couchpotato/api.py b/couchpotato/api.py index b1dee1b3..a0fd69b7 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -1,10 +1,34 @@ from flask.blueprints import Blueprint from flask.helpers import url_for +from tornado.ioloop import IOLoop +from tornado.web import RequestHandler, asynchronous from werkzeug.utils import redirect api = Blueprint('api', __name__) api_docs = {} api_docs_missing = [] +api_nonblock = {} + + +class NonBlockHandler(RequestHandler): + stoppers = [] + + @asynchronous + def get(self, route): + start, stop = api_nonblock[route] + self.stoppers.append(stop) + + start(self.on_new_messages, last_id = self.get_argument("last_id", None)) + + def on_new_messages(self, response): + if self.request.connection.stream.closed(): + return + self.finish(response) + + def on_connection_close(self): + for stop in self.stoppers: + stop(self.on_new_messages) + def addApiView(route, func, static = False, docs = None, **kwargs): api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '::') if route else 'index', view_func = func, **kwargs) @@ -13,6 +37,14 @@ def addApiView(route, func, static = False, docs = None, **kwargs): else: api_docs_missing.append(route) +def addNonBlockApiView(route, func_tuple, docs = None, **kwargs): + api_nonblock[route] = func_tuple + + if docs: + api_docs[route[4:] if route[0:4] == 'api.' else route] = docs + else: + api_docs_missing.append(route) + """ Api view """ def index(): index_url = url_for('web.index') diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index c1a24c3d..23deedfa 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -114,7 +114,6 @@ class Core(Plugin): log.debug('Save to shutdown/restart') try: - Env.get('httpserver').stop() IOLoop.instance().stop() except RuntimeError: pass diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 5419049e..6a299c48 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -1,5 +1,5 @@ from couchpotato import get_session -from couchpotato.api import addApiView +from couchpotato.api import addApiView, addNonBlockApiView from couchpotato.core.event import addEvent from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.request import jsonified, getParam @@ -8,14 +8,19 @@ from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification from couchpotato.core.settings.model import Notification as Notif from sqlalchemy.sql.expression import or_ +import threading import time +import uuid log = CPLog(__name__) class CoreNotifier(Notification): + m_lock = threading.RLock() messages = [] + listeners = [] + listen_to = [ 'movie.downloaded', 'movie.snatched', 'updater.available', 'updater.updated', @@ -46,8 +51,17 @@ class CoreNotifier(Notification): }"""} }) + addNonBlockApiView('notification.listener', (self.addListener, self.removeListener)) addApiView('notification.listener', self.listener) + + def test(): + while True: + time.sleep(1) + + addEvent('app.load', test) + + def markAsRead(self): ids = [x.strip() for x in getParam('ids').split(',')] @@ -107,25 +121,79 @@ class CoreNotifier(Notification): ndict = n.to_dict() ndict['type'] = 'notification' ndict['time'] = time.time() - self.messages.append(ndict) + + self.frontend(type = listener, data = data) #db.close() return True def frontend(self, type = 'notification', data = {}): - self.messages.append({ + + self.m_lock.acquire() + message = { + 'id': str(uuid.uuid4()), 'time': time.time(), 'type': type, 'data': data, - }) + } + self.messages.append(message) + + while True and not self.shuttingDown(): + try: + listener, last_id = self.listeners.pop() + listener({ + 'success': True, + 'result': [message], + }) + except: + break + + self.m_lock.release() + + self.cleanMessages() + + def addListener(self, callback, last_id = None): + + if last_id: + messages = self.getMessages(last_id) + if len(messages) > 0: + return callback({ + 'success': True, + 'result': messages, + }) + + self.listeners.append((callback, last_id)) + + def removeListener(self, callback): + for list_tuple in self.listeners: + try: + listener, last_id = list_tuple + if listener == callback: + self.listeners.remove(list_tuple) + except: + pass + + def cleanMessages(self): + + for message in self.messages: + if message['time'] < (time.time() - 15): + self.messages.remove(message) + + def getMessages(self, last_id): + self.m_lock.acquire() + recent = [] + index = 0 + for i in xrange(len(self.messages)): + index = len(self.messages) - i - 1 + if self.messages[index]["id"] == last_id: break + recent = self.messages[index + 1:] + + self.m_lock.release() + return recent or [] def listener(self): messages = [] - for message in self.messages: - #delete message older then 15s - if message['time'] > (time.time() - 15): - messages.append(message) # Get unread if getParam('init'): @@ -139,9 +207,6 @@ class CoreNotifier(Notification): ndict['type'] = 'notification' messages.append(ndict) - #db.close() - - self.messages = [] return jsonified({ 'success': True, 'result': messages, diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index e35a394c..350c65f6 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -8,8 +8,7 @@ var NotificationBase = new Class({ self.setOptions(options); // Listener - App.addEvent('load', self.startInterval.bind(self)); - App.addEvent('unload', self.stopTimer.bind(self)); + App.addEvent('unload', self.stopPoll.bind(self)); App.addEvent('notification', self.notify.bind(self)); // Add test buttons to settings page @@ -30,7 +29,11 @@ var NotificationBase = new Class({ 'href': App.createUrl('notifications'), 'text': 'Show older notifications' })); */ - }) + }); + + window.addEvent('load', function(){ + self.startInterval() + }); }, @@ -85,35 +88,55 @@ var NotificationBase = new Class({ startInterval: function(){ var self = this; + + if(self.stopped) return; - self.request = Api.request('notification.listener', { - 'initialDelay': 100, - 'delay': 1500, + Api.request('notification.listener', { 'data': {'init':true}, 'onSuccess': self.processData.bind(self) - }) - - self.request.startTimer() + }).send() }, - startTimer: function(){ - if(this.request) - this.request.startTimer() + startPoll: function(){ + var self = this; + + if(self.stopped || (self.request && self.request.isRunning())) + return; + + self.request = Api.request('nonblock/notification.listener', { + 'onSuccess': self.processData.bind(self), + 'data': { + 'last_id': self.last_id + }, + 'onFailure': function(){ + self.startPoll.delay(2000, self) + } + }).send() + }, - stopTimer: function(){ + stopPoll: function(){ if(this.request) - this.request.stopTimer() + this.request.cancel() + this.stopped = true; }, processData: function(json){ var self = this; - self.request.options.data = {} - Array.each(json.result, function(result){ - App.fireEvent(result.type, result) - }) + + // Process data + if(json){ + Array.each(json.result, function(result){ + App.fireEvent(result.type, result) + }) + + self.last_id = json.result.getLast().id + } + + // Restart poll + self.startPoll() }, addTestButtons: function(){ diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index a7960d7c..741f48bd 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -127,9 +127,6 @@ class LibraryPlugin(Plugin): library_dict = library.to_dict(self.default_dict) - fireEvent('notify.frontend', type = 'library.update.%s' % identifier, data = library_dict) - - #db.close() return library_dict def updateReleaseDate(self, identifier): diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index ce386d14..83a20247 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -239,6 +239,7 @@ class MoviePlugin(Plugin): db = get_session() for id in getParam('id').split(','): + fireEvent('notify.frontend', type = 'movie.busy.%s' % id, data = True) movie = db.query(Movie).filter_by(id = id).first() # Get current selected title diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index c93a98ea..9877b12f 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -17,14 +17,16 @@ var Movie = new Class({ self.parent(self, options); App.addEvent('movie.update.'+data.id, self.update.bind(self)); - App.addEvent('searcher.started.'+data.id, self.searching.bind(self)); - App.addEvent('searcher.ended.'+data.id, self.searching.bind(self)); + App.addEvent('movie.busy.'+data.id, function(notification){ + if(notification.data) + self.busy(true) + }); }, - searching: function(notification){ + busy: function(set_busy){ var self = this; - if(notification && notification.type.indexOf('ended') > -1){ + if(!set_busy){ if(self.spinner){ self.mask.fade('out'); setTimeout(function(){ @@ -72,8 +74,11 @@ var Movie = new Class({ self.data = notification.data; self.container.destroy(); + self.profile = Quality.getProfile(self.data.profile_id) || {}; self.create(); + + self.busy(false); }, create: function(){ diff --git a/couchpotato/environment.py b/couchpotato/environment.py index a6f3ebb3..e804170d 100644 --- a/couchpotato/environment.py +++ b/couchpotato/environment.py @@ -23,7 +23,6 @@ class Env(object): _deamonize = False _desktop = None _session = None - _httpserver = None ''' Data paths and directories ''' _app_dir = "" diff --git a/couchpotato/runner.py b/couchpotato/runner.py index 280ef758..35a3bf96 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -1,13 +1,13 @@ from argparse import ArgumentParser from couchpotato import web -from couchpotato.api import api +from couchpotato.api import api, NonBlockHandler from couchpotato.core.event import fireEventAsync, fireEvent from couchpotato.core.helpers.variable import getDataDir, tryInt from logging import handlers from tornado import autoreload from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop -from tornado.web import RequestHandler +from tornado.web import RequestHandler, Application, FallbackHandler from tornado.wsgi import WSGIContainer from werkzeug.contrib.cache import FileSystemCache import locale @@ -227,20 +227,22 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # Go go go! web_container = WSGIContainer(app) web_container._log = _log - http_server = HTTPServer(web_container, no_keep_alive = True) - Env.set('httpserver', http_server) loop = IOLoop.instance() + application = Application([ + (r'%s/api/%s/nonblock/(.*)/' % (url_base, api_key), NonBlockHandler), + (r'.*', FallbackHandler, dict(fallback = web_container)), + ], + log_function = lambda x : None, + debug = config['use_reloader'] + ) + try_restart = True restart_tries = 5 while try_restart: try: - http_server.listen(config['port'], config['host']) - - if config['use_reloader']: - autoreload.start(loop) - + application.listen(config['port'], config['host'], no_keep_alive = True) loop.start() except Exception, e: try: diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 2ec49e2b..26d04666 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -137,7 +137,6 @@ window.addEvent('domready', function(){ var self = this; (e).preventDefault(); - self.movie.searching(); Api.request('movie.refresh', { 'data': { 'id': self.movie.get('id') From b65923b36d40d0ca116abd20ede169f464dc825a Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 23:11:58 +0200 Subject: [PATCH 27/47] Leftover print. fix #221 --- couchpotato/core/plugins/renamer/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 89c33e5e..d33ba89f 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -314,7 +314,6 @@ class Renamer(Plugin): break elif release.status_id is snatched_status.get('id'): - print release.quality.label, group['meta_data']['quality']['label'] if release.quality.id is group['meta_data']['quality']['id']: log.debug('Marking release as downloaded') release.status_id = downloaded_status.get('id') From ed1ae6e81a6d0d7b95c51f9ec8e95463a85978d6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 23:12:48 +0200 Subject: [PATCH 28/47] Aquire lock for long poll --- couchpotato/core/notifications/core/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 6a299c48..196a9ac1 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -165,6 +165,7 @@ class CoreNotifier(Notification): self.listeners.append((callback, last_id)) def removeListener(self, callback): + self.m_lock.aquire() for list_tuple in self.listeners: try: listener, last_id = list_tuple @@ -172,12 +173,15 @@ class CoreNotifier(Notification): self.listeners.remove(list_tuple) except: pass + self.m_lock.release() def cleanMessages(self): + self.m_lock.aquire() for message in self.messages: if message['time'] < (time.time() - 15): self.messages.remove(message) + self.m_lock.release() def getMessages(self, last_id): self.m_lock.acquire() From b4d98878588990721f6c4627656eb226bb735804 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 26 May 2012 23:15:01 +0200 Subject: [PATCH 29/47] Typo --- couchpotato/core/notifications/core/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 196a9ac1..b5f51eae 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -165,7 +165,7 @@ class CoreNotifier(Notification): self.listeners.append((callback, last_id)) def removeListener(self, callback): - self.m_lock.aquire() + self.m_lock.acquire() for list_tuple in self.listeners: try: listener, last_id = list_tuple @@ -177,7 +177,7 @@ class CoreNotifier(Notification): def cleanMessages(self): - self.m_lock.aquire() + self.m_lock.acquire() for message in self.messages: if message['time'] < (time.time() - 15): self.messages.remove(message) From ab7989773ad28902c09e188a9cf47bbd72c53de8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 12:47:30 +0200 Subject: [PATCH 30/47] Add truefrench to ignored words --- couchpotato/core/plugins/searcher/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py index a3f88555..7ecd29b7 100644 --- a/couchpotato/core/plugins/searcher/__init__.py +++ b/couchpotato/core/plugins/searcher/__init__.py @@ -29,7 +29,7 @@ config = [{ { 'name': 'ignored_words', 'label': 'Ignored words', - 'default': 'german, dutch, french, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub', + 'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub', }, ], }, { From a378778d10dfd9847e66c4558d45ce891fd42f71 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 12:50:40 +0200 Subject: [PATCH 31/47] Group files based on foldername. fix #352 --- couchpotato/core/plugins/scanner/main.py | 44 ++++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index bbf25239..d6057229 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -237,19 +237,51 @@ class Scanner(Plugin): # Group the files based on the identifier - for identifier, group in movie_files.iteritems(): + delete_identifiers = [] + for identifier, found_files in self.path_identifiers.iteritems(): log.debug('Grouping files on identifier: %s' % identifier) - found_files = set(self.path_identifiers.get(identifier, [])) - group['unsorted_files'].extend(found_files) + group = movie_files.get(identifier) + if group: + group['unsorted_files'].extend(found_files) + delete_identifiers.append(identifier) - # Remove the found files from the leftover stack - leftovers = leftovers - found_files + # Remove the found files from the leftover stack + leftovers = leftovers - set(found_files) # Break if CP wants to shut down if self.shuttingDown(): break + # Cleaning up used + for identifier in delete_identifiers: + del self.path_identifiers[identifier] + del delete_identifiers + + # Group based on folder + delete_identifiers = [] + for identifier, found_files in self.path_identifiers.iteritems(): + log.debug('Grouping files on foldername: %s' % identifier) + + for ff in found_files: + new_identifier = self.createStringIdentifier(os.path.dirname(ff), folder) + + group = movie_files.get(new_identifier) + if group: + group['unsorted_files'].extend([ff]) + delete_identifiers.append(identifier) + + # Remove the found files from the leftover stack + leftovers = leftovers - set([ff]) + + # Break if CP wants to shut down + if self.shuttingDown(): + break + + # Cleaning up used + for identifier in delete_identifiers: + del self.path_identifiers[identifier] + del delete_identifiers # Determine file types processed_movies = {} @@ -647,7 +679,7 @@ class Scanner(Plugin): identifier = self.removeCPTag(identifier) # groups, release tags, scenename cleaner, regex isn't correct - identifier = re.sub(self.clean, '::', simplifyString(identifier)) + identifier = re.sub(self.clean, '::', simplifyString(identifier)).strip(':') # Year year = self.findYear(identifier) From 52dd73dc88b455de26bed69b7979cdc9569072e7 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 12:54:20 +0200 Subject: [PATCH 32/47] Leftover debug code --- couchpotato/core/notifications/core/main.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index b5f51eae..162e8653 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -55,13 +55,6 @@ class CoreNotifier(Notification): addApiView('notification.listener', self.listener) - def test(): - while True: - time.sleep(1) - - addEvent('app.load', test) - - def markAsRead(self): ids = [x.strip() for x in getParam('ids').split(',')] From ca7ae89c855a55bc2db7b7ea0cea9bf20dd3f5d9 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 22:28:11 +0200 Subject: [PATCH 33/47] Imdb watchlist not working. fix #356 --- couchpotato/core/providers/automation/imdb/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/automation/imdb/main.py b/couchpotato/core/providers/automation/imdb/main.py index 1f9c62c5..7a09053c 100644 --- a/couchpotato/core/providers/automation/imdb/main.py +++ b/couchpotato/core/providers/automation/imdb/main.py @@ -30,7 +30,7 @@ class IMDB(Automation): index += 1 if not enablers[index]: continue - elif 'author_id=' in csv_url: + elif 'author_id=' not in csv_url: log.error('This isn\'t the correct url.: %s' % csv_url) continue From ac96b33611e52d81b89dd6a5a3db88b7b0ea1682 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 22:39:05 +0200 Subject: [PATCH 34/47] Check title when movie exists only thanks ipsec --- couchpotato/core/plugins/movie/main.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 83a20247..66609303 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -242,12 +242,13 @@ class MoviePlugin(Plugin): fireEvent('notify.frontend', type = 'movie.busy.%s' % id, data = True) movie = db.query(Movie).filter_by(id = id).first() - # Get current selected title - default_title = '' - for title in movie.library.titles: - if title.default: default_title = title.title - if movie: + + # Get current selected title + default_title = '' + for title in movie.library.titles: + if title.default: default_title = title.title + fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id)) From a8095d5d42eaa13f4662e229fbfc8e7213749f60 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 22:39:23 +0200 Subject: [PATCH 35/47] variable not assigned thanks ipsec --- couchpotato/core/plugins/movie/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 66609303..bcd5d63e 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -290,6 +290,7 @@ class MoviePlugin(Plugin): db = get_session() m = db.query(Movie).filter_by(library_id = library.get('id')).first() added = True + do_search = False if not m: m = Movie( library_id = library.get('id'), From 5a3919cb087f2ef38673d72c39d4971d57774c6d Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 23:28:59 +0200 Subject: [PATCH 36/47] Better locking for longpoll --- couchpotato/api.py | 15 ++++++++++----- couchpotato/core/notifications/core/main.py | 18 ++++++++++-------- .../notifications/core/static/notification.js | 10 +++++++--- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/couchpotato/api.py b/couchpotato/api.py index a0fd69b7..15ef2b4c 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -15,19 +15,24 @@ class NonBlockHandler(RequestHandler): @asynchronous def get(self, route): + cls = NonBlockHandler start, stop = api_nonblock[route] - self.stoppers.append(stop) + cls.stoppers.append(stop) - start(self.on_new_messages, last_id = self.get_argument("last_id", None)) + start(self.onNewMessage, last_id = self.get_argument("last_id", None)) - def on_new_messages(self, response): + def onNewMessage(self, response): if self.request.connection.stream.closed(): return self.finish(response) def on_connection_close(self): - for stop in self.stoppers: - stop(self.on_new_messages) + cls = NonBlockHandler + + for stop in cls.stoppers: + stop(self.onNewMessage) + + cls.stoppers = [] def addApiView(route, func, static = False, docs = None, **kwargs): diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 162e8653..d9789488 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -17,7 +17,7 @@ log = CPLog(__name__) class CoreNotifier(Notification): - m_lock = threading.RLock() + m_lock = threading.Lock() messages = [] listeners = [] @@ -124,14 +124,14 @@ class CoreNotifier(Notification): self.m_lock.acquire() message = { - 'id': str(uuid.uuid4()), + 'message_id': str(uuid.uuid4()), 'time': time.time(), 'type': type, 'data': data, } self.messages.append(message) - while True and not self.shuttingDown(): + while len(self.listeners) > 0 and not self.shuttingDown(): try: listener, last_id = self.listeners.pop() listener({ @@ -142,7 +142,6 @@ class CoreNotifier(Notification): break self.m_lock.release() - self.cleanMessages() def addListener(self, callback, last_id = None): @@ -157,8 +156,9 @@ class CoreNotifier(Notification): self.listeners.append((callback, last_id)) + def removeListener(self, callback): - self.m_lock.acquire() + for list_tuple in self.listeners: try: listener, last_id = list_tuple @@ -166,26 +166,28 @@ class CoreNotifier(Notification): self.listeners.remove(list_tuple) except: pass - self.m_lock.release() def cleanMessages(self): - self.m_lock.acquire() + for message in self.messages: if message['time'] < (time.time() - 15): self.messages.remove(message) + self.m_lock.release() def getMessages(self, last_id): self.m_lock.acquire() + recent = [] index = 0 for i in xrange(len(self.messages)): index = len(self.messages) - i - 1 - if self.messages[index]["id"] == last_id: break + if self.messages[index]["message_id"] == last_id: break recent = self.messages[index + 1:] self.m_lock.release() + return recent or [] def listener(self): diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 350c65f6..4cf71e00 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -9,6 +9,7 @@ var NotificationBase = new Class({ // Listener App.addEvent('unload', self.stopPoll.bind(self)); + App.addEvent('reload', self.startInterval.bind(self, [true])); App.addEvent('notification', self.notify.bind(self)); // Add test buttons to settings page @@ -86,10 +87,13 @@ var NotificationBase = new Class({ }, - startInterval: function(){ + startInterval: function(force){ var self = this; - if(self.stopped) return; + if(self.stopped && !force){ + self.stopped = false; + return; + } Api.request('notification.listener', { 'data': {'init':true}, @@ -132,7 +136,7 @@ var NotificationBase = new Class({ App.fireEvent(result.type, result) }) - self.last_id = json.result.getLast().id + self.last_id = json.result.getLast().message_id } // Restart poll From 767ad46d90ccc9a70dce9c5f149420a524b88435 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 27 May 2012 23:30:29 +0200 Subject: [PATCH 37/47] Reload after update --- couchpotato/core/_base/updater/static/updater.js | 5 +++-- couchpotato/static/scripts/couchpotato.js | 14 +++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/couchpotato/core/_base/updater/static/updater.js b/couchpotato/core/_base/updater/static/updater.js index 54310d7c..a2660086 100644 --- a/couchpotato/core/_base/updater/static/updater.js +++ b/couchpotato/core/_base/updater/static/updater.js @@ -82,10 +82,11 @@ var UpdaterBase = new Class({ 'onComplete': function(json){ if(json.success){ App.restart('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating'); - App.checkAvailable.delay(500, App); + App.checkAvailable.delay(500, App, [1000, function(){ + window.location.reload(); + }]); if(self.message) self.message.destroy(); - window.location.reload(); } } }); diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index c3a63397..21cd315b 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -220,18 +220,21 @@ var CouchPotato = new Class({ self.checkAvailable(3000); }, - checkAvailable: function(delay){ + checkAvailable: function(delay, onAvailable){ var self = this; (function(){ Api.request('app.available', { 'onFailure': function(){ - self.checkAvailable.delay(1000, self); + self.checkAvailable.delay(1000, self, [delay, onAvailable]); self.fireEvent('unload'); }, 'onSuccess': function(){ + if(onAvailable) + onAvailable() self.unBlockPage(); + self.fireEvent('reload'); } }); @@ -263,13 +266,6 @@ var CouchPotato = new Class({ createUrl: function(action, params){ return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '') - }, - - notify: function(options){ - return this.growl.notify({ - title: "this scrolls away", - text: "test - hello there. mouseover to pause away action" - }); } }); From 9575dff83870a4b2ca6dba565d3beb20ef1ae122 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 28 May 2012 00:26:49 +0200 Subject: [PATCH 38/47] One message missing in return --- couchpotato/core/notifications/core/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index d9789488..e2c7c6a0 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -184,7 +184,7 @@ class CoreNotifier(Notification): for i in xrange(len(self.messages)): index = len(self.messages) - i - 1 if self.messages[index]["message_id"] == last_id: break - recent = self.messages[index + 1:] + recent = self.messages[index:] self.m_lock.release() From 57b8f0e9b73bcf87d90c8239c0325e0004d2e806 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 28 May 2012 12:21:07 +0200 Subject: [PATCH 39/47] Return movie_id after rename/download. fix #362 --- couchpotato/core/plugins/scanner/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index d6057229..6fa3f7d5 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -4,7 +4,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString from couchpotato.core.helpers.variable import getExt, getImdb, tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import File +from couchpotato.core.settings.model import File, Movie from couchpotato.environment import Env from enzyme.exceptions import NoParserError, ParseError from guessit import guess_movie_info @@ -161,6 +161,8 @@ class Scanner(Plugin): except: log.error('Failed getting files from %s: %s' % (folder, traceback.format_exc())) + db = get_session() + for file_path in files: if not os.path.exists(file_path): @@ -359,6 +361,10 @@ class Scanner(Plugin): group['library'] = self.determineMovie(group) if not group['library']: log.error('Unable to determine movie: %s' % group['identifiers']) + else: + movie = db.query(Movie).filter_by(library_id = group['library']['id']).first() + group['movie_id'] = None if not movie else movie.id + processed_movies[identifier] = group From b2ccca9299c57733f672ade3cd1a6bd0abe43863 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 28 May 2012 14:05:27 +0200 Subject: [PATCH 40/47] Updated freebsd script --- init/freebsd | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/init/freebsd b/init/freebsd index 770e22cd..e3cf408e 100644 --- a/init/freebsd +++ b/init/freebsd @@ -31,24 +31,14 @@ load_rc_config ${name} : ${couchpotato_user:="_sabnzbd"} : ${couchpotato_dir:="/usr/local/couchpotato"} : ${couchpotato_chdir:="${couchpotato_dir}"} -: ${couchpotato_pid:="${couchpotato_dir}/couchpotato.pid"} - -WGET="/usr/local/bin/wget" # You need wget for this script to safely shutdown CouchPotato. -HOST="127.0.0.1" # Set CouchPotato address here. -PORT="8081" # Set CouchPotato port here. -CPAPI="" # Set CouchPotato API key +: ${couchpotato_pid:="/var/run/couchpotato.pid"} +pidfile="${couchpotato_pid}" status_cmd="${name}_status" stop_cmd="${name}_stop" command="/usr/sbin/daemon" -command_args="-f -p ${couchpotato_pid} python ${couchpotato_dir}/couchpotato.py ${couchpotato_flags}" - -# Check for wget and refuse to start without it. -if [ ! -x "${WGET}" ]; then - warn "couchpotato not started: You need wget to safely shut down CouchPotato." - exit 1 -fi +command_args="-f -p ${couchpotato_pid} python ${couchpotato_dir}/CouchPotato.py ${couchpotato_flags} --pid_file=${couchpotato_pid}" # Ensure user is root when running this script. if [ `id -u` != "0" ]; then @@ -59,19 +49,23 @@ fi verify_couchpotato_pid() { # Make sure the pid corresponds to the CouchPotato process. pid=`cat ${couchpotato_pid} 2>/dev/null` - ps -p ${pid} | grep -q "python ${couchpotato_dir}/couchpotato.py" + ps -p ${pid} | grep -q "python ${couchpotato_dir}/CouchPotato.py" return $? } # Try to stop CouchPotato cleanly by calling shutdown over http. couchpotato_stop() { + echo "Stopping $name" verify_couchpotato_pid - ${WGET} -O - -q "http://${HOST}:${PORT}/${CPAPI}/app.shutdown/" >/dev/null + if [ -n "${pid}" ]; then + kill -SIGTERM ${pid} 2> /dev/null wait_for_pids ${pid} + kill -9 ${pid} 2> /dev/null echo "Stopped" fi + } couchpotato_status() { From 523a1e7d63d4e4b83b87b4933c319d1870896ab3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 28 May 2012 20:50:21 +0200 Subject: [PATCH 41/47] Always do restart after update. fix #363 --- couchpotato/core/_base/updater/main.py | 38 ++++++++++++++----- .../core/_base/updater/static/updater.js | 26 +++++++++---- couchpotato/static/scripts/couchpotato.js | 15 +++++--- couchpotato/static/scripts/page/about.js | 2 +- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 0d7b04f6..5d2e7e2e 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -28,7 +28,7 @@ class Updater(Plugin): else: self.updater = SourceUpdater() - fireEvent('schedule.interval', 'updater.check', self.check, hours = 6) + fireEvent('schedule.interval', 'updater.check', self.autoUpdate, hours = 6) addEvent('app.load', self.check) addEvent('updater.info', self.info) @@ -48,17 +48,20 @@ class Updater(Plugin): 'return': {'type': 'see updater.info'} }) + def autoUpdate(self): + if self.check() and self.conf('automatic') and not self.updater.update_failed: + self.updater.doUpdate() + def check(self): if self.isDisabled(): return if self.updater.check(): - if self.conf('automatic') and not self.updater.update_failed: - if self.updater.doUpdate(): - fireEventAsync('app.restart') - else: - if self.conf('notification'): - fireEvent('updater.available', message = 'A new update is available', data = self.updater.info()) + if self.conf('notification') and not self.conf('automatic'): + fireEvent('updater.available', message = 'A new update is available', data = self.updater.info()) + return True + + return False def info(self): return self.updater.info() @@ -67,12 +70,22 @@ class Updater(Plugin): return jsonified(self.updater.info()) def checkView(self): - self.check() - return self.updater.getInfo() + return jsonified({ + 'update_available': self.check(), + 'info': self.updater.info() + }) def doUpdateView(self): + + self.check() + if not self.update_version: + log.error('Trying to update when no update is available.') + success = False + else: + success = self.updater.doUpdate() + return jsonified({ - 'success': self.updater.doUpdate() + 'success': success }) @@ -137,6 +150,7 @@ class GitUpdater(BaseUpdater): self.repo = LocalRepository(Env.get('app_dir'), command = git_command) def doUpdate(self): + try: log.debug('Stashing local changes') self.repo.saveStash() @@ -152,6 +166,8 @@ class GitUpdater(BaseUpdater): version_date = datetime.fromtimestamp(info['update_version']['date']) fireEvent('updater.updated', 'Updated to a new version with hash "%s", this version is from %s' % (info['update_version']['hash'], version_date), data = info) + fireEventAsync('app.restart') + return True except: log.error('Failed updating via GIT: %s' % traceback.format_exc()) @@ -243,6 +259,8 @@ class SourceUpdater(BaseUpdater): # Write update version to file self.createFile(self.version_file, json.dumps(self.update_version)) + fireEventAsync('app.restart') + return True except: log.error('Failed updating: %s' % traceback.format_exc()) diff --git a/couchpotato/core/_base/updater/static/updater.js b/couchpotato/core/_base/updater/static/updater.js index a2660086..fe0a632c 100644 --- a/couchpotato/core/_base/updater/static/updater.js +++ b/couchpotato/core/_base/updater/static/updater.js @@ -16,7 +16,15 @@ var UpdaterBase = new Class({ var self = this; Api.request('updater.check', { - 'onComplete': onComplete || Function.from() + 'onComplete': function(json){ + if(onComplete) + onComplete(json); + + if(json.update_available) + self.doUpdate(); + else + App.unBlockPage() + } }) }, @@ -81,15 +89,19 @@ var UpdaterBase = new Class({ Api.request('updater.update', { 'onComplete': function(json){ if(json.success){ - App.restart('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating'); - App.checkAvailable.delay(500, App, [1000, function(){ - window.location.reload(); - }]); - if(self.message) - self.message.destroy(); + self.updating(); } } }); + }, + + updating: function(){ + App.blockPage('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating'); + App.checkAvailable.delay(500, App, [1000, function(){ + window.location.reload(); + }]); + if(self.message) + self.message.destroy(); } }); diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 21cd315b..b983fb5a 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -24,7 +24,7 @@ var CouchPotato = new Class({ if(window.location.hash) History.handleInitialState(); - + self.openPage(window.location.pathname); History.addEvent('change', self.openPage.bind(self)); @@ -211,10 +211,10 @@ var CouchPotato = new Class({ }]); }, - checkForUpdate: function(func){ + checkForUpdate: function(onComplete){ var self = this; - Updater.check(func) + Updater.check(onComplete) self.blockPage('Please wait. If this takes to long, something must have gone wrong.', 'Checking for updates'); self.checkAvailable(3000); @@ -244,6 +244,8 @@ var CouchPotato = new Class({ blockPage: function(message, title){ var self = this; + self.unBlockPage(); + var body = $(document.body); self.mask = new Element('div.mask').adopt( new Element('div').adopt( @@ -259,9 +261,10 @@ var CouchPotato = new Class({ unBlockPage: function(){ var self = this; - self.mask.get('tween').start('opacity', 0).chain(function(){ - this.element.destroy() - }); + if(self.mask) + self.mask.get('tween').start('opacity', 0).chain(function(){ + this.element.destroy() + }); }, createUrl: function(action, params){ diff --git a/couchpotato/static/scripts/page/about.js b/couchpotato/static/scripts/page/about.js index ad0dd5b9..93687b49 100644 --- a/couchpotato/static/scripts/page/about.js +++ b/couchpotato/static/scripts/page/about.js @@ -48,7 +48,7 @@ var AboutSettingTab = new Class({ 'text': 'Getting version...', 'events': { 'click': App.checkForUpdate.bind(App, function(json){ - self.fillVersion(json) + self.fillVersion(json.info) }), 'mouseenter': function(){ this.set('text', 'Check for updates') From 4b946b9d5f7e3e67a3ce6b7af9181bbf2b99e4ae Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 28 May 2012 21:47:34 +0200 Subject: [PATCH 42/47] Use rss for imdb watchlist. fix #322 --- .../providers/automation/imdb/__init__.py | 2 +- .../core/providers/automation/imdb/main.py | 49 ++++++++---------- couchpotato/static/images/imdb_watchlist.png | Bin 32721 -> 42410 bytes 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/couchpotato/core/providers/automation/imdb/__init__.py b/couchpotato/core/providers/automation/imdb/__init__.py index 142ebab4..925138d0 100644 --- a/couchpotato/core/providers/automation/imdb/__init__.py +++ b/couchpotato/core/providers/automation/imdb/__init__.py @@ -10,7 +10,7 @@ config = [{ 'tab': 'automation', 'name': 'imdb_automation', 'label': 'IMDB', - 'description': 'From any public IMDB watchlists. Url should end with export?list_id=XXXXX&author_id=XXXXX', + 'description': 'From any public IMDB watchlists. Url should be the RSS link.', 'options': [ { 'name': 'automation_enabled', diff --git a/couchpotato/core/providers/automation/imdb/main.py b/couchpotato/core/providers/automation/imdb/main.py index 7a09053c..6364a75f 100644 --- a/couchpotato/core/providers/automation/imdb/main.py +++ b/couchpotato/core/providers/automation/imdb/main.py @@ -1,17 +1,17 @@ -from couchpotato.core.helpers.variable import md5 +from couchpotato.core.helpers.rss import RSS +from couchpotato.core.helpers.variable import md5, getImdb from couchpotato.core.logger import CPLog from couchpotato.core.providers.automation.base import Automation from couchpotato.environment import Env from dateutil.parser import parse -import StringIO -import csv import time import traceback +import xml.etree.ElementTree as XMLTree log = CPLog(__name__) -class IMDB(Automation): +class IMDB(Automation, RSS): interval = 1800 @@ -21,46 +21,41 @@ class IMDB(Automation): return movies = [] - headers = {} enablers = self.conf('automation_urls_use').split(',') index = -1 - for csv_url in self.conf('automation_urls').split(','): + for rss_url in self.conf('automation_urls').split(','): + index += 1 if not enablers[index]: continue - elif 'author_id=' not in csv_url: - log.error('This isn\'t the correct url.: %s' % csv_url) + elif 'rss.imdb' not in rss_url: + log.error('This isn\'t the correct url.: %s' % rss_url) continue - prop_name = 'automation.imdb.last_update.%s' % md5(csv_url) + prop_name = 'automation.imdb.last_update.%s' % md5(rss_url) last_update = float(Env.prop(prop_name, default = 0)) try: - cache_key = 'imdb_csv.%s' % md5(csv_url) - csv_data = self.getCache(cache_key, csv_url) - csv_reader = csv.reader(StringIO.StringIO(csv_data)) - if not headers: - nr = 0 - for column in csv_reader.next(): - headers[column] = nr - nr += 1 - else: - csv_reader.next() + cache_key = 'imdb.rss.%s' % md5(rss_url) - for row in csv_reader: - created = int(time.mktime(parse(row[headers['created']]).timetuple())) - if created < last_update: + rss_data = self.getCache(cache_key, rss_url) + data = XMLTree.fromstring(rss_data) + rss_movies = self.getElements(data, 'channel/item') + + for movie in rss_movies: + created = int(time.mktime(parse(self.getTextElement(movie, "pubDate")).timetuple())) + imdb = getImdb(self.getTextElement(movie, "link")) + + if not imdb or created < last_update: continue - imdb = row[headers['const']] - if imdb: - movies.append(imdb) + movies.append(imdb) + except: - log.error('Failed loading IMDB watchlist: %s %s' % (csv_url, traceback.format_exc())) + log.error('Failed loading IMDB watchlist: %s %s' % (rss_url, traceback.format_exc())) Env.prop(prop_name, time.time()) - return movies diff --git a/couchpotato/static/images/imdb_watchlist.png b/couchpotato/static/images/imdb_watchlist.png index b16fd0940abf5d140eba24c8c0d8e5b7d432e488..fc4158bd8e46efb88d00a7e382a1528a67ccf2cf 100644 GIT binary patch delta 42021 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 u3AYf^fi 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 Date: Mon, 28 May 2012 22:02:05 +0200 Subject: [PATCH 43/47] Quality list not wide enough. fix #351 --- couchpotato/core/plugins/movie/static/movie.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index 6365f798..f626f2ab 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -89,7 +89,7 @@ font-size: 16px; font-weight: normal; text-overflow: ellipsis; - width: 64%; + width: auto; } .movies .info .year { @@ -152,7 +152,6 @@ .movies .list_view .data .quality, .movies .mass_edit_view .data .quality { text-align: right; float: right; - width: 30%; } .movies .data .quality .available, .movies .data .quality .snatched { @@ -200,6 +199,7 @@ .movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions { margin: -34px 2px 0 0; background: #4e5969; + position: relative; } .movies .delete_container { From e62b177940f1b41555804f1f8d5263672712fdb3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 28 May 2012 22:05:40 +0200 Subject: [PATCH 44/47] Add cd number to renaming options --- couchpotato/core/plugins/renamer/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py index 27566d17..9ceccb8b 100644 --- a/couchpotato/core/plugins/renamer/__init__.py +++ b/couchpotato/core/plugins/renamer/__init__.py @@ -20,6 +20,8 @@ rename_options = { 'original': 'Original filename', 'original_folder': 'Original foldername', 'imdb_id': 'IMDB id (tt0123456)', + 'cd': 'CD number (cd1)', + 'cd_nr': 'Just the cd nr. (1)', }, } From ef8dfc082c49de9d347999f5e1557f8cd916b2ec Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 28 May 2012 22:09:21 +0200 Subject: [PATCH 45/47] Don't get last of empty list --- couchpotato/core/notifications/core/static/notification.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 4cf71e00..d11b6400 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -136,7 +136,8 @@ var NotificationBase = new Class({ App.fireEvent(result.type, result) }) - self.last_id = json.result.getLast().message_id + if(json.result.length > 0) + self.last_id = json.result.getLast().message_id } // Restart poll From d935fbb82b1f3a2d9fb44ec6a7c7695947bc4cc3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 29 May 2012 22:02:06 +0200 Subject: [PATCH 46/47] Navigate renaming choices with keyboard --- couchpotato/static/scripts/page/settings.js | 106 +++++++++++++++++++- couchpotato/static/style/page/settings.css | 14 +-- 2 files changed, 112 insertions(+), 8 deletions(-) diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 39ef9465..8bf04053 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -909,6 +909,7 @@ Option.Choice = new Class({ var input = self.tag_input.getElement('li:last-child input'); input.fireEvent('focus'); input.focus(); + input.setCaretPosition(input.get('value').length); } self.el.addEvent('outerClick', function(){ @@ -965,6 +966,12 @@ Option.Choice = new Class({ 'onChange': self.setOrder.bind(self), 'onBlur': function(){ self.addLastTag(); + }, + 'onGoLeft': function(){ + self.goLeft(this) + }, + 'onGoRight': function(){ + self.goRight(this) } }); $(tag).inject(self.tag_input); @@ -979,6 +986,30 @@ Option.Choice = new Class({ return tag; }, + goLeft: function(from_tag){ + var self = this; + + from_tag.blur(); + + var prev_index = self.tags.indexOf(from_tag)-1; + if(prev_index >= 0) + self.tags[prev_index].selectFrom('right') + else + from_tag.focus(); + + }, + goRight: function(from_tag){ + var self = this; + + from_tag.blur(); + + var next_index = self.tags.indexOf(from_tag)+1; + if(next_index < self.tags.length) + self.tags[next_index].selectFrom('left') + else + from_tag.focus(); + }, + setOrder: function(){ var self = this; @@ -1059,7 +1090,16 @@ Option.Choice.Tag = new Class({ 'width': 0 }, 'events': { - 'keyup': self.is_choice ? null : function(){ + 'keyup': self.is_choice ? null : function(e){ + var current_caret_pos = self.input.getCaretPosition(); + if(e.key == 'left' && current_caret_pos == self.last_caret_pos){ + self.fireEvent('goLeft'); + } + else if (e.key == 'right' && self.last_caret_pos === current_caret_pos){ + self.fireEvent('goRight'); + } + self.last_caret_pos = self.input.getCaretPosition(); + self.setWidth(); self.fireEvent('change'); }, @@ -1081,8 +1121,70 @@ Option.Choice.Tag = new Class({ }, + blur: function(){ + var self = this; + + self.input.blur(); + + self.selected = false; + self.el.removeClass('selected'); + self.input.removeEvents('outerClick'); + }, + focus: function(){ - this.input.focus(); + var self = this; + if(!self.is_choice){ + this.input.focus(); + } + else { + if(self.selected) return; + self.selected = true; + self.el.addClass('selected'); + self.input.addEvent('outerClick', self.blur.bind(self)); + + var temp_input = new Element('input', { + 'events': { + 'keyup': function(e){ + e.stop(); + + if(e.key == 'right'){ + self.fireEvent('goRight'); + this.destroy(); + } + else if (e.key == 'left'){ + self.fireEvent('goLeft'); + this.destroy(); + } + else if (e.key == 'backspace'){ + self.del(); + this.destroy(); + } + } + }, + 'styles': { + 'height': 0, + 'width': 0, + 'position': 'absolute', + 'top': -200 + } + }); + self.el.adopt(temp_input) + temp_input.focus(); + } + }, + + selectFrom: function(direction){ + var self = this; + + if(!direction || self.is_choice){ + self.focus(); + } + else { + self.focus(); + var position = direction == 'left' ? 0 : self.input.get('value').length; + self.input.setCaretPosition(position); + } + }, setWidth: function(){ diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/page/settings.css index d85279be..59a03c82 100644 --- a/couchpotato/static/style/page/settings.css +++ b/couchpotato/static/style/page/settings.css @@ -362,28 +362,29 @@ border-radius: 2px; } .page .tag_input > ul:hover > li.choice { - background: url('../../images/sprite.png') no-repeat 94% -53px, -webkit-gradient( + background: -webkit-gradient( linear, left bottom, left top, color-stop(0, rgba(255,255,255,0.1)), color-stop(1, rgba(255,255,255,0.3)) ); - background: url('../../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient( + background: -moz-linear-gradient( center top, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.1) 100% ); } - .page .tag_input > ul > li.choice:hover { - background: url('../../images/sprite.png') no-repeat 94% -53px, -webkit-gradient( + .page .tag_input > ul > li.choice:hover, + .page .tag_input > ul > li.choice.selected { + background: -webkit-gradient( linear, left bottom, left top, color-stop(0, #406db8), color-stop(1, #5b9bd1) ); - background: url('../../images/sprite.png') no-repeat 94% -53px, -moz-linear-gradient( + background: -moz-linear-gradient( center top, #5b9bd1 0%, #406db8 100% @@ -436,7 +437,8 @@ ); background-size: 65%; } - .page .tag_input .choice:hover .delete { display: inline-block; } + .page .tag_input .choice:hover .delete, + .page .tag_input .choice.selected .delete { display: inline-block; } .page .tag_input .choice .delete:hover { height: 14px; margin-top: -13px; From 95e5282e2c2c2ebb775cfe3f0256003d71943ddc Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 29 May 2012 22:38:49 +0200 Subject: [PATCH 47/47] Delete all movies from view in mass edit. fix #371 --- couchpotato/core/plugins/movie/static/list.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index 208c4e28..52afbc20 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -287,14 +287,19 @@ var MovieList = new Class({ 'onSuccess': function(){ qObj.close(); + var erase_movies = []; self.movies.each(function(movie){ if (movie.isSelected()){ $(movie).destroy() - self.movies.erase(movie) + erase_movies.include(movie) } }); - self.calculateSelected() + erase_movies.each(function(movie){ + self.movies.erase(movie); + }); + + self.calculateSelected(); } });