diff --git a/couchpotato/cli.py b/couchpotato/cli.py index b35bdf69..92d4e50c 100644 --- a/couchpotato/cli.py +++ b/couchpotato/cli.py @@ -3,6 +3,7 @@ from couchpotato import web from couchpotato.api import api from libs.daemon import createDaemon from logging import handlers +from werkzeug.contrib.cache import FileSystemCache import logging import os.path import sys @@ -48,6 +49,7 @@ def cmd_couchpotato(base_path, args): Env.set('data_dir', options.data_dir) Env.set('db_path', 'sqlite:///' + os.path.join(options.data_dir, 'couchpotato.db')) Env.set('cache_dir', os.path.join(options.data_dir, 'cache')) + Env.set('cache', FileSystemCache(Env.get('cache_dir'))) Env.set('quiet', options.quiet) Env.set('daemonize', options.daemonize) Env.set('args', args) @@ -133,7 +135,7 @@ def cmd_couchpotato(base_path, args): # Register modules app.register_module(web, url_prefix = '%s/' % url_base) - app.register_module(api, url_prefix = '%s/%s/%s/' % (url_base, 'api', api_key if not debug else 'apikey')) + app.register_module(api, url_prefix = '%s/%s/' % (url_base, api_key if not debug else 'api')) # Go go go! app.run(use_reloader = reloader) diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py index 87febde3..bade6027 100644 --- a/couchpotato/core/helpers/encoding.py +++ b/couchpotato/core/helpers/encoding.py @@ -24,3 +24,11 @@ def toUnicode(original, *args): except UnicodeDecodeError: ascii_text = str(original).encode('string_escape') return unicode(ascii_text) + + +def is_int(value): + try: + int(value) + return True + except ValueError: + return False diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 3cde2cb1..a85cdd2e 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -1,17 +1,67 @@ from flask.globals import current_app -from flask.helpers import json, jsonify +from flask.helpers import json +from libs.werkzeug.urls import url_decode import flask - +import re def getParams(): - return getattr(flask.request, 'args') + + params = url_decode(getattr(flask.request, 'environ').get('QUERY_STRING', '')) + reg = re.compile('^[a-z0-9_\.]+$') + + current = temp = {} + for param, value in sorted(params.iteritems()): + + nest = re.split("([\[\]]+)", param) + if len(nest) > 1: + nested = [] + for key in nest: + if reg.match(key): + nested.append(key) + + current = temp + + for item in nested: + if item is nested[-1]: + current[item] = value + else: + try: + current[item] + except: + current[item] = {} + + current = current[item] + else: + temp[param] = value + + return dictToList(temp) + +def dictToList(params): + + if type(params) is dict: + new = {} + for x, value in params.iteritems(): + try: + new_value = [dictToList(value2) for value2 in value.itervalues()] + except: + new_value = value + + new[x] = new_value + else: + new = params + + return new def getParam(attr, default = None): return getattr(flask.request, 'args').get(attr, default) def padded_jsonify(callback, *args, **kwargs): content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')' - return current_app.response_class(content, mimetype = 'text/javascript') + return getattr(current_app, 'response_class')(content, mimetype = 'text/javascript') + +def jsonify(mimetype, *args, **kwargs): + content = json.dumps(dict(*args, **kwargs)) + return getattr(current_app, 'response_class')(content, mimetype = mimetype) def jsonified(*args, **kwargs): from couchpotato.environment import Env @@ -19,4 +69,5 @@ def jsonified(*args, **kwargs): if callback: return padded_jsonify(callback, *args, **kwargs) else: - return jsonify(*args, **kwargs) + return jsonify('text/javascript' if Env.doDebug() else 'application/json', *args, **kwargs) + diff --git a/couchpotato/core/plugins/library/__init__.py b/couchpotato/core/plugins/library/__init__.py new file mode 100644 index 00000000..e13a2e0f --- /dev/null +++ b/couchpotato/core/plugins/library/__init__.py @@ -0,0 +1,6 @@ +from couchpotato.core.plugins.library.main import LibraryPlugin + +def start(): + return LibraryPlugin() + +config = [] diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py new file mode 100644 index 00000000..dad3c352 --- /dev/null +++ b/couchpotato/core/plugins/library/main.py @@ -0,0 +1,32 @@ +from couchpotato import get_session +from couchpotato.core.event import addEvent +from couchpotato.core.plugins.base import Plugin +from couchpotato.core.settings.model import Library + + +class LibraryPlugin(Plugin): + + def __init__(self): + addEvent('library.add', self.add) + + def add(self, attrs = {}): + + db = get_session(); + + l = db.query(Library).filter_by(identifier = attrs.get('identifier')).first() + + if not l: + l = Library( + name = attrs.get('name'), + year = attrs.get('year'), + identifier = attrs.get('identifier'), + description = attrs.get('description') + ) + db.add(l) + db.commit() + + return l + + def update(self, item): + + pass diff --git a/couchpotato/core/plugins/movie/__init__.py b/couchpotato/core/plugins/movie/__init__.py new file mode 100644 index 00000000..51ec65b0 --- /dev/null +++ b/couchpotato/core/plugins/movie/__init__.py @@ -0,0 +1,6 @@ +from couchpotato.core.plugins.movie.main import MoviePlugin + +def start(): + return MoviePlugin() + +config = [] diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py new file mode 100644 index 00000000..cd5c5acc --- /dev/null +++ b/couchpotato/core/plugins/movie/main.py @@ -0,0 +1,94 @@ +from couchpotato import get_session +from couchpotato.api import addApiView +from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.request import getParams, jsonified +from couchpotato.core.plugins.base import Plugin +from couchpotato.core.settings.model import Movie, Release, Profile +from couchpotato.environment import Env +from urllib import urlencode + + +class MoviePlugin(Plugin): + + def __init__(self): + addApiView('movie.search', self.search) + addApiView('movie.list', self.list) + addApiView('movie.add', self.add) + + def list(self): + + a = getParams() + + results = get_session().query(Movie).filter( + Movie.releases.any( + Release.status.has(identifier = 'wanted') + ) + ).all() + + movies = [] + for movie in results: + temp = { + 'id': movie.id, + 'name': movie.id, + 'releases': [], + } + for release in movie.releases: + temp['releases'].append({ + 'status': release.status.label, + 'quality': release.quality.label + }) + + movies.append(temp) + + return jsonified({ + 'success': True, + 'empty': len(movies) == 0, + 'movies': movies, + }) + + def search(self): + + a = getParams() + cache_key = '%s/%s' % (__name__, urlencode(a)) + movies = Env.get('cache').get(cache_key) + + if not movies: + results = fireEvent('provider.movie.search', q = a.get('q')) + + # Combine movie results + movies = [] + for r in results: + movies += r + + Env.get('cache').set(cache_key, movies, timeout = 10) + + + return jsonified({ + 'success': True, + 'empty': len(movies) == 0, + 'movies': movies, + }) + + def add(self): + + a = getParams() + db = get_session(); + + library = fireEvent('library.add', attrs = a) + profile = db.query(Profile).filter_by(identifier = a.get('profile_identifier')) + + m = db.query(Movie).filter_by(library = library).first() + + if not m: + m = Movie( + library = library, + profile = profile, + ) + db.add(m) + db.commit() + + return jsonified({ + 'success': True, + 'added': True, + 'params': a, + }) diff --git a/couchpotato/core/plugins/movie_add/__init__.py b/couchpotato/core/plugins/movie_add/__init__.py deleted file mode 100644 index a77db650..00000000 --- a/couchpotato/core/plugins/movie_add/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from couchpotato.core.plugins.movie_add.main import MovieAdd - -def start(): - return MovieAdd() - -config = [] diff --git a/couchpotato/core/plugins/movie_add/main.py b/couchpotato/core/plugins/movie_add/main.py deleted file mode 100644 index c4b0eb7c..00000000 --- a/couchpotato/core/plugins/movie_add/main.py +++ /dev/null @@ -1,36 +0,0 @@ -from couchpotato.api import addApiView -from couchpotato.core.event import fireEvent -from couchpotato.core.helpers.request import getParams, jsonified -from couchpotato.core.plugins.base import Plugin - -class MovieAdd(Plugin): - - def __init__(self): - addApiView('movie.add.search', self.search) - addApiView('movie.add.select', self.select) - - def search(self): - - a = getParams() - - results = fireEvent('provider.movie.search', q = a.get('q')) - - # Combine movie results - movies = [] - for r in results: - movies += r - - return jsonified({ - 'success': True, - 'empty': len(movies) == 0, - 'movies': movies, - }) - - def select(self): - - a = getParams() - - return jsonified({ - 'success': True, - 'added': True, - }) diff --git a/couchpotato/core/plugins/movie_list/__init__.py b/couchpotato/core/plugins/movie_list/__init__.py deleted file mode 100644 index b7ca857d..00000000 --- a/couchpotato/core/plugins/movie_list/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from couchpotato.core.plugins.movie_list.main import MovieList - -def start(): - return MovieList() - -config = [] diff --git a/couchpotato/core/plugins/movie_list/main.py b/couchpotato/core/plugins/movie_list/main.py deleted file mode 100644 index 008511ff..00000000 --- a/couchpotato/core/plugins/movie_list/main.py +++ /dev/null @@ -1,41 +0,0 @@ -from couchpotato import get_session -from couchpotato.api import addApiView -from couchpotato.core.helpers.request import getParams, jsonified -from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Movie, Release - -class MovieList(Plugin): - - def __init__(self): - addApiView('movie.list', self.list) - - def list(self): - - a = getParams() - - results = get_session().query(Movie).filter( - Movie.releases.any( - Release.status.has(identifier = 'wanted') - ) - ).all() - - movies = [] - for movie in results: - temp = { - 'id': movie.id, - 'name': movie.id, - 'releases': [], - } - for release in movie.releases: - temp['releases'].append({ - 'status': release.status.label, - 'quality': release.quality.label - }) - - movies.append(temp) - - return jsonified({ - 'success': True, - 'empty': len(movies) == 0, - 'movies': movies, - }) diff --git a/couchpotato/core/plugins/profile/__init__.py b/couchpotato/core/plugins/profile/__init__.py new file mode 100644 index 00000000..b104d5f0 --- /dev/null +++ b/couchpotato/core/plugins/profile/__init__.py @@ -0,0 +1,6 @@ +from couchpotato.core.plugins.profile.main import ProfilePlugin + +def start(): + return ProfilePlugin() + +config = [] diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py new file mode 100644 index 00000000..0b5d6a75 --- /dev/null +++ b/couchpotato/core/plugins/profile/main.py @@ -0,0 +1,28 @@ +from couchpotato.api import addApiView +from couchpotato.core.event import addEvent +from couchpotato.core.helpers.request import jsonified, getParams +from couchpotato.core.plugins.base import Plugin + +class ProfilePlugin(Plugin): + + def __init__(self): + addEvent('profile.get', self.get) + + addApiView('profile.save', self.save) + addApiView('profile.delete', self.delete) + + def get(self, key = ''): + + pass + + def save(self): + + a = getParams() + + return jsonified({ + 'success': True, + 'a': a + }) + + def delete(self): + pass diff --git a/couchpotato/core/providers/tmdb/__init__.py b/couchpotato/core/providers/tmdb/__init__.py index b08ec8f7..8d3b1ac7 100644 --- a/couchpotato/core/providers/tmdb/__init__.py +++ b/couchpotato/core/providers/tmdb/__init__.py @@ -1,7 +1,7 @@ -from couchpotato.core.providers.tmdb.main import TMDB +from couchpotato.core.providers.tmdb.main import TMDBWrapper def start(): - return TMDB() + return TMDBWrapper() config = [{ 'name': 'themoviedb', diff --git a/couchpotato/core/providers/tmdb/main.py b/couchpotato/core/providers/tmdb/main.py index 35abaa74..e24a98eb 100644 --- a/couchpotato/core/providers/tmdb/main.py +++ b/couchpotato/core/providers/tmdb/main.py @@ -4,6 +4,7 @@ from couchpotato.core.helpers.encoding import simplifyString, toUnicode from couchpotato.core.logger import CPLog from couchpotato.core.providers.base import Provider from couchpotato.environment import Env +from libs.themoviedb import tmdb from urllib import quote_plus import copy import simplejson as json @@ -11,7 +12,7 @@ import urllib2 log = CPLog(__name__) -class TMDB(Provider): +class TMDBWrapper(Provider): """Api for theMovieDb""" type = 'movie' @@ -20,6 +21,10 @@ class TMDB(Provider): def __init__(self): addEvent('provider.movie.search', self.search) + addEvent('provider.movie.info', self.getInfo) + + # Use base wrapper + tmdb.Config.api_key = self.conf('api_key') def conf(self, attr): return Env.setting(attr, 'themoviedb') @@ -32,31 +37,32 @@ class TMDB(Provider): log.debug('TheMovieDB - Searching for movie: %s' % q) - url = "%s/%s/%s/json/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(simplifyString(q))) + raw = tmdb.search(simplifyString(q)) - log.info('Searching: %s' % url) + #url = "%s/%s/%s/json/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(simplifyString(q))) - data = urllib2.urlopen(url) - jsn = json.load(data) - return self.parse(jsn, limit, alternative = alternative) +# data = urllib2.urlopen(url) +# jsn = json.load(data) - def parse(self, data, limit, alternative = True): - if data: + if raw: log.debug('TheMovieDB - Parsing RSS') try: results = [] nr = 0 - for movie in data: + for movie in raw: - year = str(movie['released'])[:4] + for k, x in movie.iteritems(): + print k + print x + + year = str(movie.get('released', 'none'))[:4] # Poster url poster = '' - for p in movie['posters']: - p = p['image'] - if(p['size'] == 'thumb'): - poster = p['url'] + for p in movie.get('images'): + if(p.get('type') == 'poster'): + poster = p.get('thumb') break # 1900 is the same as None @@ -64,16 +70,16 @@ class TMDB(Provider): year = None movie_data = { - 'id': int(movie['id']), - 'name': toUnicode(movie['name']), + 'id': int(movie.get('id', 0)), + 'name': toUnicode(movie.get('name')), 'poster': poster, - 'imdb': movie['imdb_id'], + 'imdb': movie.get('imdb_id'), 'year': year, 'tagline': 'This is the tagline of the movie', } results.append(copy.deepcopy(movie_data)) - alternativeName = movie['alternative_name'] + alternativeName = movie.get('alternative_name') if alternativeName and alternative: if alternativeName.lower() != movie['name'].lower() and alternativeName.lower() != 'none' and alternativeName != None: movie_data['name'] = toUnicode(alternativeName) @@ -88,6 +94,10 @@ class TMDB(Provider): log.error('TheMovieDB - Failed to parse XML response from TheMovieDb: %s' % e) return False + return results + + def getInfo(self): + pass def isDisabled(self): if self.conf('api_key') == '': diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py index acd26c75..afa8872d 100644 --- a/couchpotato/core/settings/__init__.py +++ b/couchpotato/core/settings/__init__.py @@ -1,6 +1,7 @@ from __future__ import with_statement from couchpotato.api import addApiView from couchpotato.core.event import addEvent +from couchpotato.core.helpers.encoding import is_int from couchpotato.core.helpers.request import getParams, jsonified import ConfigParser import os.path @@ -60,7 +61,7 @@ class Settings(): return default def cleanValue(self, value): - if(self.is_int(value)): + if(is_int(value)): return int(value) if str(value).lower() in self.bool: @@ -91,13 +92,6 @@ class Settings(): if not self.p.has_option(section, option): self.p.set(section, option, value) - def is_int(self, value): - try: - int(value) - return True - except ValueError: - return False - def addOptions(self, section_name, options): self.options[section_name] = options diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py index d07cbb2c..f8351f97 100644 --- a/couchpotato/core/settings/model.py +++ b/couchpotato/core/settings/model.py @@ -2,7 +2,8 @@ from elixir.entity import Entity from elixir.fields import Field from elixir.options import options_defaults from elixir.relationships import OneToMany, ManyToOne -from sqlalchemy.types import Integer, String, Unicode, UnicodeText, Boolean +from sqlalchemy.types import Integer, String, Unicode, UnicodeText, Boolean, \ + Float options_defaults["shortnames"] = True @@ -19,13 +20,26 @@ class Movie(Entity): The files belonging to the movie object are global for the whole movie such as trailers, nfo, thumbnails""" - mooli_id = Field(Integer) + library = ManyToOne('Library') profile = ManyToOne('Profile') releases = OneToMany('Release') files = OneToMany('File') +class Library(Entity): + + title = Field(Unicode) + year = Field(Integer) + identifier = Field(Unicode) + rating = Field(Float) + + plot = Field(UnicodeText) + tagline = Field(UnicodeText(255)) + + movie = OneToMany('Movie') + + class Release(Entity): """Logically groups all files that belong to a certain release, such as parts of a movie, subtitles.""" @@ -55,6 +69,7 @@ class Quality(Entity): releases = OneToMany('Release') profile_types = ManyToOne('ProfileType') + class Profile(Entity): """""" @@ -66,6 +81,7 @@ class Profile(Entity): movie = OneToMany('Movie') profile_type = OneToMany('ProfileType') + class ProfileType(Entity): """""" @@ -76,6 +92,7 @@ class ProfileType(Entity): type = OneToMany('Quality') profile = ManyToOne('Profile') + class File(Entity): """File that belongs to a release.""" @@ -114,6 +131,7 @@ class History(Entity): message = Field(UnicodeText()) release = ManyToOne('Release') + class RenameHistory(Entity): """Remembers from where to where files have been moved.""" diff --git a/couchpotato/environment.py b/couchpotato/environment.py index b295eaa3..6fe033b9 100644 --- a/couchpotato/environment.py +++ b/couchpotato/environment.py @@ -7,6 +7,7 @@ class Env: _debug = False _settings = Settings() _loader = Loader() + _cache = None _options = None _args = None _quiet = False diff --git a/couchpotato/static/scripts/block/search.js b/couchpotato/static/scripts/block/search.js index bdf05e83..88fbf252 100644 --- a/couchpotato/static/scripts/block/search.js +++ b/couchpotato/static/scripts/block/search.js @@ -35,6 +35,9 @@ Block.Search = new Class({ self.OuterClickStack = new EventStack.OuterClick(); + //debug + //self.input.set('value', 'kick ass') + //self.autocomplete() }, clear: function(e){ @@ -98,7 +101,7 @@ Block.Search = new Class({ self.hideResults(false) if(!cache){ - self.api_request = Api.request('movie.add.search', { + self.api_request = Api.request('movie.search', { 'data': { 'q': q }, @@ -118,7 +121,7 @@ Block.Search = new Class({ self.spinner.hide(); self.cache[q] = json - self.movies = [] + self.movies = {} self.results.empty() Object.each(json.movies, function(movie){ @@ -126,9 +129,14 @@ Block.Search = new Class({ if(!movie.imdb || (movie.imdb && !self.results.getElement('#'+movie.imdb))){ var m = new Block.Search.Item(movie); $(m).inject(self.results) + self.movies[movie.imdb || 'r-'+Math.floor(Math.random()*10000)] = m + } + else { + self.movies[movie.imdb].alternativeName({ + 'name': movie.name + }) } - self.movies.include(m) }); }, @@ -149,6 +157,7 @@ Block.Search.Item = new Class({ var self = this; self.info = info; + self.alternative_names = []; self.create(); @@ -163,11 +172,7 @@ Block.Search.Item = new Class({ self.el = new Element('div.movie', { 'id': info.imdb }).adopt( - new Element('div.add').adopt( - new Element('span', { - 'text': 'test' - }) - ), + self.options = new Element('div.options'), self.data_container = new Element('div.data', { 'tween': { duration: 400, @@ -208,11 +213,23 @@ Block.Search.Item = new Class({ }).inject(self.starring) }) } + + self.alternativeName({ + 'name': info.name + }); + }, + + alternativeName: function(alternative){ + var self = this; + + self.alternative_names.include(alternative); }, showOptions: function(){ var self = this; + self.createOptions(); + if(!self.width) self.width = self.data_container.getCoordinates().width @@ -222,6 +239,80 @@ Block.Search.Item = new Class({ }, + add: function(e){ + var self = this; + (e).stop(); + + Api.request('movie.add', { + 'data': { + 'identifier': self.info.imdb, + 'name': self.name_select.get('value'), + 'quality': self.quality_select.get('value') + }, + 'useSpinner': true, + 'spinnerTarget': self.options, + 'onComplete': function(){ + self.options.empty(); + self.options.adopt( + new Element('div.message', { + 'text': 'Movie succesfully added.' + }) + ); + }, + 'onFailure': function(){ + self.options.empty(); + self.options.adopt( + new Element('div.message', { + 'text': 'Something went wrong, check the logs for more info.' + }) + ); + } + }); + }, + + createOptions: function(){ + var self = this; + + if(!self.options.hasClass('set')){ + + self.options.adopt( + new Element('div').adopt( + self.info.poster ? new Element('img.thumbnail', { + 'src': self.info.poster + }) : null, + self.name_select = new Element('select', { + 'name': 'name' + }), + self.quality_select = new Element('select', { + 'name': 'profile_identifier' + }), + new Element('a.button', { + 'text': 'Add', + 'events': { + 'click': self.add.bind(self) + } + }) + ) + ); + + Array.each(self.alternative_names, function(alt){ + new Element('option', { + 'text': alt.name + }).inject(self.name_select) + }) + + Array.each(Quality.profiles, function(q){ + new Element('option', { + 'value': q.indentifier, + 'text': q.label + }).inject(self.quality_select) + }); + + self.options.addClass('set'); + } + + }, + closeOptions: function(){ var self = this; @@ -232,4 +323,4 @@ Block.Search.Item = new Class({ return this.el } -}) +}); diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index f495bd1a..d2d469fb 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -1,6 +1,6 @@ var CouchPotato = new Class({ - Implements: [Options], + Implements: [Events, Options], defaults: { page: 'wanted', @@ -11,7 +11,7 @@ var CouchPotato = new Class({ pages: [], block: [], - initialize: function(options) { + setup: function(options) { var self = this; self.setOptions(options); @@ -62,6 +62,8 @@ var CouchPotato = new Class({ $(pg).inject(self.content); }); + self.fireEvent('load') + }, openPage: function(url) { @@ -85,9 +87,13 @@ var CouchPotato = new Class({ getBlock: function(block_name){ return this.block[block_name] + }, + + getPage: function(name){ + return this.pages[name] } }); - +window.App = new CouchPotato(); var ApiClass = new Class({ diff --git a/couchpotato/static/scripts/eventstack.js b/couchpotato/static/scripts/library/eventstack.js similarity index 100% rename from couchpotato/static/scripts/eventstack.js rename to couchpotato/static/scripts/library/eventstack.js diff --git a/couchpotato/static/scripts/eventstack_outerclick.js b/couchpotato/static/scripts/library/eventstack_outerclick.js similarity index 100% rename from couchpotato/static/scripts/eventstack_outerclick.js rename to couchpotato/static/scripts/library/eventstack_outerclick.js diff --git a/couchpotato/static/scripts/history.js b/couchpotato/static/scripts/library/history.js similarity index 100% rename from couchpotato/static/scripts/history.js rename to couchpotato/static/scripts/library/history.js diff --git a/couchpotato/static/scripts/mootools.js b/couchpotato/static/scripts/library/mootools.js similarity index 100% rename from couchpotato/static/scripts/mootools.js rename to couchpotato/static/scripts/library/mootools.js diff --git a/couchpotato/static/scripts/mootools_more.js b/couchpotato/static/scripts/library/mootools_more.js similarity index 100% rename from couchpotato/static/scripts/mootools_more.js rename to couchpotato/static/scripts/library/mootools_more.js diff --git a/couchpotato/static/scripts/mootools_tween_css3.js b/couchpotato/static/scripts/library/mootools_tween_css3.js similarity index 100% rename from couchpotato/static/scripts/mootools_tween_css3.js rename to couchpotato/static/scripts/library/mootools_tween_css3.js diff --git a/couchpotato/static/scripts/uniform.js b/couchpotato/static/scripts/library/uniform.js similarity index 100% rename from couchpotato/static/scripts/uniform.js rename to couchpotato/static/scripts/library/uniform.js diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index dbb36f5e..90c5e868 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -105,20 +105,7 @@ Page.Settings = new Class({ // Create tabs Object.each(self.tabs, function(tab, tab_name){ - if(!self.tabs[tab_name].tab){ - var tab_el = new Element('li').adopt( - new Element('a', { - 'href': '/'+self.name+'/'+tab_name+'/', - 'text': tab.label.capitalize() - }) - ).inject(self.tabs_container); - - self.tabs[tab_name] = Object.merge(self.tabs[tab_name], { - 'tab': tab_el, - 'content': new Element('div.tab_content').inject(self.containers), - 'groups': {} - }) - } + self.createTab(tab_name, tab) }); // Add content to tabs @@ -128,17 +115,7 @@ Page.Settings = new Class({ section.groups.sortBy('order').each(function(group){ // Create the group - var group_el = new Element('fieldset', { - 'class': group.advanced ? 'inlineLabels advanced' : 'inlineLabels' - }).adopt( - new Element('h2', { - 'text': group.label - }).adopt( - new Element('span.hint', { - 'text': group.description - }) - ) - ).inject(self.tabs[group.tab].content); + var group_el = self.createGroup(group).inject(self.tabs[group.tab].content); self.tabs[group.tab].groups[group.name] = group_el @@ -152,8 +129,57 @@ Page.Settings = new Class({ }); }); + + + self.fireEvent('create'); self.openTab(); + }, + + createTab: function(tab_name, tab){ + var self = this; + + if(self.tabs[tab_name] && self.tabs[tab_name].tab) + return self.tabs[tab_name].tab + + var tab_el = new Element('li').adopt( + new Element('a', { + 'href': '/'+self.name+'/'+tab_name+'/', + 'text': tab.label.capitalize() + }) + ).inject(self.tabs_container); + + if(!self.tabs[tab_name]) + self.tabs[tab_name] = { + 'label': tab.label + } + + self.tabs[tab_name] = Object.merge(self.tabs[tab_name], { + 'tab': tab_el, + 'content': new Element('div.tab_content').inject(self.containers), + 'groups': {} + }) + + return self.tabs[tab_name] + + }, + + createGroup: function(group){ + var self = this; + + var group_el = new Element('fieldset', { + 'class': group.advanced ? 'inlineLabels advanced' : 'inlineLabels' + }).adopt( + new Element('h2', { + 'text': group.label + }).adopt( + new Element('span.hint', { + 'text': group.description + }) + ) + ) + + return group_el } }); @@ -226,7 +252,7 @@ var OptionBase = new Class({ save: function(){ var self = this; - self.api().request('setting.save', { + Api.request('setting.save', { 'data': { 'section': self.section, 'name': self.name, @@ -273,10 +299,6 @@ var OptionBase = new Class({ return self.page.getValue(self.section, self.name); }, - api: function(){ - return this.page.api(); - }, - inject: function(el, position){ this.el.inject(el, position); return this.el; @@ -448,7 +470,7 @@ Option.Directory = new Class({ self.fillBrowser() } else { - self.api().request('directory.list', { + Api.request('directory.list', { 'data': { 'path': c }, @@ -461,7 +483,7 @@ Option.Directory = new Class({ var self = this; var v = self.input.get('value'); - var sep = self.api().getOption('path_sep'); + var sep = Api.getOption('path_sep'); var dirs = v.split(sep); dirs.pop(); diff --git a/couchpotato/static/scripts/quality.js b/couchpotato/static/scripts/quality.js new file mode 100644 index 00000000..f15bc680 --- /dev/null +++ b/couchpotato/static/scripts/quality.js @@ -0,0 +1,317 @@ +var QualityBase = new Class({ + + tab: '', + content: '', + + setup: function(data){ + var self = this; + + self.profiles = data.profiles; + self.qualities = data.qualities; + + App.addEvent('load', self.addSettings.bind(self)) + + }, + + addSettings: function(){ + var self = this; + + self.settings = App.getPage('Settings') + self.settings.addEvent('create', function(){ + var tab = self.settings.createTab('profile', { + 'label': 'Profile', + 'name': 'profile' + }); + + self.tab = tab.tab; + self.content = tab.content; + + self.createProfiles(); + self.createOrdering(); + self.createSizes(); + + }) + + }, + + /** + * Profiles + */ + createProfiles: function(){ + var self = this; + + self.settings.createGroup({ + 'label': 'Custom', + 'description': 'Discriptions' + }).inject(self.content).adopt( + new Element('a.add_new', { + 'text': 'Create a new quality profile', + 'events': { + 'click': self.createNewProfile.bind(self) + } + }), + self.profile_container = new Element('div.container') + ) + + Object.each(self.profiles, self.createNewProfile.bind(self)) + + }, + + createNewProfile: function(data, nr){ + var self = this; + + self.profiles[nr] = new Profile(data); + $(self.profiles[nr]).inject(self.profile_container) + + }, + + /** + * Ordering + */ + createOrdering: function(){ + var self = this; + + self.settings.createGroup({ + 'label': 'Order', + 'description': 'Discriptions' + }).inject(self.content) + + }, + + /** + * Sizes + */ + createSizes: function(){ + var self = this; + + self.settings.createGroup({ + 'label': 'Sizes', + 'description': 'Discriptions', + 'advanced': true + }).inject(self.content) + } + +}); +window.Quality = new QualityBase(); + +var Profile = new Class({ + + data: {}, + types: [], + + initialize: function(data){ + var self = this; + + self.data = data; + self.types = []; + + self.create(); + + self.el.addEvents({ + 'change:relay(select, input[type=checkbox])': self.save.bind(self, 0), + 'keyup:relay(input[type=text])': self.save.bind(self, [300]) + }); + + }, + + create: function(){ + var self = this; + + var data = self.data; + + self.el = new Element('div', { + 'class': 'profile' + }).adopt( + new Element('h4', {'text': data.label}), + new Element('span.delete', { + 'html': 'del', + 'events': { + 'click': self.del.bind(self) + } + }), + new Element('div', { + 'class': 'ctrlHolder' + }).adopt( + new Element('label', {'text':'Name'}), + new Element('input.label.textInput.large', { + 'type':'text', + 'value': data.label + }) + ), + new Element('div.ctrlHolder').adopt( + new Element('label', {'text':'Wait'}), + new Element('input.wait_for.textInput.xsmall', { + 'type':'text', + 'value': data.wait_for + }), + new Element('span', {'text':' day(s) for better quality.'}) + ), + new Element('div.ctrlHolder').adopt( + new Element('label', {'text': 'Qualities'}), + self.type_container = new Element('div.types').adopt( + new Element('div.head').adopt( + new Element('span.quality_type', {'text': 'Search for'}), + new Element('span.finish', {'html': 'Finish'}) + ) + ), + new Element('a.addType', { + 'text': 'Add another quality to search for.', + 'href': '#', + 'events': { + 'click': self.addType.bind(self) + } + }) + ) + ); + + if(data.types) + Object.each(data.types, self.addType.bind(self)) + }, + + save: function(delay){ + var self = this; + + if(self.save_timer) clearTimeout(self.save_timer); + self.save_timer = (function(){ + + Api.request('profile.save', { + 'data': self.getData(), + 'useSpinner': true, + 'spinnerOptions': { + 'target': self.el + } + }); + }).delay(delay, self) + + }, + + getData: function(){ + var self = this; + + var data = { + 'id' : self.data.id, + 'label' : self.el.getElement('.label').get('value'), + 'wait_for' : self.el.getElement('.wait_for').get('value'), + 'types': [] + } + + Object.each(self.types, function(type){ + if(!type.deleted) + data.types.include(type.getData()); + }) + + return data + }, + + addType: function(data){ + var self = this; + + var t = new Profile.Type(data); + $(t).inject(self.type_container); + + self.types.include(t); + + }, + + del: function(){ + var self = this; + + Api.request('profile.delete', { + 'data': { + 'id': self.data.id + }, + 'useSpinner': true, + 'spinnerOptions': { + 'target': self.el + }, + 'onComplete': function(){ + self.el.destroy(); + } + }); + }, + + toElement: function(){ + return this.el + } + +}); + +Profile.Type = Class({ + + deleted: false, + + initialize: function(data){ + var self = this; + + self.data = data; + self.create(); + + }, + + create: function(){ + var self = this; + var data = self.data; + + self.el = new Element('div.type').adopt( + new Element('span.quality_type').adopt( + self.fillQualities() + ), + new Element('span.finish').adopt( + self.finish = new Element('input', { + 'type':'checkbox', + 'class':'finish', + 'checked': data.finish + }) + ), + new Element('span.delete', { + 'html': 'del', + 'events': { + 'click': self.del.bind(self) + } + }), + new Element('span', { + 'class':'handle' + }) + ) + + }, + + fillQualities: function(){ + var self = this; + + self.qualities = new Element('select'); + + Object.each(Quality.qualities, function(q){ + new Element('option', { + 'text': q.label, + 'value': q.identifier + }).inject(self.qualities) + }); + + self.qualities.set('value', self.data.quality); + + return self.qualities; + + }, + + getData: function(){ + var self = this; + + return { + 'quality': self.qualities.get('value'), + 'finish': +self.finish.checked + } + }, + + del: function(){ + var self = this; + + self.el.hide(); + self.deleted = true; + }, + + toElement: function(){ + return this.el; + } + +}) diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 12ce6795..0cc01d56 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -101,6 +101,26 @@ form { background: #f7f7f7 url('../images/spinner.gif') no-repeat center; } +.button { + background: #5082bc url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAyCAYAAACd+7GKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAClJREFUeNpi/v//vwMTAwPDfzjBgMpFI/7hFSOT9Y8qRuF3JLoHAQIMAHYtMmRA+CugAAAAAElFTkSuQmCC") repeat-x; + padding: 5px 10px 6px; + color: #fff; + text-decoration: none; + font-weight: bold; + line-height: 1; + + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + + -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + box-shadow: 0 1px 3px rgba(0,0,0,0.5); + text-shadow: 0 -1px 1px rgba(0,0,0,0.25); + border-bottom: 1px solid rgba(0,0,0,0.25); + cursor: pointer; +} + /*** Navigation ***/ .header { background: #f7f7f7; diff --git a/couchpotato/static/style/movie_add.css b/couchpotato/static/style/movie_add.css index 48fcbf4b..8e507de4 100644 --- a/couchpotato/static/style/movie_add.css +++ b/couchpotato/static/style/movie_add.css @@ -66,9 +66,30 @@ min-height: 140px; } - .search_form .results .movie .add { - min-height: 140px; + .search_form .results .movie .options { + height: 140px; } + + .search_form .results .movie .options > div { + margin: 0 10px; + } + + .search_form .results .movie .options .thumbnail { + vertical-align: middle; + } + + .search_form .results .movie .options select { + vertical-align: middle; + display: inline-block; + margin-right: 10px; + } + .search_form .results .movie .options select[name=name] { width: 180px; } + .search_form .results .movie .options select[name=quality] { width: 90px; } + + .search_form .results .movie .options .button { + vertical-align: middle; + display: inline-block; + } .search_form .results .movie .data { padding: 0 15px; diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index 4f46f73c..dd5c4c77 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -8,14 +8,14 @@ - - + + - + - - - + + + @@ -29,6 +29,8 @@ + +