diff --git a/couchpotato/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py index fd5ca6a..f2a30f6 100644 --- a/couchpotato/core/_base/clientscript/main.py +++ b/couchpotato/core/_base/clientscript/main.py @@ -40,6 +40,7 @@ class ClientScript(Plugin): 'scripts/block/navigation.js', 'scripts/block/footer.js', 'scripts/block/menu.js', + 'scripts/page/home.js', 'scripts/page/wanted.js', 'scripts/page/settings.js', 'scripts/page/about.js', diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 1ecb35b..82bf88f 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -168,4 +168,4 @@ def randomString(size = 8, chars = string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for x in range(size)) def splitString(str, split_on = ','): - return [x.strip() for x in str.split(split_on)] + return [x.strip() for x in str.split(split_on)] if str else [] diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index edecaa9..9330631 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -240,7 +240,6 @@ class Plugin(object): del kwargs['cache_timeout'] data = self.urlopen(url, **kwargs) - if data: self.setCache(cache_key, data, timeout = cache_timeout) return data diff --git a/couchpotato/core/plugins/browser/main.py b/couchpotato/core/plugins/browser/main.py index b5839e7..3eee85b 100644 --- a/couchpotato/core/plugins/browser/main.py +++ b/couchpotato/core/plugins/browser/main.py @@ -15,7 +15,7 @@ if os.name == 'nt': raise ImportError("Missing the win32file module, which is a part of the prerequisite \ pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/"); else: - import win32file + import win32file #@UnresolvedImport class FileBrowser(Plugin): @@ -98,7 +98,7 @@ class FileBrowser(Plugin): def has_hidden_attribute(self, filepath): try: - attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath)) + attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath)) #@UndefinedVariable assert attrs != -1 result = bool(attrs & 2) except (AttributeError, AssertionError): diff --git a/couchpotato/core/plugins/dashboard/__init__.py b/couchpotato/core/plugins/dashboard/__init__.py new file mode 100644 index 0000000..8127929 --- /dev/null +++ b/couchpotato/core/plugins/dashboard/__init__.py @@ -0,0 +1,6 @@ +from .main import Dashboard + +def start(): + return Dashboard() + +config = [] diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py new file mode 100644 index 0000000..d5f9ef0 --- /dev/null +++ b/couchpotato/core/plugins/dashboard/main.py @@ -0,0 +1,120 @@ +from couchpotato import get_session +from couchpotato.api import addApiView +from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.request import jsonified, getParams +from couchpotato.core.helpers.variable import splitString, tryInt +from couchpotato.core.logger import CPLog +from couchpotato.core.plugins.base import Plugin +from couchpotato.core.settings.model import Movie +from sqlalchemy.sql.expression import or_ +import random + +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) + print suggestions + + return jsonified({ + 'result': True, + 'suggestions': suggestions + }) + + def getSoonView(self): + + params = getParams() + db = get_session() + + # Get profiles first, determine pre or post theater + profiles = fireEvent('profile.all', single = True) + qualities = fireEvent('quality.all', single = True) + pre_releases = fireEvent('quality.pre_releases', single = True) + + id_pre = {} + for quality in qualities: + id_pre[quality.get('id')] = quality.get('identifier') in pre_releases + + # See what the profile contain and cache it + profile_pre = {} + for profile in profiles: + contains = {} + for profile_type in profile.get('types', []): + contains['theater' if id_pre.get(profile_type.get('quality_id')) else 'dvd'] = True + + profile_pre[profile.get('id')] = contains + + # Get all active movies + q = db.query(Movie) \ + .join(Movie.profile, Movie.library) \ + .filter(or_(*[Movie.status.has(identifier = s) for s in ['active']])) \ + .group_by(Movie.id) + + # Add limit + limit_offset = params.get('limit_offset') + limit = 12 + if limit_offset: + splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset + limit = tryInt(splt[0]) + + all_movies = q.all() + + if params.get('random', False): + random.shuffle(all_movies) + + movies = [] + for movie in all_movies: + pp = profile_pre.get(movie.profile.id) + eta = movie.library.info.get('release_date', {}) + coming_soon = False + + # Theater quality + if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, single = True): + coming_soon = True + if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, single = True): + coming_soon = True + + if coming_soon: + temp = movie.to_dict({ + 'profile': {'types': {}}, + 'releases': {'files':{}, 'info': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {}, + }) + movies.append(temp) + + if len(movies) >= limit: + break + + return jsonified({ + 'success': True, + 'empty': len(movies) == 0, + 'movies': movies, + }) diff --git a/couchpotato/core/plugins/file/static/file.js b/couchpotato/core/plugins/file/static/file.js index 2093e2f..7b893e8 100644 --- a/couchpotato/core/plugins/file/static/file.js +++ b/couchpotato/core/plugins/file/static/file.js @@ -4,6 +4,7 @@ var File = new Class({ var self = this; if(!file){ + self.empty = true; self.el = new Element('div'); return } diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 0e942f0..c708ec4 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -6,10 +6,11 @@ from couchpotato.core.helpers.request import getParams, jsonified, getParam from couchpotato.core.helpers.variable import getImdb, splitString from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Library, LibraryTitle, Movie +from couchpotato.core.settings.model import Library, LibraryTitle, Movie, \ + Release from couchpotato.environment import Env from sqlalchemy.orm import joinedload_all -from sqlalchemy.sql.expression import or_, asc, not_ +from sqlalchemy.sql.expression import or_, asc, not_, desc from string import ascii_lowercase log = CPLog(__name__) @@ -41,6 +42,7 @@ class MoviePlugin(Plugin): 'desc': 'List movies in wanted list', 'params': { 'status': {'type': 'array or csv', 'desc': 'Filter movie by status. Example:"active,done"'}, + 'release_status': {'type': 'array or csv', 'desc': 'Filter movie by status of its releases. Example:"snatched,available"'}, 'limit_offset': {'desc': 'Limit and offset the movie list. Examples: "50" or "50,30"'}, 'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'}, 'search': {'desc': 'Search movie title'}, @@ -94,6 +96,33 @@ class MoviePlugin(Plugin): addEvent('movie.list', self.list) addEvent('movie.restatus', self.restatus) + addEvent('app.load', self.cleanReleases) + + def cleanReleases(self): + + prop_name = 'cleaned_releases' + already_cleaned = Env.prop(prop_name, default = False) + if already_cleaned: + return True + + log.info('Removing releases from library movies') + + db = get_session() + + movies = db.query(Movie).all() + + done_status = fireEvent('status.get', 'done', single = True) + available_status = fireEvent('status.get', 'available', single = True) + snatched_status = fireEvent('status.get', 'snatched', single = True) + + for movie in movies: + if movie.status_id == done_status.get('id'): + for rel in movie.releases: + if rel.status_id in [available_status.get('id'), snatched_status.get('id')]: + fireEvent('release.delete', id = rel.id, single = True) + + Env.prop(prop_name, True) + def getView(self): movie_id = getParam('id') @@ -121,20 +150,29 @@ class MoviePlugin(Plugin): return results - def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None): + def list(self, status = None, release_status = None, limit_offset = None, starts_with = None, search = None, order = None): db = get_session() # Make a list from string - if not isinstance(status, (list, tuple)): + if status and not isinstance(status, (list, tuple)): status = [status] + if release_status and not isinstance(release_status, (list, tuple)): + release_status = [release_status] q = db.query(Movie) \ - .join(Movie.library, Library.titles) \ + .outerjoin(Movie.releases, Movie.library, Library.titles) \ .filter(LibraryTitle.default == True) \ - .filter(or_(*[Movie.status.has(identifier = s) for s in status])) \ .group_by(Movie.id) + # Filter on movie status + if status and len(status) > 0: + q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status])) + + # Filter on release status + if release_status and len(release_status) > 0: + q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status])) + total_count = q.count() filter_or = [] @@ -154,7 +192,10 @@ class MoviePlugin(Plugin): if filter_or: q = q.filter(or_(*filter_or)) - q = q.order_by(asc(LibraryTitle.simple_title)) + if order == 'release_order': + q = q.order_by(desc(Release.last_edit)) + else: + q = q.order_by(asc(LibraryTitle.simple_title)) q = q.subquery() q2 = db.query(Movie).join((q, q.c.id == Movie.id)) \ @@ -166,7 +207,7 @@ class MoviePlugin(Plugin): .options(joinedload_all('files')) if limit_offset: - splt = splitString(limit_offset) + splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset limit = splt[0] offset = 0 if len(splt) is 1 else splt[1] q2 = q2.limit(limit).offset(offset) @@ -185,7 +226,7 @@ class MoviePlugin(Plugin): #db.close() return (total_count, movies) - def availableChars(self, status = ['active']): + def availableChars(self, status = None, release_status = None): chars = '' @@ -194,11 +235,20 @@ class MoviePlugin(Plugin): # Make a list from string if not isinstance(status, (list, tuple)): status = [status] + if release_status and not isinstance(release_status, (list, tuple)): + release_status = [release_status] q = db.query(Movie) \ - .join(Movie.library, Library.titles, Movie.status) \ - .options(joinedload_all('library.titles')) \ - .filter(or_(*[Movie.status.has(identifier = s) for s in status])) + .outerjoin(Movie.releases, Movie.library, Library.titles, Movie.status) \ + .options(joinedload_all('library.titles')) + + # Filter on movie status + if status and len(status) > 0: + q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status])) + + # Filter on release status + if release_status and len(release_status) > 0: + q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status])) results = q.all() @@ -206,20 +256,29 @@ class MoviePlugin(Plugin): char = movie.library.titles[0].simple_title[0] char = char if char in ascii_lowercase else '#' if char not in chars: - chars += char + chars += str(char) #db.close() - return chars + return ''.join(sorted(chars, key = str.lower)) def listView(self): params = getParams() - status = params.get('status', ['active']) + status = splitString(params.get('status', None)) + release_status = splitString(params.get('release_status', None)) limit_offset = params.get('limit_offset', None) starts_with = params.get('starts_with', None) search = params.get('search', None) + order = params.get('order', None) - total_movies, movies = self.list(status = status, limit_offset = limit_offset, starts_with = starts_with, search = search) + total_movies, movies = self.list( + status = status, + release_status = release_status, + limit_offset = limit_offset, + starts_with = starts_with, + search = search, + order = order + ) return jsonified({ 'success': True, @@ -231,8 +290,9 @@ class MoviePlugin(Plugin): def charView(self): params = getParams() - status = params.get('status', ['active']) - chars = self.availableChars(status) + status = splitString(params.get('status', None)) + release_status = splitString(params.get('release_status', None)) + chars = self.availableChars(status, release_status) return jsonified({ 'success': True, @@ -283,7 +343,7 @@ class MoviePlugin(Plugin): 'movies': movies, }) - def add(self, params = {}, force_readd = True, search_after = True, update_library = False): + def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None): if not params.get('identifier'): msg = 'Can\'t add movie without imdb identifier.' @@ -318,7 +378,7 @@ class MoviePlugin(Plugin): m = Movie( library_id = library.get('id'), profile_id = params.get('profile_id', default_profile.get('id')), - status_id = status_active.get('id'), + status_id = status_id if status_id else status_active.get('id'), ) db.add(m) db.commit() @@ -341,7 +401,7 @@ class MoviePlugin(Plugin): added = False if force_readd: - m.status_id = status_active.get('id') + m.status_id = status_id if status_id else status_active.get('id') do_search = True db.commit() @@ -447,7 +507,7 @@ class MoviePlugin(Plugin): total_deleted = 0 new_movie_status = None for release in movie.releases: - if delete_from == 'wanted': + if delete_from in ['wanted', 'snatched']: if release.status_id != done_status.get('id'): db.delete(release) total_deleted += 1 diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index b61ea7e..9e76fad 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -5,6 +5,7 @@ var MovieList = new Class({ options: { navigation: true, limit: 50, + load_more: true, menu: [], add_new: false }, @@ -12,25 +13,32 @@ var MovieList = new Class({ movies: [], movies_added: {}, letters: {}, - filter: { - 'startswith': null, - 'search': null - }, + filter: null, initialize: function(options){ var self = this; self.setOptions(options); self.offset = 0; + self.filter = self.options.filter || { + 'startswith': null, + 'search': null + } self.el = new Element('div.movies').adopt( + self.title = self.options.title ? new Element('h2', { + 'text': self.options.title + }) : null, self.movie_list = new Element('div'), - self.load_more = new Element('a.load_more', { + self.load_more = self.options.load_more ? new Element('a.load_more', { 'events': { 'click': self.loadMore.bind(self) } - }) + }) : null ); + + self.changeView(self.options.view || 'details'); + self.getMovies(); App.addEvent('movie.added', self.movieAdded.bind(self)) @@ -70,22 +78,14 @@ var MovieList = new Class({ if(self.options.navigation) self.createNavigation(); - self.movie_list.addEvents({ - 'mouseenter:relay(.movie)': function(e, el){ - el.addClass('hover'); - }, - 'mouseleave:relay(.movie)': function(e, el){ - el.removeClass('hover'); - } - }); - - self.scrollspy = new ScrollSpy({ - min: function(){ - var c = self.load_more.getCoordinates() - return c.top - window.document.getSize().y - 300 - }, - onEnter: self.loadMore.bind(self) - }); + if(self.options.load_more) + self.scrollspy = new ScrollSpy({ + min: function(){ + var c = self.load_more.getCoordinates() + return c.top - window.document.getSize().y - 300 + }, + onEnter: self.loadMore.bind(self) + }); self.created = true; }, @@ -96,7 +96,7 @@ var MovieList = new Class({ if(!self.created) self.create(); // do scrollspy - if(movies.length < self.options.limit){ + if(movies.length < self.options.limit && self.scrollspy){ self.load_more.hide(); self.scrollspy.stop(); } @@ -124,8 +124,8 @@ var MovieList = new Class({ // Attach proper actions var a = self.options.actions, - status = Status.get(movie.status_id); - var actions = a[status.identifier.capitalize()] || a.Wanted || {}; + status = Status.get(movie.status_id), + actions = a ? a[status.identifier.capitalize()] || a.Wanted : {}; var m = new Movie(self, { 'actions': actions, @@ -216,7 +216,7 @@ var MovieList = new Class({ }); // Actions - ['mass_edit', 'thumbs', 'list'].each(function(view){ + ['mass_edit', 'details', 'list'].each(function(view){ self.navigation_actions.adopt( new Element('li.'+view+(self.current_view == view ? '.active' : '')+'[data-view='+view+']', { 'events': { @@ -401,8 +401,10 @@ var MovieList = new Class({ self.calculateSelected() self.navigation_alpha.getElements('.active').removeClass('active') self.offset = 0; - self.load_more.show(); - self.scrollspy.start(); + if(self.scrollspy){ + self.load_more.show(); + self.scrollspy.start(); + } }, activateLetter: function(letter){ @@ -418,10 +420,6 @@ var MovieList = new Class({ changeView: function(new_view){ var self = this; - self.movies.each(function(movie){ - movie.changeView(new_view) - }); - self.el .removeClass(self.current_view+'_list') .addClass(new_view+'_list') @@ -468,9 +466,12 @@ var MovieList = new Class({ getMovies: function(){ var self = this; - if(self.scrollspy) self.scrollspy.stop(); - self.load_more.set('text', 'loading...'); - Api.request('movie.list', { + if(self.scrollspy){ + self.scrollspy.stop(); + self.load_more.set('text', 'loading...'); + } + + Api.request(self.options.api_call || 'movie.list', { 'data': Object.merge({ 'status': self.options.status, 'limit_offset': self.options.limit + ',' + self.offset @@ -478,8 +479,10 @@ var MovieList = new Class({ 'onComplete': function(json){ self.store(json.movies); self.addMovies(json.movies, json.total); - self.load_more.set('text', 'load more movies'); - if(self.scrollspy) self.scrollspy.start(); + if(self.scrollspy) { + self.load_more.set('text', 'load more movies'); + self.scrollspy.start(); + } self.checkIfEmpty() } @@ -503,6 +506,9 @@ var MovieList = new Class({ var self = this; var is_empty = self.movies.length == 0 && self.total_movies == 0; + + if(self.title) + self.title[is_empty ? 'hide' : 'show']() if(is_empty && self.options.on_empty_element){ self.el.grab(self.options.on_empty_element); diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index 68e0428..9d67e62 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -2,6 +2,18 @@ padding: 60px 0 20px; } + .movies h2 { + margin-bottom: 20px; + } + + .movies.thumbs_list { + padding: 20px 0 20px; + } + + .home .movies { + padding-top: 6px; + } + .movies.mass_edit_list { padding-top: 90px; } @@ -12,33 +24,58 @@ margin: 10px 0; overflow: hidden; width: 100%; + height: 180px; transition: all 0.2s linear; } - .movies .movie.list_view, .movies .movie.mass_edit_view { + + .movies.list_list .movie:not(.details_view), + .movies.mass_edit_list .movie { + height: 32px; + } + + .movies.thumbs_list .movie { + width: 153px; + height: 230px; + display: inline-block; + margin: 0 8px 0 0; + } + .movies.thumbs_list .movie:nth-child(6n+6) { + margin: 0; + } + + .movies .movie .mask { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + } + + .movies.list_list .movie:not(.details_view), + .movies.mass_edit_list .movie { margin: 1px 0; border-radius: 0; background: no-repeat; box-shadow: none; border-bottom: 1px solid rgba(255,255,255,0.05); } - .movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover { - background: rgba(255,255,255,0.03); - } - .movies .movie_container { - overflow: hidden; + .movies.list_list .movie:hover:not(.details_view), + .movies.mass_edit_list .movie { + background: rgba(255,255,255,0.03); } .movies .data { padding: 20px; - height: 180px; + height: 100%; width: 840px; - position: relative; - float: right; + position: absolute; + right: 0; border-radius: 0; - transition: all 0.2s linear; + transition: all .6s cubic-bezier(0.9,0,0.1,1); } - .movies .list_view .data, .movies .mass_edit_view .data { + .movies.list_list .movie:not(.details_view) .data, + .movies.mass_edit_list .movie .data { height: 30px; padding: 3px 0 3px 10px; width: 938px; @@ -46,79 +83,148 @@ border: 0; background: none; } + + .movies.thumbs_list .data { + left: 0; + width: 100%; + padding: 10px; + height: 100%; + background: none; + transition: none; + } + + .movies.thumbs_list .movie.no_thumbnail .data { background-image: linear-gradient(-30deg, rgba(255, 0, 85, .2) 0,rgba(125, 185, 235, .2) 100%); + } + .movies.thumbs_list .movie.no_thumbnail:nth-child(2n+6) .data { background-image: linear-gradient(-20deg, rgba(125, 0, 215, .2) 0, rgba(4, 55, 5, .7) 100%); } + .movies.thumbs_list .movie.no_thumbnail:nth-child(3n+6) .data { background-image: linear-gradient(-30deg, rgba(155, 0, 85, .2) 0,rgba(25, 185, 235, .7) 100%); } + .movies.thumbs_list .movie.no_thumbnail:nth-child(4n+6) .data { background-image: linear-gradient(-30deg, rgba(115, 5, 235, .2) 0, rgba(55, 180, 5, .7) 100%); } + .movies.thumbs_list .movie.no_thumbnail:nth-child(5n+6) .data { background-image: linear-gradient(-30deg, rgba(35, 15, 215, .2) 0, rgba(135, 215, 115, .7) 100%); } + .movies.thumbs_list .movie.no_thumbnail:nth-child(6n+6) .data { background-image: linear-gradient(-30deg, rgba(35, 15, 215, .2) 0, rgba(135, 15, 115, .7) 100%); } + + .movies.thumbs_list .movie:hover .data { + background: rgba(0,0,0,0.9); + } + + .movies .data.hide_right { + right: -100%; + } .movies .movie .check { display: none; } .movies.mass_edit_list .movie .check { - float: left; + position: absolute; + left: 0; + top: 0; display: block; margin: 7px 0 0 5px; } .movies .poster { - float: left; + position: absolute; + left: 0; width: 120px; line-height: 0; overflow: hidden; - height: 180px; + height: 100%; border-radius: 4px 0 0 4px; - transition: all 0.2s linear; + transition: all .6s cubic-bezier(0.9,0,0.1,1); } - .movies .list_view .poster, .movies .mass_edit_view .poster { + .movies.list_list .movie:not(.details_view) .poster, + .movies.mass_edit_list .poster { width: 20px; height: 30px; + border-radius: 1px 0 0 1px; } .movies.mass_edit_list .poster { display: none; } + + .movies.thumbs_list .poster { + width: 100%; + height: 100%; + } - .movies .poster img, .options .poster img { + .movies .poster img, + .options .poster img { width: 101%; height: 101%; } + + .movies .info { + position: relative; + height: 100%; + } .movies .info .title { - font-size: 30px; + display: inline; + position: absolute; + font-size: 28px; font-weight: bold; margin-bottom: 10px; - float: left; + left: 0; + top: 0; width: 90%; transition: all 0.2s linear; } - .movies .list_view .info .title, .movies .mass_edit_view .info .title { + .movies.list_list .movie:not(.details_view) .info .title, + .movies.mass_edit_list .info .title { font-size: 16px; font-weight: normal; text-overflow: ellipsis; width: auto; + overflow: hidden; + + } + + .movies.thumbs_list .movie:not(.no_thumbnail) .info { + display: none; } + .movies.thumbs_list .movie:hover .info { + display: block; + } + + .movies.thumbs_list .info .title { + font-size: 21px; + text-shadow: 0 0 10px #000; + word-wrap: break-word; + } .movies .info .year { + position: absolute; font-size: 30px; margin-bottom: 10px; - float: right; color: #bbb; width: 10%; + right: 0; + top: 0; text-align: right; transition: all 0.2s linear; } - .movies .list_view .info .year, .movies .mass_edit_view .info .year { + .movies.list_list .movie:not(.details_view) .info .year, + .movies.mass_edit_list .info .year { font-size: 16px; width: 6%; + right: 10px; + } + + .movies.thumbs_list .info .year { + font-size: 23px; + margin: 0; + bottom: 0; + left: 0; + top: auto; + right: auto; + color: #FFF; + text-shadow: none; + text-shadow: 0 0 6px #000; } - - .movies .info .rating { - font-size: 30px; - margin-bottom: 10px; - color: #444; - float: left; - width: 5%; - padding: 0 0 0 3%; - } .movies .info .description { + position: absolute; + top: 30px; clear: both; height: 80px; overflow: hidden; @@ -126,61 +232,75 @@ .movies .data:hover .description { overflow: auto; } - .movies .list_view .info .description, .movies .mass_edit_view .info .description { + .movies.list_list .movie:not(.details_view) .info .description, + .movies.mass_edit_list .info .description, + .movies.thumbs_list .info .description { display: none; } .movies .data .quality { + position: absolute; + bottom: 0; display: block; min-height: 20px; vertical-align: mid; } - - .movies .data .quality span { - padding: 2px 3px; - font-weight: bold; - opacity: 0.5; - font-size: 10px; - height: 16px; - line-height: 12px; - vertical-align: middle; - display: inline-block; - text-transform: uppercase; - text-shadow: none; - font-weight: normal; - margin: 0 2px; - border-radius: 2px; - background-color: rgba(255,255,255,0.1); - } - .movies .list_view .data .quality, .movies .mass_edit_view .data .quality { - text-align: right; - float: right; + + .movies .status_suggest .data .quality, + .movies.thumbs_list .data .quality { + display: none; } - .movies .data .quality .available, .movies .data .quality .snatched { - opacity: 1; - box-shadow: 1px 1px 0 rgba(0,0,0,0.2); - cursor: pointer; - } - - .movies .data .quality .available { background-color: #578bc3; } - .movies .data .quality .snatched { background-color: #369545; } - .movies .data .quality .done { - background-color: #369545; - opacity: 1; - } - .movies .data .quality .finish { - background-image: url('../images/sprite.png'); - background-repeat: no-repeat; - background-position: 0 2px; - padding-left: 14px; - background-size: 14px - } + .movies .data .quality span { + padding: 2px 3px; + font-weight: bold; + opacity: 0.5; + font-size: 10px; + height: 16px; + line-height: 12px; + vertical-align: middle; + display: inline-block; + text-transform: uppercase; + text-shadow: none; + font-weight: normal; + margin: 0 2px; + border-radius: 2px; + background-color: rgba(255,255,255,0.1); + } + .movies.list_list .data .quality, + .movies.mass_edit_list .data .quality { + text-align: right; + right: 0; + margin-right: 50px; + z-index: 2; + } + + .movies .data .quality .available, + .movies .data .quality .snatched { + opacity: 1; + box-shadow: 1px 1px 0 rgba(0,0,0,0.2); + cursor: pointer; + } + + .movies .data .quality .available { background-color: #578bc3; } + .movies .data .quality .snatched { background-color: #369545; } + .movies .data .quality .done { + background-color: #369545; + opacity: 1; + } + .movies .data .quality .finish { + background-image: url('../images/sprite.png'); + background-repeat: no-repeat; + background-position: 0 2px; + padding-left: 14px; + background-size: 14px + } .movies .data .actions { + position: absolute; + bottom: 20px; + right: 20px; line-height: 0; - clear: both; - float: right; margin-top: -25px; } .movies .data:hover .action { opacity: 0.6; } @@ -199,10 +319,14 @@ opacity: 0; } - .movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions { - margin: -34px 2px 0 0; + .movies.list_list .movie:not(.details_view) .data:hover .actions, + .movies.mass_edit_list .data:hover .actions { + margin: 0; background: #4e5969; - position: relative; + top: 2px; + bottom: 2px; + right: 5px; + z-index: 3; } .movies .delete_container { @@ -336,11 +460,11 @@ padding: 3px 10px; background: #4e5969; border-radius: 0 0 2px 2px; - transition: all .6s cubic-bezier(0.9,0,0.1,1) .2s; - } - .movies .movie .hide_trailer.hide { - top: -30px; + transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s; } + .movies .movie .hide_trailer.hide { + top: -30px; + } .movies .movie .try_container { padding: 5px 10px; @@ -410,7 +534,8 @@ text-align: center; } - .movies .alph_nav .numbers li, .movies .alph_nav .actions li { + .movies .alph_nav .numbers li, + .movies .alph_nav .actions li { display: inline-block; vertical-align: top; width: 20px; @@ -473,7 +598,7 @@ background-position: 3px -95px; } - .movies .alph_nav .actions li.thumbs span { + .movies .alph_nav .actions li.details span { background-position: 3px -74px; } diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index ff2cd12..283bb41 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -8,7 +8,7 @@ var Movie = new Class({ var self = this; self.data = data; - self.view = options.view || 'thumbs'; + self.view = options.view || 'details'; self.list = list; self.el = new Element('div.movie.inlay'); @@ -72,7 +72,6 @@ var Movie = new Class({ else if(!self.spinner) { self.createMask(); self.spinner = createSpinner(self.mask); - self.positionMask(); self.mask.fade('in'); } }, @@ -84,7 +83,6 @@ var Movie = new Class({ 'z-index': '1' } }).inject(self.el, 'top').fade('hide'); - self.positionMask(); }, positionMask: function(){ @@ -103,7 +101,7 @@ var Movie = new Class({ var self = this; self.data = notification.data; - self.container.destroy(); + self.el.empty(); self.profile = Quality.getProfile(self.data.profile_id) || {}; self.create(); @@ -114,52 +112,50 @@ var Movie = new Class({ create: function(){ var self = this; + var s = Status.get(self.get('status_id')); + self.el.addClass('status_'+s.identifier); + self.el.adopt( - self.container = new Element('div.movie_container').adopt( - self.select_checkbox = new Element('input[type=checkbox].inlay', { - 'events': { - 'change': function(){ - self.fireEvent('select') - } + 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.thumbnail = File.Select.single('poster', self.data.library.files), + self.data_container = new Element('div.data.inlay.light').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') - ) + } + }) + ), + self.actions = new Element('div.actions') ) ); - self.changeView(self.view); + if(self.thumbnail.empty) + self.el.addClass('no_thumbnail'); + + //self.changeView(self.view); self.select_checkbox_class = new Form.Check(self.select_checkbox); // Add profile @@ -174,7 +170,7 @@ var Movie = new Class({ }); - // Add done releases + // Add releases self.data.releases.each(function(release){ var q = self.quality.getElement('.q_id'+ release.quality_id), @@ -241,23 +237,23 @@ var Movie = new Class({ if(direction == 'in'){ self.temp_view = self.view; - self.changeView('thumbs') + self.changeView('details') self.el.addEvent('outerClick', function(){ - self.changeView(self.temp_view) + self.removeView() self.slide('out') }) el.show(); - self.data_container.tween('right', 0, -840); + self.data_container.addClass('hide_right'); } else { self.el.removeEvents('outerClick') - self.addEvent('slideEnd:once', function(){ + setTimeout(function(){ self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide(); - }); + }, 600); - self.data_container.tween('right', -840, 0); + self.data_container.removeClass('hide_right'); } }, @@ -271,6 +267,12 @@ var Movie = new Class({ self.view = new_view; }, + removeView: function(){ + var self = this; + + self.el.removeClass(self.view+'_view') + }, + get: function(attr){ return this.data[attr] || this.data.library[attr] }, @@ -320,7 +322,7 @@ var MovieAction = new Class({ 'z-index': '1' } }).inject(self.movie, 'top').fade('hide'); - self.positionMask(); + //self.positionMask(); }, positionMask: function(){ @@ -379,20 +381,27 @@ var ReleaseAction = new Class({ } }); - var buttons_done = false; + if(self.movie.data.releases.length == 0){ + self.el.hide() + } + else { + + var buttons_done = false; - self.movie.data.releases.sortBy('-info.score').each(function(release){ - if(buttons_done) return; + self.movie.data.releases.sortBy('-info.score').each(function(release){ + if(buttons_done) return; - var status = Status.get(release.status_id); + var status = Status.get(release.status_id); - if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){ - self.hide_on_click = false; - self.show(); - buttons_done = true; - } + if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){ + self.hide_on_click = false; + self.show(); + buttons_done = true; + } - }); + }); + + } }, @@ -603,7 +612,7 @@ var TrailerAction = new Class({ self.player_container = new Element('div[id='+id+']'); self.container = new Element('div.hide.trailer_container') .adopt(self.player_container) - .inject(self.movie.container, 'top'); + .inject($(self.movie), 'top'); self.container.setStyle('height', 0); self.container.removeClass('hide'); @@ -615,10 +624,8 @@ var TrailerAction = new Class({ } }).inject(self.movie); - setTimeout(function(){ - $(self.movie).setStyle('max-height', height); - self.container.setStyle('height', height); - }, 100) + self.container.setStyle('height', height); + $(self.movie).setStyle('height', height); new Request.JSONP({ 'url': url, @@ -665,6 +672,7 @@ var TrailerAction = new Class({ self.player.stopVideo(); self.container.addClass('hide'); self.close_button.addClass('hide'); + $(self.movie).setStyle('height', null); setTimeout(function(){ self.container.destroy() diff --git a/couchpotato/core/plugins/movie/static/search.css b/couchpotato/core/plugins/movie/static/search.css index af94229..391e347 100644 --- a/couchpotato/core/plugins/movie/static/search.css +++ b/couchpotato/core/plugins/movie/static/search.css @@ -191,6 +191,8 @@ .movie_result .info h2 { margin: 0; + font-size: 17px; + line-height: 20px; } .movie_result .info h2 span { diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 8c1929a..c5ed138 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -30,6 +30,7 @@ class Searcher(Plugin): addEvent('searcher.correct_movie', self.correctMovie) addEvent('searcher.download', self.download) addEvent('searcher.try_next_release', self.tryNextRelease) + addEvent('searcher.could_be_released', self.couldBeReleased) addApiView('searcher.try_next', self.tryNextReleaseView, docs = { 'desc': 'Marks the snatched results as ignored and try the next best release', @@ -156,7 +157,7 @@ class Searcher(Plugin): ret = False for quality_type in movie['profile']['types']: - if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases): + if not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates): log.info('Too early to search for %s, %s', (quality_type['quality']['identifier'], default_title)) continue @@ -526,7 +527,7 @@ class Searcher(Plugin): return False - def couldBeReleased(self, wanted_quality, dates, pre_releases): + def couldBeReleased(self, is_pre_release, dates): now = int(time.time()) @@ -538,7 +539,7 @@ class Searcher(Plugin): if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0: return True - if wanted_quality in pre_releases: + if is_pre_release: # Prerelease 1 week before theaters if dates.get('theater') - 604800 < now: return True diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 8f4e665..d2e8fa0 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -3,7 +3,7 @@ var CouchPotato = new Class({ Implements: [Events, Options], defaults: { - page: 'wanted', + page: 'home', action: 'index', params: {} }, @@ -135,7 +135,7 @@ var CouchPotato = new Class({ self.current_page.hide() try { - var page = self.pages[page_name] || self.pages.Wanted; + var page = self.pages[page_name] || self.pages.Home; page.open(action, params, current_url); page.show(); } @@ -342,7 +342,14 @@ var Route = new Class({ parse: function(){ var self = this; - var path = History.getPath().replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/') + var rep = function(pa){ + return pa.replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/') + } + + var path = rep(History.getPath()) + if(path == '/' && location.hash){ + path = rep(location.hash.replace('#', '/')) + } self.current = path.replace(/^\/+|\/+$/g, '') var url = self.current.split('/') diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js new file mode 100644 index 0000000..caf4d64 --- /dev/null +++ b/couchpotato/static/scripts/page/home.js @@ -0,0 +1,91 @@ +Page.Home = new Class({ + + Extends: PageBase, + + name: 'home', + title: 'Manage new stuff for things and such', + + indexAction: function(param){ + var self = this; + + if(self.soon_list) + return + + // Snatched + self.available_list = new MovieList({ + 'navigation': false, + 'identifier': 'snatched', + 'load_more': false, + 'view': 'list', + 'actions': MovieActions, + 'title': 'Snatched & Available', + 'filter': { + 'release_status': 'snatched,available' + } + }); + + // Downloaded + // self.downloaded_list = new MovieList({ + // 'navigation': false, + // 'identifier': 'downloaded', + // 'load_more': false, + // 'view': 'titles', + // 'filter': { + // 'release_status': 'done', + // 'order': 'release_order' + // } + // }); + // self.el.adopt( + // new Element('h2', { + // 'text': 'Just downloaded' + // }), + // $(self.downloaded_list) + // ); + + // Comming Soon + self.soon_list = new MovieList({ + 'navigation': false, + 'identifier': 'soon', + 'limit': 24, + 'title': 'Soon', + 'filter': { + 'random': true + }, + 'load_more': false, + 'view': 'thumbs', + 'api_call': 'dashboard.soon' + }); + + self.el.adopt( + $(self.available_list), + $(self.soon_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 + // Added + + // Free space + + // Shortcuts + + } + +}) \ No newline at end of file diff --git a/couchpotato/static/scripts/page/manage.js b/couchpotato/static/scripts/page/manage.js index 0e44e76..ec293f8 100644 --- a/couchpotato/static/scripts/page/manage.js +++ b/couchpotato/static/scripts/page/manage.js @@ -27,7 +27,9 @@ Page.Manage = new Class({ self.list = new MovieList({ 'identifier': 'manage', - 'status': 'done', + 'filter': { + 'release_status': 'done' + }, 'actions': MovieActions, 'menu': [self.refresh_button, self.refresh_quick], 'on_empty_element': new Element('div.empty_manage').adopt( diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 97ca183..ff29222 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -80,6 +80,12 @@ a:hover { color: #f3f3f3; } padding: 80px 0 10px; } +h2 { + font-size: 30px; + padding: 0; + margin: 20px 0 0 0; +} + .footer { text-align:center; padding: 50px 0 0 0;