diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py
index 85ae33f4..740523ac 100644
--- a/couchpotato/__init__.py
+++ b/couchpotato/__init__.py
@@ -12,19 +12,21 @@ from sqlalchemy.orm.session import sessionmaker
from werkzeug.utils import redirect
import os
-app = Flask(__name__)
log = CPLog(__name__)
+
+app = Flask(__name__)
web = Module(__name__, 'web')
-def get_session(engine):
+def get_session(engine = None):
engine = engine if engine else get_engine()
- return scoped_session(sessionmaker(transactional = True, bind = engine))
+ return scoped_session(sessionmaker(bind = engine))
def get_engine():
return create_engine(Env.get('db_path'), echo = False)
+""" Web view """
@web.route('/')
@requires_auth
def index():
@@ -33,5 +35,6 @@ def index():
@app.errorhandler(404)
def page_not_found(error):
index_url = url_for('web.index')
- url = request.path[len(index_url):]
+ url = getattr(request, 'path')[len(index_url):]
return redirect(index_url + '#' + url)
+
diff --git a/couchpotato/api/__init__.py b/couchpotato/api/__init__.py
index 901bbac3..33546af1 100644
--- a/couchpotato/api/__init__.py
+++ b/couchpotato/api/__init__.py
@@ -1,30 +1,20 @@
-from couchpotato.core.settings.model import Resource
+from couchpotato.core.helpers.request import jsonified
from flask import Module
-from flask.helpers import jsonify
api = Module(__name__)
def addApiView(route, func):
- api.add_url_rule(route + '/', route, func)
+ api.add_url_rule(route + '/', endpoint = route if route else 'index', view_func = func)
-
-@api.route('')
+""" Api view """
def index():
- return jsonify({'test': 'bla'})
+ from couchpotato import app
+ routes = []
+ for route, x in sorted(app.view_functions.iteritems()):
+ if route[0:4] == 'api.':
+ routes += [route[4:]]
-@api.route('movie/')
-def movie():
- return jsonify({
- 'success': True,
- 'movies': [
- {
- 'name': 'Movie 1',
- 'description': 'Description 1',
- },
- {
- 'name': 'Movie 2',
- 'description': 'Description 2',
- }
- ]
- })
+ return jsonified({'routes': routes})
+
+addApiView('', index)
diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py
index 793a9150..2babf0c3 100644
--- a/couchpotato/core/event.py
+++ b/couchpotato/core/event.py
@@ -1,5 +1,6 @@
-from couchpotato.core.logger import CPLog
from axl.axel import Event
+from couchpotato.core.logger import CPLog
+import traceback
log = CPLog(__name__)
events = {}
@@ -21,7 +22,17 @@ def fireEvent(name, *args, **kwargs):
try:
e = events[name]
e.asynchronous = False
- return e(*args, **kwargs)
+ result = e(*args, **kwargs)
+
+ results = []
+ for r in result:
+ if r[0] == True:
+ results.append(r[1])
+ else:
+ etype, value, tb = r[1]
+ log.debug(''.join(traceback.format_exception(etype, value, tb)))
+
+ return results
except Exception, e:
log.debug(e)
@@ -29,7 +40,8 @@ def fireEventAsync(name, *args, **kwargs):
try:
e = events[name]
e.asynchronous = True
- return e(*args, **kwargs)
+ e(*args, **kwargs)
+ return True
except Exception, e:
log.debug(e)
diff --git a/couchpotato/core/helpers.py b/couchpotato/core/helpers.py
deleted file mode 100644
index 112ecf70..00000000
--- a/couchpotato/core/helpers.py
+++ /dev/null
@@ -1,37 +0,0 @@
-def latinToAscii(unicrap):
- xlate = {0xc0:'A', 0xc1:'A', 0xc2:'A', 0xc3:'A', 0xc4:'A', 0xc5:'A',
- 0xc6:'Ae', 0xc7:'C',
- 0xc8:'E', 0xc9:'E', 0xca:'E', 0xcb:'E', 0x86:'e',
- 0xcc:'I', 0xcd:'I', 0xce:'I', 0xcf:'I',
- 0xd0:'Th', 0xd1:'N',
- 0xd2:'O', 0xd3:'O', 0xd4:'O', 0xd5:'O', 0xd6:'O', 0xd8:'O',
- 0xd9:'U', 0xda:'U', 0xdb:'U', 0xdc:'U',
- 0xdd:'Y', 0xde:'th', 0xdf:'ss',
- 0xe0:'a', 0xe1:'a', 0xe2:'a', 0xe3:'a', 0xe4:'a', 0xe5:'a',
- 0xe6:'ae', 0xe7:'c',
- 0xe8:'e', 0xe9:'e', 0xea:'e', 0xeb:'e',
- 0xec:'i', 0xed:'i', 0xee:'i', 0xef:'i',
- 0xf0:'th', 0xf1:'n',
- 0xf2:'o', 0xf3:'o', 0xf4:'o', 0xf5:'o', 0xf6:'o', 0xf8:'o',
- 0xf9:'u', 0xfa:'u', 0xfb:'u', 0xfc:'u',
- 0xfd:'y', 0xfe:'th', 0xff:'y',
- 0xa1:'!', 0xa2:'{cent}', 0xa3:'{pound}', 0xa4:'{currency}',
- 0xa5:'{yen}', 0xa6:'|', 0xa7:'{section}', 0xa8:'{umlaut}',
- 0xa9:'{C}', 0xaa:'{^a}', 0xab:'<<', 0xac:'{not}',
- 0xad:'-', 0xae:'{R}', 0xaf:'_', 0xb0:'{degrees}',
- 0xb1:'{+/-}', 0xb2:'{^2}', 0xb3:'{^3}', 0xb4:"'",
- 0xb5:'{micro}', 0xb6:'{paragraph}', 0xb7:'*', 0xb8:'{cedilla}',
- 0xb9:'{^1}', 0xba:'{^o}', 0xbb:'>>',
- 0xbc:'{1/4}', 0xbd:'{1/2}', 0xbe:'{3/4}', 0xbf:'?',
- 0xd7:'*', 0xf7:'/'
- }
-
- r = ''
- for i in unicrap:
- if xlate.has_key(ord(i)):
- r += xlate[ord(i)]
- elif ord(i) >= 0x80:
- pass
- else:
- r += str(i)
- return r
diff --git a/couchpotato/core/helpers/__init__.py b/couchpotato/core/helpers/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/couchpotato/core/helpers/__init__.py
@@ -0,0 +1 @@
+
diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py
new file mode 100644
index 00000000..87febde3
--- /dev/null
+++ b/couchpotato/core/helpers/encoding.py
@@ -0,0 +1,26 @@
+from couchpotato.core.logger import CPLog
+from string import ascii_letters, digits
+import re
+import unicodedata
+
+log = CPLog(__name__)
+
+def toSafeString(original):
+ valid_chars = "-_.() %s%s" % (ascii_letters, digits)
+ cleanedFilename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
+ return ''.join(c for c in cleanedFilename if c in valid_chars)
+
+def simplifyString(original):
+ string = toSafeString(original)
+ split = re.split('\W+', string.lower())
+ return toUnicode(' '.join(split))
+
+def toUnicode(original, *args):
+ try:
+ if type(original) is unicode:
+ return original
+ else:
+ return unicode(original, *args)
+ except UnicodeDecodeError:
+ ascii_text = str(original).encode('string_escape')
+ return unicode(ascii_text)
diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py
new file mode 100644
index 00000000..3cde2cb1
--- /dev/null
+++ b/couchpotato/core/helpers/request.py
@@ -0,0 +1,22 @@
+from flask.globals import current_app
+from flask.helpers import json, jsonify
+import flask
+
+
+def getParams():
+ return getattr(flask.request, 'args')
+
+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')
+
+def jsonified(*args, **kwargs):
+ from couchpotato.environment import Env
+ callback = getParam('json_callback', None)
+ if callback:
+ return padded_jsonify(callback, *args, **kwargs)
+ else:
+ return jsonify(*args, **kwargs)
diff --git a/couchpotato/core/helpers/rss.py b/couchpotato/core/helpers/rss.py
new file mode 100644
index 00000000..cacbdec2
--- /dev/null
+++ b/couchpotato/core/helpers/rss.py
@@ -0,0 +1,33 @@
+from couchpotato.core.logger import CPLog
+import xml.etree.ElementTree as XMLTree
+
+log = CPLog(__name__)
+
+class RSS():
+
+ def gettextelements(self, xml, path):
+ ''' Find elements and return tree'''
+
+ textelements = []
+ try:
+ elements = xml.findall(path)
+ except:
+ return
+ for element in elements:
+ textelements.append(element.text)
+ return textelements
+
+ def gettextelement(self, xml, path):
+ ''' Find element and return text'''
+
+ try:
+ return xml.find(path).text
+ except:
+ return
+
+ def getItems(self, data, path = 'channel/item'):
+ try:
+ return XMLTree.parse(data).findall(path)
+ except Exception, e:
+ log.error('Error parsing RSS. %s' % e)
+ return []
diff --git a/couchpotato/core/plugins/file_browser/main.py b/couchpotato/core/plugins/file_browser/main.py
index 619dc8b9..31e987fc 100644
--- a/couchpotato/core/plugins/file_browser/main.py
+++ b/couchpotato/core/plugins/file_browser/main.py
@@ -1,6 +1,5 @@
from couchpotato.api import addApiView
-from couchpotato.environment import Env
-from flask.helpers import jsonify
+from couchpotato.core.helpers.request import getParam, jsonified
import os
import string
@@ -45,12 +44,12 @@ class FileBrowser():
def view(self):
try:
- fb = FileBrowser(Env.getParam('path', '/'))
+ fb = FileBrowser(getParam('path', '/'))
dirs = fb.getDirectories()
except:
dirs = []
- return jsonify({
+ return jsonified({
'empty': len(dirs) == 0,
'dirs': dirs,
})
diff --git a/couchpotato/core/plugins/movie_add/main.py b/couchpotato/core/plugins/movie_add/main.py
index e82a8457..c4b0eb7c 100644
--- a/couchpotato/core/plugins/movie_add/main.py
+++ b/couchpotato/core/plugins/movie_add/main.py
@@ -1,30 +1,36 @@
from couchpotato.api import addApiView
-from couchpotato.core.event import getEvent, fireEvent
+from couchpotato.core.event import fireEvent
+from couchpotato.core.helpers.request import getParams, jsonified
from couchpotato.core.plugins.base import Plugin
-from couchpotato.environment import Env
-from flask.helpers import jsonify
class MovieAdd(Plugin):
def __init__(self):
addApiView('movie.add.search', self.search)
+ addApiView('movie.add.select', self.select)
def search(self):
- a = Env.getParams()
+ a = getParams()
- print fireEvent('provider.movie.search', q = a.get('q'))
+ results = fireEvent('provider.movie.search', q = a.get('q'))
- movies = [
- {'id': 1, 'name': 'test'}
- ]
+ # Combine movie results
+ movies = []
+ for r in results:
+ movies += r
- return jsonify({
+ return jsonified({
'success': True,
'empty': len(movies) == 0,
'movies': movies,
})
-
def select(self):
- pass
+
+ 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
new file mode 100644
index 00000000..b7ca857d
--- /dev/null
+++ b/couchpotato/core/plugins/movie_list/__init__.py
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 00000000..008511ff
--- /dev/null
+++ b/couchpotato/core/plugins/movie_list/main.py
@@ -0,0 +1,41 @@
+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/providers/base.py b/couchpotato/core/providers/base.py
index 1e20b986..ec9bc013 100644
--- a/couchpotato/core/providers/base.py
+++ b/couchpotato/core/providers/base.py
@@ -1,9 +1,4 @@
-from couchpotato.core.helpers import latinToAscii
from couchpotato.core.logger import CPLog
-from string import ascii_letters, digits
-import re
-import unicodedata
-import xml.etree.ElementTree as XMLTree
log = CPLog(__name__)
@@ -15,43 +10,3 @@ class Provider():
def __init__(self):
pass
- def toSaveString(self, string):
- string = latinToAscii(string)
- string = ''.join((c for c in unicodedata.normalize('NFD', unicode(string)) if unicodedata.category(c) != 'Mn'))
- safe_chars = ascii_letters + digits + '_ -.,\':!?'
- r = ''.join([char if char in safe_chars else ' ' for char in string])
- return re.sub('\s+' , ' ', r)
-
- def toSearchString(self, string):
- string = latinToAscii(string)
- string = ''.join((c for c in unicodedata.normalize('NFD', unicode(string)) if unicodedata.category(c) != 'Mn'))
- safe_chars = ascii_letters + digits + ' \''
- r = ''.join([char if char in safe_chars else ' ' for char in string])
- return re.sub('\s+' , ' ', r).replace('\'s', 's').replace('\'', ' ')
-
- def gettextelements(self, xml, path):
- ''' Find elements and return tree'''
-
- textelements = []
- try:
- elements = xml.findall(path)
- except:
- return
- for element in elements:
- textelements.append(element.text)
- return textelements
-
- def gettextelement(self, xml, path):
- ''' Find element and return text'''
-
- try:
- return xml.find(path).text
- except:
- return
-
- def getItems(self, data, path = 'channel/item'):
- try:
- return XMLTree.parse(data).findall(path)
- except Exception, e:
- log.error('Error parsing RSS. %s' % e)
- return []
diff --git a/couchpotato/core/providers/tmdb/main.py b/couchpotato/core/providers/tmdb/main.py
index 484e9ce2..edfed972 100644
--- a/couchpotato/core/providers/tmdb/main.py
+++ b/couchpotato/core/providers/tmdb/main.py
@@ -1,10 +1,12 @@
from __future__ import with_statement
from couchpotato.core.event import addEvent
+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 urllib import quote_plus
import urllib2
+import simplejson as json
log = CPLog(__name__)
@@ -21,7 +23,7 @@ class TMDB(Provider):
def conf(self, attr):
return Env.setting(attr, 'TheMovieDB')
- def search(self, q, limit = 8, alternative = True):
+ def search(self, q, limit = 12, alternative = True):
''' Find movie by name '''
if self.isDisabled():
@@ -29,58 +31,61 @@ class TMDB(Provider):
log.debug('TheMovieDB - Searching for movie: %s' % q)
- url = "%s/%s/%s/xml/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(self.toSearchString(q)))
+ url = "%s/%s/%s/json/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(simplifyString(q)))
log.info('Searching: %s' % url)
data = urllib2.urlopen(url)
+ jsn = json.load(data)
- return self.parseXML(data, limit, alternative = alternative)
+ return self.parse(jsn, limit, alternative = alternative)
- def parseXML(self, data, limit, alternative = True):
+ def parse(self, data, limit, alternative = True):
if data:
log.debug('TheMovieDB - Parsing RSS')
try:
- xml = self.getItems(data, 'movies/movie')
-
results = []
nr = 0
- for movie in xml:
- id = int(self.gettextelement(movie, "id"))
+ for movie in data:
- name = self.gettextelement(movie, "name")
- imdb = self.gettextelement(movie, "imdb_id")
- year = str(self.gettextelement(movie, "released"))[:4]
+ year = movie['released'][:4]
+
+ # Poster url
+ poster = ''
+ for p in movie['posters']:
+ p = p['image']
+ if(p['size'] == 'thumb'):
+ poster = p['url']
+ break
# 1900 is the same as None
if year == '1900':
- year = 'None'
+ year = None
- results.append({
- 'id': id,
- 'name': self.toSaveString(name),
- 'imdb': imdb,
- 'year': year
- })
+ movie_data = {
+ 'id': int(movie['id']),
+ 'name': toUnicode(movie['name']),
+ 'poster': poster,
+ 'imdb': movie['imdb_id'],
+ 'year': year,
+ 'tagline': 'This is the tagline of the movie',
+ }
+ results.append(movie_data)
- alternativeName = self.gettextelement(movie, "alternative_name")
+ alternativeName = movie['alternative_name']
if alternativeName and alternative:
- if alternativeName.lower() != name.lower() and alternativeName.lower() != 'none' and alternativeName != None:
- results.append({
- 'id': id,
- 'name': self.toSaveString(alternativeName),
- 'imdb': imdb,
- 'year': year
- })
+ if alternativeName.lower() != movie['name'].lower() and alternativeName.lower() != 'none' and alternativeName != None:
+ movie_data['name'] = toUnicode(alternativeName)
+ results.append(movie_data)
nr += 1
if nr == limit:
break
- #log.info('TheMovieDB - Found: %s' % results)
+ log.info('TheMovieDB - Found: %s' % [result['name'] + u' (' + str(result['year']) + ')' for result in results])
return results
- except SyntaxError:
- log.error('TheMovieDB - Failed to parse XML response from TheMovieDb')
+ except SyntaxError, e:
+ log.error('TheMovieDB - Failed to parse XML response from TheMovieDb: %s' % e)
return False
diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py
index 0d088c2b..2d76e05d 100644
--- a/couchpotato/core/settings/__init__.py
+++ b/couchpotato/core/settings/__init__.py
@@ -1,7 +1,7 @@
from __future__ import with_statement
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent
-from flask.helpers import jsonify
+from couchpotato.core.helpers.request import getParams, jsonified
import ConfigParser
import os.path
import time
@@ -107,14 +107,14 @@ class Settings():
def view(self):
- return jsonify({
+ return jsonified({
'options': self.getOptions(),
'values': self.getValues()
})
def saveView(self):
- a = Env.getParams()
+ a = getParams()
section = a.get('section')
option = a.get('name')
@@ -123,6 +123,6 @@ class Settings():
self.set(option, section, value)
self.save()
- return jsonify({
+ return jsonified({
'success': True,
})
diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py
index 6949a25a..1cea72cb 100644
--- a/couchpotato/core/settings/model.py
+++ b/couchpotato/core/settings/model.py
@@ -2,7 +2,7 @@ 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
+from sqlalchemy.types import Integer, String, Unicode, UnicodeText, Boolean
options_defaults["shortnames"] = True
@@ -13,43 +13,111 @@ options_defaults["shortnames"] = True
# http://elixir.ematia.de/trac/wiki/Recipes/MultipleDatabasesOneMetadata
__session__ = None
-class Resource(Entity):
- """Represents a resource of movies.
- This resources can be online or offline."""
- name = Field(Unicode(255))
- path = Field(Unicode(255))
+
+class Movie(Entity):
+ """Movie Resource a movie could have multiple releases
+ The files belonging to the movie object are global for the whole movie
+ such as trailers, nfo, thumbnails"""
+
+ mooli_id = Field(Integer)
+
+ profile = ManyToOne('Profile')
releases = OneToMany('Release')
+ files = OneToMany('File')
class Release(Entity):
"""Logically groups all files that belong to a certain release, such as
- parts of a movie, subtitles, nfo, trailers etc."""
- files = OneToMany('File')
- mooli_id = Field(Integer)
- resource = ManyToOne('Resource')
+ parts of a movie, subtitles."""
+ movie = ManyToOne('Movie')
+ status = ManyToOne('Status')
+ quality = ManyToOne('Quality')
+ files = OneToMany('File')
+ history = OneToMany('History')
+
+
+class Status(Entity):
+ """The status of a release, such as Downloaded, Deleted, Wanted etc"""
+
+ identifier = Field(String(20), unique = True)
+ label = Field(String(20))
+
+ releases = OneToMany('Release')
+
+
+class Quality(Entity):
+ """Quality name of a release, DVD, 720P, DVD-Rip etc"""
+
+ identifier = Field(String(20), unique = True)
+ label = Field(String(20))
+
+ releases = OneToMany('Release')
+ profile_types = ManyToOne('ProfileType')
+
+class Profile(Entity):
+ """"""
+
+ identifier = Field(String(20), unique = True)
+ label = Field(Unicode(50))
+ order = Field(Integer)
+ wait_for = Field(Integer)
+
+ movie = OneToMany('Movie')
+ profile_type = OneToMany('ProfileType')
+
+class ProfileType(Entity):
+ """"""
+
+ order = Field(Integer)
+ mark_completed = Field(Boolean)
+ wait_for = Field(Integer)
+
+ type = OneToMany('Quality')
+ profile = ManyToOne('Profile')
class File(Entity):
"""File that belongs to a release."""
- history = OneToMany('RenameHistory')
+
path = Field(Unicode(255), nullable = False, unique = True)
- # Subtitles can have multiple parts, too
part = Field(Integer)
+
+ history = OneToMany('RenameHistory')
+ movie = ManyToOne('Movie')
release = ManyToOne('Release')
- # Let's remember the size so we know about offline media.
- size = Field(Integer, nullable = False)
type = ManyToOne('FileType')
+ properties = OneToMany('FileProperty')
class FileType(Entity):
"""Types could be trailer, subtitle, movie, partial movie etc."""
+
identifier = Field(String(20), unique = True)
- name = Field(Unicode(255), nullable = False)
+ name = Field(Unicode(50), nullable = False)
+
files = OneToMany('File')
+class FileProperty(Entity):
+ """Properties that can be bound to a file for off-line usage"""
+
+ identifier = Field(String(20))
+ value = Field(Unicode(255), nullable = False)
+
+ file = ManyToOne('File')
+
+
+class History(Entity):
+ """History of actions that are connected to a certain release,
+ such as, renamed to, downloaded, deleted, download subtitles etc"""
+
+ message = Field(UnicodeText())
+ release = ManyToOne('Release')
+
class RenameHistory(Entity):
"""Remembers from where to where files have been moved."""
- file = ManyToOne('File')
+
old = Field(String(255))
new = Field(String(255))
+
+ file = ManyToOne('File')
diff --git a/couchpotato/environment.py b/couchpotato/environment.py
index fd742f62..bf02023d 100644
--- a/couchpotato/environment.py
+++ b/couchpotato/environment.py
@@ -1,6 +1,5 @@
from couchpotato.core.loader import Loader
from couchpotato.core.settings import Settings
-import flask
class Env:
@@ -42,11 +41,3 @@ class Env:
s = Env.get('settings')
s.set(section, attr, value)
return s
-
- @staticmethod
- def getParams():
- return getattr(flask.request, 'args')
-
- @staticmethod
- def getParam(attr, default = None):
- return getattr(flask.request, 'args').get(attr, default)
diff --git a/couchpotato/static/scripts/block/search.js b/couchpotato/static/scripts/block/search.js
index a6d2d700..2f5b47a0 100644
--- a/couchpotato/static/scripts/block/search.js
+++ b/couchpotato/static/scripts/block/search.js
@@ -2,40 +2,131 @@ Block.Search = new Class({
Extends: BlockBase,
+ cache: {},
+
create: function(){
var self = this;
self.el = new Element('div.search_form').adopt(
self.input = new Element('input', {
'events': {
- 'keyup': self.autocomplete.bind(self)
+ 'keyup': self.keyup.bind(self)
}
- })
+ }),
+ self.results = new Element('div.results')
);
+
+ // Debug
+ self.input.set('value', 'iron man');
+ self.autocomplete(0)
},
-
- autocomplete: function(){
+
+ keyup: function(e){
var self = this;
-
- if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer)
- self.autocomplete_timer = self.list.delay(300, self)
+
+ if(['up', 'down'].indexOf(e.key) > -1){
+ p('select item')
+ }
+ else if(self.q() != self.last_q) {
+ self.autocomplete()
+ }
+
},
-
+
+ autocomplete: function(delay){
+ var self = this;
+
+ if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer)
+ self.autocomplete_timer = self.list.delay((delay || 300), self)
+ },
+
list: function(){
var self = this;
-
+
if(self.api_request) self.api_request.cancel();
- self.api_request = self.api().request('movie.add.search', {
- 'data': {
- 'q': self.input.get('value')
- },
- 'onComplete': self.fill.bind(self)
- })
-
+
+ var q = self.q();
+ var cache = self.cache[q];
+
+ if(!cache)
+ self.api_request = self.api().request('movie.add.search', {
+ 'data': {
+ 'q': q
+ },
+ 'onComplete': self.fill.bind(self, q)
+ })
+ else
+ self.fill(q, cache)
+
+ self.last_q = q;
+
},
-
- fill: function(){
-
+
+ fill: function(q, json){
+ var self = this;
+
+ self.cache[q] = json
+
+ self.movies = []
+ self.results.empty()
+
+ Object.each(json.movies, function(movie){
+ var m = new Block.Search.Item(movie);
+ $(m).inject(self.results)
+
+ self.movies.include(m)
+ });
+
+ },
+
+ q: function(){
+ return this.input.get('value').trim();
}
-});
\ No newline at end of file
+});
+
+Block.Search.Item = new Class({
+
+ initialize: function(info){
+ var self = this;
+
+ self.info = info;
+
+ self.create();
+ },
+
+ create: function(){
+ var self = this;
+
+ self.el = new Element('div.movie').adopt(
+ self.name = new Element('h2', {
+ 'text': self.info.name
+ }),
+ self.tagline = new Element('span', {
+ 'text': self.info.tagline
+ }),
+ self.year = self.info.year ? new Element('span', {
+ 'text': self.info.year
+ }) : null,
+ self.director = self.info.director ? new Element('span', {
+ 'text': 'Director:' + self.info.director
+ }) : null,
+ self.starring = self.info.actors ? new Element('span', {
+ 'text': 'Starring:'
+ }) : null
+ )
+
+ if(self.info.actors){
+ Object.each(self.info.actors, function(actor){
+ new Element('span', {
+ 'text': actor.name
+ }).inject(self.starring)
+ })
+ }
+ },
+
+ toElement: function(){
+ return this.el
+ }
+
+})
diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js
index 380dfccc..c4601e96 100644
--- a/couchpotato/static/scripts/couchpotato.js
+++ b/couchpotato/static/scripts/couchpotato.js
@@ -100,16 +100,15 @@ var Api = new Class({
var self = this
self.options = options;
- self.req = new Request.JSON({
- 'method': 'get'
- })
},
request: function(type, options){
var self = this;
- return new Request.JSON(Object.merge({
+ var r_type = self.options.is_remote ? 'JSONP' : 'JSON';
+ return new Request[r_type](Object.merge({
+ 'callbackKey': 'json_callback',
'method': 'get',
'url': self.createUrl(type),
}, options)).send()
diff --git a/couchpotato/static/scripts/mootools.js b/couchpotato/static/scripts/mootools.js
index efaec23c..541b9848 100644
--- a/couchpotato/static/scripts/mootools.js
+++ b/couchpotato/static/scripts/mootools.js
@@ -8,4212 +8,355 @@ web build:
packager build:
- packager build Core/Class Core/Class.Extras Core/Element Core/Element.Style Core/Request.JSON Core/DOMReady
-/*
----
-
-name: Core
-
-description: The heart of MooTools.
-
-license: MIT-style license.
-
-copyright: Copyright (c) 2006-2010 [Valerio Proietti](http://mad4milk.net/).
-
-authors: The MooTools production team (http://mootools.net/developers/)
-
-inspiration:
- - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
- - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
-
-provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+copyrights:
+ - [MooTools](http://mootools.net)
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
...
*/
-
-(function(){
-
-this.MooTools = {
- version: '1.3',
- build: 'a3eed692dd85050d80168ec2c708efe901bb7db3'
-};
-
-// typeOf, instanceOf
-
-var typeOf = this.typeOf = function(item){
- if (item == null) return 'null';
- if (item.$family) return item.$family();
-
- if (item.nodeName){
- if (item.nodeType == 1) return 'element';
- if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
- } else if (typeof item.length == 'number'){
- if (item.callee) return 'arguments';
- if ('item' in item) return 'collection';
- }
-
- return typeof item;
-};
-
-var instanceOf = this.instanceOf = function(item, object){
- if (item == null) return false;
- var constructor = item.$constructor || item.constructor;
- while (constructor){
- if (constructor === object) return true;
- constructor = constructor.parent;
- }
- return item instanceof object;
-};
-
-// Function overloading
-
-var Function = this.Function;
-
-var enumerables = true;
-for (var i in {toString: 1}) enumerables = null;
-if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
-
-Function.prototype.overloadSetter = function(usePlural){
- var self = this;
- return function(a, b){
- if (a == null) return this;
- if (usePlural || typeof a != 'string'){
- for (var k in a) self.call(this, k, a[k]);
- if (enumerables) for (var i = enumerables.length; i--;){
- k = enumerables[i];
- if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
- }
- } else {
- self.call(this, a, b);
- }
- return this;
- };
-};
-
-Function.prototype.overloadGetter = function(usePlural){
- var self = this;
- return function(a){
- var args, result;
- if (usePlural || typeof a != 'string') args = a;
- else if (arguments.length > 1) args = arguments;
- if (args){
- result = {};
- for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
- } else {
- result = self.call(this, a);
- }
- return result;
- };
-};
-
-Function.prototype.extend = function(key, value){
- this[key] = value;
-}.overloadSetter();
-
-Function.prototype.implement = function(key, value){
- this.prototype[key] = value;
-}.overloadSetter();
-
-// From
-
-var slice = Array.prototype.slice;
-
-Function.from = function(item){
- return (typeOf(item) == 'function') ? item : function(){
- return item;
- };
-};
-
-Array.from = function(item){
- if (item == null) return [];
- return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
-};
-
-Number.from = function(item){
- var number = parseFloat(item);
- return isFinite(number) ? number : null;
-};
-
-String.from = function(item){
- return item + '';
-};
-
-// hide, protect
-
-Function.implement({
-
- hide: function(){
- this.$hidden = true;
- return this;
- },
-
- protect: function(){
- this.$protected = true;
- return this;
- }
-
-});
-
-// Type
-
-var Type = this.Type = function(name, object){
- if (name){
- var lower = name.toLowerCase();
- var typeCheck = function(item){
- return (typeOf(item) == lower);
- };
-
- Type['is' + name] = typeCheck;
- if (object != null){
- object.prototype.$family = (function(){
- return lower;
- }).hide();
-
- }
- }
-
- if (object == null) return null;
-
- object.extend(this);
- object.$constructor = Type;
- object.prototype.$constructor = object;
-
- return object;
-};
-
-var toString = Object.prototype.toString;
-
-Type.isEnumerable = function(item){
- return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
-};
-
-var hooks = {};
-
-var hooksOf = function(object){
- var type = typeOf(object.prototype);
- return hooks[type] || (hooks[type] = []);
-};
-
-var implement = function(name, method){
- if (method && method.$hidden) return this;
-
- var hooks = hooksOf(this);
-
- for (var i = 0; i < hooks.length; i++){
- var hook = hooks[i];
- if (typeOf(hook) == 'type') implement.call(hook, name, method);
- else hook.call(this, name, method);
- }
-
- var previous = this.prototype[name];
- if (previous == null || !previous.$protected) this.prototype[name] = method;
-
- if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
- return method.apply(item, slice.call(arguments, 1));
- });
-
- return this;
-};
-
-var extend = function(name, method){
- if (method && method.$hidden) return this;
- var previous = this[name];
- if (previous == null || !previous.$protected) this[name] = method;
- return this;
-};
-
-Type.implement({
-
- implement: implement.overloadSetter(),
-
- extend: extend.overloadSetter(),
-
- alias: function(name, existing){
- implement.call(this, name, this.prototype[existing]);
- }.overloadSetter(),
-
- mirror: function(hook){
- hooksOf(this).push(hook);
- return this;
- }
-
-});
-
-new Type('Type', Type);
-
-// Default Types
-
-var force = function(name, object, methods){
- var isType = (object != Object),
- prototype = object.prototype;
-
- if (isType) object = new Type(name, object);
-
- for (var i = 0, l = methods.length; i < l; i++){
- var key = methods[i],
- generic = object[key],
- proto = prototype[key];
-
- if (generic) generic.protect();
-
- if (isType && proto){
- delete prototype[key];
- prototype[key] = proto.protect();
- }
- }
-
- if (isType) object.implement(prototype);
-
- return force;
-};
-
-force('String', String, [
- 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
- 'slice', 'split', 'substr', 'substring', 'toLowerCase', 'toUpperCase'
-])('Array', Array, [
- 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
- 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
-])('Number', Number, [
- 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
-])('Function', Function, [
- 'apply', 'call', 'bind'
-])('RegExp', RegExp, [
- 'exec', 'test'
-])('Object', Object, [
- 'create', 'defineProperty', 'defineProperties', 'keys',
- 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
- 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
-])('Date', Date, ['now']);
-
-Object.extend = extend.overloadSetter();
-
-Date.extend('now', function(){
- return +(new Date);
-});
-
-new Type('Boolean', Boolean);
-
-// fixes NaN returning as Number
-
-Number.prototype.$family = function(){
- return isFinite(this) ? 'number' : 'null';
-}.hide();
-
-// Number.random
-
-Number.extend('random', function(min, max){
- return Math.floor(Math.random() * (max - min + 1) + min);
-});
-
-// forEach, each
-
-Object.extend('forEach', function(object, fn, bind){
- for (var key in object){
- if (object.hasOwnProperty(key)) fn.call(bind, object[key], key, object);
- }
-});
-
-Object.each = Object.forEach;
-
-Array.implement({
-
- forEach: function(fn, bind){
- for (var i = 0, l = this.length; i < l; i++){
- if (i in this) fn.call(bind, this[i], i, this);
- }
- },
-
- each: function(fn, bind){
- Array.forEach(this, fn, bind);
- return this;
- }
-
-});
-
-// Array & Object cloning, Object merging and appending
-
-var cloneOf = function(item){
- switch (typeOf(item)){
- case 'array': return item.clone();
- case 'object': return Object.clone(item);
- default: return item;
- }
-};
-
-Array.implement('clone', function(){
- var i = this.length, clone = new Array(i);
- while (i--) clone[i] = cloneOf(this[i]);
- return clone;
-});
-
-var mergeOne = function(source, key, current){
- switch (typeOf(current)){
- case 'object':
- if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
- else source[key] = Object.clone(current);
- break;
- case 'array': source[key] = current.clone(); break;
- default: source[key] = current;
- }
- return source;
-};
-
-Object.extend({
-
- merge: function(source, k, v){
- if (typeOf(k) == 'string') return mergeOne(source, k, v);
- for (var i = 1, l = arguments.length; i < l; i++){
- var object = arguments[i];
- for (var key in object) mergeOne(source, key, object[key]);
- }
- return source;
- },
-
- clone: function(object){
- var clone = {};
- for (var key in object) clone[key] = cloneOf(object[key]);
- return clone;
- },
-
- append: function(original){
- for (var i = 1, l = arguments.length; i < l; i++){
- var extended = arguments[i] || {};
- for (var key in extended) original[key] = extended[key];
- }
- return original;
- }
-
-});
-
-// Object-less types
-
-['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
- new Type(name);
-});
-
-// Unique ID
-
-var UID = Date.now();
-
-String.extend('uniqueID', function(){
- return (UID++).toString(36);
-});
-
-
-
-})();
-
-
-/*
----
-
-name: Array
-
-description: Contains Array Prototypes like each, contains, and erase.
-
-license: MIT-style license.
-
-requires: Type
-
-provides: Array
-
-...
-*/
-
-Array.implement({
-
- invoke: function(methodName){
- var args = Array.slice(arguments, 1);
- return this.map(function(item){
- return item[methodName].apply(item, args);
- });
- },
-
- every: function(fn, bind){
- for (var i = 0, l = this.length; i < l; i++){
- if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
- }
- return true;
- },
-
- filter: function(fn, bind){
- var results = [];
- for (var i = 0, l = this.length; i < l; i++){
- if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]);
- }
- return results;
- },
-
- clean: function(){
- return this.filter(function(item){
- return item != null;
- });
- },
-
- indexOf: function(item, from){
- var len = this.length;
- for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
- if (this[i] === item) return i;
- }
- return -1;
- },
-
- map: function(fn, bind){
- var results = [];
- for (var i = 0, l = this.length; i < l; i++){
- if (i in this) results[i] = fn.call(bind, this[i], i, this);
- }
- return results;
- },
-
- some: function(fn, bind){
- for (var i = 0, l = this.length; i < l; i++){
- if ((i in this) && fn.call(bind, this[i], i, this)) return true;
- }
- return false;
- },
-
- associate: function(keys){
- var obj = {}, length = Math.min(this.length, keys.length);
- for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
- return obj;
- },
-
- link: function(object){
- var result = {};
- for (var i = 0, l = this.length; i < l; i++){
- for (var key in object){
- if (object[key](this[i])){
- result[key] = this[i];
- delete object[key];
- break;
- }
- }
- }
- return result;
- },
-
- contains: function(item, from){
- return this.indexOf(item, from) != -1;
- },
-
- append: function(array){
- this.push.apply(this, array);
- return this;
- },
-
- getLast: function(){
- return (this.length) ? this[this.length - 1] : null;
- },
-
- getRandom: function(){
- return (this.length) ? this[Number.random(0, this.length - 1)] : null;
- },
-
- include: function(item){
- if (!this.contains(item)) this.push(item);
- return this;
- },
-
- combine: function(array){
- for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
- return this;
- },
-
- erase: function(item){
- for (var i = this.length; i--;){
- if (this[i] === item) this.splice(i, 1);
- }
- return this;
- },
-
- empty: function(){
- this.length = 0;
- return this;
- },
-
- flatten: function(){
- var array = [];
- for (var i = 0, l = this.length; i < l; i++){
- var type = typeOf(this[i]);
- if (type == 'null') continue;
- array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
- }
- return array;
- },
-
- pick: function(){
- for (var i = 0, l = this.length; i < l; i++){
- if (this[i] != null) return this[i];
- }
- return null;
- },
-
- hexToRgb: function(array){
- if (this.length != 3) return null;
- var rgb = this.map(function(value){
- if (value.length == 1) value += value;
- return value.toInt(16);
- });
- return (array) ? rgb : 'rgb(' + rgb + ')';
- },
-
- rgbToHex: function(array){
- if (this.length < 3) return null;
- if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
- var hex = [];
- for (var i = 0; i < 3; i++){
- var bit = (this[i] - 0).toString(16);
- hex.push((bit.length == 1) ? '0' + bit : bit);
- }
- return (array) ? hex : '#' + hex.join('');
- }
-
-});
-
-
-
-
-/*
----
-
-name: String
-
-description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
-
-license: MIT-style license.
-
-requires: Type
-
-provides: String
-
-...
-*/
-
-String.implement({
-
- test: function(regex, params){
- return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
- },
-
- contains: function(string, separator){
- return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
- },
-
- trim: function(){
- return this.replace(/^\s+|\s+$/g, '');
- },
-
- clean: function(){
- return this.replace(/\s+/g, ' ').trim();
- },
-
- camelCase: function(){
- return this.replace(/-\D/g, function(match){
- return match.charAt(1).toUpperCase();
- });
- },
-
- hyphenate: function(){
- return this.replace(/[A-Z]/g, function(match){
- return ('-' + match.charAt(0).toLowerCase());
- });
- },
-
- capitalize: function(){
- return this.replace(/\b[a-z]/g, function(match){
- return match.toUpperCase();
- });
- },
-
- escapeRegExp: function(){
- return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
- },
-
- toInt: function(base){
- return parseInt(this, base || 10);
- },
-
- toFloat: function(){
- return parseFloat(this);
- },
-
- hexToRgb: function(array){
- var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
- return (hex) ? hex.slice(1).hexToRgb(array) : null;
- },
-
- rgbToHex: function(array){
- var rgb = this.match(/\d{1,3}/g);
- return (rgb) ? rgb.rgbToHex(array) : null;
- },
-
- substitute: function(object, regexp){
- return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
- if (match.charAt(0) == '\\') return match.slice(1);
- return (object[name] != null) ? object[name] : '';
- });
- }
-
-});
-
-
-/*
----
-
-name: Function
-
-description: Contains Function Prototypes like create, bind, pass, and delay.
-
-license: MIT-style license.
-
-requires: Type
-
-provides: Function
-
-...
-*/
-
-Function.extend({
-
- attempt: function(){
- for (var i = 0, l = arguments.length; i < l; i++){
- try {
- return arguments[i]();
- } catch (e){}
- }
- return null;
- }
-
-});
-
-Function.implement({
-
- attempt: function(args, bind){
- try {
- return this.apply(bind, Array.from(args));
- } catch (e){}
-
- return null;
- },
-
- bind: function(bind){
- var self = this,
- args = (arguments.length > 1) ? Array.slice(arguments, 1) : null;
-
- return function(){
- if (!args && !arguments.length) return self.call(bind);
- if (args && arguments.length) return self.apply(bind, args.concat(Array.from(arguments)));
- return self.apply(bind, args || arguments);
- };
- },
-
- pass: function(args, bind){
- var self = this;
- if (args != null) args = Array.from(args);
- return function(){
- return self.apply(bind, args || arguments);
- };
- },
-
- delay: function(delay, bind, args){
- return setTimeout(this.pass(args, bind), delay);
- },
-
- periodical: function(periodical, bind, args){
- return setInterval(this.pass(args, bind), periodical);
- }
-
-});
-
-
-
-
-/*
----
-
-name: Number
-
-description: Contains Number Prototypes like limit, round, times, and ceil.
-
-license: MIT-style license.
-
-requires: Type
-
-provides: Number
-
-...
-*/
-
-Number.implement({
-
- limit: function(min, max){
- return Math.min(max, Math.max(min, this));
- },
-
- round: function(precision){
- precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
- return Math.round(this * precision) / precision;
- },
-
- times: function(fn, bind){
- for (var i = 0; i < this; i++) fn.call(bind, i, this);
- },
-
- toFloat: function(){
- return parseFloat(this);
- },
-
- toInt: function(base){
- return parseInt(this, base || 10);
- }
-
-});
-
-Number.alias('each', 'times');
-
-(function(math){
- var methods = {};
- math.each(function(name){
- if (!Number[name]) methods[name] = function(){
- return Math[name].apply(null, [this].concat(Array.from(arguments)));
- };
- });
- Number.implement(methods);
-})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
-
-
-/*
----
-
-name: Class
-
-description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
-
-license: MIT-style license.
-
-requires: [Array, String, Function, Number]
-
-provides: Class
-
-...
-*/
-
-(function(){
-
-var Class = this.Class = new Type('Class', function(params){
- if (instanceOf(params, Function)) params = {initialize: params};
-
- var newClass = function(){
- reset(this);
- if (newClass.$prototyping) return this;
- this.$caller = null;
- var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
- this.$caller = this.caller = null;
- return value;
- }.extend(this).implement(params);
-
- newClass.$constructor = Class;
- newClass.prototype.$constructor = newClass;
- newClass.prototype.parent = parent;
-
- return newClass;
-});
-
-var parent = function(){
- if (!this.$caller) throw new Error('The method "parent" cannot be called.');
- var name = this.$caller.$name,
- parent = this.$caller.$owner.parent,
- previous = (parent) ? parent.prototype[name] : null;
- if (!previous) throw new Error('The method "' + name + '" has no parent.');
- return previous.apply(this, arguments);
-};
-
-var reset = function(object){
- for (var key in object){
- var value = object[key];
- switch (typeOf(value)){
- case 'object':
- var F = function(){};
- F.prototype = value;
- object[key] = reset(new F);
- break;
- case 'array': object[key] = value.clone(); break;
- }
- }
- return object;
-};
-
-var wrap = function(self, key, method){
- if (method.$origin) method = method.$origin;
- var wrapper = function(){
- if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
- var caller = this.caller, current = this.$caller;
- this.caller = current; this.$caller = wrapper;
- var result = method.apply(this, arguments);
- this.$caller = current; this.caller = caller;
- return result;
- }.extend({$owner: self, $origin: method, $name: key});
- return wrapper;
-};
-
-var implement = function(key, value, retain){
- if (Class.Mutators.hasOwnProperty(key)){
- value = Class.Mutators[key].call(this, value);
- if (value == null) return this;
- }
-
- if (typeOf(value) == 'function'){
- if (value.$hidden) return this;
- this.prototype[key] = (retain) ? value : wrap(this, key, value);
- } else {
- Object.merge(this.prototype, key, value);
- }
-
- return this;
-};
-
-var getInstance = function(klass){
- klass.$prototyping = true;
- var proto = new klass;
- delete klass.$prototyping;
- return proto;
-};
-
-Class.implement('implement', implement.overloadSetter());
-
-Class.Mutators = {
-
- Extends: function(parent){
- this.parent = parent;
- this.prototype = getInstance(parent);
- },
-
- Implements: function(items){
- Array.from(items).each(function(item){
- var instance = new item;
- for (var key in instance) implement.call(this, key, instance[key], true);
- }, this);
- }
-};
-
-})();
-
-
-/*
----
-
-name: Class.Extras
-
-description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
-
-license: MIT-style license.
-
-requires: Class
-
-provides: [Class.Extras, Chain, Events, Options]
-
-...
-*/
-
-(function(){
-
-this.Chain = new Class({
-
- $chain: [],
-
- chain: function(){
- this.$chain.append(Array.flatten(arguments));
- return this;
- },
-
- callChain: function(){
- return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
- },
-
- clearChain: function(){
- this.$chain.empty();
- return this;
- }
-
-});
-
-var removeOn = function(string){
- return string.replace(/^on([A-Z])/, function(full, first){
- return first.toLowerCase();
- });
-};
-
-this.Events = new Class({
-
- $events: {},
-
- addEvent: function(type, fn, internal){
- type = removeOn(type);
-
-
-
- this.$events[type] = (this.$events[type] || []).include(fn);
- if (internal) fn.internal = true;
- return this;
- },
-
- addEvents: function(events){
- for (var type in events) this.addEvent(type, events[type]);
- return this;
- },
-
- fireEvent: function(type, args, delay){
- type = removeOn(type);
- var events = this.$events[type];
- if (!events) return this;
- args = Array.from(args);
- events.each(function(fn){
- if (delay) fn.delay(delay, this, args);
- else fn.apply(this, args);
- }, this);
- return this;
- },
-
- removeEvent: function(type, fn){
- type = removeOn(type);
- var events = this.$events[type];
- if (events && !fn.internal){
- var index = events.indexOf(fn);
- if (index != -1) delete events[index];
- }
- return this;
- },
-
- removeEvents: function(events){
- var type;
- if (typeOf(events) == 'object'){
- for (type in events) this.removeEvent(type, events[type]);
- return this;
- }
- if (events) events = removeOn(events);
- for (type in this.$events){
- if (events && events != type) continue;
- var fns = this.$events[type];
- for (var i = fns.length; i--;) this.removeEvent(type, fns[i]);
- }
- return this;
- }
-
-});
-
-this.Options = new Class({
-
- setOptions: function(){
- var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
- if (!this.addEvent) return this;
- for (var option in options){
- if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
- this.addEvent(option, options[option]);
- delete options[option];
- }
- return this;
- }
-
-});
-
-})();
-
-
-/*
----
-
-name: Browser
-
-description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
-
-license: MIT-style license.
-
-requires: [Array, Function, Number, String]
-
-provides: [Browser, Window, Document]
-
-...
-*/
-
-(function(){
-
-var document = this.document;
-var window = document.window = this;
-
-var UID = 1;
-
-this.$uid = (window.ActiveXObject) ? function(item){
- return (item.uid || (item.uid = [UID++]))[0];
-} : function(item){
- return item.uid || (item.uid = UID++);
-};
-
-$uid(window);
-$uid(document);
-
-var ua = navigator.userAgent.toLowerCase(),
- platform = navigator.platform.toLowerCase(),
- UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0],
- mode = UA[1] == 'ie' && document.documentMode;
-
-var Browser = this.Browser = {
-
- extend: Function.prototype.extend,
-
- name: (UA[1] == 'version') ? UA[3] : UA[1],
-
- version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
-
- Platform: {
- name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]
- },
-
- Features: {
- xpath: !!(document.evaluate),
- air: !!(window.runtime),
- query: !!(document.querySelector),
- json: !!(window.JSON)
- },
-
- Plugins: {}
-
-};
-
-Browser[Browser.name] = true;
-Browser[Browser.name + parseInt(Browser.version, 10)] = true;
-Browser.Platform[Browser.Platform.name] = true;
-
-// Request
-
-Browser.Request = (function(){
-
- var XMLHTTP = function(){
- return new XMLHttpRequest();
- };
-
- var MSXML2 = function(){
- return new ActiveXObject('MSXML2.XMLHTTP');
- };
-
- var MSXML = function(){
- return new ActiveXObject('Microsoft.XMLHTTP');
- };
-
- return Function.attempt(function(){
- XMLHTTP();
- return XMLHTTP;
- }, function(){
- MSXML2();
- return MSXML2;
- }, function(){
- MSXML();
- return MSXML;
- });
-
-})();
-
-Browser.Features.xhr = !!(Browser.Request);
-
-// Flash detection
-
-var version = (Function.attempt(function(){
- return navigator.plugins['Shockwave Flash'].description;
-}, function(){
- return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
-}) || '0 r0').match(/\d+/g);
-
-Browser.Plugins.Flash = {
- version: Number(version[0] || '0.' + version[1]) || 0,
- build: Number(version[2]) || 0
-};
-
-// String scripts
-
-Browser.exec = function(text){
- if (!text) return text;
- if (window.execScript){
- window.execScript(text);
- } else {
- var script = document.createElement('script');
- script.setAttribute('type', 'text/javascript');
- script.text = text;
- document.head.appendChild(script);
- document.head.removeChild(script);
- }
- return text;
-};
-
-String.implement('stripScripts', function(exec){
- var scripts = '';
- var text = this.replace(/
+
+
@@ -25,11 +28,13 @@