Merge branch 'develop' of github.com:RuudBurger/CouchPotatoServer into develop
Conflicts: couchpotato/core/_base/_core/__init__.py
@@ -33,7 +33,7 @@ def start():
|
||||
new_environ[key] = value.encode('iso-8859-1')
|
||||
|
||||
subprocess.call(args, env = new_environ)
|
||||
return os.path.isfile(os.path.join(options.data_dir, 'restart'))
|
||||
return os.path.isfile(os.path.join(base_path, 'restart'))
|
||||
except Exception, e:
|
||||
log.critical(e)
|
||||
return 0
|
||||
|
||||
@@ -18,3 +18,4 @@ def index():
|
||||
return jsonified({'routes': routes})
|
||||
|
||||
addApiView('', index)
|
||||
addApiView('default', index)
|
||||
|
||||
@@ -46,7 +46,7 @@ config = [{
|
||||
{
|
||||
'tab': 'general',
|
||||
'name': 'advanced',
|
||||
'description': "For those who know what the're doing",
|
||||
'description': "For those who know what they're doing",
|
||||
'advanced': True,
|
||||
'options': [
|
||||
{
|
||||
@@ -67,7 +67,7 @@ config = [{
|
||||
'name': 'data_dir',
|
||||
'label': 'Data dir',
|
||||
'type': 'directory',
|
||||
'description': 'Where cache/logs/etc are stored.',
|
||||
'description': 'Where cache/logs/etc are stored. Keep empty for <strong>./_data</strong>.',
|
||||
},
|
||||
{
|
||||
'name': 'url_base',
|
||||
@@ -79,13 +79,13 @@ config = [{
|
||||
'name': 'permission_folder',
|
||||
'default': 0755,
|
||||
'label': 'Folder CHMOD',
|
||||
'description': 'Permission for creating/copying folders',
|
||||
'description': 'Permission (octal) for creating/copying folders.',
|
||||
},
|
||||
{
|
||||
'name': 'permission_file',
|
||||
'default': 0755,
|
||||
'label': 'File CHMOD',
|
||||
'description': 'Permission for creating/copying files',
|
||||
'description': 'Permission (octal) for creating/copying files',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -43,14 +43,13 @@ class Core(Plugin):
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
if restart:
|
||||
self.createFile(self.restartFilePath(), 'This is the most suckiest way to register if CP is restarted. Ever...')
|
||||
|
||||
func = request.environ.get('werkzeug.server.shutdown')
|
||||
if func is None:
|
||||
try:
|
||||
request.environ.get('werkzeug.server.shutdown')()
|
||||
except:
|
||||
log.error('Failed shutting down the server')
|
||||
func()
|
||||
|
||||
def removeRestartFile(self):
|
||||
try:
|
||||
@@ -59,4 +58,4 @@ class Core(Plugin):
|
||||
pass
|
||||
|
||||
def restartFilePath(self):
|
||||
return os.path.join(Env.get('data_dir'), 'restart')
|
||||
return os.path.join(Env.get('app_dir'), 'restart')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -16,7 +17,10 @@ class Downloader(Plugin):
|
||||
pass
|
||||
|
||||
def cpTag(self, movie):
|
||||
return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
|
||||
if Env.setting('enabled', 'renamer'):
|
||||
return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
|
||||
|
||||
return ''
|
||||
|
||||
def isDisabled(self):
|
||||
return not self.isEnabled()
|
||||
|
||||
@@ -27,18 +27,16 @@ class Blackhole(Downloader):
|
||||
try:
|
||||
if not os.path.isfile(fullPath):
|
||||
log.info('Downloading %s to %s.' % (data.get('type'), fullPath))
|
||||
if isfunction(data.get('download')):
|
||||
file = data.get('download')()
|
||||
else:
|
||||
file = self.urlopen(data.get('url'))
|
||||
|
||||
if not file or file == '':
|
||||
try:
|
||||
file = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
|
||||
with open(fullPath, 'wb') as f:
|
||||
f.write(file)
|
||||
except:
|
||||
log.debug('Failed download file: %s' % data.get('name'))
|
||||
return False
|
||||
|
||||
with open(fullPath, 'wb') as f:
|
||||
f.write(file)
|
||||
|
||||
return True
|
||||
else:
|
||||
log.info('File %s already exists.' % fullPath)
|
||||
|
||||
@@ -6,6 +6,7 @@ from urllib import urlencode
|
||||
import base64
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -37,15 +38,11 @@ class Sabnzbd(Downloader):
|
||||
params = {
|
||||
'apikey': self.conf('api_key'),
|
||||
'cat': self.conf('category'),
|
||||
'mode': 'addurl',
|
||||
'name': data.get('url'),
|
||||
'mode': 'addfile',
|
||||
'nzbname': '%s%s' % (data.get('name'), self.cpTag(movie)),
|
||||
}
|
||||
|
||||
# sabNzbd complains about "invalid archive file" for newzbin urls
|
||||
# added using addurl, works fine with addid
|
||||
if data.get('addbyid'):
|
||||
params['mode'] = 'addid'
|
||||
nzb_file = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
|
||||
if pp:
|
||||
params['script'] = pp_script_fn
|
||||
@@ -53,9 +50,9 @@ class Sabnzbd(Downloader):
|
||||
url = cleanHost(self.conf('host')) + "api?" + urlencode(params)
|
||||
|
||||
try:
|
||||
data = self.urlopen(url)
|
||||
except Exception, e:
|
||||
log.error("Unable to connect to SAB: %s" % e)
|
||||
data = self.urlopen(url, params = {"nzbfile": (params['nzbname'] + ".nzb", nzb_file)}, multipart = True)
|
||||
except Exception:
|
||||
log.error("Unable to connect to SAB: %s" % traceback.format_exc())
|
||||
return False
|
||||
|
||||
result = data.strip()
|
||||
@@ -63,7 +60,7 @@ class Sabnzbd(Downloader):
|
||||
log.error("SABnzbd didn't return anything.")
|
||||
return False
|
||||
|
||||
log.debug("Result text from SAB: " + result)
|
||||
log.debug("Result text from SAB: " + result[:40])
|
||||
if result == "ok":
|
||||
log.info("NZB sent to SAB successfully.")
|
||||
return True
|
||||
@@ -71,7 +68,7 @@ class Sabnzbd(Downloader):
|
||||
log.error("Incorrect username/password.")
|
||||
return False
|
||||
else:
|
||||
log.error("Unknown error: " + result)
|
||||
log.error("Unknown error: " + result[:40])
|
||||
return False
|
||||
|
||||
def buildPp(self, imdb_id):
|
||||
|
||||
@@ -8,7 +8,7 @@ log = CPLog(__name__)
|
||||
events = {}
|
||||
|
||||
|
||||
def addEvent(name, handler):
|
||||
def addEvent(name, handler, priority = 0):
|
||||
|
||||
if events.get(name):
|
||||
e = events[name]
|
||||
@@ -27,7 +27,7 @@ def addEvent(name, handler):
|
||||
|
||||
return h
|
||||
|
||||
e += createHandle
|
||||
e.handle(createHandle, priority = priority)
|
||||
|
||||
def removeEvent(name, handler):
|
||||
e = events[name]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from couchpotato.core.helpers.variable import natcmp
|
||||
from flask.globals import current_app
|
||||
from flask.helpers import json
|
||||
from libs.werkzeug.urls import url_decode
|
||||
@@ -42,7 +43,7 @@ def dictToList(params):
|
||||
new = {}
|
||||
for x, value in params.iteritems():
|
||||
try:
|
||||
new_value = [dictToList(value[k]) for k in sorted(value.iterkeys())]
|
||||
new_value = [dictToList(value[k]) for k in sorted(value.iterkeys(), cmp = natcmp)]
|
||||
except:
|
||||
new_value = value
|
||||
|
||||
@@ -70,4 +71,3 @@ def jsonified(*args, **kwargs):
|
||||
return padded_jsonify(callback, *args, **kwargs)
|
||||
else:
|
||||
return jsonify('text/javascript' if Env.doDebug() else 'application/json', *args, **kwargs)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import hashlib
|
||||
import os.path
|
||||
import re
|
||||
|
||||
def isDict(object):
|
||||
return isinstance(object, dict)
|
||||
|
||||
|
||||
def mergeDicts(a, b):
|
||||
assert isDict(a), isDict(b)
|
||||
dst = a.copy()
|
||||
@@ -16,7 +16,7 @@ def mergeDicts(a, b):
|
||||
if key not in current_dst:
|
||||
current_dst[key] = current_src[key]
|
||||
else:
|
||||
if isDict(current_src[key]) and isDict(current_dst[key]) :
|
||||
if isDict(current_src[key]) and isDict(current_dst[key]):
|
||||
stack.append((current_dst[key], current_src[key]))
|
||||
else:
|
||||
current_dst[key] = current_src[key]
|
||||
@@ -42,3 +42,13 @@ def cleanHost(host):
|
||||
host += '/'
|
||||
|
||||
return host
|
||||
|
||||
def tryInt(s):
|
||||
try: return int(s)
|
||||
except: return s
|
||||
|
||||
def natsortKey(s):
|
||||
return map(tryInt, re.findall(r'(\d+|\D+)', s))
|
||||
|
||||
def natcmp(a, b):
|
||||
return cmp(natsortKey(a), natsortKey(b))
|
||||
|
||||
@@ -4,7 +4,7 @@ import re
|
||||
class CPLog():
|
||||
|
||||
context = ''
|
||||
replace_private = ['api', 'apikey', 'api_key', 'password', 'username']
|
||||
replace_private = ['api', 'apikey', 'api_key', 'password', 'username', 'h']
|
||||
|
||||
def __init__(self, context = ''):
|
||||
self.context = context
|
||||
|
||||
@@ -39,7 +39,7 @@ class Notification(Plugin):
|
||||
data = {}
|
||||
)
|
||||
|
||||
#return jsonified({'success': success})
|
||||
return jsonified({'success': success})
|
||||
|
||||
def testNotifyName(self):
|
||||
return 'notify.%s.test' % self.getName().lower()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from couchpotato.core.settings.model import History as Hist
|
||||
from couchpotato.environment import Env
|
||||
import time
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -13,12 +11,6 @@ class History(Notification):
|
||||
|
||||
listen_to = ['movie.downloaded', 'movie.snatched', 'renamer.canceled']
|
||||
|
||||
def __init__(self):
|
||||
super(Notification, self).__init__()
|
||||
|
||||
if Env.doDebug():
|
||||
addEvent('app.load', self.test)
|
||||
|
||||
def notify(self, message = '', data = {}):
|
||||
|
||||
db = get_session()
|
||||
|
||||
@@ -12,6 +12,7 @@ class Synoindex(Notification):
|
||||
addEvent('renamer.after', self.addToLibrary)
|
||||
|
||||
def addToLibrary(self, group = {}):
|
||||
if self.isDisabled(): return
|
||||
|
||||
command = ['/usr/syno/bin/synoindex', '-A', group.get('destination_dir')]
|
||||
log.info(u'Executing synoindex command: %s ' % command)
|
||||
|
||||
@@ -1,75 +1 @@
|
||||
from uuid import uuid4
|
||||
|
||||
def start():
|
||||
pass
|
||||
|
||||
config = [{
|
||||
'name': 'core',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'general',
|
||||
'name': 'basics',
|
||||
'description': 'Needs restart before changes take effect.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'username',
|
||||
'default': '',
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'default': '',
|
||||
'type': 'password',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'advanced': True,
|
||||
'default': '0.0.0.0',
|
||||
'label': 'IP',
|
||||
'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.',
|
||||
},
|
||||
{
|
||||
'name': 'port',
|
||||
'default': 5000,
|
||||
'type': 'int',
|
||||
'description': 'The port I should listen to.',
|
||||
},
|
||||
{
|
||||
'name': 'launch_browser',
|
||||
'default': 1,
|
||||
'type': 'bool',
|
||||
'label': 'Launch Browser',
|
||||
'description': 'Launch the browser when I start.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'tab': 'general',
|
||||
'name': 'advanced',
|
||||
'description': "For those who know what the're doing",
|
||||
'advanced': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'api_key',
|
||||
'default': uuid4().hex,
|
||||
'readonly': 1,
|
||||
'label': 'Api Key',
|
||||
'description': "This is top-secret! Don't share this!",
|
||||
},
|
||||
{
|
||||
'name': 'debug',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'label': 'Debug',
|
||||
'description': 'Enable debugging.',
|
||||
},
|
||||
{
|
||||
'name': 'url_base',
|
||||
'default': '',
|
||||
'label': 'Url Base',
|
||||
'description': 'When using mod_proxy use this to append the url with this.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ from couchpotato.core.helpers.variable import getExt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
from flask.helpers import send_from_directory
|
||||
from libs.multipartpost import MultipartPostHandler
|
||||
from urlparse import urlparse
|
||||
import cookielib
|
||||
import glob
|
||||
import math
|
||||
import os.path
|
||||
@@ -73,11 +75,14 @@ class Plugin(object):
|
||||
try:
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path, Env.getPermission('folder'))
|
||||
return True
|
||||
except Exception, e:
|
||||
log.error('Unable to create folder "%s": %s' % (path, e))
|
||||
|
||||
return False
|
||||
|
||||
# http request
|
||||
def urlopen(self, url, timeout = 10, params = {}, headers = {}):
|
||||
def urlopen(self, url, timeout = 10, params = {}, headers = {}, multipart = False):
|
||||
|
||||
socket.setdefaulttimeout(timeout)
|
||||
|
||||
@@ -85,15 +90,24 @@ class Plugin(object):
|
||||
self.wait(host)
|
||||
|
||||
try:
|
||||
log.info('Opening url: %s, params: %s' % (url, params))
|
||||
|
||||
data = urllib.urlencode(params) if len(params) > 0 else None
|
||||
request = urllib2.Request(url, data, headers)
|
||||
if multipart:
|
||||
log.info('Opening multipart url: %s, params: %s' % (url, params.iterkeys()))
|
||||
request = urllib2.Request(url, params, headers)
|
||||
|
||||
data = urllib2.urlopen(request).read()
|
||||
cookies = cookielib.CookieJar()
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
||||
|
||||
data = opener.open(request).read()
|
||||
else:
|
||||
log.info('Opening url: %s, params: %s' % (url, params))
|
||||
data = urllib.urlencode(params) if len(params) > 0 else None
|
||||
request = urllib2.Request(url, data, headers)
|
||||
|
||||
data = urllib2.urlopen(request).read()
|
||||
except IOError, e:
|
||||
log.error('Failed opening url, %s: %s' % (url, e))
|
||||
data = ''
|
||||
raise
|
||||
|
||||
self.http_last_use[host] = time.time()
|
||||
|
||||
@@ -111,7 +125,7 @@ class Plugin(object):
|
||||
time.sleep(last_use - now + self.http_time_between_calls)
|
||||
|
||||
def beforeCall(self, handler):
|
||||
log.debug('Calling %s.%s' % (self.getName(), handler.__name__))
|
||||
#log.debug('Calling %s.%s' % (self.getName(), handler.__name__))
|
||||
self.isRunning('%s.%s' % (self.getName(), handler.__name__))
|
||||
|
||||
def afterCall(self, handler):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.helpers.request import getParam, jsonified
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
import ctypes
|
||||
import os
|
||||
import string
|
||||
|
||||
@@ -23,7 +24,7 @@ class FileBrowser(Plugin):
|
||||
dirs = []
|
||||
for f in os.listdir(path):
|
||||
p = os.path.join(path, f)
|
||||
if(os.path.isdir(p)):
|
||||
if os.path.isdir(p) and ((self.is_hidden(p) and bool(int(show_hidden))) or not self.is_hidden(p)):
|
||||
dirs.append(p + '/')
|
||||
|
||||
return dirs
|
||||
@@ -48,6 +49,21 @@ class FileBrowser(Plugin):
|
||||
dirs = []
|
||||
|
||||
return jsonified({
|
||||
'is_root': getParam('path', '/') == '/',
|
||||
'empty': len(dirs) == 0,
|
||||
'dirs': dirs,
|
||||
})
|
||||
|
||||
|
||||
def is_hidden(self, filepath):
|
||||
name = os.path.basename(os.path.abspath(filepath))
|
||||
return name.startswith('.') or self.has_hidden_attribute(filepath)
|
||||
|
||||
def has_hidden_attribute(self, filepath):
|
||||
try:
|
||||
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath))
|
||||
assert attrs != -1
|
||||
result = bool(attrs & 2)
|
||||
except (AttributeError, AssertionError):
|
||||
result = False
|
||||
return result
|
||||
|
||||
@@ -42,17 +42,11 @@ class FileManager(Plugin):
|
||||
if not dest: # to Cache
|
||||
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
|
||||
|
||||
if overwrite or not os.path.exists(dest):
|
||||
log.debug('Writing file to: %s' % dest)
|
||||
output = open(dest, 'wb')
|
||||
output.write(file)
|
||||
output.close()
|
||||
else:
|
||||
log.debug('File already exists: %s' % dest)
|
||||
if overwrite or not os.path.isfile(dest):
|
||||
self.createFile(dest, file)
|
||||
|
||||
return dest
|
||||
|
||||
|
||||
def add(self, path = '', part = 1, type = (), available = 1, properties = {}):
|
||||
db = get_session()
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@ from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, File
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, File, \
|
||||
LibraryGenre
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class LibraryPlugin(Plugin):
|
||||
|
||||
default_dict = {'titles': {}, 'files':{}, 'info':{}}
|
||||
default_dict = {'titles': {}, 'files':{}, 'info':{}, 'genres':{}}
|
||||
|
||||
def __init__(self):
|
||||
addEvent('library.add', self.add)
|
||||
@@ -51,7 +52,9 @@ class LibraryPlugin(Plugin):
|
||||
library = db.query(Library).filter_by(identifier = identifier).first()
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
|
||||
library_dict = library.to_dict(self.default_dict)
|
||||
if library:
|
||||
library_dict = library.to_dict(self.default_dict)
|
||||
|
||||
do_update = True
|
||||
|
||||
if library.status_id == done_status.get('id') and not force:
|
||||
@@ -60,7 +63,7 @@ class LibraryPlugin(Plugin):
|
||||
info = fireEvent('provider.movie.info', merge = True, identifier = identifier)
|
||||
if not info or len(info) == 0:
|
||||
log.error('Could not update, no movie info to work with: %s' % identifier)
|
||||
do_update = False
|
||||
return False
|
||||
|
||||
# Main info
|
||||
if do_update:
|
||||
@@ -75,7 +78,6 @@ class LibraryPlugin(Plugin):
|
||||
db.commit()
|
||||
|
||||
titles = info.get('titles', [])
|
||||
|
||||
log.debug('Adding titles: %s' % titles)
|
||||
for title in titles:
|
||||
t = LibraryTitle(
|
||||
@@ -86,6 +88,20 @@ class LibraryPlugin(Plugin):
|
||||
|
||||
db.commit()
|
||||
|
||||
# Genres
|
||||
[db.delete(genre) for genre in library.genres]
|
||||
db.commit()
|
||||
|
||||
genres = info.get('genres', [])
|
||||
log.debug('Adding genres: %s' % genres)
|
||||
for genre in genres:
|
||||
g = LibraryGenre(
|
||||
name = genre
|
||||
)
|
||||
library.genres.append(g)
|
||||
|
||||
db.commit()
|
||||
|
||||
# Files
|
||||
images = info.get('images', [])
|
||||
for type in images:
|
||||
|
||||
@@ -8,9 +8,7 @@ log = CPLog(__name__)
|
||||
class MetaData(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('renaming.after', self.add)
|
||||
|
||||
addEvent('app.load', self.add)
|
||||
addEvent('renamer.after', self.add)
|
||||
|
||||
def add(self, data = {}):
|
||||
log.info('Getting meta data')
|
||||
|
||||
@@ -5,11 +5,20 @@ from couchpotato.core.helpers.request import getParams, jsonified
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie
|
||||
from couchpotato.environment import Env
|
||||
from sqlalchemy.sql.expression import or_
|
||||
from urllib import urlencode
|
||||
|
||||
|
||||
class MoviePlugin(Plugin):
|
||||
|
||||
default_dict = {
|
||||
'profile': {'types': {'quality': {}}},
|
||||
'releases': {'status': {}, 'quality': {}, 'files':{}, 'info': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {},
|
||||
'status': {}
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
addApiView('movie.search', self.search)
|
||||
addApiView('movie.list', self.list)
|
||||
@@ -24,18 +33,16 @@ class MoviePlugin(Plugin):
|
||||
params = getParams()
|
||||
db = get_session()
|
||||
|
||||
results = db.query(Movie).filter(
|
||||
Movie.status.has(identifier = params.get('status', 'active'))
|
||||
).all()
|
||||
# Make a list from string
|
||||
status = params.get('status', ['active'])
|
||||
if not isinstance(status, (list, tuple)):
|
||||
status = [status]
|
||||
|
||||
results = db.query(Movie).filter(or_(*[Movie.status.has(identifier = s) for s in status])).all()
|
||||
|
||||
movies = []
|
||||
for movie in results:
|
||||
temp = movie.to_dict(deep = {
|
||||
'releases': {'status': {}, 'quality': {}, 'files':{}, 'info': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {}
|
||||
})
|
||||
|
||||
temp = movie.to_dict(self.default_dict)
|
||||
movies.append(temp)
|
||||
|
||||
return jsonified({
|
||||
@@ -59,12 +66,7 @@ class MoviePlugin(Plugin):
|
||||
if movie:
|
||||
#addEvent('library.update.after', )
|
||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
||||
fireEventAsync('searcher.single', movie.to_dict(deep = {
|
||||
'profile': {'types': {'quality': {}}},
|
||||
'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {}
|
||||
}))
|
||||
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
@@ -116,13 +118,14 @@ class MoviePlugin(Plugin):
|
||||
if release.status_id == status_snatched.get('id'):
|
||||
release.delete()
|
||||
|
||||
m.profile_id = params.get('profile_id')
|
||||
|
||||
m.status_id = status_active.get('id')
|
||||
db.commit()
|
||||
|
||||
movie_dict = m.to_dict(deep = {
|
||||
'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}},
|
||||
'library': {'titles': {}}
|
||||
})
|
||||
movie_dict = m.to_dict(self.default_dict)
|
||||
|
||||
fireEventAsync('searcher.single', movie_dict)
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
|
||||
@@ -27,12 +27,17 @@ var MovieList = new Class({
|
||||
self.createNavigation();
|
||||
|
||||
Object.each(self.movies, function(info){
|
||||
|
||||
// Attach proper actions
|
||||
var a = self.options.actions
|
||||
var actions = a[info.status.identifier.capitalize()] || a.Wanted || {};
|
||||
|
||||
var m = new Movie(self, {
|
||||
'actions': self.options.actions
|
||||
'actions': actions
|
||||
}, info);
|
||||
$(m).inject(self.el);
|
||||
m.fireEvent('injected');
|
||||
|
||||
|
||||
if(self.options.navigation){
|
||||
var first_char = m.getTitle().substr(0, 1);
|
||||
self.activateLetter(first_char);
|
||||
@@ -71,7 +76,7 @@ var MovieList = new Class({
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
activateLetter: function(letter){
|
||||
this.letters[letter].addClass('active');
|
||||
},
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
/* @override http://localhost:5000/static/movie_plugin/movie.css */
|
||||
/* @override
|
||||
http://localhost:5000/static/movie_plugin/movie.css
|
||||
http://192.168.1.20:5000/static/movie_plugin/movie.css
|
||||
http://127.0.0.1:5000/static/movie_plugin/movie.css
|
||||
*/
|
||||
|
||||
.movies {
|
||||
padding: 20px 0;
|
||||
@@ -79,13 +83,21 @@
|
||||
float: left;
|
||||
width: 5%;
|
||||
padding: 0 0 0 3%;
|
||||
background: url('../images/rating.png') no-repeat left center;
|
||||
}
|
||||
|
||||
.movies .info .description {
|
||||
clear: both;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.movies .data .quality span {
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.movies .data .quality .available { color: orange; }
|
||||
.movies .data .quality .snatched { color: lightgreen; }
|
||||
|
||||
.movies .data .actions {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
@@ -96,17 +108,14 @@
|
||||
.movies .data:hover .action:hover { opacity: 1; }
|
||||
|
||||
.movies .data .action {
|
||||
background: no-repeat center;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 3px;
|
||||
opacity: 0;
|
||||
}
|
||||
.movies .data .action.refresh { background-image: url('../images/reload.png'); }
|
||||
.movies .data .action.delete { background-image: url('../images/delete.png'); }
|
||||
.movies .data .action.edit { background-image: url('../images/edit.png'); }
|
||||
.movies .data .action.imdb { background-image: url('../images/imdb.png'); }
|
||||
|
||||
.movies .delete_container {
|
||||
clear: both;
|
||||
@@ -142,6 +151,65 @@
|
||||
padding: 2%;
|
||||
}
|
||||
|
||||
.movies .options .releases {
|
||||
height: 157px;
|
||||
overflow: auto;
|
||||
margin: -20px -20px -20px 110px;
|
||||
padding: 15px 0 5px;
|
||||
}
|
||||
.movies .options .releases .item {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.movies .options .releases .item:last-child { border: 0; }
|
||||
.movies .options .releases .item:nth-child(even) {
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
.movies .options .releases .item:not(.head):hover {
|
||||
background: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.movies .options .releases .item > * {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
width: 50px;
|
||||
min-height: 24px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-moz-text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.movies .options .releases .item > *:first-child {
|
||||
border: 0;
|
||||
}
|
||||
.movies .options .releases .provider {
|
||||
width: 120px;
|
||||
}
|
||||
.movies .options .releases .name {
|
||||
width: 360px;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.movies .options .releases a {
|
||||
width: 16px !important;
|
||||
height: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.movies .options .releases a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.movies .options .releases .head > * {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.movies .alph_nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var Movie = new Class({
|
||||
|
||||
Extends: BlockBase,
|
||||
|
||||
|
||||
action: {},
|
||||
|
||||
initialize: function(self, options, data){
|
||||
@@ -32,7 +32,7 @@ var Movie = new Class({
|
||||
self.year = new Element('div.year', {
|
||||
'text': self.data.library.year || 'Unknown'
|
||||
}),
|
||||
self.rating = new Element('div.rating', {
|
||||
self.rating = new Element('div.rating.icon', {
|
||||
'text': self.data.library.rating
|
||||
}),
|
||||
self.description = new Element('div.description', {
|
||||
@@ -45,13 +45,20 @@ var Movie = new Class({
|
||||
self.actions = new Element('div.actions')
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
self.profile.get('types').each(function(type){
|
||||
|
||||
// Check if quality is snatched
|
||||
var is_snatched = self.data.releases.filter(function(release){
|
||||
return release.quality_id == type.quality_id && release.status.identifier == 'snatched'
|
||||
}).pick();
|
||||
|
||||
var q = Quality.getQuality(type.quality_id);
|
||||
new Element('span', {
|
||||
'text': ' '+q.label
|
||||
'text': q.label,
|
||||
'class': is_snatched ? 'snatched' : ''
|
||||
}).inject(self.quality);
|
||||
})
|
||||
});
|
||||
|
||||
Object.each(self.options.actions, function(action, key){
|
||||
self.actions.adopt(
|
||||
@@ -127,7 +134,7 @@ var Movie = new Class({
|
||||
|
||||
var MovieAction = new Class({
|
||||
|
||||
class_name: 'action',
|
||||
class_name: 'action icon',
|
||||
|
||||
initialize: function(movie){
|
||||
var self = this;
|
||||
@@ -193,7 +200,7 @@ var ReleaseAction = new Class({
|
||||
|
||||
self.id = self.movie.get('identifier');
|
||||
|
||||
self.el = new Element('a.releases', {
|
||||
self.el = new Element('a.releases.icon.download', {
|
||||
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
|
||||
'events': {
|
||||
'click': self.show.bind(self)
|
||||
@@ -211,16 +218,78 @@ var ReleaseAction = new Class({
|
||||
$(self.movie.thumbnail).clone(),
|
||||
self.release_container = new Element('div.releases')
|
||||
).inject(self.movie, 'top');
|
||||
|
||||
// Header
|
||||
new Element('div.item.head').adopt(
|
||||
new Element('span.name', {'text': 'Release name'}),
|
||||
new Element('span.quality', {'text': 'Quality'}),
|
||||
new Element('span.size', {'text': 'Size (MB)'}),
|
||||
new Element('span.age', {'text': 'Age'}),
|
||||
new Element('span.score', {'text': 'Score'}),
|
||||
new Element('span.provider', {'text': 'Provider'})
|
||||
).inject(self.release_container)
|
||||
|
||||
Array.each(self.movie.data.releases, function(release){
|
||||
p(release);
|
||||
new Element('div', {
|
||||
'text': release.title
|
||||
}).inject(self.release_container)
|
||||
'class': 'item ' + release.status.identifier
|
||||
}).adopt(
|
||||
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
||||
new Element('span.quality', {'text': release.quality.label}),
|
||||
new Element('span.size', {'text': (self.get(release, 'size') || 'unknown')}),
|
||||
new Element('span.age', {'text': self.get(release, 'age')}),
|
||||
new Element('span.score', {'text': self.get(release, 'score')}),
|
||||
new Element('span.provider', {'text': self.get(release, 'provider')}),
|
||||
new Element('a.download.icon', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop();
|
||||
self.download(release);
|
||||
}
|
||||
}
|
||||
}),
|
||||
new Element('a.delete.icon', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop();
|
||||
self.del(release);
|
||||
this.getParent('.item').destroy();
|
||||
}
|
||||
}
|
||||
})
|
||||
).inject(self.release_container)
|
||||
});
|
||||
|
||||
}
|
||||
self.movie.slide('in');
|
||||
},
|
||||
|
||||
get: function(release, type){
|
||||
var self = this;
|
||||
|
||||
return (release.info.filter(function(info){
|
||||
return type == info.identifier
|
||||
}).pick() || {}).value
|
||||
},
|
||||
|
||||
download: function(release){
|
||||
var self = this;
|
||||
|
||||
Api.request('release.download', {
|
||||
'data': {
|
||||
'id': release.id
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
del: function(release){
|
||||
var self = this;
|
||||
|
||||
Api.request('release.delete', {
|
||||
'data': {
|
||||
'id': release.id
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,4 +1,7 @@
|
||||
/* @override http://localhost:5000/static/movie_plugin/search.css */
|
||||
/* @override
|
||||
http://localhost:5000/static/movie_plugin/search.css
|
||||
http://192.168.1.20:5000/static/movie_plugin/search.css
|
||||
*/
|
||||
|
||||
.search_form {
|
||||
display: inline-block;
|
||||
@@ -86,7 +89,7 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
.search_form .results .movie .options select[name=title] { width: 180px; }
|
||||
.search_form .results .movie .options select[name=quality] { width: 90px; }
|
||||
.search_form .results .movie .options select[name=profile] { width: 90px; }
|
||||
|
||||
.search_form .results .movie .options .button {
|
||||
vertical-align: middle;
|
||||
|
||||
@@ -10,6 +10,7 @@ Block.Search = new Class({
|
||||
self.el = new Element('div.search_form').adopt(
|
||||
new Element('div.input').adopt(
|
||||
self.input = new Element('input.inlay', {
|
||||
'placeholder': 'Search for new movies',
|
||||
'events': {
|
||||
'keyup': self.keyup.bind(self),
|
||||
'focus': self.hideResults.bind(self, false)
|
||||
@@ -28,7 +29,7 @@ Block.Search = new Class({
|
||||
}).adopt(
|
||||
new Element('div.pointer'),
|
||||
self.results = new Element('div.results')
|
||||
).fade('hide')
|
||||
).hide()
|
||||
);
|
||||
|
||||
self.spinner = new Spinner(self.result_container);
|
||||
@@ -51,7 +52,7 @@ Block.Search = new Class({
|
||||
|
||||
if(self.hidden == bool) return;
|
||||
|
||||
self.result_container.fade(bool ? 0 : 1)
|
||||
self.result_container[bool ? 'hide' : 'show']();
|
||||
|
||||
if(bool){
|
||||
History.removeEvent('change', self.hideResults.bind(self, !bool));
|
||||
@@ -302,7 +303,7 @@ Block.Search.Item = new Class({
|
||||
}).inject(self.title_select)
|
||||
})
|
||||
|
||||
Object.each(Quality.profiles, function(profile){
|
||||
Object.each(Quality.getActiveProfiles(), function(profile){
|
||||
new Element('option', {
|
||||
'value': profile.id ? profile.id : profile.data.id,
|
||||
'text': profile.label ? profile.label : profile.data.label
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.request import jsonified, getParams, getParam
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
@@ -15,8 +16,11 @@ class ProfilePlugin(Plugin):
|
||||
addEvent('profile.all', self.all)
|
||||
|
||||
addApiView('profile.save', self.save)
|
||||
addApiView('profile.save_order', self.saveOrder)
|
||||
addApiView('profile.delete', self.delete)
|
||||
|
||||
addEvent('app.initialize', self.fill, priority = 90)
|
||||
|
||||
def all(self):
|
||||
|
||||
db = get_session()
|
||||
@@ -50,7 +54,7 @@ class ProfilePlugin(Plugin):
|
||||
for type in params.get('types', []):
|
||||
t = ProfileType(
|
||||
order = order,
|
||||
finish = type.get('finish'),
|
||||
finish = type.get('finish') if order > 0 else 1,
|
||||
wait_for = params.get('wait_for'),
|
||||
quality_id = type.get('quality_id')
|
||||
)
|
||||
@@ -67,6 +71,25 @@ class ProfilePlugin(Plugin):
|
||||
'profile': profile_dict
|
||||
})
|
||||
|
||||
def saveOrder(self):
|
||||
|
||||
params = getParams()
|
||||
db = get_session()
|
||||
|
||||
order = 0
|
||||
for profile in params.get('ids', []):
|
||||
p = db.query(Profile).filter_by(id = profile).first()
|
||||
p.hide = params.get('hidden')[order]
|
||||
p.order = order
|
||||
|
||||
order += 1
|
||||
|
||||
db.commit()
|
||||
|
||||
return jsonified({
|
||||
'success': True
|
||||
})
|
||||
|
||||
def delete(self):
|
||||
|
||||
id = getParam('id')
|
||||
@@ -90,3 +113,44 @@ class ProfilePlugin(Plugin):
|
||||
'success': success,
|
||||
'message': message
|
||||
})
|
||||
|
||||
def fill(self):
|
||||
|
||||
db = get_session();
|
||||
|
||||
profiles = [{
|
||||
'label': 'Best',
|
||||
'qualities': ['720p', '1080p', 'brrip', 'dvdrip']
|
||||
}, {
|
||||
'label': 'HD',
|
||||
'qualities': ['720p', '1080p']
|
||||
}]
|
||||
|
||||
# Create default quality profile
|
||||
order = -2
|
||||
for profile in profiles:
|
||||
log.info('Creating default profile: %s' % profile.get('label'))
|
||||
p = Profile(
|
||||
label = toUnicode(profile.get('label')),
|
||||
order = order
|
||||
)
|
||||
db.add(p)
|
||||
|
||||
quality_order = 0
|
||||
for quality in profile.get('qualities'):
|
||||
quality = fireEvent('quality.single', identifier = quality, single = True)
|
||||
profile_type = ProfileType(
|
||||
quality_id = quality.get('id'),
|
||||
profile = p,
|
||||
finish = True,
|
||||
wait_for = 0,
|
||||
order = quality_order
|
||||
)
|
||||
p.types.append(profile_type)
|
||||
|
||||
db.commit()
|
||||
quality_order += 1
|
||||
|
||||
order += 1
|
||||
|
||||
return True
|
||||
|
||||
|
After Width: | Height: | Size: 160 B |
@@ -1,18 +1,134 @@
|
||||
.profile > .delete {
|
||||
background-position: center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
/* @override http://192.168.1.20:5000/static/profile_plugin/profile.css */
|
||||
|
||||
.add_new_profile {
|
||||
padding: 20px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.profile .types .type .handle {
|
||||
background: url('../../images/handle.png') center;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
.profile { border-bottom: 1px solid rgba(255,255,255,0.2) }
|
||||
|
||||
.profile > .delete {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
position: absolute;
|
||||
margin-left: 690px;
|
||||
padding: 14px;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.profile .qualities {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.profile .formHint {
|
||||
width: 250px !important;
|
||||
}
|
||||
|
||||
.profile .wait_for {
|
||||
position: absolute;
|
||||
margin: -45px 0 0 437px;
|
||||
}
|
||||
|
||||
.profile .wait_for input {
|
||||
margin: 0 5px !important;
|
||||
}
|
||||
|
||||
.profile .types {
|
||||
padding: 0;
|
||||
margin: 0 20px 0 -4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.profile .types li {
|
||||
padding: 3px 5px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
list-style: none;
|
||||
}
|
||||
.profile .types li:last-child { border: 0; }
|
||||
|
||||
.profile .types li > * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.profile .quality_type select {
|
||||
width: 186px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.profile .types li.is_empty .check, .profile .types li.is_empty .delete, .profile .types li.is_empty .handle {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.profile .types .type .handle {
|
||||
background: url('./handle.png') center;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
cursor: grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: -webkit-grab;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.profile .types .type .delete {
|
||||
background-position: left center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile .types .type:hover:not(.is_empty) .delete {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#profile_ordering {
|
||||
|
||||
}
|
||||
|
||||
.profile .types .type .delete {
|
||||
background-position: center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
#profile_ordering ul {
|
||||
float: left;
|
||||
margin: 0;
|
||||
width: 275px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#profile_ordering li {
|
||||
cursor: grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: -webkit-grab;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
padding: 0 5px;
|
||||
}
|
||||
#profile_ordering li:last-child { border: 0; }
|
||||
|
||||
#profile_ordering li .check {
|
||||
margin: 2px 10px 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#profile_ordering li > span {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
vertical-align: top;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#profile_ordering li .handle {
|
||||
background: url('./handle.png') center;
|
||||
width: 20px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#profile_ordering .formHint {
|
||||
clear: none;
|
||||
float: right;
|
||||
width: 250px;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -24,47 +24,32 @@ var Profile = new Class({
|
||||
var data = self.data;
|
||||
|
||||
self.el = new Element('div.profile').adopt(
|
||||
self.header = new Element('h4', {'text': data.label}),
|
||||
new Element('span.delete.icon', {
|
||||
'events': {
|
||||
'click': self.del.bind(self)
|
||||
}
|
||||
}),
|
||||
new Element('div', {
|
||||
'class': 'ctrlHolder'
|
||||
}).adopt(
|
||||
new Element('.quality_label.ctrlHolder').adopt(
|
||||
new Element('label', {'text':'Name'}),
|
||||
new Element('input.label.textInput.large', {
|
||||
new Element('input.inlay', {
|
||||
'type':'text',
|
||||
'value': data.label,
|
||||
'events': {
|
||||
'keyup': function(){
|
||||
self.header.set('text', this.get('value'))
|
||||
}
|
||||
}
|
||||
'placeholder': 'Profile name'
|
||||
})
|
||||
),
|
||||
new Element('div.ctrlHolder').adopt(
|
||||
new Element('label', {'text':'Wait'}),
|
||||
new Element('input.wait_for.textInput.xsmall', {
|
||||
new Element('div.wait_for.ctrlHolder').adopt(
|
||||
new Element('span', {'text':'Wait'}),
|
||||
new Element('input.inlay.xsmall', {
|
||||
'type':'text',
|
||||
'value': data.types && data.types.length > 0 ? data.types[0].wait_for : 0
|
||||
}),
|
||||
new Element('span', {'text':' day(s) for better quality.'})
|
||||
new Element('span', {'text':'day(s) for a better quality.'})
|
||||
),
|
||||
new Element('div.ctrlHolder').adopt(
|
||||
new Element('label', {'text': 'Qualities'}),
|
||||
new Element('div.head').adopt(
|
||||
new Element('span.quality_type', {'text': 'Search for'}),
|
||||
new Element('span.finish', {'html': '<acronym title="Won\'t download anything else if it has found this quality.">Finish</acronym>'})
|
||||
),
|
||||
new Element('div.qualities.ctrlHolder').adopt(
|
||||
new Element('label', {'text': 'Search for'}),
|
||||
self.type_container = new Element('ol.types'),
|
||||
new Element('a.addType', {
|
||||
'text': 'Add another quality to search for.',
|
||||
'href': '#',
|
||||
'events': {
|
||||
'click': self.addType.bind(self)
|
||||
}
|
||||
new Element('div.formHint', {
|
||||
'html': "Search these qualities (2 minimum), from top to bottom. Use the checkbox, to stop searching after it found this quality."
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -73,6 +58,8 @@ var Profile = new Class({
|
||||
|
||||
if(data.types)
|
||||
Object.each(data.types, self.addType.bind(self))
|
||||
|
||||
self.addType();
|
||||
},
|
||||
|
||||
save: function(delay){
|
||||
@@ -81,6 +68,8 @@ var Profile = new Class({
|
||||
if(self.save_timer) clearTimeout(self.save_timer);
|
||||
self.save_timer = (function(){
|
||||
|
||||
self.addType();
|
||||
|
||||
var data = self.getData();
|
||||
if(data.types.length < 2) return;
|
||||
|
||||
@@ -96,6 +85,7 @@ var Profile = new Class({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}).delay(delay, self)
|
||||
|
||||
},
|
||||
@@ -105,8 +95,8 @@ var Profile = new Class({
|
||||
|
||||
var data = {
|
||||
'id' : self.data.id,
|
||||
'label' : self.el.getElement('.label').get('value'),
|
||||
'wait_for' : self.el.getElement('.wait_for').get('value'),
|
||||
'label' : self.el.getElement('.quality_label input').get('value'),
|
||||
'wait_for' : self.el.getElement('.wait_for input').get('value'),
|
||||
'types': []
|
||||
}
|
||||
|
||||
@@ -124,8 +114,19 @@ var Profile = new Class({
|
||||
addType: function(data){
|
||||
var self = this;
|
||||
|
||||
var t = new Profile.Type(data);
|
||||
var has_empty = false;
|
||||
self.types.each(function(type){
|
||||
if($(type).hasClass('is_empty'))
|
||||
has_empty = true;
|
||||
});
|
||||
|
||||
if(has_empty) return;
|
||||
|
||||
var t = new Profile.Type(data, {
|
||||
'onChange': self.save.bind(self, 0)
|
||||
});
|
||||
$(t).inject(self.type_container);
|
||||
|
||||
self.sortable.addItems($(t));
|
||||
|
||||
self.types.include(t);
|
||||
@@ -135,23 +136,35 @@ var Profile = new Class({
|
||||
del: function(){
|
||||
var self = this;
|
||||
|
||||
if(!confirm('Are you sure you want to delete this profile?')) return
|
||||
|
||||
Api.request('profile.delete', {
|
||||
'data': {
|
||||
'id': self.data.id
|
||||
},
|
||||
'useSpinner': true,
|
||||
'spinnerOptions': {
|
||||
'target': self.el
|
||||
},
|
||||
'onComplete': function(json){
|
||||
if(json.success)
|
||||
self.el.destroy();
|
||||
else
|
||||
alert(json.message)
|
||||
var label = self.el.getElement('.quality_label input').get('value');
|
||||
new Question('Are you sure you want to delete <strong>"'+label+'"</strong>?', 'Items using this profile, will be set to the default quality.', [{
|
||||
'text': 'Delete "'+label+'"',
|
||||
'class': 'delete',
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop();
|
||||
Api.request('profile.delete', {
|
||||
'data': {
|
||||
'id': self.data.id
|
||||
},
|
||||
'useSpinner': true,
|
||||
'spinnerOptions': {
|
||||
'target': self.el
|
||||
},
|
||||
'onComplete': function(json){
|
||||
if(json.success)
|
||||
self.el.destroy();
|
||||
else
|
||||
alert(json.message)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, {
|
||||
'text': 'Cancel',
|
||||
'cancel': true
|
||||
}]);
|
||||
|
||||
},
|
||||
|
||||
makeSortable: function(){
|
||||
@@ -180,16 +193,24 @@ var Profile = new Class({
|
||||
|
||||
});
|
||||
|
||||
Profile.Type = Class({
|
||||
Profile.Type = new Class({
|
||||
|
||||
Implements: [Events, Options],
|
||||
|
||||
deleted: false,
|
||||
|
||||
initialize: function(data){
|
||||
initialize: function(data, options){
|
||||
var self = this;
|
||||
self.setOptions(options);
|
||||
|
||||
self.data = data;
|
||||
self.data = data || {};
|
||||
self.create();
|
||||
|
||||
self.addEvent('change', function(){
|
||||
self.el[self.qualities.get('value') == '-1' ? 'addClass' : 'removeClass']('is_empty');
|
||||
self.deleted = self.qualities.get('value') == '-1';
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
create: function(){
|
||||
@@ -201,10 +222,11 @@ Profile.Type = Class({
|
||||
self.fillQualities()
|
||||
),
|
||||
new Element('span.finish').adopt(
|
||||
self.finish = new Element('input', {
|
||||
'type':'checkbox',
|
||||
'class':'finish',
|
||||
'checked': data.finish
|
||||
self.finish = new Element('input.inlay.finish[type=checkbox]', {
|
||||
'checked': data.finish,
|
||||
'events': {
|
||||
'change': self.fireEvent.bind(self, 'change')
|
||||
}
|
||||
})
|
||||
),
|
||||
new Element('span.delete.icon', {
|
||||
@@ -213,14 +235,27 @@ Profile.Type = Class({
|
||||
}
|
||||
}),
|
||||
new Element('span.handle')
|
||||
)
|
||||
);
|
||||
|
||||
self.el[self.data.quality_id > 0 ? 'removeClass' : 'addClass']('is_empty');
|
||||
|
||||
new Form.Check(self.finish);
|
||||
|
||||
},
|
||||
|
||||
fillQualities: function(){
|
||||
var self = this;
|
||||
|
||||
self.qualities = new Element('select');
|
||||
self.qualities = new Element('select', {
|
||||
'events': {
|
||||
'change': self.fireEvent.bind(self, 'change')
|
||||
}
|
||||
}).adopt(
|
||||
new Element('option', {
|
||||
'text': '+ Add another quality',
|
||||
'value': -1
|
||||
})
|
||||
);
|
||||
|
||||
Object.each(Quality.qualities, function(q){
|
||||
new Element('option', {
|
||||
@@ -250,6 +285,8 @@ Profile.Type = Class({
|
||||
self.el.addClass('deleted');
|
||||
self.el.hide();
|
||||
self.deleted = true;
|
||||
|
||||
self.fireEvent('change');
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
|
||||
@@ -13,13 +13,13 @@ log = CPLog(__name__)
|
||||
class QualityPlugin(Plugin):
|
||||
|
||||
qualities = [
|
||||
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['x264', 'h264', 'bluray']},
|
||||
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate']},
|
||||
{'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
||||
{'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
||||
{'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc']},
|
||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['dvdscr'], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['dvdscr', 'ppvrip'], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
@@ -31,7 +31,8 @@ class QualityPlugin(Plugin):
|
||||
addEvent('quality.all', self.all)
|
||||
addEvent('quality.single', self.single)
|
||||
addEvent('quality.guess', self.guess)
|
||||
addEvent('app.load', self.fill)
|
||||
|
||||
addEvent('app.initialize', self.fill, priority = 10)
|
||||
|
||||
def all(self):
|
||||
|
||||
@@ -112,40 +113,44 @@ class QualityPlugin(Plugin):
|
||||
|
||||
return True
|
||||
|
||||
def guess(self, files, extra = {}):
|
||||
found = False
|
||||
def guess(self, files, extra = {}, loose = False):
|
||||
|
||||
for file in files:
|
||||
size = (os.path.getsize(file) / 1024 / 1024)
|
||||
words = re.split('\W+', file.lower())
|
||||
for quality in self.all():
|
||||
correctSize = False
|
||||
|
||||
if size >= quality['size_min'] and size <= quality['size_max']:
|
||||
correctSize = True
|
||||
for quality in self.all():
|
||||
|
||||
# Check tags
|
||||
if type in words:
|
||||
found = True
|
||||
|
||||
for alt in quality.get('alternative'):
|
||||
if alt in words:
|
||||
found = True
|
||||
|
||||
for tag in quality.get('tags', []):
|
||||
if tag in words:
|
||||
found = True
|
||||
|
||||
# Check extension + filesize
|
||||
for ext in quality.get('ext'):
|
||||
if ext in words and correctSize:
|
||||
found = True
|
||||
|
||||
# Last check on resolution only
|
||||
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
||||
found = True
|
||||
|
||||
if found:
|
||||
if quality['identifier'] in words:
|
||||
log.debug('Found via identifier "%s" in %s' % (quality['identifier'], file))
|
||||
return quality
|
||||
|
||||
return ''
|
||||
if list(set(quality.get('alternative', [])) & set(words)):
|
||||
log.debug('Found %s via alt %s in %s' % (quality['identifier'], quality.get('alternative'), file))
|
||||
return quality
|
||||
|
||||
if list(set(quality.get('tags', [])) & set(words)):
|
||||
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), file))
|
||||
return quality
|
||||
|
||||
# Check on unreliable stuff
|
||||
if loose:
|
||||
# Check extension + filesize
|
||||
if list(set(quality.get('ext', [])) & set(words)) and size >= quality['size_min'] and size <= quality['size_max']:
|
||||
log.debug('Found %s via ext %s in %s' % (quality['identifier'], quality.get('ext'), words))
|
||||
return quality
|
||||
|
||||
# Last check on resolution only
|
||||
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
||||
log.debug('Found %s via resolution_width: %s == %s' % (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
||||
return quality
|
||||
|
||||
|
||||
# Try again with loose testing
|
||||
quality = self.guess(files, extra = extra, loose = True)
|
||||
if quality:
|
||||
return quality
|
||||
|
||||
log.error('Could not identify quality for: %s' % files)
|
||||
return {}
|
||||
|
||||
@@ -19,6 +19,13 @@ var QualityBase = new Class({
|
||||
return this.profiles[id]
|
||||
},
|
||||
|
||||
// Hide items when getting profiles
|
||||
getActiveProfiles: function(){
|
||||
return Object.filter(this.profiles, function(profile){
|
||||
return !profile.data.hide
|
||||
});
|
||||
},
|
||||
|
||||
getQuality: function(id){
|
||||
return this.qualities.filter(function(q){
|
||||
return q.id == id;
|
||||
@@ -31,7 +38,7 @@ var QualityBase = new Class({
|
||||
self.settings = App.getPage('Settings')
|
||||
self.settings.addEvent('create', function(){
|
||||
var tab = self.settings.createTab('profile', {
|
||||
'label': 'Profile',
|
||||
'label': 'Quality',
|
||||
'name': 'profile'
|
||||
});
|
||||
|
||||
@@ -39,6 +46,7 @@ var QualityBase = new Class({
|
||||
self.content = tab.content;
|
||||
|
||||
self.createProfiles();
|
||||
self.createProfileOrdering();
|
||||
self.createSizes();
|
||||
|
||||
})
|
||||
@@ -50,42 +58,104 @@ var QualityBase = new Class({
|
||||
*/
|
||||
createProfiles: function(){
|
||||
var self = this;
|
||||
|
||||
var non_core_profiles = Object.filter(self.profiles, function(profile){ return !profile.isCore() });
|
||||
var count = Object.getLength(non_core_profiles);
|
||||
|
||||
self.settings.createGroup({
|
||||
'label': 'Custom',
|
||||
'description': 'Discriptions'
|
||||
'label': 'Quality Profiles',
|
||||
'description': 'Create your own profiles with multiple qualities.'
|
||||
}).inject(self.content).adopt(
|
||||
new Element('a.add_new', {
|
||||
'text': 'Create a new quality profile',
|
||||
self.profile_container = new Element('div.container'),
|
||||
new Element('a.add_new_profile', {
|
||||
'text': count > 0 ? 'Create another quality profile' : 'Click here to create a quality profile.',
|
||||
'events': {
|
||||
'click': function(){
|
||||
var profile = self.createProfilesClass();
|
||||
$(profile).inject(self.profile_container, 'top')
|
||||
$(profile).inject(self.profile_container)
|
||||
}
|
||||
}
|
||||
}),
|
||||
self.profile_container = new Element('div.container')
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
Object.each(self.profiles, function(profile){
|
||||
if(!profile.isCore())
|
||||
$(profile).inject(self.profile_container, 'top')
|
||||
})
|
||||
// Add profiles, that aren't part of the core (for editing)
|
||||
Object.each(non_core_profiles, function(profile){
|
||||
$(profile).inject(self.profile_container)
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
createProfilesClass: function(data){
|
||||
var self = this;
|
||||
|
||||
if(data){
|
||||
return self.profiles[data.id] = new Profile(data);
|
||||
}
|
||||
else {
|
||||
var data = {
|
||||
'id': randomString()
|
||||
var data = data || {'id': randomString()}
|
||||
|
||||
return self.profiles[data.id] = new Profile(data);
|
||||
},
|
||||
|
||||
createProfileOrdering: function(){
|
||||
var self = this;
|
||||
|
||||
var profile_list;
|
||||
var group = self.settings.createGroup({
|
||||
'label': 'Profile Defaults'
|
||||
}).adopt(
|
||||
new Element('.ctrlHolder#profile_ordering').adopt(
|
||||
new Element('label[text=Order]'),
|
||||
profile_list = new Element('ul'),
|
||||
new Element('p.formHint', {
|
||||
'html': 'Change the order the profiles are in the dropdown list. Uncheck to hide it completely.<br />First one will be default.'
|
||||
})
|
||||
)
|
||||
).inject(self.content)
|
||||
|
||||
Object.each(self.profiles, function(profile){
|
||||
var check;
|
||||
new Element('li', {'data-id': profile.data.id}).adopt(
|
||||
check = new Element('input.inlay[type=checkbox]', {
|
||||
'checked': !profile.data.hide,
|
||||
'events': {
|
||||
'change': self.saveProfileOrdering.bind(self)
|
||||
}
|
||||
}),
|
||||
new Element('span.profile_label', {
|
||||
'text': profile.data.label
|
||||
}),
|
||||
new Element('span.handle')
|
||||
).inject(profile_list);
|
||||
|
||||
new Form.Check(check);
|
||||
|
||||
});
|
||||
|
||||
// Sortable
|
||||
self.profile_sortable = new Sortables(profile_list, {
|
||||
'revert': true,
|
||||
'handle': '',
|
||||
'opacity': 0.5,
|
||||
'onComplete': self.saveProfileOrdering.bind(self)
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
saveProfileOrdering: function(){
|
||||
var self = this;
|
||||
|
||||
var ids = [];
|
||||
var hidden = [];
|
||||
|
||||
self.profile_sortable.list.getElements('li').each(function(el, nr){
|
||||
ids.include(el.get('data-id'));
|
||||
hidden[nr] = +!el.getElement('input[type=checkbox]').get('checked');
|
||||
});
|
||||
|
||||
Api.request('profile.save_order', {
|
||||
'data': {
|
||||
'ids': ids,
|
||||
'hidden': hidden
|
||||
}
|
||||
return self.profiles[data.id] = new Profile(data);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -96,24 +166,25 @@ var QualityBase = new Class({
|
||||
|
||||
var group = self.settings.createGroup({
|
||||
'label': 'Sizes',
|
||||
'description': 'Discriptions',
|
||||
'description': 'Edit the minimal and maximum sizes (in MB) for each quality.',
|
||||
'advanced': true
|
||||
}).inject(self.content)
|
||||
|
||||
new Element('div.item.header').adopt(
|
||||
|
||||
|
||||
new Element('div.item.head').adopt(
|
||||
new Element('span.label', {'text': 'Quality'}),
|
||||
new Element('span.min', {'text': 'Min'}),
|
||||
new Element('span.max', {'text': 'Max'})
|
||||
).inject(group)
|
||||
|
||||
|
||||
Object.each(self.qualities, function(quality){
|
||||
new Element('div.item').adopt(
|
||||
new Element('div.ctrlHolder.item').adopt(
|
||||
new Element('span.label', {'text': quality.label}),
|
||||
new Element('input.min', {'value': quality.size_min}),
|
||||
new Element('input.max', {'value': quality.size_max})
|
||||
).inject(group)
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.helpers.request import getParam, jsonified
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import File, Release, Movie
|
||||
from couchpotato.core.settings.model import File, Release as Relea, Movie
|
||||
from sqlalchemy.sql.expression import and_, or_
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -13,6 +15,9 @@ class Release(Plugin):
|
||||
def __init__(self):
|
||||
addEvent('release.add', self.add)
|
||||
|
||||
addApiView('release.download', self.download)
|
||||
addApiView('release.delete', self.delete)
|
||||
|
||||
def add(self, group):
|
||||
db = get_session()
|
||||
|
||||
@@ -30,22 +35,22 @@ class Release(Plugin):
|
||||
db.add(movie)
|
||||
db.commit()
|
||||
|
||||
# Add release
|
||||
# Add Release
|
||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||
release = db.query(Release).filter(
|
||||
rel = db.query(Relea).filter(
|
||||
or_(
|
||||
Release.identifier == identifier,
|
||||
and_(Release.identifier.startswith(group['library']['identifier'], Release.status_id == snatched_status.get('id')))
|
||||
Relea.identifier == identifier,
|
||||
and_(Relea.identifier.startswith(group['library']['identifier'], Relea.status_id == snatched_status.get('id')))
|
||||
)
|
||||
).first()
|
||||
if not release:
|
||||
release = Release(
|
||||
if not rel:
|
||||
rel = Relea(
|
||||
identifier = identifier,
|
||||
movie = movie,
|
||||
quality_id = group['meta_data']['quality'].get('id'),
|
||||
status_id = done_status.get('id')
|
||||
)
|
||||
db.add(release)
|
||||
db.add(rel)
|
||||
db.commit()
|
||||
|
||||
# Add each file type
|
||||
@@ -54,10 +59,10 @@ class Release(Plugin):
|
||||
added_file = self.saveFile(file, type = type, include_media_info = type is 'movie')
|
||||
try:
|
||||
added_file = db.query(File).filter_by(id = added_file.get('id')).one()
|
||||
release.files.append(added_file)
|
||||
Relea.files.append(added_file)
|
||||
db.commit()
|
||||
except Exception, e:
|
||||
log.debug('Failed to attach "%s" to release: %s' % (file, e))
|
||||
log.debug('Failed to attach "%s" to Relea: %s' % (file, e))
|
||||
|
||||
db.remove()
|
||||
|
||||
@@ -73,3 +78,48 @@ class Release(Plugin):
|
||||
# Check database and update/insert if necessary
|
||||
return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True)
|
||||
|
||||
def delete(self):
|
||||
|
||||
db = get_session()
|
||||
id = getParam('id')
|
||||
|
||||
rel = db.query(Relea).filter_by(id = id).first()
|
||||
if rel:
|
||||
rel.delete()
|
||||
db.commit()
|
||||
|
||||
return jsonified({
|
||||
'success': True
|
||||
})
|
||||
|
||||
def download(self):
|
||||
|
||||
db = get_session()
|
||||
id = getParam('id')
|
||||
|
||||
rel = db.query(Relea).filter_by(id = id).first()
|
||||
if rel:
|
||||
item = {}
|
||||
for info in rel.info:
|
||||
item[info.identifier] = info.value
|
||||
|
||||
# Get matching provider
|
||||
provider = fireEvent('provider.belongs_to', item['url'], single = True)
|
||||
item['download'] = provider.download
|
||||
|
||||
fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
|
||||
'profile': {'types': {'quality': {}}},
|
||||
'releases': {'status': {}, 'quality': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {}
|
||||
}))
|
||||
|
||||
return jsonified({
|
||||
'success': True
|
||||
})
|
||||
else:
|
||||
log.error('Couldn\'t find release with id: %s' % id)
|
||||
|
||||
return jsonified({
|
||||
'success': False
|
||||
})
|
||||
|
||||
@@ -61,20 +61,22 @@ config = [{
|
||||
'advanced': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'trailer_name',
|
||||
'label': 'Trailer naming',
|
||||
'default': '<filename>-trailer.<ext>',
|
||||
'name': 'rename_nfo',
|
||||
'label': 'Rename .NFO',
|
||||
'description': 'Rename original .nfo file',
|
||||
'type': 'bool',
|
||||
'default': True,
|
||||
},
|
||||
{
|
||||
'name': 'nfo_name',
|
||||
'label': 'NFO naming',
|
||||
'default': '<filename>.<ext>',
|
||||
'default': '<filename>.<ext>-orig',
|
||||
},
|
||||
{
|
||||
'name': 'backdrop_name',
|
||||
'label': 'Backdrop naming',
|
||||
'default': '<filename>-backdrop.<ext>',
|
||||
}
|
||||
'name': 'trailer_name',
|
||||
'label': 'Trailer naming',
|
||||
'default': '<filename>-trailer.<ext>',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import getExt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library
|
||||
from couchpotato.core.settings.model import Library, Movie
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
@@ -40,7 +40,7 @@ class Renamer(Plugin):
|
||||
group = groups[group_identifier]
|
||||
rename_files = {}
|
||||
|
||||
# Add _UNKNOWN_ if no library is connected
|
||||
# Add _UNKNOWN_ if no library item is connected
|
||||
if not group['library']:
|
||||
if group['dirname']:
|
||||
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_UNKNOWN_%s' % group['dirname'])
|
||||
@@ -53,6 +53,10 @@ class Renamer(Plugin):
|
||||
# Rename the files using the library data
|
||||
else:
|
||||
group['library'] = fireEvent('library.update', identifier = group['library']['identifier'], single = True)
|
||||
if not group['library']:
|
||||
log.error('Could not rename, no library item to work with: %s' % group_identifier)
|
||||
continue
|
||||
|
||||
library = group['library']
|
||||
|
||||
# Find subtitle for renaming
|
||||
@@ -85,12 +89,9 @@ class Renamer(Plugin):
|
||||
|
||||
for file_type in group['files']:
|
||||
|
||||
# Move DVD files (no renaming)
|
||||
if group['is_dvd'] and file_type is 'movie':
|
||||
continue
|
||||
|
||||
# Move nfo depending on settings
|
||||
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
||||
log.debug('Skipping, renaming of %s disabled' % file_type)
|
||||
continue
|
||||
|
||||
# Subtitle extra
|
||||
@@ -98,7 +99,7 @@ class Renamer(Plugin):
|
||||
continue
|
||||
|
||||
# Move other files
|
||||
multiple = len(group['files']['movie']) > 1
|
||||
multiple = len(group['files']['movie']) > 1 and not group['is_dvd']
|
||||
cd = 1 if multiple else 0
|
||||
|
||||
for file in sorted(list(group['files'][file_type])):
|
||||
@@ -118,21 +119,35 @@ class Renamer(Plugin):
|
||||
final_folder_name = self.doReplace(folder_name, replacements)
|
||||
final_file_name = self.doReplace(file_name, replacements)
|
||||
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
|
||||
group['filename'] = replacements['filename']
|
||||
|
||||
# Meta naming
|
||||
if file_type is 'trailer':
|
||||
final_file_name = self.doReplace(trailer_name, replacements)
|
||||
elif file_type is 'nfo':
|
||||
final_file_name = self.doReplace(nfo_name, replacements) + '-orig'
|
||||
elif file_type is 'backdrop':
|
||||
final_file_name = self.doReplace(backdrop_name, replacements)
|
||||
final_file_name = self.doReplace(nfo_name, replacements)
|
||||
|
||||
# Seperator replace
|
||||
if separator:
|
||||
final_file_name = final_file_name.replace(' ', separator)
|
||||
|
||||
# Main file
|
||||
rename_files[file] = os.path.join(destination, final_folder_name, final_file_name)
|
||||
# Move DVD files (no structure renaming)
|
||||
if group['is_dvd'] and file_type is 'movie':
|
||||
found = False
|
||||
for top_dir in ['video_ts', 'audio_ts', 'bdmv', 'certificate']:
|
||||
has_string = file.lower().find(os.path.sep + top_dir + os.path.sep)
|
||||
if has_string >= 0:
|
||||
structure_dir = file[has_string:].lstrip(os.path.sep)
|
||||
rename_files[file] = os.path.join(destination, final_folder_name, structure_dir)
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
log.error('Could not determin dvd structure for: %s' % file)
|
||||
|
||||
# Do rename others
|
||||
else:
|
||||
rename_files[file] = os.path.join(destination, final_folder_name, final_file_name)
|
||||
|
||||
# Check for extra subtitle files
|
||||
if file_type is 'subtitle':
|
||||
@@ -154,21 +169,43 @@ class Renamer(Plugin):
|
||||
if multiple:
|
||||
cd += 1
|
||||
|
||||
# Notify on download
|
||||
download_message = 'Download of %s (%s) successful.' % (group['library']['titles'][0]['title'], replacements['quality'])
|
||||
fireEvent('movie.downloaded', message = download_message, data = group)
|
||||
|
||||
# Before renaming, remove the lower quality files
|
||||
db = get_session()
|
||||
|
||||
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
active_status = fireEvent('status.get', 'active', single = True)
|
||||
|
||||
for movie in library.movies:
|
||||
|
||||
# Mark movie "done" onces it found the quality with the finish check
|
||||
try:
|
||||
if movie.status_id == active_status.get('id'):
|
||||
for type in movie.profile.types:
|
||||
if type.quality_id == group['meta_data']['quality']['id'] and type.finish:
|
||||
movie.status_id = done_status.get('id')
|
||||
db.commit()
|
||||
except Exception, e:
|
||||
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
||||
|
||||
# Go over current movie releases
|
||||
for release in movie.releases:
|
||||
if release.quality.order < group['meta_data']['quality']['order']:
|
||||
|
||||
# This is where CP removes older, lesser quality releases
|
||||
if release.quality.order > group['meta_data']['quality']['order']:
|
||||
log.info('Removing older release for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
|
||||
|
||||
for file in release.files:
|
||||
log.info('Removing (not really) "%s"' % file.path)
|
||||
|
||||
# When a release already exists
|
||||
elif release.status_id is done_status.get('id'):
|
||||
|
||||
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
|
||||
if release.quality.order is group['meta_data']['quality']['order']:
|
||||
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label))
|
||||
|
||||
# Downloaded a lower quality, rename the newly downloaded files/folder to exclude them from scan
|
||||
else:
|
||||
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
|
||||
|
||||
@@ -188,10 +225,7 @@ class Renamer(Plugin):
|
||||
|
||||
break
|
||||
|
||||
for file in release.files:
|
||||
log.info('Removing (not really) "%s"' % file.path)
|
||||
|
||||
# Rename
|
||||
# Rename all files marked
|
||||
for src in rename_files:
|
||||
if rename_files[src]:
|
||||
|
||||
@@ -200,21 +234,24 @@ class Renamer(Plugin):
|
||||
log.info('Renaming "%s" to "%s"' % (src, dst))
|
||||
|
||||
path = os.path.dirname(dst)
|
||||
try:
|
||||
if not os.path.isdir(path): os.makedirs(path)
|
||||
except:
|
||||
log.error('Failed creating dir %s: %s' % (path, traceback.format_exc()))
|
||||
continue
|
||||
|
||||
# Create dir
|
||||
self.makeDir(path)
|
||||
|
||||
try:
|
||||
shutil.move(src, dst)
|
||||
pass
|
||||
#shutil.move(src, dst)
|
||||
except:
|
||||
log.error('Failed moving the file "%s" : %s' % (os.path.basename(src), traceback.format_exc()))
|
||||
|
||||
#print rename_me, rename_files[rename_me]
|
||||
|
||||
# Search for trailers etc
|
||||
fireEvent('renamer.after', group)
|
||||
fireEventAsync('renamer.after', group)
|
||||
|
||||
# Notify on download
|
||||
download_message = 'Download of %s (%s) successful.' % (group['library']['titles'][0]['title'], replacements['quality'])
|
||||
fireEventAsync('movie.downloaded', message = download_message, data = group)
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
|
||||
@@ -4,13 +4,13 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||
from couchpotato.core.helpers.variable import getExt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import File, Release, Movie
|
||||
from couchpotato.core.settings.model import File
|
||||
from couchpotato.environment import Env
|
||||
from flask.helpers import json
|
||||
from sqlalchemy.sql.expression import and_, or_
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -23,11 +23,11 @@ class Scanner(Plugin):
|
||||
'trailer': 1048576, # 1MB
|
||||
}
|
||||
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
||||
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads']
|
||||
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
||||
extensions = {
|
||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img'],
|
||||
'dvd': ['vts_*', 'vob'],
|
||||
'nfo': ['nfo', 'txt', 'tag'],
|
||||
'nfo': ['nfo', 'nfo-orig', 'txt', 'tag'],
|
||||
'subtitle': ['sub', 'srt', 'ssa', 'ass'],
|
||||
'subtitle_extra': ['idx'],
|
||||
'trailer': ['mov', 'mp4', 'flv']
|
||||
@@ -172,9 +172,21 @@ class Scanner(Plugin):
|
||||
|
||||
|
||||
# Determine file types
|
||||
delete_identifier = []
|
||||
for identifier in movie_files:
|
||||
group = movie_files[identifier]
|
||||
|
||||
# Check if movie is fresh and maybe still unpacking, ignore files new then 1 minute
|
||||
file_too_new = False
|
||||
for file in group['unsorted_files']:
|
||||
if os.path.getmtime(file) > time.time() - 60:
|
||||
file_too_new = True
|
||||
|
||||
if file_too_new:
|
||||
log.info('Files seem to be still unpacking or just unpacked, ignoring for now: %s' % identifier)
|
||||
delete_identifier.append(identifier)
|
||||
continue
|
||||
|
||||
# Group extra (and easy) files first
|
||||
images = self.getImages(group['unsorted_files'])
|
||||
group['files'] = {
|
||||
@@ -182,7 +194,7 @@ class Scanner(Plugin):
|
||||
'subtitle_extra': self.getSubtitlesExtras(group['unsorted_files']),
|
||||
'nfo': self.getNfo(group['unsorted_files']),
|
||||
'trailer': self.getTrailers(group['unsorted_files']),
|
||||
'backdrop': images['backdrop'],
|
||||
#'backdrop': images['backdrop'],
|
||||
'leftover': set(group['unsorted_files']),
|
||||
}
|
||||
|
||||
@@ -198,12 +210,13 @@ class Scanner(Plugin):
|
||||
group['parentdir'] = os.path.dirname(movie_file)
|
||||
group['dirname'] = None
|
||||
|
||||
folders = group['parentdir'].replace(folder, '').split(os.path.sep)
|
||||
folder_names = group['parentdir'].replace(folder, '').split(os.path.sep)
|
||||
folder_names.reverse()
|
||||
|
||||
# Try and get a proper dirname, so no "A", "Movie", "Download"
|
||||
for folder in folders:
|
||||
if folder.lower() in self.ignore_names or len(folder) < 2:
|
||||
group['dirname'] = folder
|
||||
# Try and get a proper dirname, so no "A", "Movie", "Download" etc
|
||||
for folder_name in folder_names:
|
||||
if folder_name.lower() not in self.ignore_names and len(folder_name) > 2:
|
||||
group['dirname'] = folder_name
|
||||
break
|
||||
|
||||
break
|
||||
@@ -220,12 +233,16 @@ class Scanner(Plugin):
|
||||
if not group['library']:
|
||||
log.error('Unable to determin movie: %s' % group['identifiers'])
|
||||
|
||||
# Delete still (asuming) unpacking files
|
||||
for identifier in delete_identifier:
|
||||
del movie_files[identifier]
|
||||
|
||||
return movie_files
|
||||
|
||||
def getMetaData(self, group):
|
||||
|
||||
data = {}
|
||||
files = group['files']['movie']
|
||||
files = list(group['files']['movie'])
|
||||
|
||||
for file in files:
|
||||
if os.path.getsize(file) < self.minimal_filesize['media']: continue # Ignore smaller files
|
||||
@@ -246,10 +263,11 @@ class Scanner(Plugin):
|
||||
if not data['quality']:
|
||||
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
||||
|
||||
data['quality_type'] = 'HD' if data.get('resolution_width', 0) >= 720 else 'SD'
|
||||
data['quality_type'] = 'HD' if data.get('resolution_width', 0) >= 1280 else 'SD'
|
||||
|
||||
data['group'] = self.getGroup(file[0])
|
||||
data['source'] = self.getSourceMedia(file[0])
|
||||
file = re.sub('(.cp\(tt[0-9{7}]+\))', '', files[0])
|
||||
data['group'] = self.getGroup(file)
|
||||
data['source'] = self.getSourceMedia(file)
|
||||
|
||||
return data
|
||||
|
||||
@@ -365,7 +383,6 @@ class Scanner(Plugin):
|
||||
return set(filter(test, files))
|
||||
|
||||
def getDVDFiles(self, files):
|
||||
|
||||
def test(s):
|
||||
return self.isDVDFile(s)
|
||||
|
||||
@@ -409,7 +426,7 @@ class Scanner(Plugin):
|
||||
if list(set(file.lower().split(os.path.sep)) & set(['video_ts', 'audio_ts'])):
|
||||
return True
|
||||
|
||||
for needle in ['vts_', 'video_ts', 'audio_ts']:
|
||||
for needle in ['vts_', 'video_ts', 'audio_ts', 'bdmv', 'certificate']:
|
||||
if needle in file.lower():
|
||||
return True
|
||||
|
||||
@@ -510,8 +527,8 @@ class Scanner(Plugin):
|
||||
|
||||
def getGroup(self, file):
|
||||
try:
|
||||
group = re.search('-(?P<group>[A-Z0-9]+)$', file, re.I)
|
||||
return group.group('group') or ''
|
||||
match = re.search('-(?P<group>[A-Z0-9]+).', file, re.I)
|
||||
return match.group('group') or ''
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ config = [{
|
||||
'name': 'required_words',
|
||||
'label': 'Required words',
|
||||
'default': '',
|
||||
'description': 'Ignore releases that doesn\'t contain one of these words.'
|
||||
'description': 'Ignore releases that don\'t contain at least one of these words.'
|
||||
},
|
||||
{
|
||||
'name': 'ignored_words',
|
||||
|
||||
@@ -6,7 +6,9 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||
from couchpotato.environment import Env
|
||||
from sqlalchemy.exc import InterfaceError
|
||||
import re
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -17,6 +19,7 @@ class Searcher(Plugin):
|
||||
addEvent('searcher.all', self.all)
|
||||
addEvent('searcher.single', self.single)
|
||||
addEvent('searcher.correct_movie', self.correctMovie)
|
||||
addEvent('searcher.download', self.download)
|
||||
|
||||
# Schedule cronjob
|
||||
fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
||||
@@ -34,7 +37,7 @@ class Searcher(Plugin):
|
||||
|
||||
for movie in movies:
|
||||
|
||||
self.single(movie.to_dict(deep = {
|
||||
self.single(movie.to_dict({
|
||||
'profile': {'types': {'quality': {}}},
|
||||
'releases': {'status': {}, 'quality': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
@@ -47,11 +50,8 @@ class Searcher(Plugin):
|
||||
|
||||
def single(self, movie):
|
||||
|
||||
downloaded_status = fireEvent('status.get', 'downloaded', single = True)
|
||||
available_status = fireEvent('status.get', 'available', single = True)
|
||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||
|
||||
successful = False
|
||||
for type in movie['profile']['types']:
|
||||
|
||||
has_better_quality = 0
|
||||
@@ -85,37 +85,22 @@ class Searcher(Plugin):
|
||||
db.commit()
|
||||
|
||||
for info in nzb:
|
||||
rls_info = ReleaseInfo(
|
||||
identifier = info,
|
||||
value = nzb[info]
|
||||
)
|
||||
rls.info.append(rls_info)
|
||||
db.commit()
|
||||
try:
|
||||
if not isinstance(nzb[info], (str, unicode, int, long)):
|
||||
continue
|
||||
|
||||
rls_info = ReleaseInfo(
|
||||
identifier = info,
|
||||
value = nzb[info]
|
||||
)
|
||||
rls.info.append(rls_info)
|
||||
db.commit()
|
||||
except InterfaceError:
|
||||
log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc()))
|
||||
|
||||
|
||||
for nzb in sorted_results:
|
||||
successful = fireEvent('download', data = nzb, movie = movie, single = True)
|
||||
|
||||
if successful:
|
||||
|
||||
# Mark release as snatched
|
||||
db = get_session()
|
||||
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
|
||||
rls.status_id = snatched_status.get('id')
|
||||
db.commit()
|
||||
|
||||
# Mark movie snatched if quality is finish-checked
|
||||
if type['finish']:
|
||||
mvie = db.query(Movie).filter_by(id = movie['id']).first()
|
||||
mvie.status_id = snatched_status.get('id')
|
||||
db.commit()
|
||||
|
||||
log.info('Downloading of %s successful.' % nzb.get('name'))
|
||||
fireEvent('movie.snatched', message = 'Downloading of %s successful.' % nzb.get('name'), data = rls.to_dict())
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
return self.download(data = nzb, movie = movie)
|
||||
else:
|
||||
log.info('Better quality (%s) already available or snatched for %s' % (type['quality']['label'], default_title))
|
||||
break
|
||||
@@ -126,6 +111,26 @@ class Searcher(Plugin):
|
||||
|
||||
return False
|
||||
|
||||
def download(self, data, movie):
|
||||
|
||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||
|
||||
successful = fireEvent('download', data = data, movie = movie, single = True)
|
||||
|
||||
if successful:
|
||||
|
||||
# Mark release as snatched
|
||||
db = get_session()
|
||||
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
|
||||
rls.status_id = snatched_status.get('id')
|
||||
db.commit()
|
||||
|
||||
log.info('Downloading of %s successful.' % data.get('name'))
|
||||
fireEvent('movie.snatched', message = 'Downloading of %s successful.' % data.get('name'), data = rls.to_dict())
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
|
||||
|
||||
@@ -192,6 +197,7 @@ class Searcher(Plugin):
|
||||
if len(movie_words) == 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
|
||||
return True
|
||||
|
||||
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie['library']['titles'][0]['title'], movie['library']['year']))
|
||||
return False
|
||||
|
||||
def containsOtherQuality(self, name, preferred_quality = {}, single_category = False):
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.request import jsonified
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -10,14 +12,13 @@ log = CPLog(__name__)
|
||||
|
||||
class Updater(Plugin):
|
||||
|
||||
git = 'git://github.com/CouchPotato/CouchPotato.git'
|
||||
repo_name = 'RuudBurger/CouchPotatoServer'
|
||||
|
||||
running = False
|
||||
version = None
|
||||
updateFailed = False
|
||||
updateAvailable = False
|
||||
updateVersion = None
|
||||
lastCheck = 0
|
||||
update_failed = False
|
||||
update_version = None
|
||||
last_check = 0
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -27,6 +28,18 @@ class Updater(Plugin):
|
||||
|
||||
addEvent('app.load', self.check)
|
||||
|
||||
addApiView('updater.info', self.getInfo)
|
||||
addApiView('updater.update', self.doUpdateView)
|
||||
|
||||
def getInfo(self):
|
||||
|
||||
return jsonified({
|
||||
'repo_name': self.repo_name,
|
||||
'last_check': self.last_check,
|
||||
'update_version': self.update_version,
|
||||
'version': self.getVersion(),
|
||||
})
|
||||
|
||||
def getVersion(self):
|
||||
|
||||
if not self.version:
|
||||
@@ -42,7 +55,7 @@ class Updater(Plugin):
|
||||
|
||||
def check(self):
|
||||
|
||||
if self.updateAvailable or self.isDisabled():
|
||||
if self.update_version or self.isDisabled():
|
||||
return
|
||||
|
||||
current_branch = self.repo.getCurrentBranch().name
|
||||
@@ -54,13 +67,17 @@ class Updater(Plugin):
|
||||
remote = branch.getHead()
|
||||
|
||||
if local.getDate() < remote.getDate():
|
||||
if self.conf('automatic') and not self.updateFailed:
|
||||
if self.conf('automatic') and not self.update_failed:
|
||||
self.doUpdate()
|
||||
else:
|
||||
self.updateAvailable = True
|
||||
self.updateVersion = remote.hash
|
||||
self.update_version = remote.hash
|
||||
|
||||
self.lastCheck = time.time()
|
||||
self.last_check = time.time()
|
||||
|
||||
def doUpdateView(self):
|
||||
return jsonified({
|
||||
'success': self.doUpdate()
|
||||
})
|
||||
|
||||
def doUpdate(self):
|
||||
try:
|
||||
@@ -70,7 +87,7 @@ class Updater(Plugin):
|
||||
except Exception, e:
|
||||
log.error('Failed updating via GIT: %s' % e)
|
||||
|
||||
self.updateFailed = True
|
||||
self.update_failed = True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
var UpdaterBase = new Class({
|
||||
|
||||
initialize: function(){
|
||||
var self = this;
|
||||
|
||||
App.addEvent('load', self.info.bind(self, 1000))
|
||||
},
|
||||
|
||||
info: function(timeout){
|
||||
var self = this;
|
||||
|
||||
if(self.timer) clearTimeout(self.timer);
|
||||
|
||||
self.timer = setTimeout(function(){
|
||||
Api.request('updater.info', {
|
||||
'onComplete': function(json){
|
||||
if(json.update_version){
|
||||
self.createMessage(json);
|
||||
}
|
||||
else {
|
||||
if(self.message)
|
||||
self.message.destroy();
|
||||
}
|
||||
}
|
||||
})
|
||||
}, (timeout || 0))
|
||||
|
||||
},
|
||||
|
||||
createMessage: function(data){
|
||||
var self = this;
|
||||
|
||||
self.message = new Element('div.message.update').adopt(
|
||||
new Element('span', {
|
||||
'text': 'A new version is available'
|
||||
}),
|
||||
new Element('a', {
|
||||
'href': 'https://github.com/'+data.repo_name+'/compare/'+data.version.substr(0, 7)+'...'+data.update_version.substr(0, 7),
|
||||
'text': 'see what has changed',
|
||||
'target': '_blank'
|
||||
}),
|
||||
new Element('span[text=or]'),
|
||||
new Element('a', {
|
||||
'text': 'just update, gogogo!',
|
||||
'events': {
|
||||
'click': self.doUpdate.bind(self)
|
||||
}
|
||||
})
|
||||
).inject($(document.body).getElement('.header'))
|
||||
},
|
||||
|
||||
doUpdate: function(){
|
||||
var self = this;
|
||||
|
||||
Api.request('updater.update', {
|
||||
'onComplete': function(json){
|
||||
|
||||
if(json.success){
|
||||
App.restart();
|
||||
|
||||
$(document.body).set('spin', {
|
||||
'message': 'Updating'
|
||||
});
|
||||
$(document.body).spin();
|
||||
|
||||
var checks = 0;
|
||||
var interval = 0;
|
||||
interval = setInterval(function(){
|
||||
Api.request('', {
|
||||
'onSuccess': function(){
|
||||
if(checks > 2){
|
||||
clearInterval(interval);
|
||||
$(document.body).unspin();
|
||||
self.info();
|
||||
}
|
||||
}
|
||||
});
|
||||
checks++;
|
||||
}, 500)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var Updater = new UpdaterBase();
|
||||
@@ -4,7 +4,7 @@ def start():
|
||||
return Wizard()
|
||||
|
||||
config = [{
|
||||
'name': 'global',
|
||||
'name': 'core',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'general',
|
||||
|
||||
@@ -2,7 +2,6 @@ from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from urllib2 import URLError
|
||||
from urlparse import urlparse
|
||||
import re
|
||||
import time
|
||||
@@ -60,22 +59,36 @@ class YarrProvider(Provider):
|
||||
sizeMb = ['mb', 'mib']
|
||||
sizeKb = ['kb', 'kib']
|
||||
|
||||
def __init__(self):
|
||||
addEvent('provider.belongs_to', self.belongsTo)
|
||||
|
||||
def belongsTo(self, url, host = None):
|
||||
try:
|
||||
hostname = urlparse(url).hostname
|
||||
download_url = host if host else self.urls['download']
|
||||
if hostname in download_url:
|
||||
return self
|
||||
except:
|
||||
log.debug('Url % s doesn\'t belong to %s' % (url, self.getName()))
|
||||
|
||||
return
|
||||
|
||||
def parseSize(self, size):
|
||||
|
||||
sizeRaw = size.lower()
|
||||
size = re.sub(r'[^0-9.]', '', size).strip()
|
||||
size = float(re.sub(r'[^0-9.]', '', size).strip())
|
||||
|
||||
for s in self.sizeGb:
|
||||
if s in sizeRaw:
|
||||
return float(size) * 1024
|
||||
return int(size) * 1024
|
||||
|
||||
for s in self.sizeMb:
|
||||
if s in sizeRaw:
|
||||
return float(size)
|
||||
return int(size)
|
||||
|
||||
for s in self.sizeKb:
|
||||
if s in sizeRaw:
|
||||
return float(size) / 1024
|
||||
return int(size) / 1024
|
||||
|
||||
return 0
|
||||
|
||||
@@ -96,11 +109,16 @@ class NZBProvider(YarrProvider):
|
||||
type = 'nzb'
|
||||
|
||||
def __init__(self):
|
||||
super(NZBProvider, self).__init__()
|
||||
|
||||
addEvent('provider.nzb.search', self.search)
|
||||
addEvent('provider.yarr.search', self.search)
|
||||
|
||||
addEvent('provider.nzb.feed', self.feed)
|
||||
|
||||
def download(self, url = '', nzb_id = ''):
|
||||
return self.urlopen(url)
|
||||
|
||||
def feed(self):
|
||||
return []
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
|
||||
@@ -17,7 +17,7 @@ class MetaDataBase(Plugin):
|
||||
|
||||
log.info('Creating %s metadata.' % self.getName())
|
||||
|
||||
root = self.getRootName()
|
||||
root = self.getRootName(release)
|
||||
|
||||
for type in ['nfo', 'thumbnail', 'fanart']:
|
||||
try:
|
||||
|
||||
@@ -8,7 +8,7 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'renamer',
|
||||
'name': 'metadata',
|
||||
'name': 'mediabrowser_metadata',
|
||||
'label': 'MediaBrowser',
|
||||
'description': 'Enable metadata MediaBrowser can understand',
|
||||
'options': [
|
||||
|
||||
@@ -8,7 +8,7 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'renamer',
|
||||
'name': 'metadata',
|
||||
'name': 'sonyps3_metadata',
|
||||
'label': 'Sony PS3',
|
||||
'description': 'Enable metadata your Playstation 3 can understand',
|
||||
'options': [
|
||||
|
||||
@@ -8,7 +8,7 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'renamer',
|
||||
'name': 'metadata',
|
||||
'name': 'wdtv_metadata',
|
||||
'label': 'WDTV',
|
||||
'description': 'Enable metadata WDTV can understand',
|
||||
'options': [
|
||||
|
||||
@@ -8,7 +8,7 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'renamer',
|
||||
'name': 'metadata',
|
||||
'name': 'xbmc_metadata',
|
||||
'label': 'XBMC',
|
||||
'description': 'Enable metadata XBMC can understand',
|
||||
'options': [
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.providers.metadata.base import MetaDataBase
|
||||
from xml.etree.ElementTree import Element, SubElement, tostring
|
||||
import os
|
||||
import re
|
||||
import xml.dom.minidom
|
||||
|
||||
class XBMC(MetaDataBase):
|
||||
|
||||
def getRootName(self, data = {}):
|
||||
|
||||
|
||||
|
||||
return '/Users/ruud/Downloads/Test/Transformers'
|
||||
return os.path.join(data['destination_dir'], data['filename'])
|
||||
|
||||
def getFanartName(self, root):
|
||||
return '%s-fanart.jpg' % root
|
||||
@@ -23,8 +22,30 @@ class XBMC(MetaDataBase):
|
||||
def getNfo(self, data):
|
||||
nfoxml = Element('movie')
|
||||
|
||||
types = ['title', 'rating', 'year', 'votes', 'rating', 'mpaa', 'originaltitle:original_title', 'outline:overview', 'premiered:released', 'id:imdb_id']
|
||||
types = ['rating', 'year', 'votes', 'rating', 'mpaa', 'originaltitle:original_title', 'outline:plot', 'premiered:released']
|
||||
|
||||
# Title
|
||||
try:
|
||||
el = SubElement(nfoxml, 'title')
|
||||
el.text = toUnicode(data['library']['titles'][0]['title'])
|
||||
except:
|
||||
pass
|
||||
|
||||
# IMDB id
|
||||
try:
|
||||
el = SubElement(nfoxml, 'id')
|
||||
el.text = toUnicode(data['library']['identifier'])
|
||||
except:
|
||||
pass
|
||||
|
||||
# Runtime
|
||||
try:
|
||||
runtime = SubElement(nfoxml, 'runtime')
|
||||
runtime.text = '%s min' % data['library']['runtime']
|
||||
except:
|
||||
pass
|
||||
|
||||
# Other values
|
||||
for type in types:
|
||||
|
||||
if ':' in type:
|
||||
@@ -33,20 +54,17 @@ class XBMC(MetaDataBase):
|
||||
name = type
|
||||
|
||||
try:
|
||||
el = SubElement(nfoxml, name)
|
||||
el.text = data.get(type, '')
|
||||
if data['library'].get(type):
|
||||
el = SubElement(nfoxml, name)
|
||||
el.text = toUnicode(data['library'].get(type, ''))
|
||||
except:
|
||||
pass
|
||||
|
||||
#for genre in self.get('genres'):
|
||||
# genres = SubElement(nfoxml, 'genre')
|
||||
# genres.text = genre
|
||||
# Genre
|
||||
for genre in data['library'].get('genres', []):
|
||||
genres = SubElement(nfoxml, 'genre')
|
||||
genres.text = genre.get('name')
|
||||
|
||||
try:
|
||||
runtime = SubElement(nfoxml, 'runtime')
|
||||
runtime.text = data.get('runtime') + " min"
|
||||
except:
|
||||
pass
|
||||
|
||||
# Clean up the xml and return it
|
||||
nfoxml = xml.dom.minidom.parseString(tostring(nfoxml))
|
||||
@@ -55,130 +73,3 @@ class XBMC(MetaDataBase):
|
||||
xml_string = text_re.sub('>\g<1></', xml_string)
|
||||
|
||||
return xml_string.encode('utf-8')
|
||||
"""
|
||||
def _get_fanart(self, min_height, min_width):
|
||||
''' Fetches the fanart for the specified imdb_id and saves it to dir.
|
||||
Arguments
|
||||
|
||||
min_height/width: Sets lowest acceptable resolution fanart. 0 means
|
||||
disregard. If no fanart available at specified resolution or greater, then
|
||||
we disregard.
|
||||
'''
|
||||
images = [image['image'] for image in self.tmdb_data['backdrops'] if image['image'].get('size') == 'original']
|
||||
if len(images) == 0:
|
||||
return
|
||||
|
||||
return self._get_image(images, min_height, min_width)
|
||||
|
||||
def get_fanart_url(self, min_height, min_width):
|
||||
return self._get_fanart(min_height, min_width)['url']
|
||||
|
||||
def write_fanart(self, filename_root, path, min_height, min_width):
|
||||
fanart_url = self.get_fanart_url(min_height, min_width)
|
||||
#fetch and write to disk
|
||||
dest = os.path.join(path, filename_root)
|
||||
try:
|
||||
f = open(dest, 'wb')
|
||||
except:
|
||||
raise IOError("Can't open for writing: %s" % dest)
|
||||
|
||||
response = urllib2.urlopen(fanart_url)
|
||||
f.write(response.read())
|
||||
f.close()
|
||||
|
||||
return True
|
||||
|
||||
def _get_poster(self, min_height, min_width):
|
||||
''' Fetches the poster for the specified imdb_id and saves it to dir.
|
||||
Arguments
|
||||
|
||||
min_height/width: Sets lowest acceptable resolution poster. 0 means
|
||||
disregard. If no poster available at specified resolution or greater, then
|
||||
we disregard.
|
||||
'''
|
||||
images = [image['image'] for image in self.tmdb_data['posters'] if image['image'].get('size') == 'original']
|
||||
if len(images) == 0:
|
||||
return
|
||||
|
||||
return self._get_image(images, min_height, min_width)
|
||||
|
||||
def get_poster_url(self, min_height, min_width):
|
||||
return self._get_poster(min_height, min_width)['url']
|
||||
|
||||
def write_poster(self, filename_root, path, min_height, min_width):
|
||||
poster_url = self.get_poster_url(min_height, min_width)
|
||||
dest = os.path.join(path, filename_root)
|
||||
|
||||
try:
|
||||
f = open(dest, 'wb')
|
||||
except:
|
||||
raise IOError("Can't open for writing: %s" % dest)
|
||||
|
||||
response = urllib2.urlopen(poster_url)
|
||||
f.write(response.read())
|
||||
f.close()
|
||||
|
||||
return True
|
||||
|
||||
def _get_tmdb_imdb(self):
|
||||
url = "http://api.themoviedb.org/2.1/Movie.imdbLookup/en/json/%s/%s" % (__tmdb_apikey__, self.imdbid)
|
||||
|
||||
count = 0
|
||||
while 1:
|
||||
count += 1
|
||||
response = urllib2.urlopen(url)
|
||||
json_string = response.read()
|
||||
try:
|
||||
tmdb_data = json.loads(json_string)[0]
|
||||
return tmdb_data
|
||||
except ValueError, e:
|
||||
if count < 3:
|
||||
continue
|
||||
else:
|
||||
raise ApiError("Invalid JSON: %s: %s" % (e, json_string))
|
||||
except:
|
||||
ApiError("JSON error with: %s" % json_string)
|
||||
|
||||
|
||||
def _get_image(self, image_list, min_height, min_width):
|
||||
#Select image
|
||||
images = []
|
||||
for image in image_list:
|
||||
if not min_height or min_width:
|
||||
images.append(image)
|
||||
break
|
||||
elif min_height and not min_width:
|
||||
if image['height'] >= min_height:
|
||||
images.append(image)
|
||||
break
|
||||
elif min_width and not min_height:
|
||||
if image['width'] >= min_width:
|
||||
images.append(image)
|
||||
break
|
||||
elif min_width and min_height:
|
||||
if image['width'] >= min_width and image['height'] >= min_height:
|
||||
images.append(image)
|
||||
break
|
||||
|
||||
#No image meets our resolution requirements, so disregard those requirements
|
||||
if len(images) == 0 and min_height or min_width:
|
||||
images.append(image_list[0])
|
||||
|
||||
return images[0]
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
try:
|
||||
id = sys.argv[1]
|
||||
except:
|
||||
id = 'tt0111161'
|
||||
|
||||
x = MetaGen(id)
|
||||
x.write_nfo("movie.nfo")
|
||||
try:
|
||||
x.write_fanart("fanart.jpg", ".", 0, 0)
|
||||
except: pass
|
||||
try:
|
||||
x.write_poster("movie.tbn", ".", 0, 0)
|
||||
except: pass
|
||||
"""
|
||||
|
||||
@@ -10,7 +10,7 @@ class IMDB(MovieProvider):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
addEvent('provider.movie.search', self.search)
|
||||
#addEvent('provider.movie.search', self.search)
|
||||
|
||||
self.p = IMDb('http')
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class TheMovieDb(MovieProvider):
|
||||
def getInfo(self, identifier = None):
|
||||
|
||||
cache_key = 'tmdb.cache.%s' % identifier
|
||||
result = self.getCache(cache_key)
|
||||
result = None #self.getCache(cache_key)
|
||||
|
||||
if not result:
|
||||
result = {}
|
||||
@@ -112,27 +112,33 @@ class TheMovieDb(MovieProvider):
|
||||
|
||||
def parseMovie(self, movie):
|
||||
|
||||
year = str(movie.get('released', 'none'))[:4]
|
||||
|
||||
# Poster url
|
||||
poster = self.getImage(movie, type = 'poster')
|
||||
backdrop = self.getImage(movie, type = 'backdrop')
|
||||
|
||||
# Genres
|
||||
genres = self.getCategory(movie, 'genre')
|
||||
|
||||
# 1900 is the same as None
|
||||
year = str(movie.get('released', 'none'))[:4]
|
||||
if year == '1900' or year.lower() == 'none':
|
||||
year = None
|
||||
|
||||
movie_data = {
|
||||
'id': int(movie.get('id', 0)),
|
||||
'titles': [toUnicode(movie.get('name'))],
|
||||
'original_title': movie.get('original_name'),
|
||||
'images': {
|
||||
'posters': [poster],
|
||||
'backdrops': [backdrop],
|
||||
},
|
||||
'imdb': movie.get('imdb_id'),
|
||||
'runtime': movie.get('runtime'),
|
||||
'released': movie.get('released'),
|
||||
'year': year,
|
||||
'plot': movie.get('overview', ''),
|
||||
'tagline': '',
|
||||
'genres': genres,
|
||||
}
|
||||
|
||||
# Add alternative names
|
||||
@@ -153,6 +159,19 @@ class TheMovieDb(MovieProvider):
|
||||
|
||||
return image
|
||||
|
||||
def getCategory(self, movie, type = 'genre'):
|
||||
|
||||
cats = movie.get('categories', {}).get(type)
|
||||
|
||||
categories = []
|
||||
for category in cats:
|
||||
try:
|
||||
categories.append(category)
|
||||
except:
|
||||
pass
|
||||
|
||||
return categories
|
||||
|
||||
def isDisabled(self):
|
||||
if self.conf('api_key') == '':
|
||||
log.error('No API key provided.')
|
||||
|
||||
@@ -13,10 +13,9 @@ log = CPLog(__name__)
|
||||
class Newzbin(NZBProvider, RSS):
|
||||
|
||||
urls = {
|
||||
'search': 'https://www.newzbin.com/search/',
|
||||
'download': 'http://www.newzbin.com/api/dnzb/',
|
||||
'search': 'https://www.newzbin.com/search/',
|
||||
}
|
||||
searchUrl = 'https://www.newzbin.com/search/'
|
||||
|
||||
format_ids = {
|
||||
2: ['scr'],
|
||||
@@ -36,7 +35,7 @@ class Newzbin(NZBProvider, RSS):
|
||||
def search(self, movie, quality):
|
||||
|
||||
results = []
|
||||
if self.isDisabled() or not self.isAvailable(self.searchUrl):
|
||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
||||
return results
|
||||
|
||||
format_id = self.getFormatId(type)
|
||||
@@ -97,11 +96,12 @@ class Newzbin(NZBProvider, RSS):
|
||||
new = {
|
||||
'id': id,
|
||||
'type': 'nzb',
|
||||
'provider': self.getName(),
|
||||
'name': title,
|
||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||
'size': self.parseSize(size),
|
||||
'url': str(self.getTextElement(nzb, '{%s}nzb' % REPORT_NS)),
|
||||
'download': lambda: self.download(id),
|
||||
'download': self.download,
|
||||
'detail_url': str(self.getTextElement(nzb, 'link')),
|
||||
'description': self.getTextElement(nzb, "description"),
|
||||
'check_nzb': False,
|
||||
@@ -121,7 +121,7 @@ class Newzbin(NZBProvider, RSS):
|
||||
|
||||
return results
|
||||
|
||||
def download(self, nzb_id):
|
||||
def download(self, url = '', nzb_id = ''):
|
||||
try:
|
||||
log.info('Download nzb from newzbin, report id: %s ' % nzb_id)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ config = [{
|
||||
{
|
||||
'tab': 'providers',
|
||||
'name': 'newznab',
|
||||
'description': 'Enable multiple NewzNab providers',
|
||||
'description': 'Enable multiple NewzNab providers such as <a href="http://nzb.su" target="_blank">NZB.su</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
@@ -21,8 +21,8 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'http://nzb.su',
|
||||
'description': 'The hostname of your newznab provider, like http://nzb.su'
|
||||
'default': 'nzb.su',
|
||||
'description': 'The hostname of your newznab provider'
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
|
||||
@@ -5,6 +5,7 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.base import NZBProvider
|
||||
from dateutil.parser import parse
|
||||
from urllib import urlencode
|
||||
from urlparse import urlparse
|
||||
import time
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
@@ -130,11 +131,13 @@ class Newznab(NZBProvider, RSS):
|
||||
id = self.getTextElement(nzb, "guid").split('/')[-1:].pop()
|
||||
new = {
|
||||
'id': id,
|
||||
'provider': self.getName(),
|
||||
'type': 'nzb',
|
||||
'name': self.getTextElement(nzb, "title"),
|
||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||
'size': int(size) / 1024 / 1024,
|
||||
'url': (self.getUrl(host['host'], self.urls['download']) % id) + self.getApiExt(host),
|
||||
'download': self.download,
|
||||
'detail_url': (self.getUrl(host['host'], self.urls['detail']) % id) + self.getApiExt(host),
|
||||
'content': self.getTextElement(nzb, "description"),
|
||||
}
|
||||
@@ -173,6 +176,17 @@ class Newznab(NZBProvider, RSS):
|
||||
|
||||
return list
|
||||
|
||||
def belongsTo(self, url):
|
||||
|
||||
hosts = self.getHosts()
|
||||
|
||||
for host in hosts:
|
||||
result = super(Newznab, self).belongsTo(url, host = host['host'])
|
||||
if result:
|
||||
return result
|
||||
|
||||
return
|
||||
|
||||
def getUrl(self, host, type):
|
||||
return cleanHost(host) + 'api?t=' + type
|
||||
|
||||
|
||||
@@ -31,28 +31,51 @@ var MultipleNewznab = new Class({
|
||||
});
|
||||
|
||||
self.inputs[name].getParent().hide()
|
||||
self.inputs[name].addEvent('change', self.addEmpty.bind(self))
|
||||
});
|
||||
|
||||
|
||||
self.values.each(function(item, nr){
|
||||
self.createItem(item.use, item.host, item.api_key);
|
||||
});
|
||||
|
||||
new Element('a.nice_button', {
|
||||
'text': 'Add new NewzNab provider',
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop();
|
||||
|
||||
self.createItem(1, '', '');
|
||||
}
|
||||
}
|
||||
}).inject(self.fieldset.getElement('h2'), 'after');
|
||||
|
||||
new Element('div.head').adopt(
|
||||
new Element('abbr.host', {
|
||||
'text': 'Host',
|
||||
'title': self.inputs['host'].getNext().get('text')
|
||||
}),
|
||||
new Element('abbr.api_key', {
|
||||
'text': 'Api Key',
|
||||
'title': self.inputs['api_key'].getNext().get('text')
|
||||
})
|
||||
).inject(self.fieldset.getElement('h2'), 'after');
|
||||
|
||||
self.addEmpty();
|
||||
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
add_empty_timeout: 0,
|
||||
addEmpty: function(){
|
||||
var self = this;
|
||||
|
||||
if(self.add_empty_timeout) clearTimeout(self.add_empty_timeout);
|
||||
|
||||
var has_empty = false;
|
||||
self.items.each(function(ctrl_holder){
|
||||
if(ctrl_holder.getElement('.host').get('value') == '' && ctrl_holder.getElement('.api_key').get('value') == ''){
|
||||
has_empty = true;
|
||||
}
|
||||
ctrl_holder[has_empty ? 'addClass' : 'removeClass']('is_empty');
|
||||
});
|
||||
if(has_empty) return;
|
||||
|
||||
self.add_empty_timeout = setTimeout(function(){
|
||||
self.createItem(false, null, null);
|
||||
}, 10);
|
||||
},
|
||||
|
||||
createItem: function(use, host, api){
|
||||
var self = this;
|
||||
|
||||
@@ -83,12 +106,12 @@ var MultipleNewznab = new Class({
|
||||
}
|
||||
}),
|
||||
new Element('a.icon.delete', {
|
||||
'text': 'delete',
|
||||
'events': {
|
||||
'click': self.deleteItem.bind(self)
|
||||
}
|
||||
})
|
||||
).inject(self.fieldset);
|
||||
item[!host ? 'addClass' : 'removeClass']('is_empty');
|
||||
|
||||
new Form.Check(checkbox, {
|
||||
'onChange': checkbox.fireEvent.bind(checkbox, 'change')
|
||||
@@ -105,6 +128,7 @@ var MultipleNewznab = new Class({
|
||||
self.items.each(function(item, nr){
|
||||
self.input_types.each(function(type){
|
||||
var input = item.getElement('input.'+type);
|
||||
if(input.getParent('.ctrlHolder').hasClass('is_empty')) return;
|
||||
|
||||
if(!temp[type]) temp[type] = [];
|
||||
temp[type][nr] = input.get('type') == 'checkbox' ? +input.get('checked') : input.get('value').trim();
|
||||
@@ -125,7 +149,7 @@ var MultipleNewznab = new Class({
|
||||
(e).stop();
|
||||
|
||||
var item = e.target.getParent();
|
||||
|
||||
|
||||
self.items.erase(item);
|
||||
item.destroy();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ log = CPLog(__name__)
|
||||
class NzbIndex(NZBProvider, RSS):
|
||||
|
||||
urls = {
|
||||
'download': 'http://www.nzbindex.nl/download/%s/%s',
|
||||
'download': 'http://www.nzbindex.nl/download/',
|
||||
'api': 'http://www.nzbindex.nl/rss/', #http://www.nzbindex.nl/rss/?q=due+date+720p&age=1000&sort=agedesc&minsize=3500&maxsize=10000
|
||||
}
|
||||
|
||||
@@ -63,10 +63,12 @@ class NzbIndex(NZBProvider, RSS):
|
||||
new = {
|
||||
'id': id,
|
||||
'type': 'nzb',
|
||||
'provider': self.getName(),
|
||||
'name': self.getTextElement(nzb, "title"),
|
||||
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
||||
'size': enclosure['length'],
|
||||
'url': enclosure['url'],
|
||||
'download': self.download,
|
||||
'detail_url': enclosure['url'].replace('/download/', '/release/'),
|
||||
'description': self.getTextElement(nzb, "description"),
|
||||
'check_nzb': True,
|
||||
|
||||
@@ -81,10 +81,12 @@ class NZBMatrix(NZBProvider, RSS):
|
||||
new = {
|
||||
'id': id,
|
||||
'type': 'nzb',
|
||||
'provider': self.getName(),
|
||||
'name': title,
|
||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||
'size': self.parseSize(size),
|
||||
'url': self.urls['download'] % id + self.getApiExt(),
|
||||
'download': self.download,
|
||||
'detail_url': self.urls['detail'] % id,
|
||||
'description': self.getTextElement(nzb, "description"),
|
||||
'check_nzb': True,
|
||||
|
||||
@@ -9,6 +9,7 @@ config = [{
|
||||
{
|
||||
'tab': 'providers',
|
||||
'name': 'nzbs',
|
||||
'description': 'Id and Key can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">on your nzbs.org RSS page</a>.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
@@ -17,12 +18,12 @@ config = [{
|
||||
{
|
||||
'name': 'id',
|
||||
'label': 'Id',
|
||||
'description': 'Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, the number after "&i="',
|
||||
'description': 'The number after "&i="',
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
'label': 'Api Key',
|
||||
'description': 'Can be found <a href="http://nzbs.org/index.php?action=rss" target="_blank">here</a>, the string after "&h="'
|
||||
'description': 'The string after "&h="'
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -71,10 +71,12 @@ class Nzbs(NZBProvider, RSS):
|
||||
new = {
|
||||
'id': id,
|
||||
'type': 'nzb',
|
||||
'provider': self.getName(),
|
||||
'name': self.getTextElement(nzb, "title"),
|
||||
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
||||
'size': self.parseSize(self.getTextElement(nzb, "description").split('</a><br />')[1].split('">')[1]),
|
||||
'url': self.urls['download'] % (id, self.getApiExt()),
|
||||
'download': self.download,
|
||||
'detail_url': self.urls['detail'] % id,
|
||||
'description': self.getTextElement(nzb, "description"),
|
||||
'check_nzb': True,
|
||||
|
||||
@@ -3,22 +3,4 @@ from .main import ThePirateBay
|
||||
def start():
|
||||
return ThePirateBay()
|
||||
|
||||
config = [{
|
||||
'name': 'themoviedb',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'providers',
|
||||
'name': 'tmdb',
|
||||
'label': 'TheMovieDB',
|
||||
'advanced': True,
|
||||
'description': 'Used for all calls to TheMovieDB.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'api_key',
|
||||
'default': '9b939aee0aaafc12a65bf448e4af9543',
|
||||
'label': 'Api Key',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
config = []
|
||||
|
||||
@@ -3,6 +3,7 @@ from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import isInt
|
||||
from couchpotato.core.helpers.request import getParams, jsonified
|
||||
from couchpotato.core.helpers.variable import mergeDicts
|
||||
import ConfigParser
|
||||
import os.path
|
||||
import time
|
||||
@@ -93,7 +94,12 @@ class Settings():
|
||||
self.p.set(section, option, value)
|
||||
|
||||
def addOptions(self, section_name, options):
|
||||
self.options[section_name] = options
|
||||
|
||||
if not self.options.get(section_name):
|
||||
self.options[section_name] = options
|
||||
else:
|
||||
options['groups'] = self.options[section_name].get('groups') + options.get('groups')
|
||||
self.options[section_name] = mergeDicts(self.options[section_name], options)
|
||||
|
||||
def getOptions(self):
|
||||
return self.options
|
||||
|
||||
@@ -44,6 +44,7 @@ class Library(Entity):
|
||||
status = ManyToOne('Status')
|
||||
movies = OneToMany('Movie')
|
||||
titles = OneToMany('LibraryTitle')
|
||||
genres = ManyToMany('LibraryGenre')
|
||||
files = ManyToMany('File')
|
||||
info = OneToMany('LibraryInfo')
|
||||
|
||||
@@ -68,6 +69,14 @@ class LibraryTitle(Entity):
|
||||
libraries = ManyToOne('Library')
|
||||
|
||||
|
||||
class LibraryGenre(Entity):
|
||||
""""""
|
||||
|
||||
name = Field(Unicode)
|
||||
|
||||
libraries = ManyToMany('Library')
|
||||
|
||||
|
||||
class Language(Entity):
|
||||
""""""
|
||||
|
||||
|
||||
@@ -115,8 +115,10 @@ def runCouchPotato(options, base_path, args):
|
||||
|
||||
latest_db_version = version(repo)
|
||||
|
||||
initialize = True
|
||||
try:
|
||||
current_db_version = db_version(db, repo)
|
||||
initialize = False
|
||||
except:
|
||||
version_control(db, repo, version = latest_db_version)
|
||||
current_db_version = db_version(db, repo)
|
||||
@@ -131,6 +133,9 @@ def runCouchPotato(options, base_path, args):
|
||||
|
||||
fireEventAsync('app.load')
|
||||
|
||||
if initialize:
|
||||
fireEventAsync('app.initialize')
|
||||
|
||||
# Create app
|
||||
from couchpotato import app
|
||||
api_key = Env.setting('api_key')
|
||||
@@ -138,10 +143,12 @@ def runCouchPotato(options, base_path, args):
|
||||
reloader = debug and not options.daemonize
|
||||
|
||||
# Basic config
|
||||
app.host = Env.setting('host', default = '0.0.0.0')
|
||||
app.port = Env.setting('port', default = 5000)
|
||||
app.debug = debug
|
||||
app.secret_key = api_key
|
||||
config = {
|
||||
'use_reloader': reloader,
|
||||
'host': Env.setting('host', default = '0.0.0.0'),
|
||||
'port': Env.setting('port', default = 5000)
|
||||
}
|
||||
|
||||
# Static path
|
||||
web.add_url_rule(url_base + '/static/<path:filename>',
|
||||
@@ -153,4 +160,4 @@ def runCouchPotato(options, base_path, args):
|
||||
app.register_blueprint(api, url_prefix = '%s/%s/' % (url_base, api_key))
|
||||
|
||||
# Go go go!
|
||||
app.run(use_reloader = reloader)
|
||||
app.run(**config)
|
||||
|
||||
|
Before Width: | Height: | Size: 1015 B |
|
Before Width: | Height: | Size: 206 B |
|
After Width: | Height: | Size: 451 B |
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 502 B |
|
After Width: | Height: | Size: 624 B |
|
After Width: | Height: | Size: 757 B |
|
Before Width: | Height: | Size: 159 B After Width: | Height: | Size: 159 B |
|
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 674 B |
|
Before Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 223 B |
@@ -29,7 +29,7 @@ var CouchPotato = new Class({
|
||||
else
|
||||
self.openPage(window.location.pathname);
|
||||
|
||||
self.c.addEvent('click:relay(a)', self.pushState.bind(self));
|
||||
self.c.addEvent('click:relay(a:not([target=_blank]))', self.pushState.bind(self));
|
||||
},
|
||||
|
||||
pushState: function(e){
|
||||
@@ -100,6 +100,14 @@ var CouchPotato = new Class({
|
||||
|
||||
getPage: function(name){
|
||||
return this.pages[name]
|
||||
},
|
||||
|
||||
shutdown: function(){
|
||||
Api.request('app.shutdown');
|
||||
},
|
||||
|
||||
restart: function(){
|
||||
Api.request('app.restart');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -96,14 +96,14 @@ Form.Check = new Class({
|
||||
this.fireEvent('removeHighlight', this);
|
||||
},
|
||||
keyToggle: function(e) {
|
||||
var evt = new Event(e);
|
||||
var evt = (e);
|
||||
if (evt.key === 'space') { this.toggle(e); }
|
||||
},
|
||||
toggle: function(e) {
|
||||
var evt;
|
||||
if (this.disabled) { return this; }
|
||||
if (e) {
|
||||
evt = new Event(e).stopPropagation();
|
||||
evt = (e).stopPropagation();
|
||||
if (evt.target.tagName.toLowerCase() !== 'a') {
|
||||
evt.stop();
|
||||
}
|
||||
@@ -114,6 +114,7 @@ Form.Check = new Class({
|
||||
this.check();
|
||||
}
|
||||
this.fireEvent('change', this);
|
||||
this.input.fireEvent('change', this);
|
||||
return this;
|
||||
},
|
||||
uncheck: function() {
|
||||
|
||||
@@ -117,7 +117,7 @@ Form.Dropdown = new Class({
|
||||
},
|
||||
expand: function(e) {
|
||||
clearTimeout(this.collapseInterval);
|
||||
var evt = e ? new Event(e).stop() : null;
|
||||
var evt = e ? (e).stop() : null;
|
||||
this.open = true;
|
||||
this.input.focus();
|
||||
this.element.addClass('active').addClass('dropdown-active');
|
||||
|
||||
@@ -22,7 +22,7 @@ Form.Radio = new Class({
|
||||
toggle: function(e) {
|
||||
if (this.element.hasClass('checked') || this.disabled) { return; }
|
||||
var evt;
|
||||
if (e) { evt = new Event(e).stop(); }
|
||||
if (e) { evt = (e).stop(); }
|
||||
if (this.checked) {
|
||||
this.uncheck();
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// MooTools: the javascript framework.
|
||||
// Load this file's selection again by visiting: http://mootools.net/more/1e3edb90c5e02d9b9013b54e6ab001ea
|
||||
// Or build this file again with packager using: packager build More/Element.Forms More/Element.Delegation More/Element.Shortcuts More/Fx.Slide More/Sortables More/Request.JSONP More/Request.Periodical More/Spinner
|
||||
// Load this file's selection again by visiting: http://mootools.net/more/13115b95c0560a5c35a61ccf237f3ed9
|
||||
// Or build this file again with packager using: packager build More/Element.Forms More/Element.Shortcuts More/Fx.Slide More/Sortables More/Request.JSONP More/Request.Periodical More/Spinner
|
||||
/*
|
||||
---
|
||||
|
||||
@@ -20,6 +20,7 @@ authors:
|
||||
- Tim Wienk
|
||||
- Christoph Pojer
|
||||
- Aaron Newton
|
||||
- Jacob Thornton
|
||||
|
||||
requires:
|
||||
- Core/MooTools
|
||||
@@ -30,8 +31,8 @@ provides: [MooTools.More]
|
||||
*/
|
||||
|
||||
MooTools.More = {
|
||||
'version': '1.3.1.1',
|
||||
'build': '0292a3af1eea242b817fecf9daa127417d10d4ce'
|
||||
'version': '1.4.0.1',
|
||||
'build': 'a4244edf2aa97ac8a196fc96082dd35af1abab87'
|
||||
};
|
||||
|
||||
|
||||
@@ -182,7 +183,7 @@ String.implement({
|
||||
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
})();
|
||||
|
||||
|
||||
/*
|
||||
@@ -327,368 +328,6 @@ Element.implement({
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
---
|
||||
|
||||
name: Events.Pseudos
|
||||
|
||||
description: Adds the functionality to add pseudo events
|
||||
|
||||
license: MIT-style license
|
||||
|
||||
authors:
|
||||
- Arian Stolwijk
|
||||
|
||||
requires: [Core/Class.Extras, Core/Slick.Parser, More/MooTools.More]
|
||||
|
||||
provides: [Events.Pseudos]
|
||||
|
||||
...
|
||||
*/
|
||||
|
||||
Events.Pseudos = function(pseudos, addEvent, removeEvent){
|
||||
|
||||
var storeKey = 'monitorEvents:';
|
||||
|
||||
var storageOf = function(object){
|
||||
return {
|
||||
store: object.store ? function(key, value){
|
||||
object.store(storeKey + key, value);
|
||||
} : function(key, value){
|
||||
(object.$monitorEvents || (object.$monitorEvents = {}))[key] = value;
|
||||
},
|
||||
retrieve: object.retrieve ? function(key, dflt){
|
||||
return object.retrieve(storeKey + key, dflt);
|
||||
} : function(key, dflt){
|
||||
if (!object.$monitorEvents) return dflt;
|
||||
return object.$monitorEvents[key] || dflt;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var splitType = function(type){
|
||||
if (type.indexOf(':') == -1 || !pseudos) return null;
|
||||
|
||||
var parsed = Slick.parse(type).expressions[0][0],
|
||||
parsedPseudos = parsed.pseudos,
|
||||
l = parsedPseudos.length,
|
||||
splits = [];
|
||||
|
||||
while (l--) if (pseudos[parsedPseudos[l].key]){
|
||||
splits.push({
|
||||
event: parsed.tag,
|
||||
value: parsedPseudos[l].value,
|
||||
pseudo: parsedPseudos[l].key,
|
||||
original: type
|
||||
});
|
||||
}
|
||||
|
||||
return splits.length ? splits : null;
|
||||
};
|
||||
|
||||
var mergePseudoOptions = function(split){
|
||||
return Object.merge.apply(this, split.map(function(item){
|
||||
return pseudos[item.pseudo].options || {};
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
addEvent: function(type, fn, internal){
|
||||
var split = splitType(type);
|
||||
if (!split) return addEvent.call(this, type, fn, internal);
|
||||
|
||||
var storage = storageOf(this),
|
||||
events = storage.retrieve(type, []),
|
||||
eventType = split[0].event,
|
||||
options = mergePseudoOptions(split),
|
||||
stack = fn,
|
||||
eventOptions = options[eventType] || {},
|
||||
args = Array.slice(arguments, 2),
|
||||
self = this,
|
||||
monitor;
|
||||
|
||||
if (eventOptions.args) args.append(Array.from(eventOptions.args));
|
||||
if (eventOptions.base) eventType = eventOptions.base;
|
||||
if (eventOptions.onAdd) eventOptions.onAdd(this);
|
||||
|
||||
split.each(function(item){
|
||||
var stackFn = stack;
|
||||
stack = function(){
|
||||
(eventOptions.listener || pseudos[item.pseudo].listener).call(self, item, stackFn, arguments, monitor, options);
|
||||
};
|
||||
});
|
||||
monitor = stack.bind(this);
|
||||
|
||||
events.include({event: fn, monitor: monitor});
|
||||
storage.store(type, events);
|
||||
|
||||
addEvent.apply(this, [type, fn].concat(args));
|
||||
return addEvent.apply(this, [eventType, monitor].concat(args));
|
||||
},
|
||||
|
||||
removeEvent: function(type, fn){
|
||||
var split = splitType(type);
|
||||
if (!split) return removeEvent.call(this, type, fn);
|
||||
|
||||
var storage = storageOf(this),
|
||||
events = storage.retrieve(type);
|
||||
if (!events) return this;
|
||||
|
||||
var eventType = split[0].event,
|
||||
options = mergePseudoOptions(split),
|
||||
eventOptions = options[eventType] || {},
|
||||
args = Array.slice(arguments, 2);
|
||||
|
||||
if (eventOptions.args) args.append(Array.from(eventOptions.args));
|
||||
if (eventOptions.base) eventType = eventOptions.base;
|
||||
if (eventOptions.onRemove) eventOptions.onRemove(this);
|
||||
|
||||
removeEvent.apply(this, [type, fn].concat(args));
|
||||
events.each(function(monitor, i){
|
||||
if (!fn || monitor.event == fn) removeEvent.apply(this, [eventType, monitor.monitor].concat(args));
|
||||
delete events[i];
|
||||
}, this);
|
||||
|
||||
storage.store(type, events);
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
(function(){
|
||||
|
||||
var pseudos = {
|
||||
|
||||
once: {
|
||||
listener: function(split, fn, args, monitor){
|
||||
fn.apply(this, args);
|
||||
this.removeEvent(split.event, monitor)
|
||||
.removeEvent(split.original, fn);
|
||||
}
|
||||
},
|
||||
|
||||
throttle: {
|
||||
listener: function(split, fn, args){
|
||||
if (!fn._throttled){
|
||||
fn.apply(this, args);
|
||||
fn._throttled = setTimeout(function(){
|
||||
fn._throttled = false;
|
||||
}, split.value || 250);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
pause: {
|
||||
listener: function(split, fn, args){
|
||||
clearTimeout(fn._pause);
|
||||
fn._pause = fn.delay(split.value || 250, this, args);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Events.definePseudo = function(key, listener){
|
||||
pseudos[key] = Type.isFunction(listener) ? {listener: listener} : listener;
|
||||
return this;
|
||||
};
|
||||
|
||||
Events.lookupPseudo = function(key){
|
||||
return pseudos[key];
|
||||
};
|
||||
|
||||
var proto = Events.prototype;
|
||||
Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
|
||||
|
||||
['Request', 'Fx'].each(function(klass){
|
||||
if (this[klass]) this[klass].implement(Events.prototype);
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
|
||||
|
||||
/*
|
||||
---
|
||||
|
||||
name: Element.Event.Pseudos
|
||||
|
||||
description: Adds the functionality to add pseudo events for Elements
|
||||
|
||||
license: MIT-style license
|
||||
|
||||
authors:
|
||||
- Arian Stolwijk
|
||||
|
||||
requires: [Core/Element.Event, Events.Pseudos]
|
||||
|
||||
provides: [Element.Event.Pseudos]
|
||||
|
||||
...
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var pseudos = {},
|
||||
copyFromEvents = ['once', 'throttle', 'pause'],
|
||||
count = copyFromEvents.length;
|
||||
|
||||
while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
|
||||
|
||||
Event.definePseudo = function(key, listener){
|
||||
pseudos[key] = Type.isFunction(listener) ? {listener: listener} : listener;
|
||||
return this;
|
||||
};
|
||||
|
||||
var proto = Element.prototype;
|
||||
[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
|
||||
|
||||
}).call(this);
|
||||
|
||||
|
||||
/*
|
||||
---
|
||||
|
||||
script: Element.Delegation.js
|
||||
|
||||
name: Element.Delegation
|
||||
|
||||
description: Extends the Element native object to include the delegate method for more efficient event management.
|
||||
|
||||
credits:
|
||||
- "Event checking based on the work of Daniel Steigerwald. License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
|
||||
|
||||
license: MIT-style license
|
||||
|
||||
authors:
|
||||
- Aaron Newton
|
||||
- Daniel Steigerwald
|
||||
|
||||
requires: [/MooTools.More, Element.Event.Pseudos]
|
||||
|
||||
provides: [Element.Delegation]
|
||||
|
||||
...
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var eventListenerSupport = !(window.attachEvent && !window.addEventListener),
|
||||
nativeEvents = Element.NativeEvents;
|
||||
|
||||
nativeEvents.focusin = 2;
|
||||
nativeEvents.focusout = 2;
|
||||
|
||||
var check = function(split, target, event){
|
||||
var elementEvent = Element.Events[split.event], condition;
|
||||
if (elementEvent) condition = elementEvent.condition;
|
||||
return Slick.match(target, split.value) && (!condition || condition.call(target, event));
|
||||
};
|
||||
|
||||
var formObserver = function(eventName){
|
||||
|
||||
var $delegationKey = '$delegation:';
|
||||
|
||||
return {
|
||||
base: 'focusin',
|
||||
|
||||
onRemove: function(element){
|
||||
element.retrieve($delegationKey + 'forms', []).each(function(el){
|
||||
el.retrieve($delegationKey + 'listeners', []).each(function(listener){
|
||||
el.removeEvent(eventName, listener);
|
||||
});
|
||||
el.eliminate($delegationKey + eventName + 'listeners')
|
||||
.eliminate($delegationKey + eventName + 'originalFn');
|
||||
});
|
||||
},
|
||||
|
||||
listener: function(split, fn, args, monitor, options){
|
||||
var event = args[0],
|
||||
forms = this.retrieve($delegationKey + 'forms', []),
|
||||
target = event.target,
|
||||
form = (target.get('tag') == 'form') ? target : event.target.getParent('form'),
|
||||
formEvents = form.retrieve($delegationKey + 'originalFn', []),
|
||||
formListeners = form.retrieve($delegationKey + 'listeners', []);
|
||||
|
||||
forms.include(form);
|
||||
this.store($delegationKey + 'forms', forms);
|
||||
|
||||
if (!formEvents.contains(fn)){
|
||||
var formListener = function(event){
|
||||
if (check(split, this, event)) fn.call(this, event);
|
||||
};
|
||||
form.addEvent(eventName, formListener);
|
||||
|
||||
formEvents.push(fn);
|
||||
formListeners.push(formListener);
|
||||
|
||||
form.store($delegationKey + eventName + 'originalFn', formEvents)
|
||||
.store($delegationKey + eventName + 'listeners', formListeners);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var inputObserver = function(eventName){
|
||||
return {
|
||||
base: 'focusin',
|
||||
listener: function(split, fn, args){
|
||||
var events = {blur: function(){
|
||||
this.removeEvents(events);
|
||||
}};
|
||||
events[eventName] = function(event){
|
||||
if (check(split, this, event)) fn.call(this, event);
|
||||
};
|
||||
args[0].target.addEvents(events);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var eventOptions = {
|
||||
mouseenter: {
|
||||
base: 'mouseover'
|
||||
},
|
||||
mouseleave: {
|
||||
base: 'mouseout'
|
||||
},
|
||||
focus: {
|
||||
base: 'focus' + (eventListenerSupport ? '' : 'in'),
|
||||
args: [true]
|
||||
},
|
||||
blur: {
|
||||
base: eventListenerSupport ? 'blur' : 'focusout',
|
||||
args: [true]
|
||||
}
|
||||
};
|
||||
|
||||
if (!eventListenerSupport) Object.append(eventOptions, {
|
||||
submit: formObserver('submit'),
|
||||
reset: formObserver('reset'),
|
||||
change: inputObserver('change'),
|
||||
select: inputObserver('select')
|
||||
});
|
||||
|
||||
|
||||
Event.definePseudo('relay', {
|
||||
listener: function(split, fn, args, monitor, options){
|
||||
var event = args[0];
|
||||
|
||||
for (var target = event.target; target && target != this; target = target.parentNode){
|
||||
var finalTarget = document.id(target);
|
||||
if (check(split, finalTarget, event)){
|
||||
if (finalTarget) fn.call(finalTarget, event, finalTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
options: eventOptions
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
---
|
||||
|
||||
@@ -826,7 +465,7 @@ Fx.Slide = new Class({
|
||||
|
||||
this.addEvent('complete', function(){
|
||||
this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
|
||||
if (this.open && options.resetHeight) wrapper.setStyle('height', '');
|
||||
if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
|
||||
}, true);
|
||||
},
|
||||
|
||||
@@ -1050,12 +689,6 @@ var Drag = new Class({
|
||||
var limit = options.limit;
|
||||
this.limit = {x: [], y: []};
|
||||
|
||||
var styles = this.element.getStyles('left', 'right', 'top', 'bottom');
|
||||
this._invert = {
|
||||
x: options.modifiers.x == 'left' && styles.left == 'auto' && !isNaN(styles.right.toInt()) && (options.modifiers.x = 'right'),
|
||||
y: options.modifiers.y == 'top' && styles.top == 'auto' && !isNaN(styles.bottom.toInt()) && (options.modifiers.y = 'bottom')
|
||||
};
|
||||
|
||||
var z, coordinates;
|
||||
for (z in options.modifiers){
|
||||
if (!options.modifiers[z]) continue;
|
||||
@@ -1072,7 +705,6 @@ var Drag = new Class({
|
||||
else this.value.now[z] = this.element[options.modifiers[z]];
|
||||
|
||||
if (options.invert) this.value.now[z] *= -1;
|
||||
if (this._invert[z]) this.value.now[z] *= -1;
|
||||
|
||||
this.mouse.pos[z] = event.page[z] - this.value.now[z];
|
||||
|
||||
@@ -1122,7 +754,6 @@ var Drag = new Class({
|
||||
this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
|
||||
|
||||
if (options.invert) this.value.now[z] *= -1;
|
||||
if (this._invert[z]) this.value.now[z] *= -1;
|
||||
|
||||
if (options.limit && this.limit[z]){
|
||||
if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
|
||||
@@ -1236,10 +867,9 @@ Drag.Move = new Class({
|
||||
this.container = document.id(this.container.getDocument().body);
|
||||
|
||||
if (this.options.style){
|
||||
if (this.options.modifiers.x == "left" && this.options.modifiers.y == "top"){
|
||||
var parentStyles,
|
||||
parent = element.getOffsetParent();
|
||||
var styles = element.getStyles('left', 'top');
|
||||
if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
|
||||
var parent = element.getOffsetParent(),
|
||||
styles = element.getStyles('left', 'top');
|
||||
if (parent && (styles.left == 'auto' || styles.top == 'auto')){
|
||||
element.setPosition(element.getPosition(parent));
|
||||
}
|
||||
@@ -1529,7 +1159,7 @@ var Sortables = new Class({
|
||||
if (
|
||||
!this.idle ||
|
||||
event.rightClick ||
|
||||
['button', 'input', 'a'].contains(event.target.get('tag'))
|
||||
['button', 'input', 'a', 'textarea'].contains(event.target.get('tag'))
|
||||
) return;
|
||||
|
||||
this.idle = false;
|
||||
@@ -1640,8 +1270,7 @@ Request.JSONP = new Class({
|
||||
|
||||
Implements: [Chain, Events, Options],
|
||||
|
||||
options: {
|
||||
/*
|
||||
options: {/*
|
||||
onRequest: function(src, scriptElement){},
|
||||
onComplete: function(data){},
|
||||
onSuccess: function(data){},
|
||||
@@ -1708,7 +1337,8 @@ Request.JSONP = new Class({
|
||||
},
|
||||
|
||||
getScript: function(src){
|
||||
if (!this.script) this.script = new Element('script[type=text/javascript]', {
|
||||
if (!this.script) this.script = new Element('script', {
|
||||
type: 'text/javascript',
|
||||
async: true,
|
||||
src: src
|
||||
});
|
||||
@@ -1716,7 +1346,7 @@ Request.JSONP = new Class({
|
||||
},
|
||||
|
||||
success: function(args, index){
|
||||
if (!this.running) return false;
|
||||
if (!this.running) return;
|
||||
this.clear()
|
||||
.fireEvent('complete', args).fireEvent('success', args)
|
||||
.callChain();
|
||||
@@ -1835,10 +1465,10 @@ Class.refactor = function(original, refactors){
|
||||
|
||||
Object.each(refactors, function(item, name){
|
||||
var origin = original.prototype[name];
|
||||
if (origin && origin.$origin) origin = origin.$origin;
|
||||
origin = (origin && origin.$origin) || origin || function(){};
|
||||
original.implement(name, (typeof item == 'function') ? function(){
|
||||
var old = this.previous;
|
||||
this.previous = origin || function(){};
|
||||
this.previous = origin;
|
||||
var value = item.apply(this, arguments);
|
||||
this.previous = old;
|
||||
return value;
|
||||
@@ -1875,7 +1505,7 @@ provides: [Class.Binds]
|
||||
|
||||
Class.Mutators.Binds = function(binds){
|
||||
if (!this.prototype.initialize) this.implement('initialize', function(){});
|
||||
return binds;
|
||||
return Array.from(binds).concat(this.prototype.Binds || []);
|
||||
};
|
||||
|
||||
Class.Mutators.initialize = function(initialize){
|
||||
@@ -2055,7 +1685,7 @@ Element.implement({
|
||||
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
})();
|
||||
|
||||
|
||||
/*
|
||||
@@ -2071,219 +1701,228 @@ license: MIT-style license
|
||||
|
||||
authors:
|
||||
- Aaron Newton
|
||||
- Jacob Thornton
|
||||
|
||||
requires:
|
||||
- Core/Options
|
||||
- Core/Element.Dimensions
|
||||
- /Element.Measure
|
||||
- Element.Measure
|
||||
|
||||
provides: [Element.Position]
|
||||
|
||||
...
|
||||
*/
|
||||
|
||||
(function(){
|
||||
(function(original){
|
||||
|
||||
var original = Element.prototype.position;
|
||||
var local = Element.Position = {
|
||||
|
||||
options: {/*
|
||||
edge: false,
|
||||
returnPos: false,
|
||||
minimum: {x: 0, y: 0},
|
||||
maximum: {x: 0, y: 0},
|
||||
relFixedPosition: false,
|
||||
ignoreMargins: false,
|
||||
ignoreScroll: false,
|
||||
allowNegative: false,*/
|
||||
relativeTo: document.body,
|
||||
position: {
|
||||
x: 'center', //left, center, right
|
||||
y: 'center' //top, center, bottom
|
||||
},
|
||||
offset: {x: 0, y: 0}
|
||||
},
|
||||
|
||||
getOptions: function(element, options){
|
||||
options = Object.merge({}, local.options, options);
|
||||
local.setPositionOption(options);
|
||||
local.setEdgeOption(options);
|
||||
local.setOffsetOption(element, options);
|
||||
local.setDimensionsOption(element, options);
|
||||
return options;
|
||||
},
|
||||
|
||||
setPositionOption: function(options){
|
||||
options.position = local.getCoordinateFromValue(options.position);
|
||||
},
|
||||
|
||||
setEdgeOption: function(options){
|
||||
var edgeOption = local.getCoordinateFromValue(options.edge);
|
||||
options.edge = edgeOption ? edgeOption :
|
||||
(options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
|
||||
{x: 'left', y: 'top'};
|
||||
},
|
||||
|
||||
setOffsetOption: function(element, options){
|
||||
var parentOffset = {x: 0, y: 0},
|
||||
offsetParent = element.measure(function(){
|
||||
return document.id(this.getOffsetParent());
|
||||
}),
|
||||
parentScroll = offsetParent.getScroll();
|
||||
|
||||
if (!offsetParent || offsetParent == element.getDocument().body) return;
|
||||
parentOffset = offsetParent.measure(function(){
|
||||
var position = this.getPosition();
|
||||
if (this.getStyle('position') == 'fixed'){
|
||||
var scroll = window.getScroll();
|
||||
position.x += scroll.x;
|
||||
position.y += scroll.y;
|
||||
}
|
||||
return position;
|
||||
});
|
||||
|
||||
options.offset = {
|
||||
parentPositioned: offsetParent != document.id(options.relativeTo),
|
||||
x: options.offset.x - parentOffset.x + parentScroll.x,
|
||||
y: options.offset.y - parentOffset.y + parentScroll.y
|
||||
};
|
||||
},
|
||||
|
||||
setDimensionsOption: function(element, options){
|
||||
options.dimensions = element.getDimensions({
|
||||
computeSize: true,
|
||||
styles: ['padding', 'border', 'margin']
|
||||
});
|
||||
},
|
||||
|
||||
getPosition: function(element, options){
|
||||
var position = {};
|
||||
options = local.getOptions(element, options);
|
||||
var relativeTo = document.id(options.relativeTo) || document.body;
|
||||
|
||||
local.setPositionCoordinates(options, position, relativeTo);
|
||||
if (options.edge) local.toEdge(position, options);
|
||||
|
||||
var offset = options.offset;
|
||||
position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
|
||||
position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
|
||||
|
||||
local.toMinMax(position, options);
|
||||
|
||||
if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
|
||||
if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
|
||||
if (options.ignoreMargins) local.toIgnoreMargins(position, options);
|
||||
|
||||
position.left = Math.ceil(position.left);
|
||||
position.top = Math.ceil(position.top);
|
||||
delete position.x;
|
||||
delete position.y;
|
||||
|
||||
return position;
|
||||
},
|
||||
|
||||
setPositionCoordinates: function(options, position, relativeTo){
|
||||
var offsetY = options.offset.y,
|
||||
offsetX = options.offset.x,
|
||||
calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
|
||||
top = calc.y,
|
||||
left = calc.x,
|
||||
winSize = window.getSize();
|
||||
|
||||
switch(options.position.x){
|
||||
case 'left': position.x = left + offsetX; break;
|
||||
case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
|
||||
default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
|
||||
}
|
||||
|
||||
switch(options.position.y){
|
||||
case 'top': position.y = top + offsetY; break;
|
||||
case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
|
||||
default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
|
||||
}
|
||||
},
|
||||
|
||||
toMinMax: function(position, options){
|
||||
var xy = {left: 'x', top: 'y'}, value;
|
||||
['minimum', 'maximum'].each(function(minmax){
|
||||
['left', 'top'].each(function(lr){
|
||||
value = options[minmax] ? options[minmax][xy[lr]] : null;
|
||||
if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
toRelFixedPosition: function(relativeTo, position){
|
||||
var winScroll = window.getScroll();
|
||||
position.top += winScroll.y;
|
||||
position.left += winScroll.x;
|
||||
},
|
||||
|
||||
toIgnoreScroll: function(relativeTo, position){
|
||||
var relScroll = relativeTo.getScroll();
|
||||
position.top -= relScroll.y;
|
||||
position.left -= relScroll.x;
|
||||
},
|
||||
|
||||
toIgnoreMargins: function(position, options){
|
||||
position.left += options.edge.x == 'right'
|
||||
? options.dimensions['margin-right']
|
||||
: (options.edge.x != 'center'
|
||||
? -options.dimensions['margin-left']
|
||||
: -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
|
||||
|
||||
position.top += options.edge.y == 'bottom'
|
||||
? options.dimensions['margin-bottom']
|
||||
: (options.edge.y != 'center'
|
||||
? -options.dimensions['margin-top']
|
||||
: -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
|
||||
},
|
||||
|
||||
toEdge: function(position, options){
|
||||
var edgeOffset = {},
|
||||
dimensions = options.dimensions,
|
||||
edge = options.edge;
|
||||
|
||||
switch(edge.x){
|
||||
case 'left': edgeOffset.x = 0; break;
|
||||
case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
|
||||
// center
|
||||
default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
|
||||
}
|
||||
|
||||
switch(edge.y){
|
||||
case 'top': edgeOffset.y = 0; break;
|
||||
case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
|
||||
// center
|
||||
default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
|
||||
}
|
||||
|
||||
position.x += edgeOffset.x;
|
||||
position.y += edgeOffset.y;
|
||||
},
|
||||
|
||||
getCoordinateFromValue: function(option){
|
||||
if (typeOf(option) != 'string') return option;
|
||||
option = option.toLowerCase();
|
||||
|
||||
return {
|
||||
x: option.test('left') ? 'left'
|
||||
: (option.test('right') ? 'right' : 'center'),
|
||||
y: option.test(/upper|top/) ? 'top'
|
||||
: (option.test('bottom') ? 'bottom' : 'center')
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Element.implement({
|
||||
|
||||
position: function(options){
|
||||
//call original position if the options are x/y values
|
||||
if (options && (options.x != null || options.y != null)){
|
||||
return original ? original.apply(this, arguments) : this;
|
||||
return (original ? original.apply(this, arguments) : this);
|
||||
}
|
||||
var position = this.setStyle('position', 'absolute').calculatePosition(options);
|
||||
return (options && options.returnPos) ? position : this.setStyles(position);
|
||||
},
|
||||
|
||||
Object.each(options || {}, function(v, k){
|
||||
if (v == null) delete options[k];
|
||||
});
|
||||
|
||||
options = Object.merge({
|
||||
// minimum: { x: 0, y: 0 },
|
||||
// maximum: { x: 0, y: 0},
|
||||
relativeTo: document.body,
|
||||
position: {
|
||||
x: 'center', //left, center, right
|
||||
y: 'center' //top, center, bottom
|
||||
},
|
||||
offset: {x: 0, y: 0}/*,
|
||||
edge: false,
|
||||
returnPos: false,
|
||||
relFixedPosition: false,
|
||||
ignoreMargins: false,
|
||||
ignoreScroll: false,
|
||||
allowNegative: false*/
|
||||
}, options);
|
||||
|
||||
//compute the offset of the parent positioned element if this element is in one
|
||||
var parentOffset = {x: 0, y: 0},
|
||||
parentPositioned = false;
|
||||
|
||||
/* dollar around getOffsetParent should not be necessary, but as it does not return
|
||||
* a mootools extended element in IE, an error occurs on the call to expose. See:
|
||||
* http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
|
||||
var offsetParent = this.measure(function(){
|
||||
return document.id(this.getOffsetParent());
|
||||
});
|
||||
if (offsetParent && offsetParent != this.getDocument().body){
|
||||
parentOffset = offsetParent.measure(function(){
|
||||
return this.getPosition();
|
||||
});
|
||||
parentPositioned = offsetParent != document.id(options.relativeTo);
|
||||
options.offset.x = options.offset.x - parentOffset.x;
|
||||
options.offset.y = options.offset.y - parentOffset.y;
|
||||
}
|
||||
|
||||
//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
|
||||
//topRight, topLeft, centerTop, centerBottom, center
|
||||
var fixValue = function(option){
|
||||
if (typeOf(option) != 'string') return option;
|
||||
option = option.toLowerCase();
|
||||
var val = {};
|
||||
|
||||
if (option.test('left')){
|
||||
val.x = 'left';
|
||||
} else if (option.test('right')){
|
||||
val.x = 'right';
|
||||
} else {
|
||||
val.x = 'center';
|
||||
}
|
||||
|
||||
if (option.test('upper') || option.test('top')){
|
||||
val.y = 'top';
|
||||
} else if (option.test('bottom')){
|
||||
val.y = 'bottom';
|
||||
} else {
|
||||
val.y = 'center';
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
options.edge = fixValue(options.edge);
|
||||
options.position = fixValue(options.position);
|
||||
if (!options.edge){
|
||||
if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
|
||||
else options.edge = {x:'left', y:'top'};
|
||||
}
|
||||
|
||||
this.setStyle('position', 'absolute');
|
||||
var rel = document.id(options.relativeTo) || document.body,
|
||||
calc = rel == document.body ? window.getScroll() : rel.getPosition(),
|
||||
top = calc.y, left = calc.x;
|
||||
|
||||
var dim = this.getDimensions({
|
||||
computeSize: true,
|
||||
styles:['padding', 'border','margin']
|
||||
});
|
||||
|
||||
var pos = {},
|
||||
prefY = options.offset.y,
|
||||
prefX = options.offset.x,
|
||||
winSize = window.getSize();
|
||||
|
||||
switch (options.position.x){
|
||||
case 'left':
|
||||
pos.x = left + prefX;
|
||||
break;
|
||||
case 'right':
|
||||
pos.x = left + prefX + rel.offsetWidth;
|
||||
break;
|
||||
default: //center
|
||||
pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (options.position.y){
|
||||
case 'top':
|
||||
pos.y = top + prefY;
|
||||
break;
|
||||
case 'bottom':
|
||||
pos.y = top + prefY + rel.offsetHeight;
|
||||
break;
|
||||
default: //center
|
||||
pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
|
||||
break;
|
||||
}
|
||||
|
||||
if (options.edge){
|
||||
var edgeOffset = {};
|
||||
|
||||
switch (options.edge.x){
|
||||
case 'left':
|
||||
edgeOffset.x = 0;
|
||||
break;
|
||||
case 'right':
|
||||
edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
|
||||
break;
|
||||
default: //center
|
||||
edgeOffset.x = -(dim.totalWidth/2);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (options.edge.y){
|
||||
case 'top':
|
||||
edgeOffset.y = 0;
|
||||
break;
|
||||
case 'bottom':
|
||||
edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
|
||||
break;
|
||||
default: //center
|
||||
edgeOffset.y = -(dim.totalHeight/2);
|
||||
break;
|
||||
}
|
||||
|
||||
pos.x += edgeOffset.x;
|
||||
pos.y += edgeOffset.y;
|
||||
}
|
||||
|
||||
pos = {
|
||||
left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
|
||||
top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
|
||||
};
|
||||
|
||||
var xy = {left: 'x', top: 'y'};
|
||||
|
||||
['minimum', 'maximum'].each(function(minmax){
|
||||
['left', 'top'].each(function(lr){
|
||||
var val = options[minmax] ? options[minmax][xy[lr]] : null;
|
||||
if (val != null && ((minmax == 'minimum') ? pos[lr] < val : pos[lr] > val)) pos[lr] = val;
|
||||
});
|
||||
});
|
||||
|
||||
if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
|
||||
var winScroll = window.getScroll();
|
||||
pos.top+= winScroll.y;
|
||||
pos.left+= winScroll.x;
|
||||
}
|
||||
if (options.ignoreScroll){
|
||||
var relScroll = rel.getScroll();
|
||||
pos.top -= relScroll.y;
|
||||
pos.left -= relScroll.x;
|
||||
}
|
||||
|
||||
if (options.ignoreMargins){
|
||||
pos.left += (
|
||||
options.edge.x == 'right' ? dim['margin-right'] :
|
||||
options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) :
|
||||
- dim['margin-left']
|
||||
);
|
||||
pos.top += (
|
||||
options.edge.y == 'bottom' ? dim['margin-bottom'] :
|
||||
options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) :
|
||||
- dim['margin-top']
|
||||
);
|
||||
}
|
||||
|
||||
pos.left = Math.ceil(pos.left);
|
||||
pos.top = Math.ceil(pos.top);
|
||||
if (options.returnPos) return pos;
|
||||
else this.setStyles(pos);
|
||||
return this;
|
||||
calculatePosition: function(options){
|
||||
return local.getPosition(this, options);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
})(Element.prototype.position);
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
var Question = new Class( {
|
||||
|
||||
initialize : function(question, hint, answers) {
|
||||
var self = this
|
||||
|
||||
self.question = question
|
||||
self.hint = hint
|
||||
self.answers = answers
|
||||
|
||||
self.createQuestion()
|
||||
self.answers.each(function(answer) {
|
||||
self.createAnswer(answer)
|
||||
})
|
||||
self.createMask()
|
||||
|
||||
},
|
||||
|
||||
createMask : function() {
|
||||
var self = this
|
||||
|
||||
$(document.body).mask( {
|
||||
'hideOnClick' : true,
|
||||
'destroyOnHide' : true,
|
||||
'onHide' : function() {
|
||||
self.container.destroy();
|
||||
}
|
||||
}).show();
|
||||
},
|
||||
|
||||
createQuestion : function() {
|
||||
|
||||
this.container = new Element('div', {
|
||||
'class' : 'question'
|
||||
}).adopt(
|
||||
new Element('h3', {
|
||||
'html': this.question
|
||||
}),
|
||||
new Element('div.hint', {
|
||||
'html': this.hint
|
||||
})
|
||||
).inject(document.body)
|
||||
|
||||
this.container.position( {
|
||||
'position' : 'center'
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
createAnswer : function(options) {
|
||||
var self = this
|
||||
|
||||
var answer = new Element('a', Object.merge(options, {
|
||||
'class' : 'answer button '+(options['class'] || '')+(options['cancel'] ? ' cancel' : '')
|
||||
})).inject(this.container)
|
||||
|
||||
if (options.cancel) {
|
||||
answer.addEvent('click', self.close.bind(self))
|
||||
}
|
||||
else if (options.request) {
|
||||
answer.addEvent('click', function(e){
|
||||
e.stop();
|
||||
new Request(Object.merge(options, {
|
||||
'url': options.href,
|
||||
'onComplete': function() {
|
||||
(options.onComplete || function(){})()
|
||||
self.close();
|
||||
}
|
||||
})).send();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
close : function() {
|
||||
$(document.body).get('mask').destroy();
|
||||
},
|
||||
|
||||
toElement : function() {
|
||||
return this.container
|
||||
}
|
||||
|
||||
})
|
||||
@@ -82,7 +82,9 @@ Page.Settings = new Class({
|
||||
var self = this;
|
||||
|
||||
var c = self.advanced_toggle.checked ? 'addClass' : 'removeClass';
|
||||
self.el[c]('show_advanced')
|
||||
self.el[c]('show_advanced');
|
||||
|
||||
Cookie.write('advanced_toggle_checked', +self.advanced_toggle.checked, {'duration': 365});
|
||||
},
|
||||
|
||||
create: function(json){
|
||||
@@ -96,6 +98,7 @@ Page.Settings = new Class({
|
||||
'text': 'Show advanced settings'
|
||||
}),
|
||||
self.advanced_toggle = new Element('input[type=checkbox].inlay', {
|
||||
'checked': +Cookie.read('advanced_toggle_checked'),
|
||||
'events': {
|
||||
'change': self.showAdvanced.bind(self)
|
||||
}
|
||||
@@ -103,10 +106,9 @@ Page.Settings = new Class({
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
new Form.Check(self.advanced_toggle, {
|
||||
'onChange': self.showAdvanced.bind(self)
|
||||
})
|
||||
self.showAdvanced();
|
||||
|
||||
new Form.Check(self.advanced_toggle)
|
||||
|
||||
// Create tabs
|
||||
Object.each(self.tabs, function(tab, tab_name){
|
||||
@@ -132,7 +134,7 @@ Page.Settings = new Class({
|
||||
var class_name = (option.type || 'string').capitalize();
|
||||
var input = new Option[class_name](self, section_name, option.name, option);
|
||||
input.inject(self.tabs[group.tab].groups[group.name]);
|
||||
input.fireEvent('injected')
|
||||
input.fireEvent('injected');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -182,7 +184,7 @@ Page.Settings = new Class({
|
||||
'text': (group.label || group.name).capitalize()
|
||||
}).adopt(
|
||||
new Element('span.hint', {
|
||||
'html': group.description
|
||||
'html': group.description || ''
|
||||
})
|
||||
)
|
||||
)
|
||||
@@ -397,9 +399,7 @@ Option.Checkbox = new Class({
|
||||
})
|
||||
);
|
||||
|
||||
new Form.Check(self.input, {
|
||||
'onChange': self.changed.bind(self)
|
||||
});
|
||||
new Form.Check(self.input);
|
||||
|
||||
},
|
||||
|
||||
@@ -435,16 +435,11 @@ Option.Enabler = new Class({
|
||||
self.input = new Element('input.inlay', {
|
||||
'type': 'checkbox',
|
||||
'checked': self.getSettingValue(),
|
||||
'id': 'r-'+randomString(),
|
||||
'events': {
|
||||
'change': self.checkState.bind(self)
|
||||
}
|
||||
'id': 'r-'+randomString()
|
||||
})
|
||||
);
|
||||
|
||||
new Form.Check(self.input, {
|
||||
'onChange': self.changed.bind(self)
|
||||
});
|
||||
new Form.Check(self.input);
|
||||
},
|
||||
|
||||
changed: function(){
|
||||
@@ -479,63 +474,86 @@ Option.Directory = new Class({
|
||||
type: 'span',
|
||||
browser: '',
|
||||
save_on_change: false,
|
||||
use_cache: false,
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
self.el.adopt(
|
||||
self.createLabel(),
|
||||
self.input = new Element('span.directory', {
|
||||
'text': self.getSettingValue(),
|
||||
new Element('span.directory.inlay', {
|
||||
'events': {
|
||||
'click': self.showBrowser.bind(self)
|
||||
}
|
||||
})
|
||||
}).adopt(
|
||||
self.input = new Element('span', {
|
||||
'text': self.getSettingValue()
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
self.cached = {};
|
||||
},
|
||||
|
||||
selectDirectory: function(e, el){
|
||||
selectDirectory: function(dir){
|
||||
var self = this;
|
||||
|
||||
self.input.set('text', el.get('data-value'));
|
||||
self.input.set('text', dir);
|
||||
|
||||
self.getDirs()
|
||||
self.fireEvent('change')
|
||||
},
|
||||
|
||||
previousDirectory: function(e){
|
||||
var self = this;
|
||||
|
||||
self.selectDirectory(null, self.back_button)
|
||||
self.selectDirectory(self.getParentDir())
|
||||
},
|
||||
|
||||
showBrowser: function(){
|
||||
var self = this;
|
||||
|
||||
if(!self.browser)
|
||||
if(!self.browser){
|
||||
self.browser = new Element('div.directory_list').adopt(
|
||||
new Element('div.pointer'),
|
||||
new Element('div.actions').adopt(
|
||||
self.back_button = new Element('a.button.back', {
|
||||
'text': '',
|
||||
self.back_button = new Element('a.back', {
|
||||
'html': '',
|
||||
'events': {
|
||||
'click': self.previousDirectory.bind(self)
|
||||
}
|
||||
}),
|
||||
new Element('label', {
|
||||
'text': 'Show hidden files'
|
||||
'text': 'Hidden folders'
|
||||
}).adopt(
|
||||
self.show_hidden = new Element('input[type=checkbox].inlay')
|
||||
self.show_hidden = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': self.getDirs.bind(self)
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
self.dir_list = new Element('ul', {
|
||||
'events': {
|
||||
'click:relay(li)': self.selectDirectory.bind(self)
|
||||
'click:relay(li)': function(e, el){
|
||||
(e).stop();
|
||||
self.selectDirectory(el.get('data-value'))
|
||||
},
|
||||
'mousewheel': function(e){
|
||||
(e).stopPropagation();
|
||||
}
|
||||
}
|
||||
}),
|
||||
new Element('div.actions').adopt(
|
||||
new Element('a.button.cancel', {
|
||||
new Element('a.clear.button', {
|
||||
'text': 'Clear',
|
||||
'events': {
|
||||
'click': function(e){
|
||||
self.input.set('text', '');
|
||||
self.hideBrowser(e, true);
|
||||
}
|
||||
}
|
||||
}),
|
||||
new Element('a.cancel', {
|
||||
'text': 'Cancel',
|
||||
'events': {
|
||||
'click': self.hideBrowser.bind(self)
|
||||
@@ -547,21 +565,32 @@ Option.Directory = new Class({
|
||||
self.save_button = new Element('a.button.save', {
|
||||
'text': 'Save',
|
||||
'events': {
|
||||
'click': self.hideBrowser.bind(self, true)
|
||||
'click': function(e){
|
||||
self.hideBrowser(e, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
).inject(self.input, 'after')
|
||||
).inject(self.el)
|
||||
|
||||
new Form.Check(self.show_hidden);
|
||||
}
|
||||
|
||||
self.initial_directory = self.input.get('text');
|
||||
|
||||
self.getDirs()
|
||||
self.browser.show()
|
||||
self.el.addEvent('outerClick', self.hideBrowser.bind(self))
|
||||
},
|
||||
|
||||
hideBrowser: function(save){
|
||||
hideBrowser: function(e, save){
|
||||
var self = this;
|
||||
(e).stop();
|
||||
|
||||
if(save) self.save()
|
||||
if(save)
|
||||
self.save()
|
||||
else
|
||||
self.input.set('text', self.initial_directory);
|
||||
|
||||
self.browser.hide()
|
||||
self.el.removeEvent('outerClick', self.hideBrowser.bind(self))
|
||||
@@ -571,41 +600,43 @@ Option.Directory = new Class({
|
||||
fillBrowser: function(json){
|
||||
var self = this;
|
||||
|
||||
var c = self.getParentDir();
|
||||
var v = self.input.get('text');
|
||||
var previous_dir = self.getParentDir(c.substring(0, c.length-1));
|
||||
var previous_dir = self.getParentDir();
|
||||
|
||||
if(previous_dir){
|
||||
if(previous_dir != v){
|
||||
self.back_button.set('data-value', previous_dir)
|
||||
self.back_button.set('text', self.getCurrentDirname(previous_dir))
|
||||
self.back_button.set('html', '« '+self.getCurrentDirname(previous_dir))
|
||||
self.back_button.show()
|
||||
}
|
||||
else {
|
||||
self.back_button.hide()
|
||||
}
|
||||
|
||||
if(!json)
|
||||
json = self.cached[c];
|
||||
else
|
||||
self.cached[c] = json;
|
||||
if(self.use_cache)
|
||||
if(!json)
|
||||
json = self.cached[v];
|
||||
else
|
||||
self.cached[v] = json;
|
||||
|
||||
self.dir_list.empty();
|
||||
json.dirs.each(function(dir){
|
||||
if(dir.indexOf(v) != -1){
|
||||
new Element('li', {
|
||||
'data-value': dir,
|
||||
'text': self.getCurrentDirname(dir)
|
||||
}).inject(self.dir_list)
|
||||
}
|
||||
})
|
||||
setTimeout(function(){
|
||||
self.dir_list.empty();
|
||||
json.dirs.each(function(dir){
|
||||
if(dir.indexOf(v) != -1){
|
||||
new Element('li', {
|
||||
'data-value': dir,
|
||||
'text': self.getCurrentDirname(dir)
|
||||
}).inject(self.dir_list)
|
||||
}
|
||||
});
|
||||
}, 50);
|
||||
},
|
||||
|
||||
getDirs: function(){
|
||||
var self = this;
|
||||
|
||||
var c = self.getParentDir();
|
||||
var c = self.input.get('text');
|
||||
|
||||
if(self.cached[c]){
|
||||
if(self.cached[c] && self.use_cache){
|
||||
self.fillBrowser()
|
||||
}
|
||||
else {
|
||||
@@ -625,7 +656,8 @@ Option.Directory = new Class({
|
||||
var v = dir || self.input.get('text');
|
||||
var sep = Api.getOption('path_sep');
|
||||
var dirs = v.split(sep);
|
||||
dirs.pop();
|
||||
if(dirs.pop() == '')
|
||||
dirs.pop();
|
||||
|
||||
return dirs.join(sep) + sep
|
||||
},
|
||||
|
||||
@@ -8,12 +8,12 @@ Page.Wanted = new Class({
|
||||
indexAction: function(param){
|
||||
var self = this;
|
||||
|
||||
if(!self.list){
|
||||
|
||||
if(!self.wanted){
|
||||
|
||||
// Wanted movies
|
||||
self.wanted = new MovieList({
|
||||
'status': 'active',
|
||||
'actions': WantedActions
|
||||
'actions': MovieActions
|
||||
});
|
||||
$(self.wanted).inject(self.el);
|
||||
App.addEvent('library.update', self.wanted.update.bind(self.wanted));
|
||||
@@ -23,30 +23,32 @@ Page.Wanted = new Class({
|
||||
|
||||
});
|
||||
|
||||
var WantedActions = {
|
||||
var MovieActions = {};
|
||||
|
||||
MovieActions.Wanted = {
|
||||
'IMBD': IMDBAction
|
||||
//,'releases': ReleaseAction
|
||||
,'releases': ReleaseAction
|
||||
|
||||
,'Edit': new Class({
|
||||
|
||||
Extends: MovieAction,
|
||||
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
|
||||
self.el = new Element('a.edit', {
|
||||
'title': 'Refresh the movie info and do a forced search',
|
||||
'events': {
|
||||
'click': self.editMovie.bind(self)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
editMovie: function(e){
|
||||
var self = this;
|
||||
(e).stop();
|
||||
|
||||
|
||||
if(!self.options_container){
|
||||
self.options_container = new Element('div.options').adopt(
|
||||
$(self.movie.thumbnail).clone(),
|
||||
@@ -69,29 +71,29 @@ var WantedActions = {
|
||||
})
|
||||
)
|
||||
).inject(self.movie, 'top');
|
||||
|
||||
|
||||
Array.each(self.movie.data.library.titles, function(alt){
|
||||
new Element('option', {
|
||||
'text': alt.title
|
||||
}).inject(self.title_select);
|
||||
});
|
||||
|
||||
Object.each(Quality.profiles, function(profile){
|
||||
|
||||
Object.each(Quality.getActiveProfiles(), function(profile){
|
||||
new Element('option', {
|
||||
'value': profile.id ? profile.id : profile.data.id,
|
||||
'text': profile.label ? profile.label : profile.data.label
|
||||
}).inject(self.profile_select);
|
||||
self.profile_select.set('value', self.movie.profile.get('id'));
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
self.movie.slide('in');
|
||||
},
|
||||
|
||||
|
||||
save: function(e){
|
||||
(e).stop();
|
||||
var self = this;
|
||||
|
||||
|
||||
Api.request('movie.edit', {
|
||||
'data': {
|
||||
'id': self.movie.get('id'),
|
||||
@@ -105,63 +107,63 @@ var WantedActions = {
|
||||
self.movie.title.set('text', self.title_select.getSelected()[0].get('text'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
self.movie.slide('out');
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
,'Refresh': new Class({
|
||||
|
||||
Extends: MovieAction,
|
||||
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
|
||||
self.el = new Element('a.refresh', {
|
||||
'title': 'Refresh the movie info and do a forced search',
|
||||
'events': {
|
||||
'click': self.doSearch.bind(self)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
doSearch: function(e){
|
||||
var self = this;
|
||||
(e).stop();
|
||||
|
||||
|
||||
Api.request('movie.refresh', {
|
||||
'data': {
|
||||
'id': self.movie.get('id')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
,'Delete': new Class({
|
||||
|
||||
Extends: MovieAction,
|
||||
|
||||
|
||||
Implements: [Chain],
|
||||
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
|
||||
self.el = new Element('a.delete', {
|
||||
'title': 'Remove the movie from your wanted list',
|
||||
'events': {
|
||||
'click': self.showConfirm.bind(self)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
showConfirm: function(e){
|
||||
var self = this;
|
||||
(e).stop();
|
||||
|
||||
|
||||
if(!self.delete_container){
|
||||
self.delete_container = new Element('div.delete_container', {
|
||||
'styles': {
|
||||
@@ -185,27 +187,26 @@ var WantedActions = {
|
||||
})
|
||||
).inject(self.movie, 'top');
|
||||
}
|
||||
|
||||
|
||||
self.movie.slide('in');
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
hideConfirm: function(e){
|
||||
var self = this;
|
||||
(e).stop();
|
||||
|
||||
|
||||
self.movie.slide('out');
|
||||
},
|
||||
|
||||
|
||||
del: function(e){
|
||||
(e).stop();
|
||||
var self = this;
|
||||
|
||||
|
||||
var movie = $(self.movie);
|
||||
|
||||
|
||||
self.chain(
|
||||
function(){
|
||||
$(movie).mask().addClass('loading');
|
||||
self.callChain();
|
||||
},
|
||||
function(){
|
||||
@@ -224,16 +225,15 @@ var WantedActions = {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
self.callChain();
|
||||
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
var SnatchedActions = {
|
||||
MovieActions.Snatched = {
|
||||
'IMBD': IMDBAction
|
||||
,'Releases': ReleaseAction
|
||||
,'Delete': WantedActions.Delete
|
||||
,'Delete': MovieActions.Wanted.Delete
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
/* @override http://localhost:5000/static/style/main.css */
|
||||
/* @override
|
||||
http://localhost:5000/static/style/main.css
|
||||
http://192.168.1.20:5000/static/style/main.css
|
||||
http://127.0.0.1:5000/static/style/main.css
|
||||
*/
|
||||
|
||||
html {
|
||||
color: #fff;
|
||||
@@ -35,15 +39,24 @@ input, textarea {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
|
||||
}
|
||||
|
||||
input:-moz-placeholder, textarea:-moz-placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder, ::-webkit-textarea-placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
a img {
|
||||
border:none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration:none;
|
||||
color: #fff;
|
||||
color: #ebfcbc;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
a:hover { color: #f3f3f3; }
|
||||
|
||||
@@ -125,10 +138,18 @@ form {
|
||||
}
|
||||
|
||||
/*** Icons ***/
|
||||
.icon.delete {
|
||||
background: url('../images/delete.png') no-repeat;
|
||||
.icon {
|
||||
display: inline-block;
|
||||
background: center no-repeat;
|
||||
}
|
||||
.icon.delete { background-image: url('../images/icon.delete.png'); }
|
||||
.icon.download { background-image: url('../images/icon.download.png'); }
|
||||
.icon.edit { background-image: url('../images/icon.edit.png'); }
|
||||
.icon.check { background-image: url('../images/icon.check.png'); }
|
||||
.icon.folder { background-image: url('../images/icon.folder.png'); }
|
||||
.icon.imdb { background-image: url('../images/icon.imdb.png'); }
|
||||
.icon.refresh { background-image: url('../images/icon.refresh.png'); }
|
||||
.icon.rating { background-image: url('../images/icon.rating.png'); }
|
||||
|
||||
/*** Navigation ***/
|
||||
.header {
|
||||
@@ -191,6 +212,27 @@ form {
|
||||
.header .navigation li a:hover, .header .navigation li a:active {
|
||||
color: #b1d8dc;
|
||||
}
|
||||
|
||||
.header .message.update {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
top: -70px;
|
||||
padding: 15px 0 20px;
|
||||
background: #ff6134;
|
||||
font-size: 26px;
|
||||
|
||||
border-radius: 0 0 5px 5px;
|
||||
-moz-border-radius: 0 0 5px 5px;
|
||||
-webkit-border-radius: 0 0 5px 5px;
|
||||
|
||||
box-shadow: 0 2px 1px rgba(0,0,0, 0.3);
|
||||
-moz-box-shadow: 0 2px 1px rgba(0,0,0, 0.3);
|
||||
-webkit-box-shadow: 0 2px 1px rgba(0,0,0, 0.3);
|
||||
}
|
||||
|
||||
.header .message a {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/*** Global Styles ***/
|
||||
.check {
|
||||
@@ -333,3 +375,59 @@ form {
|
||||
rgb(73,83,98) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.mask {
|
||||
background: rgba(0,0,0, 0.7);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.question {
|
||||
display: block;
|
||||
width: 600px;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
position:fixed;
|
||||
z-index:101;
|
||||
text-align: center;
|
||||
background: #5c697b;
|
||||
|
||||
border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
|
||||
box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||
-moz-box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||
-webkit-box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||
}
|
||||
|
||||
.question h3 {
|
||||
font-size: 25px;
|
||||
padding: 0;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.question .hint {
|
||||
font-size: 14px;
|
||||
color: #ccc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.question .answer {
|
||||
font-size: 17px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
margin: 5px 1%;
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
}
|
||||
.question .answer:hover {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.question .answer.delete {
|
||||
background-color: #a82f12;
|
||||
}
|
||||
.question .answer.cancel {
|
||||
margin-top: 20px;
|
||||
background-color: #4c5766;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
/* @override http://localhost:5000/static/style/page/settings.css */
|
||||
/* @override
|
||||
http://localhost:5000/static/style/page/settings.css
|
||||
http://192.168.1.20:5000/static/style/page/settings.css
|
||||
*/
|
||||
|
||||
.page.settings {
|
||||
overflow: hidden;
|
||||
.page.settings:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
line-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.page.settings .tabs {
|
||||
@@ -30,6 +38,7 @@
|
||||
.page.settings .tabs a {
|
||||
display: block;
|
||||
padding: 11px 15px;
|
||||
color: #fff;
|
||||
}
|
||||
.page.settings .tabs .active a {
|
||||
background: #4e5969;
|
||||
@@ -84,6 +93,7 @@
|
||||
position: relative;
|
||||
margin-bottom: -25px;
|
||||
border: none;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.page.settings .ctrlHolder {
|
||||
@@ -92,9 +102,13 @@
|
||||
font-size: 14px;
|
||||
border: 0;
|
||||
}
|
||||
.page.settings .ctrlHolder.save_success:not(:first-child) {
|
||||
background: url('../../images/icon.check.png') no-repeat 7px center;
|
||||
}
|
||||
.page.settings .ctrlHolder:last-child { border: none; }
|
||||
.page.settings .ctrlHolder:hover { background: rgba(255,255,255,0.05); }
|
||||
.page.settings .ctrlHolder.focused { background: rgba(255,255,255,0.2); }
|
||||
.page.settings .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); }
|
||||
.page.settings .ctrlHolder.focused { background-color: rgba(255,255,255,0.2); }
|
||||
.page.settings .ctrlHolder.focused:first-child, .page.settings .ctrlHolder:first-child{ background-color: transparent; }
|
||||
|
||||
.page.settings .ctrlHolder .formHint {
|
||||
float: right;
|
||||
@@ -135,6 +149,8 @@
|
||||
margin: 0;
|
||||
padding: 6px 0 0;
|
||||
}
|
||||
|
||||
.page.settings .xsmall { width: 20px !important; text-align: center; }
|
||||
|
||||
.page.settings input[type=text], .page.settings input[type=password] {
|
||||
padding: 5px 3px;
|
||||
@@ -165,39 +181,154 @@
|
||||
|
||||
.page.settings .directory {
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
padding: 0 4% 0 4px;
|
||||
font-size: 13px;
|
||||
width: 29.7%;
|
||||
}
|
||||
.page.settings .directory_list {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
margin: 0 0 0 16.5%;
|
||||
background: #282d34;
|
||||
border: 1px solid #1f242b;
|
||||
position: absolute;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||
border-radius:3px;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
width: 26.3%;
|
||||
background-image: url('../../images/icon.folder.gif');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 97% center;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
}
|
||||
.page.settings .directory > span {
|
||||
height: 25px;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page.settings .directory_list {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
width: 360px;
|
||||
margin: -2px 0 20px 60px;
|
||||
background: #5c697b;
|
||||
border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||
-moz-box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||
-webkit-box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||
}
|
||||
|
||||
.page.settings .directory_list .pointer {
|
||||
border-right: 10px solid transparent;
|
||||
border-left: 10px solid transparent;
|
||||
border-bottom: 10px solid #5c697b;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
margin: -9px 0 0 48.5%;
|
||||
}
|
||||
|
||||
.page.settings .directory_list ul {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
width: 92%;
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
margin: 0 4%;
|
||||
}
|
||||
|
||||
.page.settings .directory_list li {
|
||||
padding: 2px 10px;
|
||||
}
|
||||
.page.settings .directory_list li {
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
background: url('../../images/right.arrow.png') no-repeat 98% center;
|
||||
}
|
||||
.page.settings .directory_list li:last-child {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.page.settings .directory_list li:hover {
|
||||
background-color: #515c68;
|
||||
}
|
||||
|
||||
.page.settings .directory_list .actions {
|
||||
clear: both;
|
||||
padding: 10px;
|
||||
background: #414953;
|
||||
padding: 4% 4% 2%;
|
||||
min-height: 25px;
|
||||
}
|
||||
.page.settings .directory_list .actions:first-child { border-bottom: 1px solid #1f242b; }
|
||||
.page.settings .directory_list .actions:last-child { border-top: 1px solid #1f242b; }
|
||||
|
||||
.page.settings .directory_list .actions label {
|
||||
float: right;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
.page.settings .directory_list .actions .inlay {
|
||||
margin: -2px 0 0 7px;
|
||||
}
|
||||
|
||||
.page.settings .directory_list .actions .back {
|
||||
font-weight: bold;
|
||||
width: 160px;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
line-height: 120%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.page.settings .directory_list .actions:last-child {
|
||||
float: right;
|
||||
padding: 4%;
|
||||
}
|
||||
|
||||
.page.settings .directory_list .actions:last-child > span {
|
||||
padding: 0 5px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.page.settings .directory_list .actions:last-child > .clear {
|
||||
left: -90%;
|
||||
position: relative;
|
||||
background-color: #af3128;
|
||||
}
|
||||
|
||||
.page.settings .directory_list .actions:last-child > .cancel {
|
||||
font-weight: bold;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.page.settings .directory_list .actions:last-child > .save {
|
||||
background: #9dc156;
|
||||
}
|
||||
|
||||
.page.settings .section_newznab {
|
||||
|
||||
}
|
||||
|
||||
.page.settings .section_newznab .head {
|
||||
margin: 0 0 0 60px;
|
||||
}
|
||||
.page.settings .section_newznab .head abbr {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px dotted #fff;
|
||||
line-height: 140%;
|
||||
cursor: help;
|
||||
}
|
||||
.page.settings .section_newznab .head abbr.host {
|
||||
margin-right: 197px;
|
||||
}
|
||||
|
||||
.page.settings .section_newznab .ctrlHolder {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.page.settings .section_newznab .ctrlHolder > * {
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.page.settings .section_newznab .ctrlHolder .delete {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
background-position: left center;
|
||||
}
|
||||
|
||||
.page.settings .section_newznab .ctrlHolder.is_empty .delete, .page.settings .section_newznab .ctrlHolder.is_empty .use {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/form_replacement/form_radio.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/form_replacement/form_dropdown.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/form_replacement/form_selectoption.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/question.js') }}"></script>
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/couchpotato.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/history.js') }}"></script>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
Dependencies
|
||||
===========
|
||||
|
||||
Holds all dependencies that are required by CouchPotato.
|
||||
@@ -11,7 +11,10 @@
|
||||
# Source: http://pypi.python.org/pypi/axel
|
||||
# Docs: http://packages.python.org/axel
|
||||
|
||||
import sys, threading, Queue
|
||||
from couchpotato.core.helpers.variable import natcmp
|
||||
import Queue
|
||||
import sys
|
||||
import threading
|
||||
|
||||
class Event(object):
|
||||
"""
|
||||
@@ -100,7 +103,7 @@ class Event(object):
|
||||
self.handlers = {}
|
||||
self.memoize = {}
|
||||
|
||||
def handle(self, handler):
|
||||
def handle(self, handler, priority = 0):
|
||||
""" Registers a handler. The handler can be transmitted together
|
||||
with two arguments as a list or dictionary. The arguments are:
|
||||
|
||||
@@ -118,7 +121,7 @@ class Event(object):
|
||||
event += {'handler':handler, 'memoize':True, 'timeout':1.5}
|
||||
"""
|
||||
handler_, memoize, timeout = self._extract(handler)
|
||||
self.handlers[hash(handler_)] = (handler_, memoize, timeout)
|
||||
self.handlers['%s.%s' % (priority, hash(handler_))] = (handler_, memoize, timeout)
|
||||
return self
|
||||
|
||||
def unhandle(self, handler):
|
||||
@@ -144,7 +147,7 @@ class Event(object):
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
for handler in self.handlers:
|
||||
for handler in sorted(self.handlers.iterkeys(), cmp = natcmp):
|
||||
self.queue.put(handler)
|
||||
|
||||
if self.asynchronous:
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
####
|
||||
# 06/2010 Nic Wolfe <nic@wolfeden.ca>
|
||||
# 02/2006 Will Holcomb <wholcomb@gmail.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
import mimetools, mimetypes
|
||||
import os, sys
|
||||
|
||||
# Controls how sequences are uncoded. If true, elements may be given multiple values by
|
||||
# assigning a sequence.
|
||||
doseq = 1
|
||||
|
||||
class MultipartPostHandler(urllib2.BaseHandler):
|
||||
handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
|
||||
|
||||
def http_request(self, request):
|
||||
data = request.get_data()
|
||||
if data is not None and type(data) != str:
|
||||
v_files = []
|
||||
v_vars = []
|
||||
try:
|
||||
for(key, value) in data.items():
|
||||
if type(value) in (file, list, tuple):
|
||||
v_files.append((key, value))
|
||||
else:
|
||||
v_vars.append((key, value))
|
||||
except TypeError:
|
||||
systype, value, traceback = sys.exc_info()
|
||||
raise TypeError, "not a valid non-string sequence or mapping object", traceback
|
||||
|
||||
if len(v_files) == 0:
|
||||
data = urllib.urlencode(v_vars, doseq)
|
||||
else:
|
||||
boundary, data = MultipartPostHandler.multipart_encode(v_vars, v_files)
|
||||
contenttype = 'multipart/form-data; boundary=%s' % boundary
|
||||
if(request.has_header('Content-Type')
|
||||
and request.get_header('Content-Type').find('multipart/form-data') != 0):
|
||||
print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data')
|
||||
request.add_unredirected_header('Content-Type', contenttype)
|
||||
|
||||
request.add_data(data)
|
||||
return request
|
||||
|
||||
@staticmethod
|
||||
def multipart_encode(vars, files, boundary = None, buffer = None):
|
||||
if boundary is None:
|
||||
boundary = mimetools.choose_boundary()
|
||||
if buffer is None:
|
||||
buffer = ''
|
||||
for(key, value) in vars:
|
||||
buffer += '--%s\r\n' % boundary
|
||||
buffer += 'Content-Disposition: form-data; name="%s"' % key
|
||||
buffer += '\r\n\r\n' + value + '\r\n'
|
||||
for(key, fd) in files:
|
||||
|
||||
# allow them to pass in a file or a tuple with name & data
|
||||
if type(fd) == file:
|
||||
name_in = fd.name
|
||||
fd.seek(0)
|
||||
data_in = fd.read()
|
||||
elif type(fd) in (tuple, list):
|
||||
name_in, data_in = fd
|
||||
|
||||
filename = os.path.basename(name_in)
|
||||
contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
buffer += '--%s\r\n' % boundary
|
||||
buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)
|
||||
buffer += 'Content-Type: %s\r\n' % contenttype
|
||||
# buffer += 'Content-Length: %s\r\n' % file_size
|
||||
buffer += '\r\n' + data_in + '\r\n'
|
||||
buffer += '--%s--\r\n\r\n' % boundary
|
||||
return boundary, buffer
|
||||
|
||||
https_request = http_request
|
||||
@@ -350,7 +350,7 @@ class MovieDb:
|
||||
etree = XmlHandler(url).getEt()
|
||||
lookup_results = SearchResults()
|
||||
for cur_lookup in etree.find("movies").findall("movie"):
|
||||
cur_movie = self._parseSearchResults(cur_lookup)
|
||||
cur_movie = self._parseMovie(cur_lookup)
|
||||
lookup_results.append(cur_movie)
|
||||
return lookup_results
|
||||
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import urllib2
|
||||
|
||||
__author__ = 'Therms'
|
||||
__tmdb_apikey__ = '6d96a9efb4752ed0d126d94e12e52036'
|
||||
|
||||
class XmgException(Exception):
|
||||
pass
|
||||
|
||||
class ApiError(XmgException):
|
||||
pass
|
||||
|
||||
class IdError(XmgException):
|
||||
pass
|
||||
|
||||
class NfoError(XmgException):
|
||||
pass
|
||||
|
||||
class MetaGen():
|
||||
def __init__(self, imdbid, imdbpy = None):
|
||||
''' metagen is used to download metadata for a movie or tv show and then create
|
||||
the necessary files for the media to be imported into XBMC.
|
||||
|
||||
Arguments
|
||||
===========
|
||||
fanart/poster_height/width_min: Sets lowest acceptable image resolution. 0 means
|
||||
disregard. If no fanart available at specified resolution or greater, then
|
||||
we disregard this setting, and download highest resolution that is available.
|
||||
|
||||
name*: In the case of a movie, ideally this should be the full movie name
|
||||
followed by the year of the movie in parentheses. e.g. "The Matrix (1999)".
|
||||
If this is specific enough to generate only one search result then we'll
|
||||
continue. Otherwise, we'll raise IdError.
|
||||
|
||||
Because of the imprecise nature of this method of id, only use it if you
|
||||
don't have the imdb_id or tmdb_id
|
||||
|
||||
imdb_id: Use this argument if you know the imdb id of the show/movie. If
|
||||
this is used, the tmdb_id argument is ignored.
|
||||
|
||||
tmdb_id*: Use this argument if you know the tmdb id of the movie. If this
|
||||
is used, the imdb_id argument is ignored.
|
||||
|
||||
imdbpy: When xmg is used as a library, imdbpy may not be installed
|
||||
system-wide, but included with your application. If this is the case, pass
|
||||
your instance of imdb.IMDb() to metagen, so we can use it.
|
||||
|
||||
* These arguments are not yet supported.
|
||||
|
||||
'''
|
||||
|
||||
|
||||
if imdbid[:2].lower() == 'tt':
|
||||
self.imdbid = imdbid[2:]
|
||||
else:
|
||||
self.imdbid = imdbid
|
||||
|
||||
self.nfo_string = 'http://www.imdb.com/title/' + imdbid + '/'
|
||||
self.tmdb_data = self._get_tmdb_imdb()
|
||||
self._validate_tmdb_json()
|
||||
|
||||
#TODO: Search by movie name
|
||||
#TODO: Search by tmdb_id
|
||||
#TODO: Search by movie hash
|
||||
|
||||
|
||||
def _validate_tmdb_json(self):
|
||||
try:
|
||||
_ = self._get_fanart(0,0)
|
||||
except:
|
||||
try:
|
||||
_ = self._get_poster(0,0)
|
||||
except:
|
||||
raise ApiError("Unknown TMDB data format: %s" % self.tmdb_data)
|
||||
|
||||
def write_nfo(self, path):
|
||||
try:
|
||||
f = open(path, 'w')
|
||||
f.write(self.nfo_string)
|
||||
f.close()
|
||||
except:
|
||||
raise NfoError("Couldn't write nfo")
|
||||
|
||||
def _get_fanart(self, min_height, min_width):
|
||||
''' Fetches the fanart for the specified imdb_id and saves it to dir.
|
||||
Arguments
|
||||
|
||||
min_height/width: Sets lowest acceptable resolution fanart. 0 means
|
||||
disregard. If no fanart available at specified resolution or greater, then
|
||||
we disregard.
|
||||
'''
|
||||
images = [image['image'] for image in self.tmdb_data['backdrops'] if image['image'].get('size') == 'original']
|
||||
if len(images) == 0:
|
||||
raise ApiError("No fanart")
|
||||
|
||||
return self._get_image(images, min_height, min_width)
|
||||
|
||||
def get_fanart_url(self, min_height, min_width):
|
||||
return self._get_fanart(min_height, min_width)['url']
|
||||
|
||||
def write_fanart(self, filename_root, path, min_height, min_width):
|
||||
fanart_url = self.get_fanart_url(min_height, min_width)
|
||||
#fetch and write to disk
|
||||
dest = os.path.join(path, filename_root)
|
||||
try:
|
||||
f = open(dest, 'wb')
|
||||
except:
|
||||
raise IOError("Can't open for writing: %s" % dest)
|
||||
|
||||
response = urllib2.urlopen(fanart_url)
|
||||
f.write(response.read())
|
||||
f.close()
|
||||
|
||||
return True
|
||||
|
||||
def _get_poster(self, min_height, min_width):
|
||||
''' Fetches the poster for the specified imdb_id and saves it to dir.
|
||||
Arguments
|
||||
|
||||
min_height/width: Sets lowest acceptable resolution poster. 0 means
|
||||
disregard. If no poster available at specified resolution or greater, then
|
||||
we disregard.
|
||||
'''
|
||||
images = [image['image'] for image in self.tmdb_data['posters'] if image['image'].get('size') == 'original']
|
||||
if len(images) == 0:
|
||||
raise ApiError("No posters")
|
||||
|
||||
return self._get_image(images, min_height, min_width)
|
||||
|
||||
def get_poster_url(self, min_height, min_width):
|
||||
return self._get_poster(min_height, min_width)['url']
|
||||
|
||||
def write_poster(self, filename_root, path, min_height, min_width):
|
||||
poster_url = self.get_poster_url(min_height, min_width)
|
||||
dest = os.path.join(path, filename_root)
|
||||
|
||||
try:
|
||||
f = open(dest, 'wb')
|
||||
except:
|
||||
raise IOError("Can't open for writing: %s" % dest)
|
||||
|
||||
response = urllib2.urlopen(poster_url)
|
||||
f.write(response.read())
|
||||
f.close()
|
||||
|
||||
return True
|
||||
|
||||
def _get_tmdb_imdb(self):
|
||||
url = "http://api.themoviedb.org/2.1/Movie.imdbLookup/en/json/%s/%s" % (__tmdb_apikey__, "tt" + self.imdbid)
|
||||
|
||||
count = 0
|
||||
while 1:
|
||||
count += 1
|
||||
response = urllib2.urlopen(url)
|
||||
json_string = response.read()
|
||||
try:
|
||||
tmdb_data = json.loads(json_string)[0]
|
||||
return tmdb_data
|
||||
except ValueError, e:
|
||||
if count < 3:
|
||||
continue
|
||||
else:
|
||||
raise ApiError("Invalid JSON: %s: %s" % (e, json_string))
|
||||
except:
|
||||
ApiError("JSON error with: %s" % json_string)
|
||||
|
||||
|
||||
def _get_image(self, image_list, min_height, min_width):
|
||||
#Select image
|
||||
images = []
|
||||
for image in image_list:
|
||||
if not min_height or min_width:
|
||||
images.append(image)
|
||||
break
|
||||
elif min_height and not min_width:
|
||||
if image['height'] >= min_height:
|
||||
images.append(image)
|
||||
break
|
||||
elif min_width and not min_height:
|
||||
if image['width'] >= min_width:
|
||||
images.append(image)
|
||||
break
|
||||
elif min_width and min_height:
|
||||
if image['width'] >= min_width and image['height'] >= min_height:
|
||||
images.append(image)
|
||||
break
|
||||
|
||||
#No image meets our resolution requirements, so disregard those requirements
|
||||
if len(images) == 0 and min_height or min_width:
|
||||
images.append(image_list[0])
|
||||
|
||||
return images[0]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
try:
|
||||
id = sys.argv[1]
|
||||
except:
|
||||
id = 'tt0111161'
|
||||
|
||||
x = MetaGen(id)
|
||||
x.write_nfo(".\movie.nfo")
|
||||
x.write_fanart("fanart", ".", 0, 0)
|
||||
x.write_poster("movie", ".", 0, 0)
|
||||