diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 5b61aebe..38b7d36e 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -85,7 +85,9 @@ class Updater(Plugin): if self.updater.check(): if not self.available_notified and self.conf('notification') and not self.conf('automatic'): - fireEvent('updater.available', message = 'A new update is available', data = self.updater.info()) + info = self.updater.info() + version_date = datetime.fromtimestamp(info['update_version']['date']) + fireEvent('updater.available', message = 'A new update with hash "%s" is available, this version is from %s' % (info['update_version']['hash'], version_date), data = info) self.available_notified = True return True diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 0ac84ef8..c2249796 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -32,6 +32,8 @@ def getParams(params): current = current[item] else: temp[param] = toUnicode(unquote(value)) + if temp[param].lower() in ['true', 'false']: + temp[param] = temp[param].lower() != 'false' return dictToList(temp) diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 70c4490d..df21fefa 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -15,38 +15,8 @@ log = CPLog(__name__) class Dashboard(Plugin): def __init__(self): - - addApiView('dashboard.suggestions', self.suggestView) addApiView('dashboard.soon', self.getSoonView) - def newSuggestions(self): - - movies = fireEvent('movie.list', status = ['active', 'done'], limit_offset = (20, 0), single = True) - movie_identifiers = [m['library']['identifier'] for m in movies[1]] - - ignored_movies = fireEvent('movie.list', status = ['ignored', 'deleted'], limit_offset = (100, 0), single = True) - ignored_identifiers = [m['library']['identifier'] for m in ignored_movies[1]] - - suggestions = fireEvent('movie.suggest', movies = movie_identifiers, ignore = ignored_identifiers, single = True) - suggest_status = fireEvent('status.get', 'suggest', single = True) - - for suggestion in suggestions: - fireEvent('movie.add', params = {'identifier': suggestion}, force_readd = False, search_after = False, status_id = suggest_status.get('id')) - - def suggestView(self): - - db = get_session() - - movies = db.query(Movie).limit(20).all() - identifiers = [m.library.identifier for m in movies] - - suggestions = fireEvent('movie.suggest', movies = identifiers, single = True) - - return { - 'result': True, - 'suggestions': suggestions - } - def getSoonView(self, limit_offset = None, random = False, late = False, **kwargs): db = get_session() @@ -100,9 +70,9 @@ class Dashboard(Plugin): coming_soon = False # Theater quality - if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, single = True): + if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, movie.library.year, single = True): coming_soon = True - if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, single = True): + if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, movie.library.year, single = True): coming_soon = True # Skip if movie is snatched/downloaded/available @@ -123,8 +93,8 @@ class Dashboard(Plugin): }) # Don't list older movies - if ((not late and (not eta.get('dvd') or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \ - (late and eta.get('dvd') and eta.get('dvd') < (now - 2419200))): + if ((not late and ((not eta.get('dvd') and not eta.get('theater')) or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \ + (late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))): movies.append(temp) if len(movies) >= limit: diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index 168aead1..b463abfd 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -1,7 +1,6 @@ from couchpotato import get_session from couchpotato.core.event import addEvent, fireEventAsync, fireEvent from couchpotato.core.helpers.encoding import toUnicode, simplifyString -from couchpotato.core.helpers.variable import mergeDicts from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.core.settings.model import Library, LibraryTitle, File @@ -32,7 +31,8 @@ class LibraryPlugin(Plugin): identifier = attrs.get('identifier'), plot = toUnicode(attrs.get('plot')), tagline = toUnicode(attrs.get('tagline')), - status_id = status.get('id') + status_id = status.get('id'), + info = {}, ) title = LibraryTitle( @@ -87,7 +87,7 @@ class LibraryPlugin(Plugin): library.tagline = toUnicode(info.get('tagline', '')) library.year = info.get('year', 0) library.status_id = done_status.get('id') - library.info = info + library.info.update(info) db.commit() # Titles @@ -148,7 +148,7 @@ class LibraryPlugin(Plugin): if dates and dates.get('expires', 0) < time.time() or not dates: dates = fireEvent('movie.release_date', identifier = identifier, merge = True) - library.info = mergeDicts(library.info, {'release_date': dates }) + library.info.update({'release_date': dates }) db.commit() db.expire_all() diff --git a/couchpotato/core/plugins/movie/static/movie.actions.js b/couchpotato/core/plugins/movie/static/movie.actions.js index e0552792..a0f7bad5 100644 --- a/couchpotato/core/plugins/movie/static/movie.actions.js +++ b/couchpotato/core/plugins/movie/static/movie.actions.js @@ -1,9 +1,13 @@ var MovieAction = new Class({ + + Implements: [Options], class_name: 'action icon2', - initialize: function(movie){ + initialize: function(movie, options){ var self = this; + self.setOptions(options); + self.movie = movie; self.create(); @@ -21,6 +25,32 @@ var MovieAction = new Class({ this.el.removeClass('disable') }, + getTitle: function(){ + var self = this; + + try { + return self.movie.getTitle(); + } + catch(e){ + try { + return self.movie.original_title ? self.movie.original_title : self.movie.titles[0]; + } + catch(e){ + return 'Unknown'; + } + } + }, + + get: function(key){ + var self = this; + try { + return self.movie.get(key) + } + catch(e){ + return self.movie[key] + } + }, + createMask: function(){ var self = this; self.mask = new Element('div.mask', { @@ -62,10 +92,10 @@ MA.IMDB = new Class({ create: function(){ var self = this; - self.id = self.movie.get('identifier'); + self.id = self.movie.get('imdb') || self.movie.get('identifier'); self.el = new Element('a.imdb', { - 'title': 'Go to the IMDB page of ' + self.movie.getTitle(), + 'title': 'Go to the IMDB page of ' + self.getTitle(), 'href': 'http://www.imdb.com/title/'+self.id+'/', 'target': '_blank' }); @@ -83,7 +113,7 @@ MA.Release = new Class({ var self = this; self.el = new Element('a.releases.download', { - 'title': 'Show the releases that are available for ' + self.movie.getTitle(), + 'title': 'Show the releases that are available for ' + self.getTitle(), 'events': { 'click': self.show.bind(self) } @@ -367,7 +397,7 @@ MA.Trailer = new Class({ var self = this; self.el = new Element('a.trailer', { - 'title': 'Watch the trailer of ' + self.movie.getTitle(), + 'title': 'Watch the trailer of ' + self.getTitle(), 'events': { 'click': self.watch.bind(self) } @@ -380,12 +410,12 @@ MA.Trailer = new Class({ var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18' var url = data_url.substitute({ - 'title': encodeURI(self.movie.getTitle()), - 'year': self.movie.get('year'), + 'title': encodeURI(self.getTitle()), + 'year': self.get('year'), 'offset': offset || 1 }), size = $(self.movie).getSize(), - height = (size.x/16)*9, + height = self.options.height || (size.x/16)*9, id = 'trailer-'+randomString(); self.player_container = new Element('div[id='+id+']'); diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index 7d2054d4..60ab96b2 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -629,7 +629,7 @@ height: auto; } - .movies .movie .trailer_container { + .trailer_container { width: 100%; background: #000; text-align: center; @@ -639,11 +639,11 @@ position: absolute; z-index: 10; } - .movies .movie .trailer_container.hide { + .trailer_container.hide { height: 0 !important; } - .movies .movie .hide_trailer { + .hide_trailer { position: absolute; top: 0; left: 50%; @@ -655,7 +655,7 @@ transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s; z-index: 11; } - .movies .movie .hide_trailer.hide { + .hide_trailer.hide { top: -30px; } @@ -845,7 +845,7 @@ font-family: 'Elusive-Icons'; content: "\e03e"; position: absolute; - height: 100%; + height: 20px; line-height: 45px; font-size: 12px; margin: 0 0 0 10px; diff --git a/couchpotato/core/plugins/movie/static/search.css b/couchpotato/core/plugins/movie/static/search.css index 82fd13cc..e2aa0a47 100644 --- a/couchpotato/core/plugins/movie/static/search.css +++ b/couchpotato/core/plugins/movie/static/search.css @@ -193,7 +193,7 @@ transition: all .4s cubic-bezier(0.9,0,0.1,1); } .movie_result .data.open { - left: 100%; + left: 100% !important; } .movie_result:last-child .data { border-bottom: 0; } diff --git a/couchpotato/core/plugins/movie/static/search.js b/couchpotato/core/plugins/movie/static/search.js index 8fa4fb0c..5530505f 100644 --- a/couchpotato/core/plugins/movie/static/search.js +++ b/couchpotato/core/plugins/movie/static/search.js @@ -185,8 +185,11 @@ Block.Search = new Class({ Block.Search.Item = new Class({ + Implements: [Options, Events], + initialize: function(info, options){ var self = this; + self.setOptions(options); self.info = info; self.alternative_titles = []; @@ -208,17 +211,13 @@ Block.Search.Item = new Class({ }) : null, self.options_el = new Element('div.options.inlay'), self.data_container = new Element('div.data', { - 'tween': { - duration: 400, - transition: 'quint:in:out' - }, 'events': { 'click': self.showOptions.bind(self) } }).adopt( new Element('div.info').adopt( self.title = new Element('h2', { - 'text': info.titles[0] + 'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown' }).adopt( self.year = info.year ? new Element('span.year', { 'text': info.year @@ -228,12 +227,12 @@ Block.Search.Item = new Class({ ) ) - - info.titles.each(function(title){ - self.alternativeTitle({ - 'title': title - }); - }) + if(info.titles) + info.titles.each(function(title){ + self.alternativeTitle({ + 'title': title + }); + }) }, alternativeTitle: function(alternative){ @@ -242,6 +241,20 @@ Block.Search.Item = new Class({ self.alternative_titles.include(alternative); }, + getTitle: function(){ + var self = this; + try { + return self.info.original_title ? self.info.original_title : self.info.titles[0]; + } + catch(e){ + return 'Unknown'; + } + }, + + get: function(key){ + return this.info[key] + }, + showOptions: function(){ var self = this; @@ -279,6 +292,8 @@ Block.Search.Item = new Class({ }) ); self.mask.fade('out'); + + self.fireEvent('added'); }, 'onFailure': function(){ self.options_el.empty(); diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index ead8446e..6ac1c6b6 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -17,8 +17,8 @@ class QualityPlugin(Plugin): qualities = [ {'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]}, - {'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']}, - {'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']}, + {'identifier': '1080p', 'hd': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']}, + {'identifier': '720p', 'hd': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']}, {'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']}, {'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']}, {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]}, diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py index aec419a2..bed90eb2 100644 --- a/couchpotato/core/plugins/searcher/__init__.py +++ b/couchpotato/core/plugins/searcher/__init__.py @@ -57,6 +57,14 @@ config = [{ 'advanced': True, 'description': 'Cron settings for the searcher see: APScheduler for details.', 'options': [ + { + 'name': 'run_on_launch', + 'label': 'Run on launch', + 'advanced': True, + 'default': 0, + 'type': 'bool', + 'description': 'Force run the searcher after (re)start.', + }, { 'name': 'cron_day', 'label': 'Day', diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 3d85a565..cc2774ac 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -8,6 +8,7 @@ from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.core.settings.model import Movie, Release, ReleaseInfo from couchpotato.environment import Env +from datetime import date from inspect import ismethod, isfunction from sqlalchemy.exc import InterfaceError import datetime @@ -49,6 +50,9 @@ class Searcher(Plugin): }"""}, }) + if self.conf('run_on_launch'): + addEvent('app.load', self.allMovies) + addEvent('app.load', self.setCrons) addEvent('setting.save.searcher.cron_day.after', self.setCrons) addEvent('setting.save.searcher.cron_hour.after', self.setCrons) @@ -161,7 +165,7 @@ class Searcher(Plugin): ret = False for quality_type in movie['profile']['types']: - if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates): + if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): too_early_to_search.append(quality_type['quality']['identifier']) continue @@ -555,11 +559,12 @@ class Searcher(Plugin): return False - def couldBeReleased(self, is_pre_release, dates): + def couldBeReleased(self, is_pre_release, dates, year = None): now = int(time.time()) + now_year = date.today().year - if not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0): + if (year is None or year < now_year - 1) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)): return True else: diff --git a/couchpotato/core/plugins/suggestion/main.py b/couchpotato/core/plugins/suggestion/main.py index a22b6ee6..e913bb6c 100644 --- a/couchpotato/core/plugins/suggestion/main.py +++ b/couchpotato/core/plugins/suggestion/main.py @@ -1,20 +1,92 @@ +from couchpotato import get_session from couchpotato.api import addApiView from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.encoding import ss +from couchpotato.core.helpers.variable import splitString, md5 from couchpotato.core.plugins.base import Plugin +from couchpotato.core.settings.model import Movie +from couchpotato.environment import Env +from sqlalchemy.sql.expression import or_ class Suggestion(Plugin): def __init__(self): - addApiView('suggestion.view', self.getView) + addApiView('suggestion.view', self.suggestView) + addApiView('suggestion.ignore', self.ignoreView) - def getView(self, limit_offset = None, **kwargs): + def suggestView(self, **kwargs): - total_movies, movies = fireEvent('movie.list', status = 'suggest', limit_offset = limit_offset, single = True) + movies = splitString(kwargs.get('movies', '')) + ignored = splitString(kwargs.get('ignored', '')) + limit = kwargs.get('limit', 6) + + if not movies or len(movies) == 0: + db = get_session() + active_movies = db.query(Movie) \ + .filter(or_(*[Movie.status.has(identifier = s) for s in ['active', 'done']])).all() + movies = [x.library.identifier for x in active_movies] + + if not ignored or len(ignored) == 0: + ignored = splitString(Env.prop('suggest_ignore', default = '')) + + cached_suggestion = self.getCache('suggestion_cached') + if cached_suggestion: + suggestions = cached_suggestion + else: + suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True) + self.setCache(md5(ss('suggestion_cached')), suggestions, timeout = 6048000) # Cache for 10 weeks return { 'success': True, - 'empty': len(movies) == 0, - 'total': total_movies, - 'movies': movies, + 'count': len(suggestions), + 'suggestions': suggestions[:limit] } + + def ignoreView(self, imdb = None, limit = 6, remove_only = False, **kwargs): + + ignored = splitString(Env.prop('suggest_ignore', default = '')) + + if imdb: + if not remove_only: + ignored.append(imdb) + Env.prop('suggest_ignore', ','.join(set(ignored))) + + new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored) + + return { + 'result': True, + 'ignore_count': len(ignored), + 'suggestions': new_suggestions[limit - 1:limit] + } + + def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None): + + # Combine with previous suggestion_cache + cached_suggestion = self.getCache('suggestion_cached') + new_suggestions = [] + + if ignore_imdb: + for cs in cached_suggestion: + if cs.get('imdb') != ignore_imdb: + new_suggestions.append(cs) + + # Get new results and add them + if len(new_suggestions) - 1 < limit: + + db = get_session() + active_movies = db.query(Movie) \ + .filter(or_(*[Movie.status.has(identifier = s) for s in ['active', 'done']])).all() + movies = [x.library.identifier for x in active_movies] + + if ignored: + ignored.extend([x.get('imdb') for x in new_suggestions]) + + suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True) + + if suggestions: + new_suggestions.extend(suggestions) + + self.setCache(md5(ss('suggestion_cached')), new_suggestions, timeout = 6048000) + + return new_suggestions diff --git a/couchpotato/core/plugins/suggestion/static/suggest.css b/couchpotato/core/plugins/suggestion/static/suggest.css new file mode 100644 index 00000000..95e12b9e --- /dev/null +++ b/couchpotato/core/plugins/suggestion/static/suggest.css @@ -0,0 +1,84 @@ +.suggestions { +} + + .suggestions > h2 { + height: 40px; + } + +.suggestions .movie_result { + display: inline-block; + width: 33.333%; + height: 150px; +} + + @media all and (max-width: 960px) { + .suggestions .movie_result { + width: 50%; + } + } + + @media all and (max-width: 600px) { + .suggestions .movie_result { + width: 100%; + } + } + + .suggestions .movie_result .data { + left: 100px; + background: #4e5969; + border: none; + } + + .suggestions .movie_result .data .info { + top: 15px; + left: 15px; + right: 15px; + } + + .suggestions .movie_result .data .info h2 { + white-space: normal; + max-height: 120px; + font-size: 18px; + line-height: 18px; + } + + .suggestions .movie_result .data .info .year { + position: static; + display: block; + margin: 5px 0 0; + padding: 0; + opacity: .6; + } + + .suggestions .movie_result .data { + cursor: default; + } + + .suggestions .movie_result .options { + left: 100px; + } + + .suggestions .movie_result .thumbnail { + width: 100px; + } + + .suggestions .movie_result .actions { + position: absolute; + bottom: 10px; + right: 10px; + display: none; + width: 120px; + } + .suggestions .movie_result:hover .actions { + display: block; + } + .suggestions .movie_result .data.open .actions { + display: none; + } + + .suggestions .movie_result .actions a { + margin-left: 10px; + vertical-align: middle; + } + + \ No newline at end of file diff --git a/couchpotato/core/plugins/suggestion/static/suggest.js b/couchpotato/core/plugins/suggestion/static/suggest.js new file mode 100644 index 00000000..5be7d139 --- /dev/null +++ b/couchpotato/core/plugins/suggestion/static/suggest.js @@ -0,0 +1,102 @@ +var SuggestList = new Class({ + + Implements: [Options, Events], + + initialize: function(options){ + var self = this; + self.setOptions(options); + + self.create(); + }, + + create: function(){ + var self = this; + + self.el = new Element('div.suggestions', { + 'events': { + 'click:relay(a.delete)': function(e, el){ + (e).stop(); + + $(el).getParent('.movie_result').destroy(); + + Api.request('suggestion.ignore', { + 'data': { + 'imdb': el.get('data-ignore') + }, + 'onComplete': self.fill.bind(self) + }); + + } + } + }).grab( + new Element('h2', { + 'text': 'You might like these' + }) + ); + + self.api_request = Api.request('suggestion.view', { + 'onComplete': self.fill.bind(self) + }); + + }, + + fill: function(json){ + + var self = this; + + Object.each(json.suggestions, function(movie){ + + var m = new Block.Search.Item(movie, { + 'onAdded': function(){ + self.afterAdded(m, movie) + } + }); + m.data_container.grab( + new Element('div.actions').adopt( + new Element('a.add.icon2', { + 'title': 'Add movie with your default quality', + 'data-add': movie.imdb, + 'events': { + 'click': m.showOptions.bind(m) + } + }), + $(new MA.IMDB(m)), + $(new MA.Trailer(m, { + 'height': 150 + })), + new Element('a.delete.icon2', { + 'title': 'Don\'t suggest this movie again', + 'data-ignore': movie.imdb + }) + ) + ); + m.data_container.removeEvents('click'); + $(m).inject(self.el); + + }); + + }, + + afterAdded: function(m, movie){ + var self = this; + + setTimeout(function(){ + $(m).destroy(); + + Api.request('suggestion.ignore', { + 'data': { + 'imdb': movie.imdb, + 'remove_only': true + }, + 'onComplete': self.fill.bind(self) + }); + + }, 3000); + + }, + + toElement: function(){ + return this.el; + } + +}) diff --git a/couchpotato/core/providers/automation/base.py b/couchpotato/core/providers/automation/base.py index ea227ce0..e57f5c63 100644 --- a/couchpotato/core/providers/automation/base.py +++ b/couchpotato/core/providers/automation/base.py @@ -13,7 +13,7 @@ class Automation(Provider): enabled_option = 'automation_enabled' http_time_between_calls = 2 - interval = 86400 + interval = 1800 last_checked = 0 def __init__(self): diff --git a/couchpotato/core/providers/automation/goodfilms/main.py b/couchpotato/core/providers/automation/goodfilms/main.py index 266a284c..e1125615 100644 --- a/couchpotato/core/providers/automation/goodfilms/main.py +++ b/couchpotato/core/providers/automation/goodfilms/main.py @@ -9,6 +9,8 @@ class Goodfilms(Automation): url = 'http://goodfil.ms/%s/queue?page=%d&without_layout=1' + interval = 1800 + def getIMDBids(self): if not self.conf('automation_username'): diff --git a/couchpotato/core/providers/automation/letterboxd/main.py b/couchpotato/core/providers/automation/letterboxd/main.py index 7bae2ad5..1f106dd1 100644 --- a/couchpotato/core/providers/automation/letterboxd/main.py +++ b/couchpotato/core/providers/automation/letterboxd/main.py @@ -12,6 +12,8 @@ class Letterboxd(Automation): url = 'http://letterboxd.com/%s/watchlist/' pattern = re.compile(r'(.*)\((\d*)\)') + interval = 1800 + def getIMDBids(self): urls = splitString(self.conf('automation_urls')) diff --git a/couchpotato/core/providers/movie/_modifier/main.py b/couchpotato/core/providers/movie/_modifier/main.py index 148d9035..e4d70221 100644 --- a/couchpotato/core/providers/movie/_modifier/main.py +++ b/couchpotato/core/providers/movie/_modifier/main.py @@ -28,7 +28,6 @@ class MovieResultModifier(Plugin): 'tagline': '', 'imdb': '', 'genres': [], - 'release_date': {} } def __init__(self): diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 2905bea0..9f76381a 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -1,9 +1,7 @@ -from couchpotato import get_session from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.logger import CPLog from couchpotato.core.providers.movie.base import MovieProvider -from couchpotato.core.settings.model import Movie from couchpotato.environment import Env import time @@ -28,7 +26,7 @@ class CouchPotatoApi(MovieProvider): addEvent('movie.info', self.getInfo, priority = 1) addEvent('movie.search', self.search, priority = 1) addEvent('movie.release_date', self.getReleaseDate) - addEvent('movie.suggest', self.suggest) + addEvent('movie.suggest', self.getSuggestions) addEvent('movie.is_movie', self.isMovie) addEvent('cp.source_url', self.getSourceUrl) @@ -82,33 +80,15 @@ class CouchPotatoApi(MovieProvider): return dates - def suggest(self, movies = [], ignore = []): + def getSuggestions(self, movies = [], ignore = []): suggestions = self.getJsonData(self.urls['suggest'], params = { 'movies': ','.join(movies), 'ignore': ','.join(ignore), - }) - log.info('Found Suggestions for %s', (suggestions)) + }, headers = self.getRequestHeaders()) + log.info('Found suggestions for %s movies, %s ignored', (len(movies), len(ignore))) return suggestions - def suggestView(self, **kwargs): - - movies = kwargs.get('movies') - ignore = kwargs.get('ignore', []) - - if not movies: - db = get_session() - active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all() - movies = [x.library.identifier for x in active_movies] - - suggestions = self.suggest(movies, ignore) - - return { - 'success': True, - 'count': len(suggestions), - 'suggestions': suggestions - } - def getRequestHeaders(self): return { 'X-CP-Version': fireEvent('app.version', single = True), diff --git a/couchpotato/core/providers/nzb/nzbindex/main.py b/couchpotato/core/providers/nzb/nzbindex/main.py index 3643f55b..17b87fac 100644 --- a/couchpotato/core/providers/nzb/nzbindex/main.py +++ b/couchpotato/core/providers/nzb/nzbindex/main.py @@ -23,7 +23,7 @@ class NzbIndex(NZBProvider, RSS): def _searchOnTitle(self, title, movie, quality, results): - q = '"%s %s"' % (title, movie['library']['year']) + q = '"%s %s" | "%s (%s)"' % (title, movie['library']['year'], title, movie['library']['year']) arguments = tryUrlencode({ 'q': q, 'age': Env.setting('retention', 'nzb'), diff --git a/couchpotato/core/providers/torrent/yify/__init__.py b/couchpotato/core/providers/torrent/yify/__init__.py new file mode 100644 index 00000000..70d65687 --- /dev/null +++ b/couchpotato/core/providers/torrent/yify/__init__.py @@ -0,0 +1,33 @@ +from main import Yify + +def start(): + return Yify() + +config = [{ + 'name': 'yify', + 'groups': [ + { + 'tab': 'searcher', + 'subtab': 'providers', + 'list': 'torrent_providers', + 'name': 'Yify', + 'description': 'Free provider, less accurate. Small HD movies, encoded by Yify.', + 'wizard': False, + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': 0 + }, + { + 'name': 'extra_score', + 'advanced': True, + 'label': 'Extra Score', + 'type': 'int', + 'default': 0, + 'description': 'Starting score for each release found via this provider.', + } + ], + } + ] +}] diff --git a/couchpotato/core/providers/torrent/yify/main.py b/couchpotato/core/providers/torrent/yify/main.py new file mode 100644 index 00000000..ad21362b --- /dev/null +++ b/couchpotato/core/providers/torrent/yify/main.py @@ -0,0 +1,53 @@ +from couchpotato.core.helpers.variable import tryInt +from couchpotato.core.logger import CPLog +from couchpotato.core.providers.torrent.base import TorrentProvider +import traceback + +log = CPLog(__name__) + + +class Yify(TorrentProvider): + + urls = { + 'test' : 'https://yify-torrents.com/api', + 'search' : 'https://yify-torrents.com/api/list.json?keywords=%s&quality=%s', + 'detail': 'https://yify-torrents.com/api/movie.json?id=%s' + } + + http_time_between_calls = 1 #seconds + + def search(self, movie, quality): + + if not quality.get('hd', False): + return [] + + return super(Yify, self).search(movie, quality) + + def _searchOnTitle(self, title, movie, quality, results): + + data = self.getJsonData(self.urls['search'] % (title, quality['identifier'])) + + if data: + try: + for result in data.get('MovieList'): + + try: + title = result['TorrentUrl'].split('/')[-1][:-8].replace('_', '.').strip('._') + title = title.replace('.-.', '-') + title = title.replace('..', '.') + except: + continue + + results.append({ + 'id': result['MovieID'], + 'name': title, + 'url': result['TorrentUrl'], + 'detail_url': self.urls['detail'] % result['MovieID'], + 'size': self.parseSize(result['Size']), + 'seeders': tryInt(result['TorrentSeeds']), + 'leechers': tryInt(result['TorrentPeers']) + }) + + except: + log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) + diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py index 6c8deb1e..00ac34e5 100644 --- a/couchpotato/core/settings/model.py +++ b/couchpotato/core/settings/model.py @@ -3,6 +3,7 @@ from elixir.entity import Entity from elixir.fields import Field from elixir.options import options_defaults, using_options from elixir.relationships import ManyToMany, OneToMany, ManyToOne +from sqlalchemy.ext.mutable import Mutable from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \ TypeDecorator import json @@ -39,6 +40,37 @@ class JsonType(TypeDecorator): def process_result_value(self, value, dialect): return json.loads(value if value else '{}') +class MutableDict(Mutable, dict): + + @classmethod + def coerce(cls, key, value): + if not isinstance(value, MutableDict): + if isinstance(value, dict): + return MutableDict(value) + return Mutable.coerce(key, value) + else: + return value + + def __delitem(self, key): + dict.__delitem__(self, key) + self.changed() + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + self.changed() + + def __getstate__(self): + return dict(self) + + def __setstate__(self, state): + self.update(self) + + def update(self, *args, **kwargs): + super(MutableDict, self).update(*args, **kwargs) + self.changed() + +MutableDict.associate_with(JsonType) + class Movie(Entity): """Movie Resource a movie could have multiple releases diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js index 04fc7a8e..01344ad8 100644 --- a/couchpotato/static/scripts/page/home.js +++ b/couchpotato/static/scripts/page/home.js @@ -101,6 +101,9 @@ Page.Home = new Class({ }); }); + // Suggest + self.suggestion_list = new SuggestList(); + // Still not available self.late_list = new MovieList({ 'navigation': false, @@ -121,25 +124,10 @@ Page.Home = new Class({ self.el.adopt( $(self.available_list), $(self.soon_list), + $(self.suggestion_list), $(self.late_list) ); - // Suggest - // self.suggestion_list = new MovieList({ - // 'navigation': false, - // 'identifier': 'suggestions', - // 'limit': 6, - // 'load_more': false, - // 'view': 'thumbs', - // 'api_call': 'suggestion.suggest' - // }); - // self.el.adopt( - // new Element('h2', { - // 'text': 'You might like' - // }), - // $(self.suggestion_list) - // ); - // Recent // Snatched // Renamed diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 97983130..ae461c43 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -168,6 +168,7 @@ body > .spinner, .mask{ color: #FFF; } +.icon2.add:before { content: "\e05a"; color: #c2fac5; } .icon2.cog:before { content: "\e109"; } .icon2.eye-open:before { content: "\e09d"; } .icon2.search:before { content: "\e03e"; }