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"; }