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 @@
+
+