Browse Source

Dashboard

pull/1453/merge
Ruud 12 years ago
parent
commit
1e1abf407c
  1. 1
      couchpotato/core/_base/clientscript/main.py
  2. 2
      couchpotato/core/helpers/variable.py
  3. 1
      couchpotato/core/plugins/base.py
  4. 4
      couchpotato/core/plugins/browser/main.py
  5. 6
      couchpotato/core/plugins/dashboard/__init__.py
  6. 120
      couchpotato/core/plugins/dashboard/main.py
  7. 1
      couchpotato/core/plugins/file/static/file.js
  8. 104
      couchpotato/core/plugins/movie/main.py
  9. 80
      couchpotato/core/plugins/movie/static/list.js
  10. 289
      couchpotato/core/plugins/movie/static/movie.css
  11. 142
      couchpotato/core/plugins/movie/static/movie.js
  12. 2
      couchpotato/core/plugins/movie/static/search.css
  13. 7
      couchpotato/core/plugins/searcher/main.py
  14. 13
      couchpotato/static/scripts/couchpotato.js
  15. 91
      couchpotato/static/scripts/page/home.js
  16. 4
      couchpotato/static/scripts/page/manage.js
  17. 6
      couchpotato/static/style/main.css

1
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',

2
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 []

1
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

4
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):

6
couchpotato/core/plugins/dashboard/__init__.py

@ -0,0 +1,6 @@
from .main import Dashboard
def start():
return Dashboard()
config = []

120
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,
})

1
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
}

104
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

80
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);

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

142
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()

2
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 {

7
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

13
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('/')

91
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
}
})

4
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(

6
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;

Loading…
Cancel
Save