Compare commits
250
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
821f68909d | ||
|
|
2b8dfed475 | ||
|
|
0a749ce913 | ||
|
|
e6db505cf7 | ||
|
|
9e8d6aaaa1 | ||
|
|
e814b551b4 | ||
|
|
080da48223 | ||
|
|
897330e646 | ||
|
|
c4c7b5b1a9 | ||
|
|
b90861bc63 | ||
|
|
6d1297a85f | ||
|
|
dfd2c33657 | ||
|
|
f5af551325 | ||
|
|
7aad27c3d2 | ||
|
|
60ff3b08d4 | ||
|
|
7a5588d5de | ||
|
|
56b6fbbe7f | ||
|
|
46c408befb | ||
|
|
6f808fc25a | ||
|
|
4cba44fbb1 | ||
|
|
91c45bad71 | ||
|
|
a30caefc04 | ||
|
|
eb20fda878 | ||
|
|
4a5aa02e6c | ||
|
|
25b37ad915 | ||
|
|
bbcceb982a | ||
|
|
c41f5eb84d | ||
|
|
89dc9e90b2 | ||
|
|
39b1dedf12 | ||
|
|
be28820fb2 | ||
|
|
0654c8cf07 | ||
|
|
2f5cb81029 | ||
|
|
067d6e8514 | ||
|
|
42e19e1e2b | ||
|
|
6b846b91b4 | ||
|
|
5838a41813 | ||
|
|
3cd5513c0c | ||
|
|
bfdc8d1053 | ||
|
|
30ec8216e1 | ||
|
|
12c3fc6ce3 | ||
|
|
7b3a1409d5 | ||
|
|
924bed06cb | ||
|
|
8b0aa7a6b3 | ||
|
|
367c385fff | ||
|
|
840efb1571 | ||
|
|
9ba19d27a6 | ||
|
|
1d603e1ec2 | ||
|
|
7818b43045 | ||
|
|
3936100000 | ||
|
|
1a846b04ee | ||
|
|
384a355a53 | ||
|
|
58ad5c3938 | ||
|
|
6ee68d1418 | ||
|
|
6e45c14ac5 | ||
|
|
e786c9c79a | ||
|
|
518ac16814 | ||
|
|
1d07eafa83 | ||
|
|
1600b6d0ea | ||
|
|
6de3a7246e | ||
|
|
cbd29df52a | ||
|
|
92998bafc8 | ||
|
|
1022753213 | ||
|
|
b85942989d | ||
|
|
f2f43a2231 | ||
|
|
2979a8edec | ||
|
|
0e90739786 | ||
|
|
185a530b59 | ||
|
|
4f6b31d14a | ||
|
|
f1dde5c925 | ||
|
|
64afa3701a | ||
|
|
cb0b6614c6 | ||
|
|
177063d39c | ||
|
|
be595aba91 | ||
|
|
66d9d853af | ||
|
|
95a68af795 | ||
|
|
c1937ea71f | ||
|
|
a7bd8c822a | ||
|
|
0eff4f0096 | ||
|
|
4d7fa08805 | ||
|
|
5fd4312ff8 | ||
|
|
a600430be4 | ||
|
|
f77b598899 | ||
|
|
ac045539d1 | ||
|
|
5b0fa9054b | ||
|
|
3c2a00b17b | ||
|
|
47ddf31f76 | ||
|
|
57ae06e139 | ||
|
|
7f4373e000 | ||
|
|
63609bb52c | ||
|
|
f0af184262 | ||
|
|
72cc3576d3 | ||
|
|
3fe7d2ea15 | ||
|
|
8eed54f1f7 | ||
|
|
c7ee8a0635 | ||
|
|
33a6a7d3a0 | ||
|
|
2851781a72 | ||
|
|
45b9919f67 | ||
|
|
207e846ae6 | ||
|
|
a83c276aa2 | ||
|
|
4cdb99a383 | ||
|
|
8fe60a893c | ||
|
|
0c44c48628 | ||
|
|
6a18e546ca | ||
|
|
4cedccb178 | ||
|
|
eab9a735a9 | ||
|
|
1df05cf344 | ||
|
|
843ff0eabc | ||
|
|
5a23be2224 | ||
|
|
45c8817c62 | ||
|
|
7f87b255f9 | ||
|
|
665c84c6de | ||
|
|
b91a077c91 | ||
|
|
59b924efe7 | ||
|
|
730718a396 | ||
|
|
3c0edc0d6a | ||
|
|
7c234ab7e9 | ||
|
|
b82319cb54 | ||
|
|
6685495400 | ||
|
|
b216589e88 | ||
|
|
744aa153f6 | ||
|
|
67612fce98 | ||
|
|
72ba1a173c | ||
|
|
989e217775 | ||
|
|
b0d556c8eb | ||
|
|
1a54d8fad9 | ||
|
|
f9ace29cab | ||
|
|
a97570027d | ||
|
|
de36faa0a7 | ||
|
|
19641bd897 | ||
|
|
2c64641a1b | ||
|
|
5ac1118db3 | ||
|
|
717b88b5fe | ||
|
|
158a7fc311 | ||
|
|
2c46279617 | ||
|
|
b843d5f13b | ||
|
|
4aff3f0495 | ||
|
|
4406f133b9 | ||
|
|
572dfd529e | ||
|
|
2cb6ddfe9a | ||
|
|
250236bd25 | ||
|
|
7f24563bba | ||
|
|
5d6a9ad2d0 | ||
|
|
0115bf254e | ||
|
|
607b5ea766 | ||
|
|
88579cd71a | ||
|
|
6c57316ce6 | ||
|
|
6702683da3 | ||
|
|
b9c2b42725 | ||
|
|
e54928720a | ||
|
|
f8f22cdef7 | ||
|
|
1ed58586a1 | ||
|
|
e694276a8d | ||
|
|
a8369b4e93 | ||
|
|
73b7bcc6ce | ||
|
|
f08ccd4fd8 | ||
|
|
312562a9f5 | ||
|
|
fab8e66fe1 | ||
|
|
1cd8040692 | ||
|
|
4db1b57c70 | ||
|
|
7268e02386 | ||
|
|
805aa3ca9f | ||
|
|
29cb34551c | ||
|
|
d267be4455 | ||
|
|
92f4ade371 | ||
|
|
9235eda73b | ||
|
|
1fe23afd1b | ||
|
|
09637c3069 | ||
|
|
2cafd509fc | ||
|
|
62cc570ab2 | ||
|
|
1ec9370e68 | ||
|
|
5b4c60ecba | ||
|
|
7b7488ece8 | ||
|
|
4ba7ff9f27 | ||
|
|
df2d1aca4b | ||
|
|
4fcba70c9a | ||
|
|
d0fc20ca6e | ||
|
|
9402b54f9b | ||
|
|
f0e7795b9b | ||
|
|
bba18d8bc9 | ||
|
|
0494e5fc8f | ||
|
|
df1b46272d | ||
|
|
b06dbd3069 | ||
|
|
ed068f09b0 | ||
|
|
5e852d05ee | ||
|
|
d111393bd6 | ||
|
|
f84aa8c638 | ||
|
|
89bff73431 | ||
|
|
8e07dfc730 | ||
|
|
cd16dddf13 | ||
|
|
25605c45b9 | ||
|
|
b6d0d54609 | ||
|
|
98981dac27 | ||
|
|
ddf03cbcf2 | ||
|
|
1e1abf407c | ||
|
|
1267cdac4d | ||
|
|
05bcee12ae | ||
|
|
fc3f15e0cf | ||
|
|
0a7765f639 | ||
|
|
c214458770 | ||
|
|
bfe501c84a | ||
|
|
e034465df8 | ||
|
|
a7b78d4131 | ||
|
|
3eed34c710 | ||
|
|
9cb3bef156 | ||
|
|
46c7e3fbed | ||
|
|
a49a00a25f | ||
|
|
eed0382b41 | ||
|
|
673843fb66 | ||
|
|
4e45c94fc3 | ||
|
|
0a11dc6673 | ||
|
|
4ede2c20a1 | ||
|
|
af0cf523e3 | ||
|
|
3908e00650 | ||
|
|
f9bdf6da1c | ||
|
|
811f35b028 | ||
|
|
87cdf9222d | ||
|
|
2ca2cc9597 | ||
|
|
edb232df60 | ||
|
|
af113c0ffd | ||
|
|
856b495995 | ||
|
|
a56bbf0b3b | ||
|
|
4b54113f08 | ||
|
|
52371b7705 | ||
|
|
629bead919 | ||
|
|
c7cd72787f | ||
|
|
ec6e2c240f | ||
|
|
9e260a89af | ||
|
|
d233e4d22e | ||
|
|
a60e9dc4c3 | ||
|
|
b168c1364d | ||
|
|
23893dbcb9 | ||
|
|
3187a0f820 | ||
|
|
14fffda3ff | ||
|
|
f86b9299c4 | ||
|
|
51364a3c25 | ||
|
|
c6642ffeb7 | ||
|
|
9fe9ccf0ad | ||
|
|
d27d0abeb0 | ||
|
|
cb92b00534 | ||
|
|
7d3780133f | ||
|
|
f23b9d7cb9 | ||
|
|
7c59348138 | ||
|
|
ab53f44157 | ||
|
|
b35f325d94 | ||
|
|
393c14de54 | ||
|
|
bff17c0b95 | ||
|
|
d172828ac5 | ||
|
|
9500ac73fc | ||
|
|
e2cf7e4421 | ||
|
|
c087a6b49b |
+2
-2
@@ -166,8 +166,8 @@ class CouchPotatoApp(wx.App, SoftwareUpdate):
|
|||||||
def OnInit(self):
|
def OnInit(self):
|
||||||
|
|
||||||
# Updater
|
# Updater
|
||||||
base_url = 'http://couchpota.to/updates/%s/' % VERSION
|
base_url = 'https://couchpota.to/updates/%s'
|
||||||
self.InitUpdates(base_url, base_url + 'changelog.html',
|
self.InitUpdates(base_url % VERSION + '/', base_url % 'changelog.html',
|
||||||
icon = wx.Icon('icon.png'))
|
icon = wx.Icon('icon.png'))
|
||||||
|
|
||||||
self.frame = MainFrame(self)
|
self.frame = MainFrame(self)
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ def page_not_found(error):
|
|||||||
r = '%s%s' % (request.url.rstrip('/'), index_url + '#' + url)
|
r = '%s%s' % (request.url.rstrip('/'), index_url + '#' + url)
|
||||||
return redirect(r)
|
return redirect(r)
|
||||||
else:
|
else:
|
||||||
time.sleep(0.1)
|
if not Env.get('dev'):
|
||||||
|
time.sleep(0.1)
|
||||||
return 'Wrong API key used', 404
|
return 'Wrong API key used', 404
|
||||||
|
|
||||||
|
|||||||
+4
-9
@@ -11,16 +11,12 @@ api_nonblock = {}
|
|||||||
|
|
||||||
class NonBlockHandler(RequestHandler):
|
class NonBlockHandler(RequestHandler):
|
||||||
|
|
||||||
def __init__(self, application, request, **kwargs):
|
stoppers = []
|
||||||
cls = NonBlockHandler
|
|
||||||
cls.stoppers = []
|
|
||||||
super(NonBlockHandler, self).__init__(application, request, **kwargs)
|
|
||||||
|
|
||||||
@asynchronous
|
@asynchronous
|
||||||
def get(self, route):
|
def get(self, route):
|
||||||
cls = NonBlockHandler
|
|
||||||
start, stop = api_nonblock[route]
|
start, stop = api_nonblock[route]
|
||||||
cls.stoppers.append(stop)
|
self.stoppers.append(stop)
|
||||||
|
|
||||||
start(self.onNewMessage, last_id = self.get_argument("last_id", None))
|
start(self.onNewMessage, last_id = self.get_argument("last_id", None))
|
||||||
|
|
||||||
@@ -30,12 +26,11 @@ class NonBlockHandler(RequestHandler):
|
|||||||
self.finish(response)
|
self.finish(response)
|
||||||
|
|
||||||
def on_connection_close(self):
|
def on_connection_close(self):
|
||||||
cls = NonBlockHandler
|
|
||||||
|
|
||||||
for stop in cls.stoppers:
|
for stop in self.stoppers:
|
||||||
stop(self.onNewMessage)
|
stop(self.onNewMessage)
|
||||||
|
|
||||||
cls.stoppers = []
|
self.stoppers = []
|
||||||
|
|
||||||
|
|
||||||
def addApiView(route, func, static = False, docs = None, **kwargs):
|
def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ config = [{
|
|||||||
'name': 'development',
|
'name': 'development',
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'description': 'Disables some checks/downloads for faster reloading.',
|
'description': 'Enable this if you\'re developing, and NOT in any other case, thanks.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'data_dir',
|
'name': 'data_dir',
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from uuid import uuid4
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import signal
|
import signal
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import webbrowser
|
import webbrowser
|
||||||
@@ -80,7 +79,7 @@ class Core(Plugin):
|
|||||||
|
|
||||||
def shutdown():
|
def shutdown():
|
||||||
self.initShutdown()
|
self.initShutdown()
|
||||||
IOLoop.instance().add_callback(shutdown)
|
IOLoop.current().add_callback(shutdown)
|
||||||
|
|
||||||
return 'shutdown'
|
return 'shutdown'
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ class Core(Plugin):
|
|||||||
|
|
||||||
def restart():
|
def restart():
|
||||||
self.initShutdown(restart = True)
|
self.initShutdown(restart = True)
|
||||||
IOLoop.instance().add_callback(restart)
|
IOLoop.current().add_callback(restart)
|
||||||
|
|
||||||
return 'restarting'
|
return 'restarting'
|
||||||
|
|
||||||
@@ -129,7 +128,7 @@ class Core(Plugin):
|
|||||||
log.debug('Save to shutdown/restart')
|
log.debug('Save to shutdown/restart')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
IOLoop.instance().stop()
|
IOLoop.current().stop()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
@@ -153,7 +152,7 @@ class Core(Plugin):
|
|||||||
|
|
||||||
def createBaseUrl(self):
|
def createBaseUrl(self):
|
||||||
host = Env.setting('host')
|
host = Env.setting('host')
|
||||||
if host == '0.0.0.0':
|
if host == '0.0.0.0' or host == '':
|
||||||
host = 'localhost'
|
host = 'localhost'
|
||||||
port = Env.setting('port')
|
port = Env.setting('port')
|
||||||
|
|
||||||
@@ -177,9 +176,10 @@ class Core(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def signalHandler(self):
|
def signalHandler(self):
|
||||||
|
if Env.get('daemonized'): return
|
||||||
|
|
||||||
def signal_handler(signal, frame):
|
def signal_handler(signal, frame):
|
||||||
fireEvent('app.shutdown')
|
fireEvent('app.shutdown', single = True)
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
|||||||
@@ -1,15 +1,60 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.encoding import ss
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
from minify.jsmin import jsmin
|
||||||
|
import cssprefixer
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ClientScript(Plugin):
|
class ClientScript(Plugin):
|
||||||
|
|
||||||
urls = {
|
core_static = {
|
||||||
'style': {},
|
'style': [
|
||||||
'script': {},
|
'style/main.css',
|
||||||
|
'style/uniform.generic.css',
|
||||||
|
'style/uniform.css',
|
||||||
|
'style/settings.css',
|
||||||
|
],
|
||||||
|
'script': [
|
||||||
|
'scripts/library/mootools.js',
|
||||||
|
'scripts/library/mootools_more.js',
|
||||||
|
'scripts/library/uniform.js',
|
||||||
|
'scripts/library/form_replacement/form_check.js',
|
||||||
|
'scripts/library/form_replacement/form_radio.js',
|
||||||
|
'scripts/library/form_replacement/form_dropdown.js',
|
||||||
|
'scripts/library/form_replacement/form_selectoption.js',
|
||||||
|
'scripts/library/question.js',
|
||||||
|
'scripts/library/scrollspy.js',
|
||||||
|
'scripts/library/spin.js',
|
||||||
|
'scripts/couchpotato.js',
|
||||||
|
'scripts/api.js',
|
||||||
|
'scripts/library/history.js',
|
||||||
|
'scripts/page.js',
|
||||||
|
'scripts/block.js',
|
||||||
|
'scripts/block/navigation.js',
|
||||||
|
'scripts/block/footer.js',
|
||||||
|
'scripts/block/menu.js',
|
||||||
|
'scripts/page/home.js',
|
||||||
|
'scripts/page/wanted.js',
|
||||||
|
'scripts/page/settings.js',
|
||||||
|
'scripts/page/about.js',
|
||||||
|
'scripts/page/manage.js',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
urls = {'style': {}, 'script': {}, }
|
||||||
|
minified = {'style': {}, 'script': {}, }
|
||||||
|
paths = {'style': {}, 'script': {}, }
|
||||||
|
comment = {
|
||||||
|
'style': '/*** %s:%d ***/\n',
|
||||||
|
'script': '// %s:%d\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
html = {
|
html = {
|
||||||
@@ -24,6 +69,69 @@ class ClientScript(Plugin):
|
|||||||
addEvent('clientscript.get_styles', self.getStyles)
|
addEvent('clientscript.get_styles', self.getStyles)
|
||||||
addEvent('clientscript.get_scripts', self.getScripts)
|
addEvent('clientscript.get_scripts', self.getScripts)
|
||||||
|
|
||||||
|
if not Env.get('dev'):
|
||||||
|
addEvent('app.load', self.minify)
|
||||||
|
|
||||||
|
self.addCore()
|
||||||
|
|
||||||
|
def addCore(self):
|
||||||
|
|
||||||
|
for static_type in self.core_static:
|
||||||
|
for rel_path in self.core_static.get(static_type):
|
||||||
|
file_path = os.path.join(Env.get('app_dir'), 'couchpotato', 'static', rel_path)
|
||||||
|
core_url = 'api/%s/static/%s?%s' % (Env.setting('api_key'), rel_path, tryInt(os.path.getmtime(file_path)))
|
||||||
|
|
||||||
|
if static_type == 'script':
|
||||||
|
self.registerScript(core_url, file_path, position = 'front')
|
||||||
|
else:
|
||||||
|
self.registerStyle(core_url, file_path, position = 'front')
|
||||||
|
|
||||||
|
|
||||||
|
def minify(self):
|
||||||
|
|
||||||
|
for file_type in ['style', 'script']:
|
||||||
|
ext = 'js' if file_type is 'script' else 'css'
|
||||||
|
positions = self.paths.get(file_type, {})
|
||||||
|
for position in positions:
|
||||||
|
files = positions.get(position)
|
||||||
|
self._minify(file_type, files, position, position + '.' + ext)
|
||||||
|
|
||||||
|
def _minify(self, file_type, files, position, out):
|
||||||
|
|
||||||
|
cache = Env.get('cache_dir')
|
||||||
|
out_name = 'minified_' + out
|
||||||
|
out = os.path.join(cache, out_name)
|
||||||
|
|
||||||
|
raw = []
|
||||||
|
for file_path in files:
|
||||||
|
f = open(file_path, 'r').read()
|
||||||
|
|
||||||
|
if file_type == 'script':
|
||||||
|
data = jsmin(f)
|
||||||
|
else:
|
||||||
|
data = cssprefixer.process(f, debug = False, minify = True)
|
||||||
|
data = data.replace('../images/', '../static/images/')
|
||||||
|
data = data.replace('../fonts/', '../static/fonts/')
|
||||||
|
data = data.replace('../../static/', '../static/') # Replace inside plugins
|
||||||
|
|
||||||
|
raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data})
|
||||||
|
|
||||||
|
# Combine all files together with some comments
|
||||||
|
data = ''
|
||||||
|
for r in raw:
|
||||||
|
data += self.comment.get(file_type) % (r.get('file'), r.get('date'))
|
||||||
|
data += r.get('data') + '\n\n'
|
||||||
|
|
||||||
|
self.createFile(out, ss(data.strip()))
|
||||||
|
|
||||||
|
if not self.minified.get(file_type):
|
||||||
|
self.minified[file_type] = {}
|
||||||
|
if not self.minified[file_type].get(position):
|
||||||
|
self.minified[file_type][position] = []
|
||||||
|
|
||||||
|
minified_url = 'api/%s/file.cache/%s?%s' % (Env.setting('api_key'), out_name, tryInt(os.path.getmtime(out)))
|
||||||
|
self.minified[file_type][position].append(minified_url)
|
||||||
|
|
||||||
def getStyles(self, *args, **kwargs):
|
def getStyles(self, *args, **kwargs):
|
||||||
return self.get('style', *args, **kwargs)
|
return self.get('style', *args, **kwargs)
|
||||||
|
|
||||||
@@ -35,22 +143,30 @@ class ClientScript(Plugin):
|
|||||||
data = '' if as_html else []
|
data = '' if as_html else []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
try:
|
||||||
|
if not Env.get('dev'):
|
||||||
|
return self.minified[type][location]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return self.urls[type][location]
|
return self.urls[type][location]
|
||||||
except Exception, e:
|
except:
|
||||||
log.error(e)
|
log.error('Error getting minified %s, %s: %s', (type, location, traceback.format_exc()))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def registerStyle(self, path, position = 'head'):
|
def registerStyle(self, api_path, file_path, position = 'head'):
|
||||||
self.register(path, 'style', position)
|
self.register(api_path, file_path, 'style', position)
|
||||||
|
|
||||||
def registerScript(self, path, position = 'head'):
|
def registerScript(self, api_path, file_path, position = 'head'):
|
||||||
self.register(path, 'script', position)
|
self.register(api_path, file_path, 'script', position)
|
||||||
|
|
||||||
def register(self, filepath, type, location):
|
def register(self, api_path, file_path, type, location):
|
||||||
|
|
||||||
if not self.urls[type].get(location):
|
if not self.urls[type].get(location):
|
||||||
self.urls[type][location] = []
|
self.urls[type][location] = []
|
||||||
|
self.urls[type][location].append(api_path)
|
||||||
|
|
||||||
filePath = filepath
|
if not self.paths[type].get(location):
|
||||||
self.urls[type][location].append(filePath)
|
self.paths[type][location] = []
|
||||||
|
self.paths[type][location].append(file_path)
|
||||||
|
|||||||
@@ -16,51 +16,19 @@ class Scheduler(Plugin):
|
|||||||
|
|
||||||
addEvent('schedule.cron', self.cron)
|
addEvent('schedule.cron', self.cron)
|
||||||
addEvent('schedule.interval', self.interval)
|
addEvent('schedule.interval', self.interval)
|
||||||
addEvent('schedule.start', self.start)
|
addEvent('schedule.remove', self.remove)
|
||||||
addEvent('schedule.restart', self.start)
|
|
||||||
|
|
||||||
addEvent('app.load', self.start)
|
|
||||||
|
|
||||||
self.sched = Sched(misfire_grace_time = 60)
|
self.sched = Sched(misfire_grace_time = 60)
|
||||||
|
|
||||||
def remove(self, identifier):
|
|
||||||
for type in ['interval', 'cron']:
|
|
||||||
try:
|
|
||||||
self.sched.unschedule_job(getattr(self, type)[identifier]['job'])
|
|
||||||
log.debug('%s unscheduled %s', (type.capitalize(), identifier))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
|
|
||||||
# Stop all running
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
# Crons
|
|
||||||
for identifier in self.crons:
|
|
||||||
try:
|
|
||||||
self.remove(identifier)
|
|
||||||
cron = self.crons[identifier]
|
|
||||||
job = self.sched.add_cron_job(cron['handle'], day = cron['day'], hour = cron['hour'], minute = cron['minute'])
|
|
||||||
cron['job'] = job
|
|
||||||
except ValueError, e:
|
|
||||||
log.error('Failed adding cronjob: %s', e)
|
|
||||||
|
|
||||||
# Intervals
|
|
||||||
for identifier in self.intervals:
|
|
||||||
try:
|
|
||||||
self.remove(identifier)
|
|
||||||
interval = self.intervals[identifier]
|
|
||||||
job = self.sched.add_interval_job(interval['handle'], hours = interval['hours'], minutes = interval['minutes'], seconds = interval['seconds'])
|
|
||||||
interval['job'] = job
|
|
||||||
except ValueError, e:
|
|
||||||
log.error('Failed adding interval cronjob: %s', e)
|
|
||||||
|
|
||||||
# Start it
|
|
||||||
log.debug('Starting scheduler')
|
|
||||||
self.sched.start()
|
self.sched.start()
|
||||||
self.started = True
|
self.started = True
|
||||||
log.debug('Scheduler started')
|
|
||||||
|
def remove(self, identifier):
|
||||||
|
for cron_type in ['intervals', 'crons']:
|
||||||
|
try:
|
||||||
|
self.sched.unschedule_job(getattr(self, cron_type)[identifier]['job'])
|
||||||
|
log.debug('%s unscheduled %s', (cron_type.capitalize(), identifier))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def doShutdown(self):
|
def doShutdown(self):
|
||||||
super(Scheduler, self).doShutdown()
|
super(Scheduler, self).doShutdown()
|
||||||
@@ -82,6 +50,7 @@ class Scheduler(Plugin):
|
|||||||
'day': day,
|
'day': day,
|
||||||
'hour': hour,
|
'hour': hour,
|
||||||
'minute': minute,
|
'minute': minute,
|
||||||
|
'job': self.sched.add_cron_job(handle, day = day, hour = hour, minute = minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
def interval(self, identifier = '', handle = None, hours = 0, minutes = 0, seconds = 0):
|
def interval(self, identifier = '', handle = None, hours = 0, minutes = 0, seconds = 0):
|
||||||
@@ -93,4 +62,5 @@ class Scheduler(Plugin):
|
|||||||
'hours': hours,
|
'hours': hours,
|
||||||
'minutes': minutes,
|
'minutes': minutes,
|
||||||
'seconds': seconds,
|
'seconds': seconds,
|
||||||
|
'job': self.sched.add_interval_job(handle, hours = hours, minutes = minutes, seconds = seconds)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import tarfile
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import version
|
import version
|
||||||
|
import zipfile
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -32,7 +33,6 @@ class Updater(Plugin):
|
|||||||
else:
|
else:
|
||||||
self.updater = SourceUpdater()
|
self.updater = SourceUpdater()
|
||||||
|
|
||||||
fireEvent('schedule.interval', 'updater.check', self.autoUpdate, hours = 6)
|
|
||||||
addEvent('app.load', self.autoUpdate)
|
addEvent('app.load', self.autoUpdate)
|
||||||
addEvent('updater.info', self.info)
|
addEvent('updater.info', self.info)
|
||||||
|
|
||||||
@@ -52,6 +52,15 @@ class Updater(Plugin):
|
|||||||
'return': {'type': 'see updater.info'}
|
'return': {'type': 'see updater.info'}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
addEvent('setting.save.updater.enabled.after', self.setCrons)
|
||||||
|
|
||||||
|
def setCrons(self):
|
||||||
|
|
||||||
|
fireEvent('schedule.remove', 'updater.check', single = True)
|
||||||
|
if self.isEnabled():
|
||||||
|
fireEvent('schedule.interval', 'updater.check', self.autoUpdate, hours = 6)
|
||||||
|
self.autoUpdate() # Check after enabling
|
||||||
|
|
||||||
def autoUpdate(self):
|
def autoUpdate(self):
|
||||||
if self.check() and self.conf('automatic') and not self.updater.update_failed:
|
if self.check() and self.conf('automatic') and not self.updater.update_failed:
|
||||||
if self.updater.doUpdate():
|
if self.updater.doUpdate():
|
||||||
@@ -255,11 +264,11 @@ class SourceUpdater(BaseUpdater):
|
|||||||
def doUpdate(self):
|
def doUpdate(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = 'https://github.com/%s/%s/tarball/%s' % (self.repo_user, self.repo_name, self.branch)
|
download_data = fireEvent('cp.source_url', repo = self.repo_user, repo_name = self.repo_name, branch = self.branch, single = True)
|
||||||
destination = os.path.join(Env.get('cache_dir'), self.update_version.get('hash') + '.tar.gz')
|
destination = os.path.join(Env.get('cache_dir'), self.update_version.get('hash')) + '.' + download_data.get('type')
|
||||||
extracted_path = os.path.join(Env.get('cache_dir'), 'temp_updater')
|
|
||||||
|
|
||||||
destination = fireEvent('file.download', url = url, dest = destination, single = True)
|
extracted_path = os.path.join(Env.get('cache_dir'), 'temp_updater')
|
||||||
|
destination = fireEvent('file.download', url = download_data.get('url'), dest = destination, single = True)
|
||||||
|
|
||||||
# Cleanup leftover from last time
|
# Cleanup leftover from last time
|
||||||
if os.path.isdir(extracted_path):
|
if os.path.isdir(extracted_path):
|
||||||
@@ -267,9 +276,14 @@ class SourceUpdater(BaseUpdater):
|
|||||||
self.makeDir(extracted_path)
|
self.makeDir(extracted_path)
|
||||||
|
|
||||||
# Extract
|
# Extract
|
||||||
tar = tarfile.open(destination)
|
if download_data.get('type') == 'zip':
|
||||||
tar.extractall(path = extracted_path)
|
zip = zipfile.ZipFile(destination)
|
||||||
tar.close()
|
zip.extractall(extracted_path)
|
||||||
|
else:
|
||||||
|
tar = tarfile.open(destination)
|
||||||
|
tar.extractall(path = extracted_path)
|
||||||
|
tar.close()
|
||||||
|
|
||||||
os.remove(destination)
|
os.remove(destination)
|
||||||
|
|
||||||
if self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0])):
|
if self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0])):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var UpdaterBase = new Class({
|
|||||||
initialize: function(){
|
initialize: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
App.addEvent('load', self.info.bind(self, 1000))
|
App.addEvent('load', self.info.bind(self, 2000))
|
||||||
App.addEvent('unload', function(){
|
App.addEvent('unload', function(){
|
||||||
if(self.timer)
|
if(self.timer)
|
||||||
clearTimeout(self.timer);
|
clearTimeout(self.timer);
|
||||||
@@ -84,7 +84,7 @@ var UpdaterBase = new Class({
|
|||||||
'click': self.doUpdate.bind(self)
|
'click': self.doUpdate.bind(self)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
).inject($(document.body).getElement('.header'))
|
).inject(document.body)
|
||||||
},
|
},
|
||||||
|
|
||||||
doUpdate: function(){
|
doUpdate: function(){
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from base64 import b32decode, b16encode
|
from base64 import b32decode, b16encode
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.variable import mergeDicts
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import Provider
|
from couchpotato.core.providers.base import Provider
|
||||||
import random
|
import random
|
||||||
@@ -57,6 +58,9 @@ class Downloader(Provider):
|
|||||||
|
|
||||||
return self.getAllDownloadStatus()
|
return self.getAllDownloadStatus()
|
||||||
|
|
||||||
|
def getAllDownloadStatus(self):
|
||||||
|
return
|
||||||
|
|
||||||
def _removeFailed(self, item):
|
def _removeFailed(self, item):
|
||||||
if self.isDisabled(manual = True, data = {}):
|
if self.isDisabled(manual = True, data = {}):
|
||||||
return
|
return
|
||||||
@@ -100,6 +104,12 @@ class Downloader(Provider):
|
|||||||
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
|
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def downloadReturnId(self, download_id):
|
||||||
|
return {
|
||||||
|
'downloader': self.getName(),
|
||||||
|
'id': download_id
|
||||||
|
}
|
||||||
|
|
||||||
def isDisabled(self, manual, data):
|
def isDisabled(self, manual, data):
|
||||||
return not self.isEnabled(manual, data)
|
return not self.isEnabled(manual, data)
|
||||||
|
|
||||||
@@ -113,3 +123,35 @@ class Downloader(Provider):
|
|||||||
return super(Downloader, self).isEnabled() and \
|
return super(Downloader, self).isEnabled() and \
|
||||||
((d_manual and manual) or (d_manual is False)) and \
|
((d_manual and manual) or (d_manual is False)) and \
|
||||||
(not data or self.isCorrectType(data.get('type')))
|
(not data or self.isCorrectType(data.get('type')))
|
||||||
|
|
||||||
|
|
||||||
|
class StatusList(list):
|
||||||
|
|
||||||
|
provider = None
|
||||||
|
|
||||||
|
def __init__(self, provider, **kwargs):
|
||||||
|
|
||||||
|
self.provider = provider
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
super(StatusList, self).__init__()
|
||||||
|
|
||||||
|
def extend(self, results):
|
||||||
|
for r in results:
|
||||||
|
self.append(r)
|
||||||
|
|
||||||
|
def append(self, result):
|
||||||
|
new_result = self.fillResult(result)
|
||||||
|
super(StatusList, self).append(new_result)
|
||||||
|
|
||||||
|
def fillResult(self, result):
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'id': 0,
|
||||||
|
'status': 'busy',
|
||||||
|
'downloader': self.provider.getName(),
|
||||||
|
'folder': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeDicts(defaults, result)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.environment import Env
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ class Blackhole(Downloader):
|
|||||||
log.info('Downloading %s to %s.', (data.get('type'), fullPath))
|
log.info('Downloading %s to %s.', (data.get('type'), fullPath))
|
||||||
with open(fullPath, 'wb') as f:
|
with open(fullPath, 'wb') as f:
|
||||||
f.write(filedata)
|
f.write(filedata)
|
||||||
|
os.chmod(fullPath, Env.getPermission('file'))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
log.info('File %s already exists.', fullPath)
|
log.info('File %s already exists.', fullPath)
|
||||||
@@ -60,5 +62,11 @@ class Blackhole(Downloader):
|
|||||||
return ['nzb']
|
return ['nzb']
|
||||||
|
|
||||||
def isEnabled(self, manual, data = {}):
|
def isEnabled(self, manual, data = {}):
|
||||||
|
for_type = ['both']
|
||||||
|
if data and 'torrent' in data.get('type'):
|
||||||
|
for_type.append('torrent')
|
||||||
|
elif data:
|
||||||
|
for_type.append(data.get('type'))
|
||||||
|
|
||||||
return super(Blackhole, self).isEnabled(manual, data) and \
|
return super(Blackhole, self).isEnabled(manual, data) and \
|
||||||
((self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')]))
|
((self.conf('use_for') in for_type))
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ config = [{
|
|||||||
'default': 'localhost:6789',
|
'default': 'localhost:6789',
|
||||||
'description': 'Hostname with port. Usually <strong>localhost:6789</strong>',
|
'description': 'Hostname with port. Usually <strong>localhost:6789</strong>',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'username',
|
||||||
|
'default': 'nzbget',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Set a different username to connect. Default: nzbget',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'password',
|
'name': 'password',
|
||||||
'type': 'password',
|
'type': 'password',
|
||||||
@@ -48,6 +54,12 @@ config = [{
|
|||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'delete_failed',
|
||||||
|
'default': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'description': 'Delete a release after the download has failed.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from base64 import standard_b64encode
|
from base64 import standard_b64encode
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.encoding import ss
|
||||||
|
from couchpotato.core.helpers.variable import tryInt, md5
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
|
from datetime import timedelta
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import traceback
|
import traceback
|
||||||
import xmlrpclib
|
import xmlrpclib
|
||||||
@@ -13,7 +16,7 @@ class NZBGet(Downloader):
|
|||||||
|
|
||||||
type = ['nzb']
|
type = ['nzb']
|
||||||
|
|
||||||
url = 'http://nzbget:%(password)s@%(host)s/xmlrpc'
|
url = 'http://%(username)s:%(password)s@%(host)s/xmlrpc'
|
||||||
|
|
||||||
def download(self, data = {}, movie = {}, filedata = None):
|
def download(self, data = {}, movie = {}, filedata = None):
|
||||||
|
|
||||||
@@ -23,8 +26,8 @@ class NZBGet(Downloader):
|
|||||||
|
|
||||||
log.info('Sending "%s" to NZBGet.', data.get('name'))
|
log.info('Sending "%s" to NZBGet.', data.get('name'))
|
||||||
|
|
||||||
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
|
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
|
||||||
nzb_name = '%s.nzb' % self.createNzbName(data, movie)
|
nzb_name = ss('%s.nzb' % self.createNzbName(data, movie))
|
||||||
|
|
||||||
rpc = xmlrpclib.ServerProxy(url)
|
rpc = xmlrpclib.ServerProxy(url)
|
||||||
try:
|
try:
|
||||||
@@ -49,7 +52,124 @@ class NZBGet(Downloader):
|
|||||||
|
|
||||||
if xml_response:
|
if xml_response:
|
||||||
log.info('NZB sent successfully to NZBGet')
|
log.info('NZB sent successfully to NZBGet')
|
||||||
return True
|
nzb_id = md5(data['url']) # about as unique as they come ;)
|
||||||
|
couchpotato_id = "couchpotato=" + nzb_id
|
||||||
|
groups = rpc.listgroups()
|
||||||
|
file_id = [item['LastID'] for item in groups if item['NZBFilename'] == nzb_name]
|
||||||
|
confirmed = rpc.editqueue("GroupSetParameter", 0, couchpotato_id, file_id)
|
||||||
|
if confirmed:
|
||||||
|
log.debug('couchpotato parameter set in nzbget download')
|
||||||
|
return self.downloadReturnId(nzb_id)
|
||||||
else:
|
else:
|
||||||
log.error('NZBGet could not add %s to the queue.', nzb_name)
|
log.error('NZBGet could not add %s to the queue.', nzb_name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getAllDownloadStatus(self):
|
||||||
|
|
||||||
|
log.debug('Checking NZBGet download status.')
|
||||||
|
|
||||||
|
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
|
||||||
|
|
||||||
|
rpc = xmlrpclib.ServerProxy(url)
|
||||||
|
try:
|
||||||
|
if rpc.writelog('INFO', 'CouchPotato connected to check status'):
|
||||||
|
log.info('Successfully connected to NZBGet')
|
||||||
|
else:
|
||||||
|
log.info('Successfully connected to NZBGet, but unable to send a message')
|
||||||
|
except socket.error:
|
||||||
|
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
|
||||||
|
return False
|
||||||
|
except xmlrpclib.ProtocolError, e:
|
||||||
|
if e.errcode == 401:
|
||||||
|
log.error('Password is incorrect.')
|
||||||
|
else:
|
||||||
|
log.error('Protocol Error: %s', e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get NZBGet data
|
||||||
|
try:
|
||||||
|
status = rpc.status()
|
||||||
|
groups = rpc.listgroups()
|
||||||
|
queue = rpc.postqueue(0)
|
||||||
|
history = rpc.history()
|
||||||
|
except:
|
||||||
|
log.error('Failed getting data: %s', traceback.format_exc(1))
|
||||||
|
return False
|
||||||
|
|
||||||
|
statuses = StatusList(self)
|
||||||
|
|
||||||
|
for item in groups:
|
||||||
|
log.debug('Found %s in NZBGet download queue', item['NZBFilename'])
|
||||||
|
try:
|
||||||
|
nzb_id = [param['Value'] for param in item['Parameters'] if param['Name'] == 'couchpotato'][0]
|
||||||
|
except:
|
||||||
|
nzb_id = item['NZBID']
|
||||||
|
statuses.append({
|
||||||
|
'id': nzb_id,
|
||||||
|
'name': item['NZBFilename'],
|
||||||
|
'original_status': 'DOWNLOADING' if item['ActiveDownloads'] > 0 else 'QUEUED',
|
||||||
|
# Seems to have no native API function for time left. This will return the time left after NZBGet started downloading this item
|
||||||
|
'timeleft': str(timedelta(seconds = item['RemainingSizeMB'] / status['DownloadRate'] * 2 ^ 20)) if item['ActiveDownloads'] > 0 and not (status['DownloadPaused'] or status['Download2Paused']) else -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
for item in queue: # 'Parameters' is not passed in rpc.postqueue
|
||||||
|
log.debug('Found %s in NZBGet postprocessing queue', item['NZBFilename'])
|
||||||
|
statuses.append({
|
||||||
|
'id': item['NZBID'],
|
||||||
|
'name': item['NZBFilename'],
|
||||||
|
'original_status': item['Stage'],
|
||||||
|
'timeleft': str(timedelta(seconds = 0)) if not status['PostPaused'] else -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
for item in history:
|
||||||
|
log.debug('Found %s in NZBGet history. ParStatus: %s, ScriptStatus: %s, Log: %s', (item['NZBFilename'] , item['ParStatus'], item['ScriptStatus'] , item['Log']))
|
||||||
|
try:
|
||||||
|
nzb_id = [param['Value'] for param in item['Parameters'] if param['Name'] == 'couchpotato'][0]
|
||||||
|
except:
|
||||||
|
nzb_id = item['NZBID']
|
||||||
|
statuses.append({
|
||||||
|
'id': nzb_id,
|
||||||
|
'name': item['NZBFilename'],
|
||||||
|
'status': 'completed' if item['ParStatus'] == 'SUCCESS' and item['ScriptStatus'] == 'SUCCESS' else 'failed',
|
||||||
|
'original_status': item['ParStatus'] + ', ' + item['ScriptStatus'],
|
||||||
|
'timeleft': str(timedelta(seconds = 0)),
|
||||||
|
'folder': item['DestDir']
|
||||||
|
})
|
||||||
|
|
||||||
|
return statuses
|
||||||
|
|
||||||
|
def removeFailed(self, item):
|
||||||
|
|
||||||
|
log.info('%s failed downloading, deleting...', item['name'])
|
||||||
|
|
||||||
|
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
|
||||||
|
|
||||||
|
rpc = xmlrpclib.ServerProxy(url)
|
||||||
|
try:
|
||||||
|
if rpc.writelog('INFO', 'CouchPotato connected to delete some history'):
|
||||||
|
log.info('Successfully connected to NZBGet')
|
||||||
|
else:
|
||||||
|
log.info('Successfully connected to NZBGet, but unable to send a message')
|
||||||
|
except socket.error:
|
||||||
|
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
|
||||||
|
return False
|
||||||
|
except xmlrpclib.ProtocolError, e:
|
||||||
|
if e.errcode == 401:
|
||||||
|
log.error('Password is incorrect.')
|
||||||
|
else:
|
||||||
|
log.error('Protocol Error: %s', e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
history = rpc.history()
|
||||||
|
for hist in history:
|
||||||
|
if hist['Parameters'] and hist['Parameters']['couchpotato'] and hist['Parameters']['couchpotato'] == item['id']:
|
||||||
|
nzb_id = hist['ID']
|
||||||
|
path = hist['DestDir']
|
||||||
|
if rpc.editqueue('HistoryDelete', 0, "", [tryInt(nzb_id)]):
|
||||||
|
shutil.rmtree(path, True)
|
||||||
|
except:
|
||||||
|
log.error('Failed deleting: %s', traceback.format_exc(0))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -29,7 +29,9 @@ class NZBVortex(Downloader):
|
|||||||
nzb_filename = self.createFileName(data, filedata, movie)
|
nzb_filename = self.createFileName(data, filedata, movie)
|
||||||
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True)
|
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True)
|
||||||
|
|
||||||
return True
|
raw_statuses = self.call('nzb')
|
||||||
|
nzb_id = [item['id'] for item in raw_statuses.get('nzbs', []) if item['name'] == nzb_filename][0]
|
||||||
|
return self.downloadReturnId(nzb_id)
|
||||||
except:
|
except:
|
||||||
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
@@ -38,7 +40,7 @@ class NZBVortex(Downloader):
|
|||||||
|
|
||||||
raw_statuses = self.call('nzb')
|
raw_statuses = self.call('nzb')
|
||||||
|
|
||||||
statuses = []
|
statuses = StatusList(self)
|
||||||
for item in raw_statuses.get('nzbs', []):
|
for item in raw_statuses.get('nzbs', []):
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
@@ -53,7 +55,8 @@ class NZBVortex(Downloader):
|
|||||||
'name': item['uiTitle'],
|
'name': item['uiTitle'],
|
||||||
'status': status,
|
'status': status,
|
||||||
'original_status': item['state'],
|
'original_status': item['state'],
|
||||||
'timeleft':-1,
|
'timeleft': -1,
|
||||||
|
'folder': item['destinationPath'],
|
||||||
})
|
})
|
||||||
|
|
||||||
return statuses
|
return statuses
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||||
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
|
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
from datetime import timedelta
|
||||||
from urllib2 import URLError
|
from urllib2 import URLError
|
||||||
import json
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
@@ -16,8 +18,7 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
||||||
|
|
||||||
params = {
|
req_params = {
|
||||||
'apikey': self.conf('api_key'),
|
|
||||||
'cat': self.conf('category'),
|
'cat': self.conf('category'),
|
||||||
'mode': 'addurl',
|
'mode': 'addurl',
|
||||||
'nzbname': self.createNzbName(data, movie),
|
'nzbname': self.createNzbName(data, movie),
|
||||||
@@ -30,17 +31,15 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
||||||
nzb_filename = self.createFileName(data, filedata, movie)
|
nzb_filename = self.createFileName(data, filedata, movie)
|
||||||
params['mode'] = 'addfile'
|
req_params['mode'] = 'addfile'
|
||||||
else:
|
else:
|
||||||
params['name'] = data.get('url')
|
req_params['name'] = data.get('url')
|
||||||
|
|
||||||
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(params)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if params.get('mode') is 'addfile':
|
if req_params.get('mode') is 'addfile':
|
||||||
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False)
|
sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True)
|
||||||
else:
|
else:
|
||||||
sab = self.urlopen(url, timeout = 60, show_error = False)
|
sab_data = self.call(req_params)
|
||||||
except URLError:
|
except URLError:
|
||||||
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
|
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
|
||||||
return False
|
return False
|
||||||
@@ -48,17 +47,15 @@ class Sabnzbd(Downloader):
|
|||||||
log.error('Failed sending release, use API key, NOT the NZB key: %s', traceback.format_exc(0))
|
log.error('Failed sending release, use API key, NOT the NZB key: %s', traceback.format_exc(0))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
result = sab.strip()
|
log.debug('Result from SAB: %s', sab_data)
|
||||||
if not result:
|
if sab_data.get('status') and not sab_data.get('error'):
|
||||||
log.error('SABnzbd didn\'t return anything.')
|
|
||||||
return False
|
|
||||||
|
|
||||||
log.debug('Result text from SAB: %s', result[:40])
|
|
||||||
if result[:2] == 'ok':
|
|
||||||
log.info('NZB sent to SAB successfully.')
|
log.info('NZB sent to SAB successfully.')
|
||||||
return True
|
if filedata:
|
||||||
|
return self.downloadReturnId(sab_data.get('nzo_ids')[0])
|
||||||
|
else:
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log.error(result[:40])
|
log.error('Error getting data from SABNZBd: %s', sab_data)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getAllDownloadStatus(self):
|
def getAllDownloadStatus(self):
|
||||||
@@ -84,14 +81,13 @@ class Sabnzbd(Downloader):
|
|||||||
log.error('Failed getting history json: %s', traceback.format_exc(1))
|
log.error('Failed getting history json: %s', traceback.format_exc(1))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
statuses = []
|
statuses = StatusList(self)
|
||||||
|
|
||||||
# Get busy releases
|
# Get busy releases
|
||||||
for item in queue.get('slots', []):
|
for item in queue.get('slots', []):
|
||||||
statuses.append({
|
statuses.append({
|
||||||
'id': item['nzo_id'],
|
'id': item['nzo_id'],
|
||||||
'name': item['filename'],
|
'name': item['filename'],
|
||||||
'status': 'busy',
|
|
||||||
'original_status': item['status'],
|
'original_status': item['status'],
|
||||||
'timeleft': item['timeleft'] if not queue['paused'] else -1,
|
'timeleft': item['timeleft'] if not queue['paused'] else -1,
|
||||||
})
|
})
|
||||||
@@ -110,7 +106,8 @@ class Sabnzbd(Downloader):
|
|||||||
'name': item['name'],
|
'name': item['name'],
|
||||||
'status': status,
|
'status': status,
|
||||||
'original_status': item['status'],
|
'original_status': item['status'],
|
||||||
'timeleft': 0,
|
'timeleft': str(timedelta(seconds = 0)),
|
||||||
|
'folder': item['storage'],
|
||||||
})
|
})
|
||||||
|
|
||||||
return statuses
|
return statuses
|
||||||
@@ -132,21 +129,21 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def call(self, params, use_json = True):
|
def call(self, request_params, use_json = True, **kwargs):
|
||||||
|
|
||||||
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(params, {
|
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
|
||||||
'apikey': self.conf('api_key'),
|
'apikey': self.conf('api_key'),
|
||||||
'output': 'json'
|
'output': 'json'
|
||||||
}))
|
}))
|
||||||
|
|
||||||
data = self.urlopen(url, timeout = 60, show_error = False)
|
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}, **kwargs)
|
||||||
if use_json:
|
if use_json:
|
||||||
d = json.loads(data)
|
d = json.loads(data)
|
||||||
if d.get('error'):
|
if d.get('error'):
|
||||||
log.error('Error getting data from SABNZBd: %s', d.get('error'))
|
log.error('Error getting data from SABNZBd: %s', d.get('error'))
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return d[params['mode']]
|
return d.get(request_params['mode']) or d
|
||||||
else:
|
else:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -41,15 +41,22 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'directory',
|
'name': 'directory',
|
||||||
'type': 'directory',
|
'type': 'directory',
|
||||||
'description': 'Where should Transmission saved the downloaded files?',
|
'description': 'Download to this directory. Keep empty for default Transmission download directory.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'ratio',
|
'name': 'ratio',
|
||||||
'default': 10,
|
'default': 10,
|
||||||
'type': 'int',
|
'type': 'float',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Stop transfer when reaching ratio',
|
'description': 'Stop transfer when reaching ratio',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'ratiomode',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'int',
|
||||||
|
'advanced': True,
|
||||||
|
'description': '0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'manual',
|
'name': 'manual',
|
||||||
'default': 0,
|
'default': 0,
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import isInt
|
from couchpotato.core.helpers.encoding import isInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
from datetime import timedelta
|
||||||
import httplib
|
import httplib
|
||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
import traceback
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -18,7 +22,7 @@ class Transmission(Downloader):
|
|||||||
|
|
||||||
def download(self, data, movie, filedata = None):
|
def download(self, data, movie, filedata = None):
|
||||||
|
|
||||||
log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
|
log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
|
||||||
|
|
||||||
# Load host from config and split out port.
|
# Load host from config and split out port.
|
||||||
host = self.conf('host').split(':')
|
host = self.conf('host').split(':')
|
||||||
@@ -27,21 +31,24 @@ class Transmission(Downloader):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Set parameters for Transmission
|
# Set parameters for Transmission
|
||||||
folder_name = self.createFileName(data, filedata, movie)[:-len(data.get('type')) - 1]
|
|
||||||
folder_path = os.path.join(self.conf('directory', default = ''), folder_name).rstrip(os.path.sep)
|
|
||||||
|
|
||||||
# Create the empty folder to download too
|
|
||||||
self.makeDir(folder_path)
|
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'paused': self.conf('paused', default = 0),
|
'paused': self.conf('paused', default = 0),
|
||||||
'download-dir': folder_path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(self.conf('directory', default = '')) > 0:
|
||||||
|
folder_name = self.createFileName(data, filedata, movie)[:-len(data.get('type')) - 1]
|
||||||
|
folder_path = os.path.join(self.conf('directory', default = ''), folder_name).rstrip(os.path.sep)
|
||||||
|
|
||||||
|
# Create the empty folder to download too
|
||||||
|
self.makeDir(folder_path)
|
||||||
|
|
||||||
|
params['download-dir'] = folder_path
|
||||||
|
|
||||||
|
torrent_params = {}
|
||||||
if self.conf('ratio'):
|
if self.conf('ratio'):
|
||||||
torrent_params = {
|
torrent_params = {
|
||||||
'seedRatioLimit': self.conf('ratio'),
|
'seedRatioLimit': self.conf('ratio'),
|
||||||
'seedRatioMode': self.conf('ratio')
|
'seedRatioMode': self.conf('ratiomode')
|
||||||
}
|
}
|
||||||
|
|
||||||
if not filedata and data.get('type') == 'torrent':
|
if not filedata and data.get('type') == 'torrent':
|
||||||
@@ -57,14 +64,97 @@ class Transmission(Downloader):
|
|||||||
else:
|
else:
|
||||||
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
|
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
|
||||||
|
|
||||||
# Change settings of added torrents
|
if not remote_torrent:
|
||||||
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
return False
|
||||||
|
|
||||||
return True
|
# Change settings of added torrents
|
||||||
except Exception, err:
|
elif torrent_params:
|
||||||
log.error('Failed to change settings for transfer: %s', err)
|
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
||||||
|
|
||||||
|
log.info('Torrent sent to Transmission successfully.')
|
||||||
|
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
|
||||||
|
except:
|
||||||
|
log.error('Failed to change settings for transfer: %s', traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getAllDownloadStatus(self):
|
||||||
|
|
||||||
|
log.debug('Checking Transmission download status.')
|
||||||
|
|
||||||
|
# Load host from config and split out port.
|
||||||
|
host = self.conf('host').split(':')
|
||||||
|
if not isInt(host[1]):
|
||||||
|
log.error('Config properties are not filled in correctly, port is missing.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Go through Queue
|
||||||
|
try:
|
||||||
|
trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
|
||||||
|
return_params = {
|
||||||
|
'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isFinished', 'downloadDir', 'uploadRatio']
|
||||||
|
}
|
||||||
|
queue = trpc.get_alltorrents(return_params)
|
||||||
|
|
||||||
|
except Exception, err:
|
||||||
|
log.error('Failed getting queue: %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
statuses = StatusList(self)
|
||||||
|
|
||||||
|
# Get torrents status
|
||||||
|
# CouchPotato Status
|
||||||
|
#status = 'busy'
|
||||||
|
#status = 'failed'
|
||||||
|
#status = 'completed'
|
||||||
|
# Transmission Status
|
||||||
|
#status = 0 => "Torrent is stopped"
|
||||||
|
#status = 1 => "Queued to check files"
|
||||||
|
#status = 2 => "Checking files"
|
||||||
|
#status = 3 => "Queued to download"
|
||||||
|
#status = 4 => "Downloading"
|
||||||
|
#status = 4 => "Queued to seed"
|
||||||
|
#status = 6 => "Seeding"
|
||||||
|
#To do :
|
||||||
|
# add checking file
|
||||||
|
# manage no peer in a range time => fail
|
||||||
|
|
||||||
|
for item in queue['torrents']:
|
||||||
|
log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / eta=%s / uploadRatio=%s / confRatio=%s / isFinished=%s', (item['name'], item['id'], item['downloadDir'], item['hashString'], item['percentDone'], item['status'], item['eta'], item['uploadRatio'], self.conf('ratio'), item['isFinished']))
|
||||||
|
|
||||||
|
if not os.path.isdir(Env.setting('from', 'renamer')):
|
||||||
|
log.error('Renamer "from" folder doesn\'t to exist.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if (item['percentDone'] * 100) >= 100 and (item['status'] == 6 or item['status'] == 0) and item['uploadRatio'] > self.conf('ratio'):
|
||||||
|
try:
|
||||||
|
trpc.stop_torrent(item['hashString'], {})
|
||||||
|
statuses.append({
|
||||||
|
'id': item['hashString'],
|
||||||
|
'name': item['name'],
|
||||||
|
'status': 'completed',
|
||||||
|
'original_status': item['status'],
|
||||||
|
'timeleft': str(timedelta(seconds = 0)),
|
||||||
|
'folder': os.path.join(item['downloadDir'], item['name']),
|
||||||
|
})
|
||||||
|
except Exception, err:
|
||||||
|
log.error('Failed to stop and remove torrent "%s" with error: %s', (item['name'], err))
|
||||||
|
statuses.append({
|
||||||
|
'id': item['hashString'],
|
||||||
|
'name': item['name'],
|
||||||
|
'status': 'failed',
|
||||||
|
'original_status': item['status'],
|
||||||
|
'timeleft': str(timedelta(seconds = 0)),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
statuses.append({
|
||||||
|
'id': item['hashString'],
|
||||||
|
'name': item['name'],
|
||||||
|
'status': 'busy',
|
||||||
|
'original_status': item['status'],
|
||||||
|
'timeleft': str(timedelta(seconds = item['eta'])), # Is ETA in seconds??
|
||||||
|
})
|
||||||
|
|
||||||
|
return statuses
|
||||||
|
|
||||||
class TransmissionRPC(object):
|
class TransmissionRPC(object):
|
||||||
|
|
||||||
@@ -95,6 +185,7 @@ class TransmissionRPC(object):
|
|||||||
try:
|
try:
|
||||||
open_request = urllib2.urlopen(request)
|
open_request = urllib2.urlopen(request)
|
||||||
response = json.loads(open_request.read())
|
response = json.loads(open_request.read())
|
||||||
|
log.debug('request: %s', json.dumps(ojson))
|
||||||
log.debug('response: %s', json.dumps(response))
|
log.debug('response: %s', json.dumps(response))
|
||||||
if response['result'] == 'success':
|
if response['result'] == 'success':
|
||||||
log.debug('Transmission action successfull')
|
log.debug('Transmission action successfull')
|
||||||
@@ -144,3 +235,18 @@ class TransmissionRPC(object):
|
|||||||
arguments['ids'] = torrent_id
|
arguments['ids'] = torrent_id
|
||||||
post_data = {'arguments': arguments, 'method': 'torrent-set', 'tag': self.tag}
|
post_data = {'arguments': arguments, 'method': 'torrent-set', 'tag': self.tag}
|
||||||
return self._request(post_data)
|
return self._request(post_data)
|
||||||
|
|
||||||
|
def get_alltorrents(self, arguments):
|
||||||
|
post_data = {'arguments': arguments, 'method': 'torrent-get', 'tag': self.tag}
|
||||||
|
return self._request(post_data)
|
||||||
|
|
||||||
|
def stop_torrent(self, torrent_id, arguments):
|
||||||
|
arguments['ids'] = torrent_id
|
||||||
|
post_data = {'arguments': arguments, 'method': 'torrent-stop', 'tag': self.tag}
|
||||||
|
return self._request(post_data)
|
||||||
|
|
||||||
|
def remove_torrent(self, torrent_id, remove_local_data, arguments):
|
||||||
|
arguments['ids'] = torrent_id
|
||||||
|
arguments['delete-local-data'] = remove_local_data
|
||||||
|
post_data = {'arguments': arguments, 'method': 'torrent-remove', 'tag': self.tag}
|
||||||
|
return self._request(post_data)
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
from base64 import b16encode, b32decode
|
||||||
from bencode import bencode, bdecode
|
from bencode import bencode, bdecode
|
||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader, StatusList
|
||||||
from couchpotato.core.helpers.encoding import isInt, ss
|
from couchpotato.core.helpers.encoding import isInt, ss
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from multipartpost import MultipartPostHandler
|
from multipartpost import MultipartPostHandler
|
||||||
|
from datetime import timedelta
|
||||||
|
import os
|
||||||
import cookielib
|
import cookielib
|
||||||
import httplib
|
import httplib
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib
|
||||||
@@ -37,6 +41,7 @@ class uTorrent(Downloader):
|
|||||||
if not filedata and data.get('type') == 'torrent':
|
if not filedata and data.get('type') == 'torrent':
|
||||||
log.error('Failed sending torrent, no data')
|
log.error('Failed sending torrent, no data')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if data.get('type') == 'torrent_magnet':
|
if data.get('type') == 'torrent_magnet':
|
||||||
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
|
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
|
||||||
torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers)
|
torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers)
|
||||||
@@ -45,6 +50,10 @@ class uTorrent(Downloader):
|
|||||||
torrent_hash = sha1(bencode(info)).hexdigest().upper()
|
torrent_hash = sha1(bencode(info)).hexdigest().upper()
|
||||||
torrent_filename = self.createFileName(data, filedata, movie)
|
torrent_filename = self.createFileName(data, filedata, movie)
|
||||||
|
|
||||||
|
# Convert base 32 to hex
|
||||||
|
if len(torrent_hash) == 32:
|
||||||
|
torrent_hash = b16encode(b32decode(torrent_hash))
|
||||||
|
|
||||||
# Send request to uTorrent
|
# Send request to uTorrent
|
||||||
try:
|
try:
|
||||||
if not self.utorrent_api:
|
if not self.utorrent_api:
|
||||||
@@ -59,11 +68,99 @@ class uTorrent(Downloader):
|
|||||||
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
|
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
|
||||||
if self.conf('paused', default = 0):
|
if self.conf('paused', default = 0):
|
||||||
self.utorrent_api.pause_torrent(torrent_hash)
|
self.utorrent_api.pause_torrent(torrent_hash)
|
||||||
return True
|
return self.downloadReturnId(torrent_hash)
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
log.error('Failed to send torrent to uTorrent: %s', err)
|
log.error('Failed to send torrent to uTorrent: %s', err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getAllDownloadStatus(self):
|
||||||
|
|
||||||
|
log.debug('Checking uTorrent download status.')
|
||||||
|
|
||||||
|
# Load host from config and split out port.
|
||||||
|
host = self.conf('host').split(':')
|
||||||
|
if not isInt(host[1]):
|
||||||
|
log.error('Config properties are not filled in correctly, port is missing.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
|
||||||
|
except Exception, err:
|
||||||
|
log.error('Failed to get uTorrent object: %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
data = ''
|
||||||
|
try:
|
||||||
|
data = self.utorrent_api.get_status()
|
||||||
|
queue = json.loads(data)
|
||||||
|
if queue.get('error'):
|
||||||
|
log.error('Error getting data from uTorrent: %s', queue.get('error'))
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception, err:
|
||||||
|
log.error('Failed to get status from uTorrent: %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if queue.get('torrents', []) == []:
|
||||||
|
log.debug('Nothing in queue')
|
||||||
|
return False
|
||||||
|
|
||||||
|
statuses = StatusList(self)
|
||||||
|
download_folder = ''
|
||||||
|
settings_dict = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.utorrent_api.get_settings()
|
||||||
|
utorrent_settings = json.loads(data)
|
||||||
|
|
||||||
|
# Create settings dict
|
||||||
|
for item in utorrent_settings['settings']:
|
||||||
|
if item[1] == 0: # int
|
||||||
|
settings_dict[item[0]] = int(item[2] if not item[2].strip() == '' else '0')
|
||||||
|
elif item[1] == 1: # bool
|
||||||
|
settings_dict[item[0]] = True if item[2] == 'true' else False
|
||||||
|
elif item[1] == 2: # string
|
||||||
|
settings_dict[item[0]] = item[2]
|
||||||
|
log.debug('uTorrent settings: %s', settings_dict)
|
||||||
|
|
||||||
|
# Get the download path from the uTorrent settings
|
||||||
|
if settings_dict['dir_completed_download_flag']:
|
||||||
|
download_folder = settings_dict['dir_completed_download']
|
||||||
|
elif settings_dict['dir_active_download_flag']:
|
||||||
|
download_folder = settings_dict['dir_active_download']
|
||||||
|
else:
|
||||||
|
log.info('No download folder set in uTorrent. Please set a download folder')
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception, err:
|
||||||
|
log.error('Failed to get settings from uTorrent: %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get torrents
|
||||||
|
for item in queue.get('torrents', []):
|
||||||
|
|
||||||
|
# item[21] = Paused | Downloading | Seeding | Finished
|
||||||
|
status = 'busy'
|
||||||
|
if item[21] == 'Finished' or item[21] == 'Seeding':
|
||||||
|
status = 'completed'
|
||||||
|
|
||||||
|
if settings_dict['dir_add_label']:
|
||||||
|
release_folder = os.path.join(download_folder, item[11], item[2])
|
||||||
|
else:
|
||||||
|
release_folder = os.path.join(download_folder, item[2])
|
||||||
|
|
||||||
|
statuses.append({
|
||||||
|
'id': item[0],
|
||||||
|
'name': item[2],
|
||||||
|
'status': status,
|
||||||
|
'original_status': item[1],
|
||||||
|
'timeleft': str(timedelta(seconds = item[10])),
|
||||||
|
'folder': release_folder,
|
||||||
|
})
|
||||||
|
|
||||||
|
return statuses
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class uTorrentAPI(object):
|
class uTorrentAPI(object):
|
||||||
|
|
||||||
@@ -94,9 +191,7 @@ class uTorrentAPI(object):
|
|||||||
try:
|
try:
|
||||||
open_request = self.opener.open(request)
|
open_request = self.opener.open(request)
|
||||||
response = open_request.read()
|
response = open_request.read()
|
||||||
log.debug('response: %s', response)
|
|
||||||
if response:
|
if response:
|
||||||
log.debug('uTorrent action successfull')
|
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response)
|
log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response)
|
||||||
@@ -133,3 +228,11 @@ class uTorrentAPI(object):
|
|||||||
def pause_torrent(self, hash):
|
def pause_torrent(self, hash):
|
||||||
action = "action=pause&hash=%s" % hash
|
action = "action=pause&hash=%s" % hash
|
||||||
return self._request(action)
|
return self._request(action)
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
action = "list=1"
|
||||||
|
return self._request(action)
|
||||||
|
|
||||||
|
def get_settings(self):
|
||||||
|
action = "action=getsettings"
|
||||||
|
return self._request(action)
|
||||||
|
|||||||
+19
-23
@@ -16,10 +16,8 @@ def runHandler(name, handler, *args, **kwargs):
|
|||||||
|
|
||||||
def addEvent(name, handler, priority = 100):
|
def addEvent(name, handler, priority = 100):
|
||||||
|
|
||||||
if events.get(name):
|
if not events.get(name):
|
||||||
e = events[name]
|
events[name] = []
|
||||||
else:
|
|
||||||
e = events[name] = Event(name = name, threads = 10, exc_info = True, traceback = True, lock = threading.RLock())
|
|
||||||
|
|
||||||
def createHandle(*args, **kwargs):
|
def createHandle(*args, **kwargs):
|
||||||
|
|
||||||
@@ -35,7 +33,10 @@ def addEvent(name, handler, priority = 100):
|
|||||||
|
|
||||||
return h
|
return h
|
||||||
|
|
||||||
e.handle(createHandle, priority = priority)
|
events[name].append({
|
||||||
|
'handler': createHandle,
|
||||||
|
'priority': priority,
|
||||||
|
})
|
||||||
|
|
||||||
def removeEvent(name, handler):
|
def removeEvent(name, handler):
|
||||||
e = events[name]
|
e = events[name]
|
||||||
@@ -43,6 +44,12 @@ def removeEvent(name, handler):
|
|||||||
|
|
||||||
def fireEvent(name, *args, **kwargs):
|
def fireEvent(name, *args, **kwargs):
|
||||||
if not events.get(name): return
|
if not events.get(name): return
|
||||||
|
|
||||||
|
e = Event(name = name, threads = 10, asynch = kwargs.get('async', False), exc_info = True, traceback = True, lock = threading.RLock())
|
||||||
|
|
||||||
|
for event in events[name]:
|
||||||
|
e.handle(event['handler'], priority = event['priority'])
|
||||||
|
|
||||||
#log.debug('Firing event %s', name)
|
#log.debug('Firing event %s', name)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
@@ -52,6 +59,7 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
'single': False, # Return single handler
|
'single': False, # Return single handler
|
||||||
'merge': False, # Merge items
|
'merge': False, # Merge items
|
||||||
'in_order': False, # Fire them in specific order, waits for the other to finish
|
'in_order': False, # Fire them in specific order, waits for the other to finish
|
||||||
|
'async': False
|
||||||
}
|
}
|
||||||
|
|
||||||
# Do options
|
# Do options
|
||||||
@@ -62,13 +70,6 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
options[x] = val
|
options[x] = val
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
e = events[name]
|
|
||||||
|
|
||||||
# Lock this event
|
|
||||||
e.lock.acquire()
|
|
||||||
|
|
||||||
e.asynchronous = False
|
|
||||||
|
|
||||||
# Make sure only 1 event is fired at a time when order is wanted
|
# Make sure only 1 event is fired at a time when order is wanted
|
||||||
kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
|
kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
|
||||||
kwargs['event_return_on_result'] = options['single']
|
kwargs['event_return_on_result'] = options['single']
|
||||||
@@ -76,9 +77,6 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
# Fire
|
# Fire
|
||||||
result = e(*args, **kwargs)
|
result = e(*args, **kwargs)
|
||||||
|
|
||||||
# Release lock for this event
|
|
||||||
e.lock.release()
|
|
||||||
|
|
||||||
if options['single'] and not options['merge']:
|
if options['single'] and not options['merge']:
|
||||||
results = None
|
results = None
|
||||||
|
|
||||||
@@ -104,11 +102,14 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
|
|
||||||
# Merge
|
# Merge
|
||||||
if options['merge'] and len(results) > 0:
|
if options['merge'] and len(results) > 0:
|
||||||
|
|
||||||
# Dict
|
# Dict
|
||||||
if isinstance(results[0], dict):
|
if isinstance(results[0], dict):
|
||||||
|
results.reverse()
|
||||||
|
|
||||||
merged = {}
|
merged = {}
|
||||||
for result in results:
|
for result in results:
|
||||||
merged = mergeDicts(merged, result)
|
merged = mergeDicts(merged, result, prepend_list = True)
|
||||||
|
|
||||||
results = merged
|
results = merged
|
||||||
# Lists
|
# Lists
|
||||||
@@ -138,13 +139,8 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
log.error('%s: %s', (name, traceback.format_exc()))
|
log.error('%s: %s', (name, traceback.format_exc()))
|
||||||
|
|
||||||
def fireEventAsync(*args, **kwargs):
|
def fireEventAsync(*args, **kwargs):
|
||||||
try:
|
kwargs['async'] = True
|
||||||
my_thread = threading.Thread(target = fireEvent, args = args, kwargs = kwargs)
|
fireEvent(*args, **kwargs)
|
||||||
my_thread.setDaemon(True)
|
|
||||||
my_thread.start()
|
|
||||||
return True
|
|
||||||
except Exception, e:
|
|
||||||
log.error('%s: %s', (args[0], e))
|
|
||||||
|
|
||||||
def errorHandler(error):
|
def errorHandler(error):
|
||||||
etype, value, tb = error
|
etype, value, tb = error
|
||||||
|
|||||||
@@ -10,6 +10,20 @@ import sys
|
|||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
def link(src, dst):
|
||||||
|
if os.name == 'nt':
|
||||||
|
import ctypes
|
||||||
|
if ctypes.windll.kernel32.CreateHardLinkW(unicode(dst), unicode(src), 0) == 0: raise ctypes.WinError()
|
||||||
|
else:
|
||||||
|
os.link(src, dst)
|
||||||
|
|
||||||
|
def symlink(src, dst):
|
||||||
|
if os.name == 'nt':
|
||||||
|
import ctypes
|
||||||
|
if ctypes.windll.kernel32.CreateSymbolicLinkW(unicode(dst), unicode(src), 1 if os.path.isdir(src) else 0) in [0, 1280]: raise ctypes.WinError()
|
||||||
|
else:
|
||||||
|
os.symlink(src, dst)
|
||||||
|
|
||||||
def getUserDir():
|
def getUserDir():
|
||||||
try:
|
try:
|
||||||
import pwd
|
import pwd
|
||||||
@@ -53,7 +67,7 @@ def getDataDir():
|
|||||||
def isDict(object):
|
def isDict(object):
|
||||||
return isinstance(object, dict)
|
return isinstance(object, dict)
|
||||||
|
|
||||||
def mergeDicts(a, b):
|
def mergeDicts(a, b, prepend_list = False):
|
||||||
assert isDict(a), isDict(b)
|
assert isDict(a), isDict(b)
|
||||||
dst = a.copy()
|
dst = a.copy()
|
||||||
|
|
||||||
@@ -67,7 +81,7 @@ def mergeDicts(a, b):
|
|||||||
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]))
|
stack.append((current_dst[key], current_src[key]))
|
||||||
elif isinstance(current_src[key], list) and isinstance(current_dst[key], list):
|
elif isinstance(current_src[key], list) and isinstance(current_dst[key], list):
|
||||||
current_dst[key].extend(current_src[key])
|
current_dst[key] = current_src[key] + current_dst[key] if prepend_list else current_dst[key] + current_src[key]
|
||||||
current_dst[key] = removeListDuplicates(current_dst[key])
|
current_dst[key] = removeListDuplicates(current_dst[key])
|
||||||
else:
|
else:
|
||||||
current_dst[key] = current_src[key]
|
current_dst[key] = current_src[key]
|
||||||
@@ -168,4 +182,4 @@ def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
|
|||||||
return ''.join(random.choice(chars) for x in range(size))
|
return ''.join(random.choice(chars) for x in range(size))
|
||||||
|
|
||||||
def splitString(str, split_on = ','):
|
def splitString(str, split_on = ','):
|
||||||
return [x.strip() for x in str.split(split_on)]
|
return [x.strip() for x in str.split(split_on)] if str else []
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from migrate.changeset.schema import create_column
|
||||||
|
from sqlalchemy.schema import MetaData, Column, Table, Index
|
||||||
|
from sqlalchemy.types import Integer
|
||||||
|
|
||||||
|
meta = MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(migrate_engine):
|
||||||
|
meta.bind = migrate_engine
|
||||||
|
|
||||||
|
# Change release, add last_edit and index
|
||||||
|
last_edit_column = Column('last_edit', Integer)
|
||||||
|
release = Table('release', meta, last_edit_column)
|
||||||
|
|
||||||
|
create_column(last_edit_column, release)
|
||||||
|
Index('ix_release_last_edit', release.c.last_edit).create()
|
||||||
|
|
||||||
|
# Change movie last_edit
|
||||||
|
last_edit_column = Column('last_edit', Integer)
|
||||||
|
movie = Table('movie', meta, last_edit_column)
|
||||||
|
Index('ix_movie_last_edit', movie.c.last_edit).create()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(migrate_engine):
|
||||||
|
pass
|
||||||
@@ -2,13 +2,15 @@ from couchpotato.api import addApiView
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.request import jsonified
|
from couchpotato.core.helpers.request import jsonified
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.providers.base import Provider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Notification(Plugin):
|
class Notification(Provider):
|
||||||
|
|
||||||
|
type = 'notification'
|
||||||
|
|
||||||
default_title = Env.get('appname')
|
default_title = Env.get('appname')
|
||||||
test_message = 'ZOMG Lazors Pewpewpew!'
|
test_message = 'ZOMG Lazors Pewpewpew!'
|
||||||
@@ -16,11 +18,12 @@ class Notification(Plugin):
|
|||||||
listen_to = [
|
listen_to = [
|
||||||
'renamer.after', 'movie.snatched',
|
'renamer.after', 'movie.snatched',
|
||||||
'updater.available', 'updater.updated',
|
'updater.available', 'updater.updated',
|
||||||
|
'core.message',
|
||||||
]
|
]
|
||||||
dont_listen_to = []
|
dont_listen_to = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('notify.%s' % self.getName().lower(), self.notify)
|
addEvent('notify.%s' % self.getName().lower(), self._notify)
|
||||||
|
|
||||||
addApiView(self.testNotifyName(), self.test)
|
addApiView(self.testNotifyName(), self.test)
|
||||||
|
|
||||||
@@ -33,10 +36,17 @@ class Notification(Plugin):
|
|||||||
def notify(message = None, group = {}, data = None):
|
def notify(message = None, group = {}, data = None):
|
||||||
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
|
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
|
||||||
return
|
return
|
||||||
return self.notify(message = message, data = data if data else group, listener = listener)
|
return self._notify(message = message, data = data if data else group, listener = listener)
|
||||||
|
|
||||||
return notify
|
return notify
|
||||||
|
|
||||||
|
def getNotificationImage(self, size = 'small'):
|
||||||
|
return 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/notify.couch.%s.png' % size
|
||||||
|
|
||||||
|
def _notify(self, *args, **kwargs):
|
||||||
|
if self.isEnabled():
|
||||||
|
return self.notify(*args, **kwargs)
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -46,7 +56,7 @@ class Notification(Plugin):
|
|||||||
|
|
||||||
log.info('Sending test to %s', test_type)
|
log.info('Sending test to %s', test_type)
|
||||||
|
|
||||||
success = self.notify(
|
success = self._notify(
|
||||||
message = self.test_message,
|
message = self.test_message,
|
||||||
data = {},
|
data = {},
|
||||||
listener = 'test'
|
listener = 'test'
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class Boxcar(Notification):
|
|||||||
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = message.strip()
|
message = message.strip()
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView, addNonBlockApiView
|
from couchpotato.api import addApiView, addNonBlockApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
from couchpotato.core.helpers.variable import tryInt, splitString
|
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
from couchpotato.core.settings.model import Notification as Notif
|
from couchpotato.core.settings.model import Notification as Notif
|
||||||
|
from couchpotato.environment import Env
|
||||||
from sqlalchemy.sql.expression import or_
|
from sqlalchemy.sql.expression import or_
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -21,11 +22,6 @@ class CoreNotifier(Notification):
|
|||||||
messages = []
|
messages = []
|
||||||
listeners = []
|
listeners = []
|
||||||
|
|
||||||
listen_to = [
|
|
||||||
'renamer.after', 'movie.snatched',
|
|
||||||
'updater.available', 'updater.updated',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CoreNotifier, self).__init__()
|
super(CoreNotifier, self).__init__()
|
||||||
|
|
||||||
@@ -54,7 +50,10 @@ class CoreNotifier(Notification):
|
|||||||
addNonBlockApiView('notification.listener', (self.addListener, self.removeListener))
|
addNonBlockApiView('notification.listener', (self.addListener, self.removeListener))
|
||||||
addApiView('notification.listener', self.listener)
|
addApiView('notification.listener', self.listener)
|
||||||
|
|
||||||
|
fireEvent('schedule.interval', 'core.check_messages', self.checkMessages, hours = 12, single = True)
|
||||||
|
|
||||||
addEvent('app.load', self.clean)
|
addEvent('app.load', self.clean)
|
||||||
|
addEvent('app.load', self.checkMessages)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
@@ -112,6 +111,22 @@ class CoreNotifier(Notification):
|
|||||||
'notifications': notifications
|
'notifications': notifications
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def checkMessages(self):
|
||||||
|
|
||||||
|
prop_name = 'messages.last_check'
|
||||||
|
last_check = tryInt(Env.prop(prop_name, default = 0))
|
||||||
|
|
||||||
|
messages = fireEvent('cp.messages', last_check = last_check, single = True)
|
||||||
|
|
||||||
|
for message in messages:
|
||||||
|
if message.get('time') > last_check:
|
||||||
|
fireEvent('core.message', message = message.get('message'), data = message)
|
||||||
|
|
||||||
|
if last_check < message.get('time'):
|
||||||
|
last_check = message.get('time')
|
||||||
|
|
||||||
|
Env.prop(prop_name, value = last_check)
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|||||||
@@ -21,20 +21,17 @@ var NotificationBase = new Class({
|
|||||||
App.addEvent('load', function(){
|
App.addEvent('load', function(){
|
||||||
|
|
||||||
App.block.notification = new Block.Menu(self, {
|
App.block.notification = new Block.Menu(self, {
|
||||||
|
'button_class': 'icon2.eye-open',
|
||||||
'class': 'notification_menu',
|
'class': 'notification_menu',
|
||||||
'onOpen': self.markAsRead.bind(self)
|
'onOpen': self.markAsRead.bind(self)
|
||||||
})
|
})
|
||||||
$(App.block.notification).inject(App.getBlock('search'), 'after');
|
$(App.block.notification).inject(App.getBlock('search'), 'after');
|
||||||
self.badge = new Element('div.badge').inject(App.block.notification, 'top').hide();
|
self.badge = new Element('div.badge').inject(App.block.notification, 'top').hide();
|
||||||
|
|
||||||
/* App.getBlock('notification').addLink(new Element('a.more', {
|
|
||||||
'href': App.createUrl('notifications'),
|
|
||||||
'text': 'Show older notifications'
|
|
||||||
})); */
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEvent('load', function(){
|
window.addEvent('load', function(){
|
||||||
self.startInterval.delay(Browser.safari ? 100 : 0, self)
|
self.startInterval.delay($(window).getSize().x <= 480 ? 2000 : 300, self)
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -47,14 +44,19 @@ var NotificationBase = new Class({
|
|||||||
|
|
||||||
result.el = App.getBlock('notification').addLink(
|
result.el = App.getBlock('notification').addLink(
|
||||||
new Element('span.'+(result.read ? 'read' : '' )).adopt(
|
new Element('span.'+(result.read ? 'read' : '' )).adopt(
|
||||||
new Element('span.message', {'text': result.message}),
|
new Element('span.message', {'html': result.message}),
|
||||||
new Element('span.added', {'text': added.timeDiffInWords(), 'title': added})
|
new Element('span.added', {'text': added.timeDiffInWords(), 'title': added})
|
||||||
)
|
)
|
||||||
, 'top');
|
, 'top');
|
||||||
self.notifications.include(result);
|
self.notifications.include(result);
|
||||||
|
|
||||||
if(!result.read)
|
if(result.data.important !== undefined && !result.read){
|
||||||
|
var sticky = true
|
||||||
|
App.fireEvent('message', [result.message, sticky, result])
|
||||||
|
}
|
||||||
|
else if(!result.read){
|
||||||
self.setBadge(self.notifications.filter(function(n){ return !n.read}).length)
|
self.setBadge(self.notifications.filter(function(n){ return !n.read}).length)
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -64,20 +66,26 @@ var NotificationBase = new Class({
|
|||||||
self.badge[value ? 'show' : 'hide']()
|
self.badge[value ? 'show' : 'hide']()
|
||||||
},
|
},
|
||||||
|
|
||||||
markAsRead: function(){
|
markAsRead: function(force_ids){
|
||||||
var self = this;
|
var self = this,
|
||||||
|
ids = force_ids;
|
||||||
|
|
||||||
var rn = self.notifications.filter(function(n){
|
if(!force_ids) {
|
||||||
return !n.read
|
var rn = self.notifications.filter(function(n){
|
||||||
})
|
return !n.read && n.data.important === undefined
|
||||||
|
})
|
||||||
|
|
||||||
var ids = []
|
var ids = []
|
||||||
rn.each(function(n){
|
rn.each(function(n){
|
||||||
ids.include(n.id)
|
ids.include(n.id)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(ids.length > 0)
|
if(ids.length > 0)
|
||||||
Api.request('notification.markread', {
|
Api.request('notification.markread', {
|
||||||
|
'data': {
|
||||||
|
'ids': ids.join(',')
|
||||||
|
},
|
||||||
'onSuccess': function(){
|
'onSuccess': function(){
|
||||||
self.setBadge('')
|
self.setBadge('')
|
||||||
}
|
}
|
||||||
@@ -93,11 +101,20 @@ var NotificationBase = new Class({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Api.request('notification.listener', {
|
self.request = Api.request('notification.listener', {
|
||||||
'data': {'init':true},
|
'data': {'init':true},
|
||||||
'onSuccess': self.processData.bind(self)
|
'onSuccess': self.processData.bind(self)
|
||||||
}).send()
|
}).send()
|
||||||
|
|
||||||
|
setInterval(function(){
|
||||||
|
|
||||||
|
if(self.request && self.request.isRunning()){
|
||||||
|
self.request.cancel();
|
||||||
|
self.startPoll()
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 120000);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startPoll: function(){
|
startPoll: function(){
|
||||||
@@ -143,26 +160,41 @@ var NotificationBase = new Class({
|
|||||||
self.startPoll()
|
self.startPoll()
|
||||||
},
|
},
|
||||||
|
|
||||||
showMessage: function(message){
|
showMessage: function(message, sticky, data){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!self.message_container)
|
if(!self.message_container)
|
||||||
self.message_container = new Element('div.messages').inject(document.body);
|
self.message_container = new Element('div.messages').inject(document.body);
|
||||||
|
|
||||||
var new_message = new Element('div.message', {
|
var new_message = new Element('div', {
|
||||||
'text': message
|
'class': 'message' + (sticky ? ' sticky' : ''),
|
||||||
}).inject(self.message_container);
|
'html': message
|
||||||
|
}).inject(self.message_container, 'top');
|
||||||
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
new_message.addClass('show')
|
new_message.addClass('show')
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
setTimeout(function(){
|
var hide_message = function(){
|
||||||
new_message.addClass('hide')
|
new_message.addClass('hide')
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
new_message.destroy();
|
new_message.destroy();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}, 4000);
|
}
|
||||||
|
|
||||||
|
if(sticky)
|
||||||
|
new_message.grab(
|
||||||
|
new Element('a.close.icon2', {
|
||||||
|
'events': {
|
||||||
|
'click': function(){
|
||||||
|
self.markAsRead([data.id]);
|
||||||
|
hide_message();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
else
|
||||||
|
setTimeout(hide_message, 4000);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -178,11 +210,14 @@ var NotificationBase = new Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
addTestButton: function(fieldset, plugin_name){
|
addTestButton: function(fieldset, plugin_name){
|
||||||
var self = this;
|
var self = this,
|
||||||
|
button_name = self.testButtonName(fieldset);
|
||||||
|
|
||||||
|
if(button_name.contains('Notifications')) return;
|
||||||
|
|
||||||
new Element('.ctrlHolder.test_button').adopt(
|
new Element('.ctrlHolder.test_button').adopt(
|
||||||
new Element('a.button', {
|
new Element('a.button', {
|
||||||
'text': self.testButtonName(fieldset),
|
'text': button_name,
|
||||||
'events': {
|
'events': {
|
||||||
'click': function(){
|
'click': function(){
|
||||||
var button = fieldset.getElement('.test_button .button');
|
var button = fieldset.getElement('.test_button .button');
|
||||||
@@ -191,7 +226,7 @@ var NotificationBase = new Class({
|
|||||||
Api.request('notify.'+plugin_name+'.test', {
|
Api.request('notify.'+plugin_name+'.test', {
|
||||||
'onComplete': function(json){
|
'onComplete': function(json){
|
||||||
|
|
||||||
button.set('text', self.testButtonName(fieldset));
|
button.set('text', button_name);
|
||||||
|
|
||||||
if(json.success){
|
if(json.success){
|
||||||
var message = new Element('span.success', {
|
var message = new Element('span.success', {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
|
from couchpotato.core.helpers.variable import splitString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
@@ -11,7 +12,6 @@ log = CPLog(__name__)
|
|||||||
class Email(Notification):
|
class Email(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
# Extract all the settings from settings
|
# Extract all the settings from settings
|
||||||
from_address = self.conf('from')
|
from_address = self.conf('from')
|
||||||
@@ -39,7 +39,7 @@ class Email(Notification):
|
|||||||
|
|
||||||
# Send the e-mail
|
# Send the e-mail
|
||||||
log.debug("Sending the email")
|
log.debug("Sending the email")
|
||||||
mailserver.sendmail(from_address, to_address, message.as_string())
|
mailserver.sendmail(from_address, splitString(to_address), message.as_string())
|
||||||
|
|
||||||
# Close the SMTP connection
|
# Close the SMTP connection
|
||||||
mailserver.quit()
|
mailserver.quit()
|
||||||
@@ -49,6 +49,5 @@ class Email(Notification):
|
|||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
log.error('E-mail failed: %s', traceback.format_exc())
|
log.error('E-mail failed: %s', traceback.format_exc())
|
||||||
return False
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class Growl(Notification):
|
|||||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Notifo(Notification):
|
|||||||
url = 'https://api.notifo.com/v1/send_notification'
|
url = 'https://api.notifo.com/v1/send_notification'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
params = {
|
params = {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ log = CPLog(__name__)
|
|||||||
class NotifyMyAndroid(Notification):
|
class NotifyMyAndroid(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
nma = pynma.PyNMA()
|
nma = pynma.PyNMA()
|
||||||
keys = splitString(self.conf('api_key'))
|
keys = splitString(self.conf('api_key'))
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ log = CPLog(__name__)
|
|||||||
class NotifyMyWP(Notification):
|
class NotifyMyWP(Notification):
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
keys = splitString(self.conf('api_key'))
|
keys = splitString(self.conf('api_key'))
|
||||||
p = PyNMWP(keys, self.conf('dev_key'))
|
p = PyNMWP(keys, self.conf('dev_key'))
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ class Plex(Notification):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
|
hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
|
||||||
successful = 0
|
successful = 0
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Prowl(Notification):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'apikey': self.conf('api_key'),
|
'apikey': self.conf('api_key'),
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
from .main import Pushalot
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Pushalot()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'pushalot',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'notifications',
|
||||||
|
'list': 'notification_providers',
|
||||||
|
'name': 'pushalot',
|
||||||
|
'description': 'for Windows Phone and Windows 8',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'auth_token',
|
||||||
|
'label': 'Auth Token',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'silent',
|
||||||
|
'label': 'Silent',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'description': 'Don\'t send Toast notifications. Only update Live Tile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'important',
|
||||||
|
'label': 'High Priority',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'description': 'Send message with High priority.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'on_snatch',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Also send message when movie is snatched.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.notifications.base import Notification
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
class Pushalot(Notification):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'api': 'https://pushalot.com/api/sendmessage'
|
||||||
|
}
|
||||||
|
|
||||||
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'AuthorizationToken': self.conf('auth_token'),
|
||||||
|
'Title': self.default_title,
|
||||||
|
'Body': toUnicode(message),
|
||||||
|
'IsImportant': self.conf('important'),
|
||||||
|
'IsSilent': self.conf('silent'),
|
||||||
|
'Image': toUnicode(self.getNotificationImage('medium') + '?1'),
|
||||||
|
'Source': toUnicode(self.default_title)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Content-type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.urlopen(self.urls['api'], headers = headers, params = data, multipart = True, show_error = False)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
log.error('PushAlot failed: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
return False
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
|
from couchpotato.core.helpers.variable import getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
from httplib import HTTPSConnection
|
from httplib import HTTPSConnection
|
||||||
@@ -11,21 +12,26 @@ class Pushover(Notification):
|
|||||||
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
http_handler = HTTPSConnection("api.pushover.net:443")
|
http_handler = HTTPSConnection("api.pushover.net:443")
|
||||||
|
|
||||||
data = {
|
api_data = {
|
||||||
'user': self.conf('user_key'),
|
'user': self.conf('user_key'),
|
||||||
'token': self.app_token,
|
'token': self.app_token,
|
||||||
'message': toUnicode(message),
|
'message': toUnicode(message),
|
||||||
'priority': self.conf('priority')
|
'priority': self.conf('priority'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data and data.get('library'):
|
||||||
|
api_data.extend({
|
||||||
|
'url': toUnicode('http://www.imdb.com/title/%s/' % data['library']['identifier']),
|
||||||
|
'url_title': toUnicode('%s on IMDb' % getTitle(data['library'])),
|
||||||
|
})
|
||||||
|
|
||||||
http_handler.request('POST',
|
http_handler.request('POST',
|
||||||
"/1/messages.json",
|
"/1/messages.json",
|
||||||
headers = {'Content-type': 'application/x-www-form-urlencoded'},
|
headers = {'Content-type': 'application/x-www-form-urlencoded'},
|
||||||
body = tryUrlencode(data)
|
body = tryUrlencode(api_data)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = http_handler.getresponse()
|
response = http_handler.getresponse()
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class Toasty(Notification):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'title': self.default_title,
|
'title': self.default_title,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from .main import Trakt
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Trakt()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'trakt',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'notifications',
|
||||||
|
'list': 'notification_providers',
|
||||||
|
'name': 'trakt',
|
||||||
|
'label': 'Trakt',
|
||||||
|
'description': 'add movies to your collection once downloaded. Fill in your username and password in the <a href="../automation/">Automation Trakt settings</a>',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'notification_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'remove_watchlist_enabled',
|
||||||
|
'label': 'Remove from watchlist',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.notifications.base import Notification
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
class Trakt(Notification):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'base': 'http://api.trakt.tv/%s',
|
||||||
|
'library': 'movie/library/%s',
|
||||||
|
'unwatchlist': 'movie/unwatchlist/%s',
|
||||||
|
}
|
||||||
|
|
||||||
|
listen_to = ['movie.downloaded']
|
||||||
|
|
||||||
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
'username': self.conf('automation_username'),
|
||||||
|
'password' : self.conf('automation_password'),
|
||||||
|
'movies': [{
|
||||||
|
'imdb_id': data['library']['identifier'],
|
||||||
|
'title': data['library']['titles'][0]['title'],
|
||||||
|
'year': data['library']['year']
|
||||||
|
}] if data else []
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.call((self.urls['library'] % self.conf('automation_api_key')), post_data)
|
||||||
|
if self.conf('remove_watchlist_enabled'):
|
||||||
|
result = result and self.call((self.urls['unwatchlist'] % self.conf('automation_api_key')), post_data)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def call(self, method_url, post_data):
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.getJsonData(self.urls['base'] % method_url, params = post_data, cache_timeout = 1)
|
||||||
|
if response:
|
||||||
|
if response.get('status') == "success":
|
||||||
|
log.info('Successfully called Trakt')
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
log.error('Failed to call trakt, check your login.')
|
||||||
|
return False
|
||||||
@@ -32,7 +32,6 @@ class Twitter(Notification):
|
|||||||
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
|
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
|
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ class XBMC(Notification):
|
|||||||
|
|
||||||
listen_to = ['renamer.after']
|
listen_to = ['renamer.after']
|
||||||
use_json_notifications = {}
|
use_json_notifications = {}
|
||||||
couch_logo_url = 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/xbmc-notify.png'
|
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
|
||||||
|
|
||||||
hosts = splitString(self.conf('host'))
|
hosts = splitString(self.conf('host'))
|
||||||
|
|
||||||
@@ -28,7 +26,7 @@ class XBMC(Notification):
|
|||||||
|
|
||||||
if self.use_json_notifications.get(host):
|
if self.use_json_notifications.get(host):
|
||||||
response = self.request(host, [
|
response = self.request(host, [
|
||||||
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.couch_logo_url}),
|
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
|
||||||
('VideoLibrary.Scan', {}),
|
('VideoLibrary.Scan', {}),
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
@@ -90,7 +88,7 @@ class XBMC(Notification):
|
|||||||
self.use_json_notifications[host] = True
|
self.use_json_notifications[host] = True
|
||||||
|
|
||||||
# send the text message
|
# send the text message
|
||||||
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image':self.couch_logo_url})])
|
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
|
||||||
for result in resp:
|
for result in resp:
|
||||||
if (result.get('result') and result['result'] == 'OK'):
|
if (result.get('result') and result['result'] == 'OK'):
|
||||||
log.debug('Message delivered successfully!')
|
log.debug('Message delivered successfully!')
|
||||||
@@ -113,7 +111,7 @@ class XBMC(Notification):
|
|||||||
server = 'http://%s/xbmcCmds/' % host
|
server = 'http://%s/xbmcCmds/' % host
|
||||||
|
|
||||||
# Notification(title, message [, timeout , image])
|
# Notification(title, message [, timeout , image])
|
||||||
cmd = "xbmcHttp?command=ExecBuiltIn(Notification(%s,%s,'',%s))" % (urllib.quote(data['title']), urllib.quote(data['message']), urllib.quote(self.couch_logo_url))
|
cmd = "xbmcHttp?command=ExecBuiltIn(Notification(%s,%s,'',%s))" % (urllib.quote(data['title']), urllib.quote(data['message']), urllib.quote(self.getNotificationImage('medium')))
|
||||||
server += cmd
|
server += cmd
|
||||||
|
|
||||||
# I have no idea what to set to, just tried text/plain and seems to be working :)
|
# I have no idea what to set to, just tried text/plain and seems to be working :)
|
||||||
|
|||||||
@@ -36,6 +36,20 @@ config = [{
|
|||||||
'unit': 'hours',
|
'unit': 'hours',
|
||||||
'description': 'hours',
|
'description': 'hours',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'required_genres',
|
||||||
|
'label': 'Required Genres',
|
||||||
|
'default': '',
|
||||||
|
'placeholder': 'Example: Action, Crime & Drama',
|
||||||
|
'description': 'Ignore movies that don\'t contain at least one set of genres. Sets are separated by "," and each word within a set must be separated with "&"'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'ignored_genres',
|
||||||
|
'label': 'Ignored Genres',
|
||||||
|
'default': '',
|
||||||
|
'placeholder': 'Example: Horror, Comedy & Drama & Romance',
|
||||||
|
'description': 'Ignore movies that contain at least one set of genres. Sets work the same as above.'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -10,11 +10,16 @@ class Automation(Plugin):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
fireEvent('schedule.interval', 'automation.add_movies', self.addMovies, hours = self.conf('hour', default = 12))
|
addEvent('app.load', self.setCrons)
|
||||||
|
|
||||||
if not Env.get('dev'):
|
if not Env.get('dev'):
|
||||||
addEvent('app.load', self.addMovies)
|
addEvent('app.load', self.addMovies)
|
||||||
|
|
||||||
|
addEvent('setting.save.automation.hour.after', self.setCrons)
|
||||||
|
|
||||||
|
def setCrons(self):
|
||||||
|
fireEvent('schedule.interval', 'automation.add_movies', self.addMovies, hours = self.conf('hour', default = 12))
|
||||||
|
|
||||||
def addMovies(self):
|
def addMovies(self):
|
||||||
|
|
||||||
movies = fireEvent('automation.get_movies', merge = True)
|
movies = fireEvent('automation.get_movies', merge = True)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class Plugin(object):
|
|||||||
for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')):
|
for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')):
|
||||||
ext = getExt(f)
|
ext = getExt(f)
|
||||||
if ext in ['js', 'css']:
|
if ext in ['js', 'css']:
|
||||||
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f))
|
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
|
||||||
|
|
||||||
def showStatic(self, filename):
|
def showStatic(self, filename):
|
||||||
d = os.path.join(self.plugin_path, 'static')
|
d = os.path.join(self.plugin_path, 'static')
|
||||||
@@ -240,7 +240,6 @@ class Plugin(object):
|
|||||||
del kwargs['cache_timeout']
|
del kwargs['cache_timeout']
|
||||||
|
|
||||||
data = self.urlopen(url, **kwargs)
|
data = self.urlopen(url, **kwargs)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
self.setCache(cache_key, data, timeout = cache_timeout)
|
self.setCache(cache_key, data, timeout = cache_timeout)
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ if os.name == 'nt':
|
|||||||
raise ImportError("Missing the win32file module, which is a part of the prerequisite \
|
raise ImportError("Missing the win32file module, which is a part of the prerequisite \
|
||||||
pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/");
|
pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/");
|
||||||
else:
|
else:
|
||||||
import win32file
|
import win32file #@UnresolvedImport
|
||||||
|
|
||||||
class FileBrowser(Plugin):
|
class FileBrowser(Plugin):
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ class FileBrowser(Plugin):
|
|||||||
|
|
||||||
def has_hidden_attribute(self, filepath):
|
def has_hidden_attribute(self, filepath):
|
||||||
try:
|
try:
|
||||||
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath))
|
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath)) #@UndefinedVariable
|
||||||
assert attrs != -1
|
assert attrs != -1
|
||||||
result = bool(attrs & 2)
|
result = bool(attrs & 2)
|
||||||
except (AttributeError, AssertionError):
|
except (AttributeError, AssertionError):
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from .main import Dashboard
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Dashboard()
|
||||||
|
|
||||||
|
config = []
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
from couchpotato import get_session
|
||||||
|
from couchpotato.api import addApiView
|
||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.request import jsonified, getParams
|
||||||
|
from couchpotato.core.helpers.variable import splitString, tryInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
from couchpotato.core.settings.model import Movie
|
||||||
|
from sqlalchemy.orm import joinedload_all
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Dashboard(Plugin):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
addApiView('dashboard.suggestions', self.suggestView)
|
||||||
|
addApiView('dashboard.soon', self.getSoonView)
|
||||||
|
|
||||||
|
def newSuggestions(self):
|
||||||
|
|
||||||
|
movies = fireEvent('movie.list', status = ['active', 'done'], limit_offset = (20, 0), single = True)
|
||||||
|
movie_identifiers = [m['library']['identifier'] for m in movies[1]]
|
||||||
|
|
||||||
|
ignored_movies = fireEvent('movie.list', status = ['ignored', 'deleted'], limit_offset = (100, 0), single = True)
|
||||||
|
ignored_identifiers = [m['library']['identifier'] for m in ignored_movies[1]]
|
||||||
|
|
||||||
|
suggestions = fireEvent('movie.suggest', movies = movie_identifiers, ignore = ignored_identifiers, single = True)
|
||||||
|
suggest_status = fireEvent('status.get', 'suggest', single = True)
|
||||||
|
|
||||||
|
for suggestion in suggestions:
|
||||||
|
fireEvent('movie.add', params = {'identifier': suggestion}, force_readd = False, search_after = False, status_id = suggest_status.get('id'))
|
||||||
|
|
||||||
|
def suggestView(self):
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
|
||||||
|
movies = db.query(Movie).limit(20).all()
|
||||||
|
identifiers = [m.library.identifier for m in movies]
|
||||||
|
|
||||||
|
suggestions = fireEvent('movie.suggest', movies = identifiers, single = True)
|
||||||
|
|
||||||
|
return jsonified({
|
||||||
|
'result': True,
|
||||||
|
'suggestions': suggestions
|
||||||
|
})
|
||||||
|
|
||||||
|
def getSoonView(self):
|
||||||
|
|
||||||
|
params = getParams()
|
||||||
|
db = get_session()
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
# Get profiles first, determine pre or post theater
|
||||||
|
profiles = fireEvent('profile.all', single = True)
|
||||||
|
qualities = fireEvent('quality.all', single = True)
|
||||||
|
pre_releases = fireEvent('quality.pre_releases', single = True)
|
||||||
|
|
||||||
|
id_pre = {}
|
||||||
|
for quality in qualities:
|
||||||
|
id_pre[quality.get('id')] = quality.get('identifier') in pre_releases
|
||||||
|
|
||||||
|
# See what the profile contain and cache it
|
||||||
|
profile_pre = {}
|
||||||
|
for profile in profiles:
|
||||||
|
contains = {}
|
||||||
|
for profile_type in profile.get('types', []):
|
||||||
|
contains['theater' if id_pre.get(profile_type.get('quality_id')) else 'dvd'] = True
|
||||||
|
|
||||||
|
profile_pre[profile.get('id')] = contains
|
||||||
|
|
||||||
|
# Get all active movies
|
||||||
|
active_status = fireEvent('status.get', 'active', single = True)
|
||||||
|
subq = db.query(Movie).filter(Movie.status_id == active_status.get('id')).subquery()
|
||||||
|
|
||||||
|
q = db.query(Movie).join((subq, subq.c.id == Movie.id)) \
|
||||||
|
.options(joinedload_all('releases')) \
|
||||||
|
.options(joinedload_all('profile.types')) \
|
||||||
|
.options(joinedload_all('library.titles')) \
|
||||||
|
.options(joinedload_all('library.files')) \
|
||||||
|
.options(joinedload_all('status')) \
|
||||||
|
.options(joinedload_all('files'))
|
||||||
|
|
||||||
|
# Add limit
|
||||||
|
limit_offset = params.get('limit_offset')
|
||||||
|
limit = 12
|
||||||
|
if limit_offset:
|
||||||
|
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||||
|
limit = tryInt(splt[0])
|
||||||
|
|
||||||
|
all_movies = q.all()
|
||||||
|
|
||||||
|
if params.get('random', False):
|
||||||
|
random.shuffle(all_movies)
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
for movie in all_movies:
|
||||||
|
pp = profile_pre.get(movie.profile.id)
|
||||||
|
eta = movie.library.info.get('release_date', {}) or {}
|
||||||
|
coming_soon = False
|
||||||
|
|
||||||
|
# Theater quality
|
||||||
|
if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, single = True):
|
||||||
|
coming_soon = True
|
||||||
|
if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, single = True):
|
||||||
|
coming_soon = True
|
||||||
|
|
||||||
|
|
||||||
|
if coming_soon:
|
||||||
|
temp = movie.to_dict({
|
||||||
|
'profile': {'types': {}},
|
||||||
|
'releases': {'files':{}, 'info': {}},
|
||||||
|
'library': {'titles': {}, 'files':{}},
|
||||||
|
'files': {},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Don't list older movies
|
||||||
|
if ((not params.get('late') and (not eta.get('dvd') or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \
|
||||||
|
(params.get('late') and eta.get('dvd') and eta.get('dvd') < (now - 2419200))):
|
||||||
|
movies.append(temp)
|
||||||
|
|
||||||
|
if len(movies) >= limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
return jsonified({
|
||||||
|
'success': True,
|
||||||
|
'empty': len(movies) == 0,
|
||||||
|
'movies': movies,
|
||||||
|
})
|
||||||
|
|
||||||
|
getLateView = getSoonView
|
||||||
@@ -9,6 +9,8 @@ from couchpotato.core.plugins.base import Plugin
|
|||||||
from couchpotato.core.plugins.scanner.main import Scanner
|
from couchpotato.core.plugins.scanner.main import Scanner
|
||||||
from couchpotato.core.settings.model import FileType, File
|
from couchpotato.core.settings.model import FileType, File
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
from flask.helpers import send_file
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
import os.path
|
import os.path
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@@ -71,7 +73,7 @@ class FileManager(Plugin):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
|
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
|
||||||
for filename in walk_files:
|
for filename in walk_files:
|
||||||
if root == python_cache: continue
|
if root == python_cache or 'minified' in filename or 'version' in filename: continue
|
||||||
file_path = os.path.join(root, filename)
|
file_path = os.path.join(root, filename)
|
||||||
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
|
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
|
||||||
if not f:
|
if not f:
|
||||||
@@ -81,11 +83,13 @@ class FileManager(Plugin):
|
|||||||
|
|
||||||
def showCacheFile(self, filename = ''):
|
def showCacheFile(self, filename = ''):
|
||||||
|
|
||||||
cache_dir = Env.get('cache_dir')
|
file_path = os.path.join(Env.get('cache_dir'), os.path.basename(filename))
|
||||||
filename = os.path.basename(filename)
|
|
||||||
|
|
||||||
from flask.helpers import send_from_directory
|
if not os.path.isfile(file_path):
|
||||||
return send_from_directory(cache_dir, filename)
|
log.error('File "%s" not found', file_path)
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
|
return send_file(file_path, conditional = True)
|
||||||
|
|
||||||
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = {}):
|
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = {}):
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ var File = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!file){
|
if(!file){
|
||||||
|
self.empty = true;
|
||||||
self.el = new Element('div');
|
self.el = new Element('div');
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class LibraryPlugin(Plugin):
|
|||||||
|
|
||||||
title = LibraryTitle(
|
title = LibraryTitle(
|
||||||
title = toUnicode(attrs.get('title')),
|
title = toUnicode(attrs.get('title')),
|
||||||
simple_title = self.simplifyTitle(attrs.get('title'))
|
simple_title = self.simplifyTitle(attrs.get('title')),
|
||||||
)
|
)
|
||||||
|
|
||||||
l.titles.append(title)
|
l.titles.append(title)
|
||||||
@@ -96,6 +96,7 @@ class LibraryPlugin(Plugin):
|
|||||||
|
|
||||||
titles = info.get('titles', [])
|
titles = info.get('titles', [])
|
||||||
log.debug('Adding titles: %s', titles)
|
log.debug('Adding titles: %s', titles)
|
||||||
|
counter = 0
|
||||||
for title in titles:
|
for title in titles:
|
||||||
if not title:
|
if not title:
|
||||||
continue
|
continue
|
||||||
@@ -103,9 +104,10 @@ class LibraryPlugin(Plugin):
|
|||||||
t = LibraryTitle(
|
t = LibraryTitle(
|
||||||
title = title,
|
title = title,
|
||||||
simple_title = self.simplifyTitle(title),
|
simple_title = self.simplifyTitle(title),
|
||||||
default = title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
|
default = (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
|
||||||
)
|
)
|
||||||
library.titles.append(t)
|
library.titles.append(t)
|
||||||
|
counter += 1
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
.page.log .nav {
|
.page.log .nav {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px 0;
|
padding: 0 0 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 960px;
|
width: 100%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
background: #4E5969;
|
background: #4E5969;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.log .nav li {
|
.page.log .nav li {
|
||||||
display: inline;
|
display: inline-block;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -24,7 +25,17 @@
|
|||||||
.page.log .nav li.active {
|
.page.log .nav li.active {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
font-size: 30px;
|
background: rgba(255,255,255,.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.page.log .nav {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.log .nav li {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.log .loading {
|
.page.log .loading {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ config = [{
|
|||||||
'groups': [
|
'groups': [
|
||||||
{
|
{
|
||||||
'tab': 'manage',
|
'tab': 'manage',
|
||||||
'label': 'movie library manager',
|
'label': 'Movie Library Manager',
|
||||||
'description': 'Add your existing movie folders.',
|
'description': 'Add your existing movie folders.',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ from couchpotato.api import addApiView
|
|||||||
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import ss
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
from couchpotato.core.helpers.variable import getTitle, splitString
|
from couchpotato.core.helpers.variable import splitString, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
import ctypes
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ class Manage(Plugin):
|
|||||||
fireEvent('scheduler.interval', identifier = 'manage.update_library', handle = self.updateLibrary, hours = 2)
|
fireEvent('scheduler.interval', identifier = 'manage.update_library', handle = self.updateLibrary, hours = 2)
|
||||||
|
|
||||||
addEvent('manage.update', self.updateLibrary)
|
addEvent('manage.update', self.updateLibrary)
|
||||||
|
addEvent('manage.diskspace', self.getDiskSpace)
|
||||||
|
|
||||||
# Add files after renaming
|
# Add files after renaming
|
||||||
def after_rename(message = None, group = {}):
|
def after_rename(message = None, group = {}):
|
||||||
@@ -182,6 +185,8 @@ class Manage(Plugin):
|
|||||||
# Add it to release and update the info
|
# Add it to release and update the info
|
||||||
fireEvent('release.add', group = group)
|
fireEvent('release.add', group = group)
|
||||||
fireEventAsync('library.update', identifier = identifier, on_complete = self.createAfterUpdate(folder, identifier))
|
fireEventAsync('library.update', identifier = identifier, on_complete = self.createAfterUpdate(folder, identifier))
|
||||||
|
else:
|
||||||
|
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
|
||||||
|
|
||||||
return addToLibrary
|
return addToLibrary
|
||||||
|
|
||||||
@@ -192,13 +197,14 @@ class Manage(Plugin):
|
|||||||
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
|
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
|
||||||
total = self.in_progress[folder]['total']
|
total = self.in_progress[folder]['total']
|
||||||
movie_dict = fireEvent('movie.get', identifier, single = True)
|
movie_dict = fireEvent('movie.get', identifier, single = True)
|
||||||
|
|
||||||
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = None if total > 5 else 'Added "%s" to manage.' % getTitle(movie_dict['library']))
|
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = None if total > 5 else 'Added "%s" to manage.' % getTitle(movie_dict['library']))
|
||||||
|
|
||||||
return afterUpdate
|
return afterUpdate
|
||||||
|
|
||||||
def directories(self):
|
def directories(self):
|
||||||
try:
|
try:
|
||||||
if self.conf('library', '').strip():
|
if self.conf('library', default = '').strip():
|
||||||
return splitString(self.conf('library', default = ''), '::')
|
return splitString(self.conf('library', default = ''), '::')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -214,3 +220,31 @@ class Manage(Plugin):
|
|||||||
for group in groups.itervalues():
|
for group in groups.itervalues():
|
||||||
if group['library'] and group['library'].get('identifier'):
|
if group['library'] and group['library'].get('identifier'):
|
||||||
fireEvent('release.add', group = group)
|
fireEvent('release.add', group = group)
|
||||||
|
|
||||||
|
def getDiskSpace(self):
|
||||||
|
|
||||||
|
free_space = {}
|
||||||
|
for folder in self.directories():
|
||||||
|
|
||||||
|
size = None
|
||||||
|
if os.path.isdir(folder):
|
||||||
|
if os.name == 'nt':
|
||||||
|
_, total, free = ctypes.c_ulonglong(), ctypes.c_ulonglong(), \
|
||||||
|
ctypes.c_ulonglong()
|
||||||
|
if sys.version_info >= (3,) or isinstance(folder, unicode):
|
||||||
|
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExW #@UndefinedVariable
|
||||||
|
else:
|
||||||
|
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExA #@UndefinedVariable
|
||||||
|
ret = fun(folder, ctypes.byref(_), ctypes.byref(total), ctypes.byref(free))
|
||||||
|
if ret == 0:
|
||||||
|
raise ctypes.WinError()
|
||||||
|
used = total.value - free.value
|
||||||
|
return [total.value, used, free.value]
|
||||||
|
else:
|
||||||
|
s = os.statvfs(folder)
|
||||||
|
size = [s.f_blocks * s.f_frsize / (1024 * 1024), (s.f_bavail * s.f_frsize) / (1024 * 1024)]
|
||||||
|
|
||||||
|
free_space[folder] = size
|
||||||
|
|
||||||
|
return free_space
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ from couchpotato.core.helpers.request import getParams, jsonified, getParam
|
|||||||
from couchpotato.core.helpers.variable import getImdb, splitString
|
from couchpotato.core.helpers.variable import getImdb, splitString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Library, LibraryTitle, Movie
|
from couchpotato.core.settings.model import Library, LibraryTitle, Movie, \
|
||||||
|
Release
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from sqlalchemy.orm import joinedload_all
|
from sqlalchemy.orm import joinedload_all
|
||||||
from sqlalchemy.sql.expression import or_, asc, not_
|
from sqlalchemy.sql.expression import or_, asc, not_, desc
|
||||||
from string import ascii_lowercase
|
from string import ascii_lowercase
|
||||||
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ class MoviePlugin(Plugin):
|
|||||||
'desc': 'List movies in wanted list',
|
'desc': 'List movies in wanted list',
|
||||||
'params': {
|
'params': {
|
||||||
'status': {'type': 'array or csv', 'desc': 'Filter movie by status. Example:"active,done"'},
|
'status': {'type': 'array or csv', 'desc': 'Filter movie by status. Example:"active,done"'},
|
||||||
|
'release_status': {'type': 'array or csv', 'desc': 'Filter movie by status of its releases. Example:"snatched,available"'},
|
||||||
'limit_offset': {'desc': 'Limit and offset the movie list. Examples: "50" or "50,30"'},
|
'limit_offset': {'desc': 'Limit and offset the movie list. Examples: "50" or "50,30"'},
|
||||||
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'},
|
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'},
|
||||||
'search': {'desc': 'Search movie title'},
|
'search': {'desc': 'Search movie title'},
|
||||||
@@ -94,6 +97,33 @@ class MoviePlugin(Plugin):
|
|||||||
addEvent('movie.list', self.list)
|
addEvent('movie.list', self.list)
|
||||||
addEvent('movie.restatus', self.restatus)
|
addEvent('movie.restatus', self.restatus)
|
||||||
|
|
||||||
|
# Clean releases that didn't have activity in the last week
|
||||||
|
addEvent('app.load', self.cleanReleases)
|
||||||
|
fireEvent('schedule.interval', 'movie.clean_releases', self.cleanReleases, hours = 4)
|
||||||
|
|
||||||
|
def cleanReleases(self):
|
||||||
|
|
||||||
|
log.debug('Removing releases from dashboard')
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
week = 262080
|
||||||
|
|
||||||
|
done_status, available_status, snatched_status = \
|
||||||
|
fireEvent('status.get', ['done', 'available', 'snatched'], single = True)
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
|
||||||
|
# get movies last_edit more than a week ago
|
||||||
|
movies = db.query(Movie) \
|
||||||
|
.filter(Movie.status_id == done_status.get('id'), Movie.last_edit < (now - week)) \
|
||||||
|
.all()
|
||||||
|
|
||||||
|
#
|
||||||
|
for movie in movies:
|
||||||
|
for rel in movie.releases:
|
||||||
|
if rel.status_id in [available_status.get('id'), snatched_status.get('id')]:
|
||||||
|
fireEvent('release.delete', id = rel.id, single = True)
|
||||||
|
|
||||||
def getView(self):
|
def getView(self):
|
||||||
|
|
||||||
movie_id = getParam('id')
|
movie_id = getParam('id')
|
||||||
@@ -121,20 +151,29 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
|
def list(self, status = None, release_status = None, limit_offset = None, starts_with = None, search = None, order = None):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
# Make a list from string
|
# Make a list from string
|
||||||
if not isinstance(status, (list, tuple)):
|
if status and not isinstance(status, (list, tuple)):
|
||||||
status = [status]
|
status = [status]
|
||||||
|
if release_status and not isinstance(release_status, (list, tuple)):
|
||||||
|
release_status = [release_status]
|
||||||
|
|
||||||
q = db.query(Movie) \
|
q = db.query(Movie) \
|
||||||
.join(Movie.library, Library.titles) \
|
.outerjoin(Movie.releases, Movie.library, Library.titles) \
|
||||||
.filter(LibraryTitle.default == True) \
|
.filter(LibraryTitle.default == True) \
|
||||||
.filter(or_(*[Movie.status.has(identifier = s) for s in status])) \
|
|
||||||
.group_by(Movie.id)
|
.group_by(Movie.id)
|
||||||
|
|
||||||
|
# Filter on movie status
|
||||||
|
if status and len(status) > 0:
|
||||||
|
q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
|
||||||
|
|
||||||
|
# Filter on release status
|
||||||
|
if release_status and len(release_status) > 0:
|
||||||
|
q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
|
||||||
|
|
||||||
total_count = q.count()
|
total_count = q.count()
|
||||||
|
|
||||||
filter_or = []
|
filter_or = []
|
||||||
@@ -154,7 +193,10 @@ class MoviePlugin(Plugin):
|
|||||||
if filter_or:
|
if filter_or:
|
||||||
q = q.filter(or_(*filter_or))
|
q = q.filter(or_(*filter_or))
|
||||||
|
|
||||||
q = q.order_by(asc(LibraryTitle.simple_title))
|
if order == 'release_order':
|
||||||
|
q = q.order_by(desc(Release.last_edit))
|
||||||
|
else:
|
||||||
|
q = q.order_by(asc(LibraryTitle.simple_title))
|
||||||
|
|
||||||
q = q.subquery()
|
q = q.subquery()
|
||||||
q2 = db.query(Movie).join((q, q.c.id == Movie.id)) \
|
q2 = db.query(Movie).join((q, q.c.id == Movie.id)) \
|
||||||
@@ -166,7 +208,7 @@ class MoviePlugin(Plugin):
|
|||||||
.options(joinedload_all('files'))
|
.options(joinedload_all('files'))
|
||||||
|
|
||||||
if limit_offset:
|
if limit_offset:
|
||||||
splt = splitString(limit_offset)
|
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||||
limit = splt[0]
|
limit = splt[0]
|
||||||
offset = 0 if len(splt) is 1 else splt[1]
|
offset = 0 if len(splt) is 1 else splt[1]
|
||||||
q2 = q2.limit(limit).offset(offset)
|
q2 = q2.limit(limit).offset(offset)
|
||||||
@@ -185,7 +227,7 @@ class MoviePlugin(Plugin):
|
|||||||
#db.close()
|
#db.close()
|
||||||
return (total_count, movies)
|
return (total_count, movies)
|
||||||
|
|
||||||
def availableChars(self, status = ['active']):
|
def availableChars(self, status = None, release_status = None):
|
||||||
|
|
||||||
chars = ''
|
chars = ''
|
||||||
|
|
||||||
@@ -194,11 +236,20 @@ class MoviePlugin(Plugin):
|
|||||||
# Make a list from string
|
# Make a list from string
|
||||||
if not isinstance(status, (list, tuple)):
|
if not isinstance(status, (list, tuple)):
|
||||||
status = [status]
|
status = [status]
|
||||||
|
if release_status and not isinstance(release_status, (list, tuple)):
|
||||||
|
release_status = [release_status]
|
||||||
|
|
||||||
q = db.query(Movie) \
|
q = db.query(Movie) \
|
||||||
.join(Movie.library, Library.titles, Movie.status) \
|
.outerjoin(Movie.releases, Movie.library, Library.titles, Movie.status) \
|
||||||
.options(joinedload_all('library.titles')) \
|
.options(joinedload_all('library.titles'))
|
||||||
.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
|
|
||||||
|
# Filter on movie status
|
||||||
|
if status and len(status) > 0:
|
||||||
|
q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
|
||||||
|
|
||||||
|
# Filter on release status
|
||||||
|
if release_status and len(release_status) > 0:
|
||||||
|
q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
|
||||||
|
|
||||||
results = q.all()
|
results = q.all()
|
||||||
|
|
||||||
@@ -206,20 +257,29 @@ class MoviePlugin(Plugin):
|
|||||||
char = movie.library.titles[0].simple_title[0]
|
char = movie.library.titles[0].simple_title[0]
|
||||||
char = char if char in ascii_lowercase else '#'
|
char = char if char in ascii_lowercase else '#'
|
||||||
if char not in chars:
|
if char not in chars:
|
||||||
chars += char
|
chars += str(char)
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return chars
|
return ''.join(sorted(chars, key = str.lower))
|
||||||
|
|
||||||
def listView(self):
|
def listView(self):
|
||||||
|
|
||||||
params = getParams()
|
params = getParams()
|
||||||
status = params.get('status', ['active'])
|
status = splitString(params.get('status', None))
|
||||||
|
release_status = splitString(params.get('release_status', None))
|
||||||
limit_offset = params.get('limit_offset', None)
|
limit_offset = params.get('limit_offset', None)
|
||||||
starts_with = params.get('starts_with', None)
|
starts_with = params.get('starts_with', None)
|
||||||
search = params.get('search', None)
|
search = params.get('search', None)
|
||||||
|
order = params.get('order', None)
|
||||||
|
|
||||||
total_movies, movies = self.list(status = status, limit_offset = limit_offset, starts_with = starts_with, search = search)
|
total_movies, movies = self.list(
|
||||||
|
status = status,
|
||||||
|
release_status = release_status,
|
||||||
|
limit_offset = limit_offset,
|
||||||
|
starts_with = starts_with,
|
||||||
|
search = search,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
@@ -231,8 +291,9 @@ class MoviePlugin(Plugin):
|
|||||||
def charView(self):
|
def charView(self):
|
||||||
|
|
||||||
params = getParams()
|
params = getParams()
|
||||||
status = params.get('status', ['active'])
|
status = splitString(params.get('status', None))
|
||||||
chars = self.availableChars(status)
|
release_status = splitString(params.get('release_status', None))
|
||||||
|
chars = self.availableChars(status, release_status)
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
@@ -254,7 +315,7 @@ class MoviePlugin(Plugin):
|
|||||||
for title in movie.library.titles:
|
for title in movie.library.titles:
|
||||||
if title.default: default_title = title.title
|
if title.default: default_title = title.title
|
||||||
|
|
||||||
fireEvent('notify.frontend', type = 'movie.busy.%s' % id, data = True, message = 'Updating "%s"' % default_title)
|
fireEvent('notify.frontend', type = 'movie.busy.%s' % id, data = True)
|
||||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id))
|
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id))
|
||||||
|
|
||||||
|
|
||||||
@@ -283,7 +344,7 @@ class MoviePlugin(Plugin):
|
|||||||
'movies': movies,
|
'movies': movies,
|
||||||
})
|
})
|
||||||
|
|
||||||
def add(self, params = {}, force_readd = True, search_after = True, update_library = False):
|
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
|
||||||
|
|
||||||
if not params.get('identifier'):
|
if not params.get('identifier'):
|
||||||
msg = 'Can\'t add movie without imdb identifier.'
|
msg = 'Can\'t add movie without imdb identifier.'
|
||||||
@@ -292,9 +353,8 @@ class MoviePlugin(Plugin):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
url = 'http://thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=%s' % params.get('identifier')
|
is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), single = True)
|
||||||
tvdb = self.getCache('thetvdb.%s' % params.get('identifier'), url = url, show_error = False)
|
if not is_movie:
|
||||||
if tvdb and 'series' in tvdb.lower():
|
|
||||||
msg = 'Can\'t add movie, seems to be a TV show.'
|
msg = 'Can\'t add movie, seems to be a TV show.'
|
||||||
log.error(msg)
|
log.error(msg)
|
||||||
fireEvent('notify.frontend', type = 'movie.is_tvshow', message = msg)
|
fireEvent('notify.frontend', type = 'movie.is_tvshow', message = msg)
|
||||||
@@ -306,8 +366,8 @@ class MoviePlugin(Plugin):
|
|||||||
library = fireEvent('library.add', single = True, attrs = params, update_after = update_library)
|
library = fireEvent('library.add', single = True, attrs = params, update_after = update_library)
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
status_active = fireEvent('status.add', 'active', single = True)
|
status_active, snatched_status, ignored_status, done_status, downloaded_status = \
|
||||||
status_snatched = fireEvent('status.add', 'snatched', single = True)
|
fireEvent('status.get', ['active', 'snatched', 'ignored', 'done', 'downloaded'], single = True)
|
||||||
|
|
||||||
default_profile = fireEvent('profile.default', single = True)
|
default_profile = fireEvent('profile.default', single = True)
|
||||||
|
|
||||||
@@ -319,7 +379,7 @@ class MoviePlugin(Plugin):
|
|||||||
m = Movie(
|
m = Movie(
|
||||||
library_id = library.get('id'),
|
library_id = library.get('id'),
|
||||||
profile_id = params.get('profile_id', default_profile.get('id')),
|
profile_id = params.get('profile_id', default_profile.get('id')),
|
||||||
status_id = status_active.get('id'),
|
status_id = status_id if status_id else status_active.get('id'),
|
||||||
)
|
)
|
||||||
db.add(m)
|
db.add(m)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -331,10 +391,14 @@ class MoviePlugin(Plugin):
|
|||||||
fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
|
fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
|
||||||
search_after = False
|
search_after = False
|
||||||
elif force_readd:
|
elif force_readd:
|
||||||
|
|
||||||
# Clean snatched history
|
# Clean snatched history
|
||||||
for release in m.releases:
|
for release in m.releases:
|
||||||
if release.status_id == status_snatched.get('id'):
|
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
|
||||||
release.delete()
|
if params.get('ignore_previous', False):
|
||||||
|
release.status_id = ignored_status.get('id')
|
||||||
|
else:
|
||||||
|
fireEvent('release.delete', release.id, single = True)
|
||||||
|
|
||||||
m.profile_id = params.get('profile_id', default_profile.get('id'))
|
m.profile_id = params.get('profile_id', default_profile.get('id'))
|
||||||
else:
|
else:
|
||||||
@@ -342,7 +406,8 @@ class MoviePlugin(Plugin):
|
|||||||
added = False
|
added = False
|
||||||
|
|
||||||
if force_readd:
|
if force_readd:
|
||||||
m.status_id = status_active.get('id')
|
m.status_id = status_id if status_id else status_active.get('id')
|
||||||
|
m.last_edit = int(time.time())
|
||||||
do_search = True
|
do_search = True
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -448,7 +513,7 @@ class MoviePlugin(Plugin):
|
|||||||
total_deleted = 0
|
total_deleted = 0
|
||||||
new_movie_status = None
|
new_movie_status = None
|
||||||
for release in movie.releases:
|
for release in movie.releases:
|
||||||
if delete_from == 'wanted':
|
if delete_from in ['wanted', 'snatched']:
|
||||||
if release.status_id != done_status.get('id'):
|
if release.status_id != done_status.get('id'):
|
||||||
db.delete(release)
|
db.delete(release)
|
||||||
total_deleted += 1
|
total_deleted += 1
|
||||||
@@ -480,8 +545,7 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
def restatus(self, movie_id):
|
def restatus(self, movie_id):
|
||||||
|
|
||||||
active_status = fireEvent('status.get', 'active', single = True)
|
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,54 @@
|
|||||||
var MovieList = new Class({
|
var MovieList = new Class({
|
||||||
|
|
||||||
Implements: [Options],
|
Implements: [Events, Options],
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
navigation: true,
|
navigation: true,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
|
load_more: true,
|
||||||
|
loader: true,
|
||||||
menu: [],
|
menu: [],
|
||||||
add_new: false
|
add_new: false,
|
||||||
|
force_view: false
|
||||||
},
|
},
|
||||||
|
|
||||||
movies: [],
|
movies: [],
|
||||||
movies_added: {},
|
movies_added: {},
|
||||||
letters: {},
|
letters: {},
|
||||||
filter: {
|
filter: null,
|
||||||
'startswith': null,
|
|
||||||
'search': null
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function(options){
|
initialize: function(options){
|
||||||
var self = this;
|
var self = this;
|
||||||
self.setOptions(options);
|
self.setOptions(options);
|
||||||
|
|
||||||
self.offset = 0;
|
self.offset = 0;
|
||||||
|
self.filter = self.options.filter || {
|
||||||
|
'startswith': null,
|
||||||
|
'search': null
|
||||||
|
}
|
||||||
|
|
||||||
self.el = new Element('div.movies').adopt(
|
self.el = new Element('div.movies').adopt(
|
||||||
|
self.title = self.options.title ? new Element('h2', {
|
||||||
|
'text': self.options.title,
|
||||||
|
'styles': {'display': 'none'}
|
||||||
|
}) : null,
|
||||||
|
self.description = self.options.description ? new Element('div.description', {
|
||||||
|
'html': self.options.description,
|
||||||
|
'styles': {'display': 'none'}
|
||||||
|
}) : null,
|
||||||
self.movie_list = new Element('div'),
|
self.movie_list = new Element('div'),
|
||||||
self.load_more = new Element('a.load_more', {
|
self.load_more = self.options.load_more ? new Element('a.load_more', {
|
||||||
'events': {
|
'events': {
|
||||||
'click': self.loadMore.bind(self)
|
'click': self.loadMore.bind(self)
|
||||||
}
|
}
|
||||||
})
|
}) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if($(window).getSize().x <= 480 && !self.options.force_view)
|
||||||
|
self.changeView('list');
|
||||||
|
else
|
||||||
|
self.changeView(self.getSavedView() || self.options.view || 'details');
|
||||||
|
|
||||||
self.getMovies();
|
self.getMovies();
|
||||||
|
|
||||||
App.addEvent('movie.added', self.movieAdded.bind(self))
|
App.addEvent('movie.added', self.movieAdded.bind(self))
|
||||||
@@ -70,22 +88,14 @@ var MovieList = new Class({
|
|||||||
if(self.options.navigation)
|
if(self.options.navigation)
|
||||||
self.createNavigation();
|
self.createNavigation();
|
||||||
|
|
||||||
self.movie_list.addEvents({
|
if(self.options.load_more)
|
||||||
'mouseenter:relay(.movie)': function(e, el){
|
self.scrollspy = new ScrollSpy({
|
||||||
el.addClass('hover');
|
min: function(){
|
||||||
},
|
var c = self.load_more.getCoordinates()
|
||||||
'mouseleave:relay(.movie)': function(e, el){
|
return c.top - window.document.getSize().y - 300
|
||||||
el.removeClass('hover');
|
},
|
||||||
}
|
onEnter: self.loadMore.bind(self)
|
||||||
});
|
});
|
||||||
|
|
||||||
self.scrollspy = new ScrollSpy({
|
|
||||||
min: function(){
|
|
||||||
var c = self.load_more.getCoordinates()
|
|
||||||
return c.top - window.document.getSize().y - 300
|
|
||||||
},
|
|
||||||
onEnter: self.loadMore.bind(self)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.created = true;
|
self.created = true;
|
||||||
},
|
},
|
||||||
@@ -96,7 +106,7 @@ var MovieList = new Class({
|
|||||||
if(!self.created) self.create();
|
if(!self.created) self.create();
|
||||||
|
|
||||||
// do scrollspy
|
// do scrollspy
|
||||||
if(movies.length < self.options.limit){
|
if(movies.length < self.options.limit && self.scrollspy){
|
||||||
self.load_more.hide();
|
self.load_more.hide();
|
||||||
self.scrollspy.stop();
|
self.scrollspy.stop();
|
||||||
}
|
}
|
||||||
@@ -115,24 +125,20 @@ var MovieList = new Class({
|
|||||||
|
|
||||||
if(!self.navigation_counter) return;
|
if(!self.navigation_counter) return;
|
||||||
|
|
||||||
self.navigation_counter.set('text', (count || 0));
|
self.navigation_counter.set('text', (count || 0) + ' movies');
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createMovie: function(movie, inject_at){
|
createMovie: function(movie, inject_at){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Attach proper actions
|
|
||||||
var a = self.options.actions,
|
|
||||||
status = Status.get(movie.status_id);
|
|
||||||
var actions = a[status.identifier.capitalize()] || a.Wanted || {};
|
|
||||||
|
|
||||||
var m = new Movie(self, {
|
var m = new Movie(self, {
|
||||||
'actions': actions,
|
'actions': self.options.actions,
|
||||||
'view': self.current_view,
|
'view': self.current_view,
|
||||||
'onSelect': self.calculateSelected.bind(self)
|
'onSelect': self.calculateSelected.bind(self)
|
||||||
}, movie);
|
}, movie);
|
||||||
|
|
||||||
$(m).inject(self.movie_list, inject_at || 'bottom');
|
$(m).inject(self.movie_list, inject_at || 'bottom');
|
||||||
|
|
||||||
m.fireEvent('injected');
|
m.fireEvent('injected');
|
||||||
|
|
||||||
self.movies.include(m)
|
self.movies.include(m)
|
||||||
@@ -143,65 +149,67 @@ var MovieList = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
|
||||||
self.current_view = self.getSavedView();
|
self.el.addClass('with_navigation')
|
||||||
self.el.addClass(self.current_view+'_list')
|
|
||||||
|
|
||||||
self.navigation = new Element('div.alph_nav').adopt(
|
self.navigation = new Element('div.alph_nav').grab(
|
||||||
self.navigation_actions = new Element('ul.inlay.actions.reversed'),
|
new Element('div').adopt(
|
||||||
self.navigation_counter = new Element('span.counter[title=Total]'),
|
self.navigation_alpha = new Element('ul.numbers', {
|
||||||
self.navigation_alpha = new Element('ul.numbers', {
|
'events': {
|
||||||
'events': {
|
'click:relay(li)': function(e, el){
|
||||||
'click:relay(li)': function(e, el){
|
self.movie_list.empty()
|
||||||
self.movie_list.empty()
|
self.activateLetter(el.get('data-letter'))
|
||||||
self.activateLetter(el.get('data-letter'))
|
self.getMovies()
|
||||||
self.getMovies()
|
}
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
}),
|
self.navigation_counter = new Element('span.counter[title=Total]'),
|
||||||
self.navigation_search_input = new Element('input.inlay', {
|
self.navigation_actions = new Element('ul.inlay.actions.reversed'),
|
||||||
'placeholder': 'Search',
|
self.navigation_search_input = new Element('input.search.inlay', {
|
||||||
'events': {
|
'title': 'Search through ' + self.options.identifier,
|
||||||
'keyup': self.search.bind(self),
|
'placeholder': 'Search through ' + self.options.identifier,
|
||||||
'change': self.search.bind(self)
|
'events': {
|
||||||
}
|
'keyup': self.search.bind(self),
|
||||||
}),
|
'change': self.search.bind(self)
|
||||||
self.navigation_menu = new Block.Menu(self),
|
}
|
||||||
self.mass_edit_form = new Element('div.mass_edit_form').adopt(
|
}),
|
||||||
new Element('span.select').adopt(
|
self.navigation_menu = new Block.Menu(self),
|
||||||
self.mass_edit_select = new Element('input[type=checkbox].inlay', {
|
self.mass_edit_form = new Element('div.mass_edit_form').adopt(
|
||||||
'events': {
|
new Element('span.select').adopt(
|
||||||
'change': self.massEditToggleAll.bind(self)
|
self.mass_edit_select = new Element('input[type=checkbox].inlay', {
|
||||||
}
|
'events': {
|
||||||
}),
|
'change': self.massEditToggleAll.bind(self)
|
||||||
self.mass_edit_selected = new Element('span.count', {'text': 0}),
|
}
|
||||||
self.mass_edit_selected_label = new Element('span', {'text': 'selected'})
|
}),
|
||||||
),
|
self.mass_edit_selected = new Element('span.count', {'text': 0}),
|
||||||
new Element('div.quality').adopt(
|
self.mass_edit_selected_label = new Element('span', {'text': 'selected'})
|
||||||
self.mass_edit_quality = new Element('select'),
|
),
|
||||||
new Element('a.button.orange', {
|
new Element('div.quality').adopt(
|
||||||
'text': 'Change quality',
|
self.mass_edit_quality = new Element('select'),
|
||||||
'events': {
|
new Element('a.button.orange', {
|
||||||
'click': self.changeQualitySelected.bind(self)
|
'text': 'Change quality',
|
||||||
}
|
'events': {
|
||||||
})
|
'click': self.changeQualitySelected.bind(self)
|
||||||
),
|
}
|
||||||
new Element('div.delete').adopt(
|
})
|
||||||
new Element('span[text=or]'),
|
),
|
||||||
new Element('a.button.red', {
|
new Element('div.delete').adopt(
|
||||||
'text': 'Delete',
|
new Element('span[text=or]'),
|
||||||
'events': {
|
new Element('a.button.red', {
|
||||||
'click': self.deleteSelected.bind(self)
|
'text': 'Delete',
|
||||||
}
|
'events': {
|
||||||
})
|
'click': self.deleteSelected.bind(self)
|
||||||
),
|
}
|
||||||
new Element('div.refresh').adopt(
|
})
|
||||||
new Element('span[text=or]'),
|
),
|
||||||
new Element('a.button.green', {
|
new Element('div.refresh').adopt(
|
||||||
'text': 'Refresh',
|
new Element('span[text=or]'),
|
||||||
'events': {
|
new Element('a.button.green', {
|
||||||
'click': self.refreshSelected.bind(self)
|
'text': 'Refresh',
|
||||||
}
|
'events': {
|
||||||
})
|
'click': self.refreshSelected.bind(self)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).inject(self.el, 'top');
|
).inject(self.el, 'top');
|
||||||
@@ -216,7 +224,7 @@ var MovieList = new Class({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
['mass_edit', 'thumbs', 'list'].each(function(view){
|
['mass_edit', 'details', 'list'].each(function(view){
|
||||||
self.navigation_actions.adopt(
|
self.navigation_actions.adopt(
|
||||||
new Element('li.'+view+(self.current_view == view ? '.active' : '')+'[data-view='+view+']', {
|
new Element('li.'+view+(self.current_view == view ? '.active' : '')+'[data-view='+view+']', {
|
||||||
'events': {
|
'events': {
|
||||||
@@ -246,18 +254,19 @@ var MovieList = new Class({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get available chars and highlight
|
// Get available chars and highlight
|
||||||
Api.request('movie.available_chars', {
|
if(self.navigation.isDisplayed() || self.navigation.isVisible())
|
||||||
'data': Object.merge({
|
Api.request('movie.available_chars', {
|
||||||
'status': self.options.status
|
'data': Object.merge({
|
||||||
}, self.filter),
|
'status': self.options.status
|
||||||
'onComplete': function(json){
|
}, self.filter),
|
||||||
|
'onSuccess': function(json){
|
||||||
json.chars.split('').each(function(c){
|
|
||||||
self.letters[c.capitalize()].addClass('available')
|
json.chars.split('').each(function(c){
|
||||||
})
|
self.letters[c.capitalize()].addClass('available')
|
||||||
|
})
|
||||||
}
|
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add menu or hide
|
// Add menu or hide
|
||||||
if (self.options.menu.length > 0)
|
if (self.options.menu.length > 0)
|
||||||
@@ -265,17 +274,7 @@ var MovieList = new Class({
|
|||||||
self.navigation_menu.addLink(menu_item);
|
self.navigation_menu.addLink(menu_item);
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
self.navigation_menu.hide()
|
self.navigation_menu.hide();
|
||||||
|
|
||||||
self.nav_scrollspy = new ScrollSpy({
|
|
||||||
min: 10,
|
|
||||||
onEnter: function(){
|
|
||||||
self.navigation.addClass('float')
|
|
||||||
},
|
|
||||||
onLeave: function(){
|
|
||||||
self.navigation.removeClass('float')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -398,11 +397,16 @@ var MovieList = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.movies = []
|
self.movies = []
|
||||||
self.calculateSelected()
|
if(self.mass_edit_select)
|
||||||
self.navigation_alpha.getElements('.active').removeClass('active')
|
self.calculateSelected()
|
||||||
|
if(self.navigation_alpha)
|
||||||
|
self.navigation_alpha.getElements('.active').removeClass('active')
|
||||||
|
|
||||||
self.offset = 0;
|
self.offset = 0;
|
||||||
self.load_more.show();
|
if(self.scrollspy){
|
||||||
self.scrollspy.start();
|
self.load_more.show();
|
||||||
|
self.scrollspy.start();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
activateLetter: function(letter){
|
activateLetter: function(letter){
|
||||||
@@ -418,21 +422,17 @@ var MovieList = new Class({
|
|||||||
changeView: function(new_view){
|
changeView: function(new_view){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.movies.each(function(movie){
|
|
||||||
movie.changeView(new_view)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.el
|
self.el
|
||||||
.removeClass(self.current_view+'_list')
|
.removeClass(self.current_view+'_list')
|
||||||
.addClass(new_view+'_list')
|
.addClass(new_view+'_list')
|
||||||
|
|
||||||
self.current_view = new_view;
|
self.current_view = new_view;
|
||||||
Cookie.write(self.options.identifier+'_view', new_view, {duration: 1000});
|
Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000});
|
||||||
},
|
},
|
||||||
|
|
||||||
getSavedView: function(){
|
getSavedView: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
return Cookie.read(self.options.identifier+'_view') || 'thumbs';
|
return Cookie.read(self.options.identifier+'_view2');
|
||||||
},
|
},
|
||||||
|
|
||||||
search: function(){
|
search: function(){
|
||||||
@@ -468,20 +468,53 @@ var MovieList = new Class({
|
|||||||
getMovies: function(){
|
getMovies: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(self.scrollspy) self.scrollspy.stop();
|
if(self.scrollspy){
|
||||||
self.load_more.set('text', 'loading...');
|
self.scrollspy.stop();
|
||||||
Api.request('movie.list', {
|
self.load_more.set('text', 'loading...');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.movies.length == 0 && self.options.loader){
|
||||||
|
|
||||||
|
self.loader_first = new Element('div.loading').adopt(
|
||||||
|
new Element('div.message', {'text': self.options.title ? 'Loading \'' + self.options.title + '\'' : 'Loading...'})
|
||||||
|
).inject(self.el, 'top');
|
||||||
|
|
||||||
|
createSpinner(self.loader_first, {
|
||||||
|
radius: 4,
|
||||||
|
length: 4,
|
||||||
|
width: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
self.el.setStyle('min-height', 93);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Api.request(self.options.api_call || 'movie.list', {
|
||||||
'data': Object.merge({
|
'data': Object.merge({
|
||||||
'status': self.options.status,
|
'status': self.options.status,
|
||||||
'limit_offset': self.options.limit + ',' + self.offset
|
'limit_offset': self.options.limit + ',' + self.offset
|
||||||
}, self.filter),
|
}, self.filter),
|
||||||
'onComplete': function(json){
|
'onSuccess': function(json){
|
||||||
|
|
||||||
|
if(self.loader_first){
|
||||||
|
var lf = self.loader_first;
|
||||||
|
self.loader_first.addClass('hide')
|
||||||
|
self.loader_first = null;
|
||||||
|
setTimeout(function(){
|
||||||
|
lf.destroy();
|
||||||
|
}, 20000);
|
||||||
|
self.el.setStyle('min-height', null);
|
||||||
|
}
|
||||||
|
|
||||||
self.store(json.movies);
|
self.store(json.movies);
|
||||||
self.addMovies(json.movies, json.total);
|
self.addMovies(json.movies, json.total);
|
||||||
self.load_more.set('text', 'load more movies');
|
if(self.scrollspy) {
|
||||||
if(self.scrollspy) self.scrollspy.start();
|
self.load_more.set('text', 'load more movies');
|
||||||
|
self.scrollspy.start();
|
||||||
|
}
|
||||||
|
|
||||||
self.checkIfEmpty()
|
self.checkIfEmpty();
|
||||||
|
self.fireEvent('loaded');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -502,10 +535,16 @@ var MovieList = new Class({
|
|||||||
checkIfEmpty: function(){
|
checkIfEmpty: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var is_empty = self.movies.length == 0 && self.total_movies == 0;
|
var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined);
|
||||||
|
|
||||||
|
if(self.title)
|
||||||
|
self.title[is_empty ? 'hide' : 'show']()
|
||||||
|
|
||||||
|
if(self.description)
|
||||||
|
self.description.setStyle('display', [is_empty ? 'none' : ''])
|
||||||
|
|
||||||
if(is_empty && self.options.on_empty_element){
|
if(is_empty && self.options.on_empty_element){
|
||||||
self.el.grab(self.options.on_empty_element);
|
self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, 'after');
|
||||||
|
|
||||||
if(self.navigation)
|
if(self.navigation)
|
||||||
self.navigation.hide();
|
self.navigation.hide();
|
||||||
|
|||||||
@@ -0,0 +1,745 @@
|
|||||||
|
var MovieAction = new Class({
|
||||||
|
|
||||||
|
class_name: 'action icon',
|
||||||
|
|
||||||
|
initialize: function(movie){
|
||||||
|
var self = this;
|
||||||
|
self.movie = movie;
|
||||||
|
|
||||||
|
self.create();
|
||||||
|
if(self.el)
|
||||||
|
self.el.addClass(self.class_name)
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function(){},
|
||||||
|
|
||||||
|
disable: function(){
|
||||||
|
this.el.addClass('disable')
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function(){
|
||||||
|
this.el.removeClass('disable')
|
||||||
|
},
|
||||||
|
|
||||||
|
createMask: function(){
|
||||||
|
var self = this;
|
||||||
|
self.mask = new Element('div.mask', {
|
||||||
|
'styles': {
|
||||||
|
'z-index': '1'
|
||||||
|
}
|
||||||
|
}).inject(self.movie, 'top').fade('hide');
|
||||||
|
//self.positionMask();
|
||||||
|
},
|
||||||
|
|
||||||
|
positionMask: function(){
|
||||||
|
var self = this,
|
||||||
|
movie = $(self.movie),
|
||||||
|
s = movie.getSize()
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
return self.mask.setStyles({
|
||||||
|
'width': s.x,
|
||||||
|
'height': s.y
|
||||||
|
}).position({
|
||||||
|
'relativeTo': movie
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
toElement: function(){
|
||||||
|
return this.el || null
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var MA = {};
|
||||||
|
|
||||||
|
MA.IMDB = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
id: null,
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.id = self.movie.get('identifier');
|
||||||
|
|
||||||
|
self.el = new Element('a.imdb', {
|
||||||
|
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
|
||||||
|
'href': 'http://www.imdb.com/title/'+self.id+'/',
|
||||||
|
'target': '_blank'
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!self.id) self.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
MA.Release = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(self.movie.data.releases.length == 0)
|
||||||
|
self.el.hide()
|
||||||
|
else
|
||||||
|
self.showHelper();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
createReleases: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(!self.options_container){
|
||||||
|
self.options_container = new Element('div.options').adopt(
|
||||||
|
self.release_container = new Element('div.releases.table').adopt(
|
||||||
|
self.trynext_container = new Element('div.buttons.try_container')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header
|
||||||
|
new Element('div.item.head').adopt(
|
||||||
|
new Element('span.name', {'text': 'Release name'}),
|
||||||
|
new Element('span.status', {'text': 'Status'}),
|
||||||
|
new Element('span.quality', {'text': 'Quality'}),
|
||||||
|
new Element('span.size', {'text': 'Size'}),
|
||||||
|
new Element('span.age', {'text': 'Age'}),
|
||||||
|
new Element('span.score', {'text': 'Score'}),
|
||||||
|
new Element('span.provider', {'text': 'Provider'})
|
||||||
|
).inject(self.release_container)
|
||||||
|
|
||||||
|
self.movie.data.releases.sortBy('-info.score').each(function(release){
|
||||||
|
|
||||||
|
var status = Status.get(release.status_id),
|
||||||
|
quality = Quality.getProfile(release.quality_id) || {},
|
||||||
|
info = release.info,
|
||||||
|
provider = self.get(release, 'provider') + (release.info['provider_extra'] ? self.get(release, 'provider_extra') : '');
|
||||||
|
release.status = status;
|
||||||
|
|
||||||
|
var release_name = self.get(release, 'name');
|
||||||
|
if(release.files && release.files.length > 0){
|
||||||
|
try {
|
||||||
|
var movie_file = release.files.filter(function(file){
|
||||||
|
var type = File.Type.get(file.type_id);
|
||||||
|
return type && type.identifier == 'movie'
|
||||||
|
}).pick();
|
||||||
|
release_name = movie_file.path.split(Api.getOption('path_sep')).getLast();
|
||||||
|
}
|
||||||
|
catch(e){}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create release
|
||||||
|
new Element('div', {
|
||||||
|
'class': 'item '+status.identifier,
|
||||||
|
'id': 'release_'+release.id
|
||||||
|
}).adopt(
|
||||||
|
new Element('span.name', {'text': release_name, 'title': release_name}),
|
||||||
|
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
||||||
|
new Element('span.quality', {'text': quality.get('label') || 'n/a'}),
|
||||||
|
new Element('span.size', {'text': release.info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
|
||||||
|
new Element('span.age', {'text': self.get(release, 'age')}),
|
||||||
|
new Element('span.score', {'text': self.get(release, 'score')}),
|
||||||
|
new Element('span.provider', { 'text': provider, 'title': provider }),
|
||||||
|
release.info['detail_url'] ? new Element('a.info.icon', {
|
||||||
|
'href': release.info['detail_url'],
|
||||||
|
'target': '_blank'
|
||||||
|
}) : null,
|
||||||
|
new Element('a.download.icon', {
|
||||||
|
'events': {
|
||||||
|
'click': function(e){
|
||||||
|
(e).preventDefault();
|
||||||
|
if(!this.hasClass('completed'))
|
||||||
|
self.download(release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Element('a.delete.icon', {
|
||||||
|
'events': {
|
||||||
|
'click': function(e){
|
||||||
|
(e).preventDefault();
|
||||||
|
self.ignore(release);
|
||||||
|
this.getParent('.item').toggleClass('ignored')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).inject(self.release_container)
|
||||||
|
|
||||||
|
if(status.identifier == 'ignored' || status.identifier == 'failed' || status.identifier == 'snatched'){
|
||||||
|
if(!self.last_release || (self.last_release && self.last_release.status.identifier != 'snatched' && status.identifier == 'snatched'))
|
||||||
|
self.last_release = release;
|
||||||
|
}
|
||||||
|
else if(!self.next_release && status.identifier == 'available'){
|
||||||
|
self.next_release = release;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(self.last_release){
|
||||||
|
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.next_release){
|
||||||
|
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.next_release || self.last_release){
|
||||||
|
|
||||||
|
self.trynext_container.adopt(
|
||||||
|
new Element('span.or', {
|
||||||
|
'text': 'This movie is snatched, if anything went wrong, download'
|
||||||
|
}),
|
||||||
|
self.last_release ? new Element('a.button.orange', {
|
||||||
|
'text': 'the same release again',
|
||||||
|
'events': {
|
||||||
|
'click': self.trySameRelease.bind(self)
|
||||||
|
}
|
||||||
|
}) : null,
|
||||||
|
self.next_release && self.last_release ? new Element('span.or', {
|
||||||
|
'text': ','
|
||||||
|
}) : null,
|
||||||
|
self.next_release ? [new Element('a.button.green', {
|
||||||
|
'text': self.last_release ? 'another release' : 'the best release',
|
||||||
|
'events': {
|
||||||
|
'click': self.tryNextRelease.bind(self)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Element('span.or', {
|
||||||
|
'text': 'or pick one below'
|
||||||
|
})] : null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(e){
|
||||||
|
var self = this;
|
||||||
|
if(e)
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
self.createReleases();
|
||||||
|
self.options_container.inject(self.movie, 'top');
|
||||||
|
self.movie.slide('in', self.options_container);
|
||||||
|
},
|
||||||
|
|
||||||
|
showHelper: function(e){
|
||||||
|
var self = this;
|
||||||
|
if(e)
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
self.createReleases();
|
||||||
|
self.trynext_container = new Element('div.buttons.trynext').inject(self.movie.info_container);
|
||||||
|
|
||||||
|
if(self.next_release || self.last_release){
|
||||||
|
|
||||||
|
self.trynext_container.adopt(
|
||||||
|
self.next_release ? [new Element('a.icon.readd', {
|
||||||
|
'text': self.last_release ? 'Download another release' : 'Download the best release',
|
||||||
|
'events': {
|
||||||
|
'click': self.tryNextRelease.bind(self)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Element('a.icon.download', {
|
||||||
|
'text': 'pick one yourself',
|
||||||
|
'events': {
|
||||||
|
'click': function(){
|
||||||
|
self.movie.quality.fireEvent('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})] : null,
|
||||||
|
new Element('a.icon.completed', {
|
||||||
|
'text': 'mark this movie done',
|
||||||
|
'events': {
|
||||||
|
'click': function(){
|
||||||
|
Api.request('movie.delete', {
|
||||||
|
'data': {
|
||||||
|
'id': self.movie.get('id'),
|
||||||
|
'delete_from': 'wanted'
|
||||||
|
},
|
||||||
|
'onComplete': function(){
|
||||||
|
var movie = $(self.movie);
|
||||||
|
movie.set('tween', {
|
||||||
|
'duration': 300,
|
||||||
|
'onComplete': function(){
|
||||||
|
self.movie.destroy()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
movie.tween('height', 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(release, type){
|
||||||
|
return release.info[type] || 'n/a'
|
||||||
|
},
|
||||||
|
|
||||||
|
download: function(release){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var release_el = self.release_container.getElement('#release_'+release.id),
|
||||||
|
icon = release_el.getElement('.download.icon');
|
||||||
|
|
||||||
|
self.movie.busy(true);
|
||||||
|
|
||||||
|
Api.request('release.download', {
|
||||||
|
'data': {
|
||||||
|
'id': release.id
|
||||||
|
},
|
||||||
|
'onComplete': function(json){
|
||||||
|
self.movie.busy(false);
|
||||||
|
|
||||||
|
if(json.success)
|
||||||
|
icon.addClass('completed');
|
||||||
|
else
|
||||||
|
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
ignore: function(release){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
Api.request('release.ignore', {
|
||||||
|
'data': {
|
||||||
|
'id': release.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
tryNextRelease: function(movie_id){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.createReleases();
|
||||||
|
|
||||||
|
if(self.last_release)
|
||||||
|
self.ignore(self.last_release);
|
||||||
|
|
||||||
|
if(self.next_release)
|
||||||
|
self.download(self.next_release);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
trySameRelease: function(movie_id){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(self.last_release)
|
||||||
|
self.download(self.last_release);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
MA.Trailer = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
id: null,
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el = new Element('a.trailer', {
|
||||||
|
'title': 'Watch the trailer of ' + self.movie.getTitle(),
|
||||||
|
'events': {
|
||||||
|
'click': self.watch.bind(self)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: function(offset){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
|
||||||
|
var url = data_url.substitute({
|
||||||
|
'title': encodeURI(self.movie.getTitle()),
|
||||||
|
'year': self.movie.get('year'),
|
||||||
|
'offset': offset || 1
|
||||||
|
}),
|
||||||
|
size = $(self.movie).getSize(),
|
||||||
|
height = (size.x/16)*9,
|
||||||
|
id = 'trailer-'+randomString();
|
||||||
|
|
||||||
|
self.player_container = new Element('div[id='+id+']');
|
||||||
|
self.container = new Element('div.hide.trailer_container')
|
||||||
|
.adopt(self.player_container)
|
||||||
|
.inject($(self.movie), 'top');
|
||||||
|
|
||||||
|
self.container.setStyle('height', 0);
|
||||||
|
self.container.removeClass('hide');
|
||||||
|
|
||||||
|
self.close_button = new Element('a.hide.hide_trailer', {
|
||||||
|
'text': 'Hide trailer',
|
||||||
|
'events': {
|
||||||
|
'click': self.stop.bind(self)
|
||||||
|
}
|
||||||
|
}).inject(self.movie);
|
||||||
|
|
||||||
|
self.container.setStyle('height', height);
|
||||||
|
$(self.movie).setStyle('height', height);
|
||||||
|
|
||||||
|
new Request.JSONP({
|
||||||
|
'url': url,
|
||||||
|
'onComplete': function(json){
|
||||||
|
var video_url = json.feed.entry[0].id.$t.split('/'),
|
||||||
|
video_id = video_url[video_url.length-1];
|
||||||
|
|
||||||
|
self.player = new YT.Player(id, {
|
||||||
|
'height': height,
|
||||||
|
'width': size.x,
|
||||||
|
'videoId': video_id,
|
||||||
|
'playerVars': {
|
||||||
|
'autoplay': 1,
|
||||||
|
'showsearch': 0,
|
||||||
|
'wmode': 'transparent',
|
||||||
|
'iv_load_policy': 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.close_button.removeClass('hide');
|
||||||
|
|
||||||
|
var quality_set = false;
|
||||||
|
var change_quality = function(state){
|
||||||
|
if(!quality_set && (state.data == 1 || state.data || 2)){
|
||||||
|
try {
|
||||||
|
self.player.setPlaybackQuality('hd720');
|
||||||
|
quality_set = true;
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.player.addEventListener('onStateChange', change_quality);
|
||||||
|
|
||||||
|
}
|
||||||
|
}).send()
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
stop: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.player.stopVideo();
|
||||||
|
self.container.addClass('hide');
|
||||||
|
self.close_button.addClass('hide');
|
||||||
|
$(self.movie).setStyle('height', null);
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
self.container.destroy()
|
||||||
|
self.close_button.destroy();
|
||||||
|
}, 1800)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
MA.Edit = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el = new Element('a.edit', {
|
||||||
|
'title': 'Change movie information, like title and quality.',
|
||||||
|
'events': {
|
||||||
|
'click': self.editMovie.bind(self)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
editMovie: function(e){
|
||||||
|
var self = this;
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
if(!self.options_container){
|
||||||
|
self.options_container = new Element('div.options').adopt(
|
||||||
|
new Element('div.form').adopt(
|
||||||
|
self.title_select = new Element('select', {
|
||||||
|
'name': 'title'
|
||||||
|
}),
|
||||||
|
self.profile_select = new Element('select', {
|
||||||
|
'name': 'profile'
|
||||||
|
}),
|
||||||
|
new Element('a.button.edit', {
|
||||||
|
'text': 'Save & Search',
|
||||||
|
'events': {
|
||||||
|
'click': self.save.bind(self)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).inject(self.movie, 'top');
|
||||||
|
|
||||||
|
Array.each(self.movie.data.library.titles, function(alt){
|
||||||
|
new Element('option', {
|
||||||
|
'text': alt.title
|
||||||
|
}).inject(self.title_select);
|
||||||
|
|
||||||
|
if(alt['default'])
|
||||||
|
self.title_select.set('value', alt.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Quality.getActiveProfiles().each(function(profile){
|
||||||
|
|
||||||
|
var profile_id = profile.id ? profile.id : profile.data.id;
|
||||||
|
|
||||||
|
new Element('option', {
|
||||||
|
'value': profile_id,
|
||||||
|
'text': profile.label ? profile.label : profile.data.label
|
||||||
|
}).inject(self.profile_select);
|
||||||
|
|
||||||
|
if(self.movie.profile && self.movie.profile.data && self.movie.profile.data.id == profile_id)
|
||||||
|
self.profile_select.set('value', profile_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
self.movie.slide('in', self.options_container);
|
||||||
|
},
|
||||||
|
|
||||||
|
save: function(e){
|
||||||
|
(e).preventDefault();
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
Api.request('movie.edit', {
|
||||||
|
'data': {
|
||||||
|
'id': self.movie.get('id'),
|
||||||
|
'default_title': self.title_select.get('value'),
|
||||||
|
'profile_id': self.profile_select.get('value')
|
||||||
|
},
|
||||||
|
'useSpinner': true,
|
||||||
|
'spinnerTarget': $(self.movie),
|
||||||
|
'onComplete': function(){
|
||||||
|
self.movie.quality.set('text', self.profile_select.getSelected()[0].get('text'));
|
||||||
|
self.movie.title.set('text', self.title_select.getSelected()[0].get('text'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.movie.slide('out');
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
MA.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.doRefresh.bind(self)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
doRefresh: function(e){
|
||||||
|
var self = this;
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
Api.request('movie.refresh', {
|
||||||
|
'data': {
|
||||||
|
'id': self.movie.get('id')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
MA.Readd = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var movie_done = Status.get(self.movie.data.status_id).identifier == 'done';
|
||||||
|
if(!movie_done)
|
||||||
|
var snatched = self.movie.data.releases.filter(function(release){
|
||||||
|
return release.status && (release.status.identifier == 'snatched' || release.status.identifier == 'downloaded' || release.status.identifier == 'done');
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
if(movie_done || snatched && snatched > 0)
|
||||||
|
self.el = new Element('a.readd', {
|
||||||
|
'title': 'Readd the movie and mark all previous snatched/downloaded as ignored',
|
||||||
|
'events': {
|
||||||
|
'click': self.doReadd.bind(self)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
doReadd: function(e){
|
||||||
|
var self = this;
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
Api.request('movie.add', {
|
||||||
|
'data': {
|
||||||
|
'identifier': self.movie.get('identifier'),
|
||||||
|
'ignore_previous': 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
MA.Delete = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
|
||||||
|
Implements: [Chain],
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el = new Element('a.delete', {
|
||||||
|
'title': 'Remove the movie from this CP list',
|
||||||
|
'events': {
|
||||||
|
'click': self.showConfirm.bind(self)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
showConfirm: function(e){
|
||||||
|
var self = this;
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
if(!self.delete_container){
|
||||||
|
self.delete_container = new Element('div.buttons.delete_container').adopt(
|
||||||
|
new Element('a.cancel', {
|
||||||
|
'text': 'Cancel',
|
||||||
|
'events': {
|
||||||
|
'click': self.hideConfirm.bind(self)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Element('span.or', {
|
||||||
|
'text': 'or'
|
||||||
|
}),
|
||||||
|
new Element('a.button.delete', {
|
||||||
|
'text': 'Delete ' + self.movie.title.get('text'),
|
||||||
|
'events': {
|
||||||
|
'click': self.del.bind(self)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).inject(self.movie, 'top');
|
||||||
|
}
|
||||||
|
|
||||||
|
self.movie.slide('in', self.delete_container);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
hideConfirm: function(e){
|
||||||
|
var self = this;
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
self.movie.slide('out');
|
||||||
|
},
|
||||||
|
|
||||||
|
del: function(e){
|
||||||
|
(e).preventDefault();
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var movie = $(self.movie);
|
||||||
|
|
||||||
|
self.chain(
|
||||||
|
function(){
|
||||||
|
self.callChain();
|
||||||
|
},
|
||||||
|
function(){
|
||||||
|
Api.request('movie.delete', {
|
||||||
|
'data': {
|
||||||
|
'id': self.movie.get('id'),
|
||||||
|
'delete_from': self.movie.list.options.identifier
|
||||||
|
},
|
||||||
|
'onComplete': function(){
|
||||||
|
movie.set('tween', {
|
||||||
|
'duration': 300,
|
||||||
|
'onComplete': function(){
|
||||||
|
self.movie.destroy()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
movie.tween('height', 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
self.callChain();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
MA.Files = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el = new Element('a.directory', {
|
||||||
|
'title': 'Available files',
|
||||||
|
'events': {
|
||||||
|
'click': self.showFiles.bind(self)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
showFiles: function(e){
|
||||||
|
var self = this;
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
|
if(!self.options_container){
|
||||||
|
self.options_container = new Element('div.options').adopt(
|
||||||
|
self.files_container = new Element('div.files.table')
|
||||||
|
).inject(self.movie, 'top');
|
||||||
|
|
||||||
|
// Header
|
||||||
|
new Element('div.item.head').adopt(
|
||||||
|
new Element('span.name', {'text': 'File'}),
|
||||||
|
new Element('span.type', {'text': 'Type'}),
|
||||||
|
new Element('span.is_available', {'text': 'Available'})
|
||||||
|
).inject(self.files_container)
|
||||||
|
|
||||||
|
Array.each(self.movie.data.releases, function(release){
|
||||||
|
|
||||||
|
var rel = new Element('div.release').inject(self.files_container);
|
||||||
|
|
||||||
|
Array.each(release.files, function(file){
|
||||||
|
new Element('div.file.item').adopt(
|
||||||
|
new Element('span.name', {'text': file.path}),
|
||||||
|
new Element('span.type', {'text': File.Type.get(file.type_id).name}),
|
||||||
|
new Element('span.available', {'text': file.available})
|
||||||
|
).inject(rel)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
self.movie.slide('in', self.options_container);
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,50 +1,185 @@
|
|||||||
.movies {
|
.movies {
|
||||||
padding: 60px 0 20px;
|
padding: 10px 0 20px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies.mass_edit_list {
|
.movies > div {
|
||||||
padding-top: 90px;
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list > div:not(.description) {
|
||||||
|
margin-right: -4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .loading {
|
||||||
|
display: block;
|
||||||
|
padding: 20px 0 0 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||||
|
height: 40px;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.movies .loading.hide {
|
||||||
|
height: 0;
|
||||||
|
padding: 0;
|
||||||
|
opacity: 0;
|
||||||
|
margin-top: -20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .loading .spinner {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .loading .message {
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.movies h2 {
|
||||||
|
font-size: 25px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies > .description {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
right: 0;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.movies:hover > .description {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 860px) {
|
||||||
|
.movies > .description {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list {
|
||||||
|
padding: 20px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home .movies {
|
||||||
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .movie {
|
.movies .movie {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
padding-left: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: all 0.2s linear;
|
height: 180px;
|
||||||
|
transition: all 0.6s cubic-bezier(0.9,0,0.1,1);
|
||||||
}
|
}
|
||||||
.movies .movie.list_view, .movies .movie.mass_edit_view {
|
|
||||||
|
.movies.details_list .movie {
|
||||||
|
padding-left: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.list_list .movie:not(.details_view),
|
||||||
|
.movies.mass_edit_list .movie {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .movie {
|
||||||
|
width: 16.66667%;
|
||||||
|
height: auto;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 800px) {
|
||||||
|
.movies.thumbs_list .movie {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.list_list .movie:not(.details_view),
|
||||||
|
.movies.mass_edit_list .movie {
|
||||||
margin: 1px 0;
|
margin: 1px 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: no-repeat;
|
background: no-repeat;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||||
}
|
}
|
||||||
.movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover {
|
|
||||||
background: rgba(255,255,255,0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.movies .movie_container {
|
.movies.list_list .movie:hover:not(.details_view),
|
||||||
overflow: hidden;
|
.movies.mass_edit_list .movie {
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .data {
|
.movies .data {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
height: 180px;
|
height: 100%;
|
||||||
width: 840px;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
float: right;
|
right: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
transition: all 0.2s linear;
|
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
||||||
}
|
}
|
||||||
.movies .list_view .data, .movies .mass_edit_view .data {
|
.movies.list_list .movie:not(.details_view) .data,
|
||||||
|
.movies.mass_edit_list .movie .data {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding: 3px 0 3px 10px;
|
padding: 3px 0 3px 10px;
|
||||||
width: 938px;
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
background: #4e5969;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .data {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
height: 100%;
|
||||||
background: none;
|
background: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .movie.no_thumbnail .data { background-image: linear-gradient(-30deg, rgba(255, 0, 85, .2) 0,rgba(125, 185, 235, .2) 100%);
|
||||||
|
}
|
||||||
|
.movies.thumbs_list .movie.no_thumbnail:nth-child(2n+6) .data { background-image: linear-gradient(-20deg, rgba(125, 0, 215, .2) 0, rgba(4, 55, 5, .7) 100%); }
|
||||||
|
.movies.thumbs_list .movie.no_thumbnail:nth-child(3n+6) .data { background-image: linear-gradient(-30deg, rgba(155, 0, 85, .2) 0,rgba(25, 185, 235, .7) 100%); }
|
||||||
|
.movies.thumbs_list .movie.no_thumbnail:nth-child(4n+6) .data { background-image: linear-gradient(-30deg, rgba(115, 5, 235, .2) 0, rgba(55, 180, 5, .7) 100%); }
|
||||||
|
.movies.thumbs_list .movie.no_thumbnail:nth-child(5n+6) .data { background-image: linear-gradient(-30deg, rgba(35, 15, 215, .2) 0, rgba(135, 215, 115, .7) 100%); }
|
||||||
|
.movies.thumbs_list .movie.no_thumbnail:nth-child(6n+6) .data { background-image: linear-gradient(-30deg, rgba(35, 15, 215, .2) 0, rgba(135, 15, 115, .7) 100%); }
|
||||||
|
|
||||||
|
.movies.thumbs_list .movie:hover .data {
|
||||||
|
background: rgba(0,0,0,0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .data.hide_right {
|
||||||
|
right: -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .movie .check {
|
.movies .movie .check {
|
||||||
@@ -52,143 +187,264 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.movies.mass_edit_list .movie .check {
|
.movies.mass_edit_list .movie .check {
|
||||||
float: left;
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 7px 0 0 5px;
|
margin: 7px 0 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .poster {
|
.movies .poster {
|
||||||
float: left;
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 180px;
|
height: 100%;
|
||||||
border-radius: 4px 0 0 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
transition: all 0.2s linear;
|
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
||||||
|
|
||||||
}
|
}
|
||||||
.movies .list_view .poster, .movies .mass_edit_view .poster {
|
.movies.thumbs_list .poster {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.movies.list_list .movie:not(.details_view) .poster,
|
||||||
|
.movies.mass_edit_list .poster {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
border-radius: 1px 0 0 1px;
|
||||||
}
|
}
|
||||||
.movies.mass_edit_list .poster {
|
.movies.mass_edit_list .poster {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .poster {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .poster img, .options .poster img {
|
.movies .poster img,
|
||||||
width: 101%;
|
.options .poster img {
|
||||||
height: 101%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.movies.thumbs_list .poster img {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .info {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .info .title {
|
.movies .info .title {
|
||||||
font-size: 30px;
|
font-size: 28px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
float: left;
|
margin-top: 2px;
|
||||||
width: 90%;
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 80px;
|
||||||
transition: all 0.2s linear;
|
transition: all 0.2s linear;
|
||||||
}
|
}
|
||||||
.movies .list_view .info .title, .movies .mass_edit_view .info .title {
|
.movies .info .title span {
|
||||||
|
display: block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
top: -5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .info .title span {
|
||||||
|
white-space: normal;
|
||||||
|
overflow: auto;
|
||||||
|
height: auto;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.movies.thumbs_list .movie .info .title span,
|
||||||
|
.movies.thumbs_list .movie .info .year {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.list_list .movie:not(.details_view) .info .title,
|
||||||
|
.movies.mass_edit_list .info .title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .movie:not(.no_thumbnail) .info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.movies.thumbs_list .movie:hover .info {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .info .title {
|
||||||
|
font-size: 21px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .info .year {
|
.movies .info .year {
|
||||||
font-size: 30px;
|
position: absolute;
|
||||||
margin-bottom: 10px;
|
|
||||||
float: right;
|
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
width: 10%;
|
right: 0;
|
||||||
|
top: 1px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
transition: all 0.2s linear;
|
transition: all 0.2s linear;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
.movies .list_view .info .year, .movies .mass_edit_view .info .year {
|
.movies.list_list .movie:not(.details_view) .info .year,
|
||||||
font-size: 16px;
|
.movies.mass_edit_list .info .year {
|
||||||
width: 6%;
|
font-size: 1.25em;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .info .year {
|
||||||
|
font-size: 23px;
|
||||||
|
margin: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .info .rating {
|
|
||||||
font-size: 30px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #444;
|
|
||||||
float: left;
|
|
||||||
width: 5%;
|
|
||||||
padding: 0 0 0 3%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movies .info .description {
|
.movies .info .description {
|
||||||
|
top: 30px;
|
||||||
clear: both;
|
clear: both;
|
||||||
height: 80px;
|
bottom: 30px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
.movies .data:hover .description {
|
.movies .data:hover .description {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.movies .list_view .info .description, .movies .mass_edit_view .info .description {
|
.movies.list_list .movie:not(.details_view) .info .description,
|
||||||
|
.movies.mass_edit_list .info .description,
|
||||||
|
.movies.thumbs_list .info .description {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .data .quality {
|
.movies .data .quality {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2px;
|
||||||
display: block;
|
display: block;
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
vertical-align: mid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .data .quality span {
|
@media all and (max-width: 480px) {
|
||||||
padding: 2px 3px;
|
.movies .data .quality {
|
||||||
font-weight: bold;
|
display: none;
|
||||||
opacity: 0.5;
|
}
|
||||||
font-size: 10px;
|
|
||||||
height: 16px;
|
|
||||||
line-height: 12px;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: inline-block;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-shadow: none;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0 2px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: rgba(255,255,255,0.1);
|
|
||||||
}
|
|
||||||
.movies .list_view .data .quality, .movies .mass_edit_view .data .quality {
|
|
||||||
text-align: right;
|
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .data .quality .available, .movies .data .quality .snatched {
|
.movies .status_suggest .data .quality,
|
||||||
opacity: 1;
|
.movies.thumbs_list .data .quality {
|
||||||
box-shadow: 1px 1px 0 rgba(0,0,0,0.2);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movies .data .quality .available { background-color: #578bc3; }
|
|
||||||
.movies .data .quality .snatched { background-color: #369545; }
|
|
||||||
.movies .data .quality .done {
|
|
||||||
background-color: #369545;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.movies .data .quality .finish {
|
|
||||||
background-image: url('../images/sprite.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 2px;
|
|
||||||
padding-left: 14px;
|
|
||||||
background-size: 14px
|
|
||||||
}
|
|
||||||
|
|
||||||
.movies .data .actions {
|
|
||||||
line-height: 0;
|
|
||||||
clear: both;
|
|
||||||
float: right;
|
|
||||||
margin-top: -25px;
|
|
||||||
}
|
|
||||||
.movies .data:hover .action { opacity: 0.6; }
|
|
||||||
.movies .data:hover .action:hover { opacity: 1; }
|
|
||||||
.movies.mass_edit_list .data .actions {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .data .quality span {
|
||||||
|
padding: 2px 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 10px;
|
||||||
|
height: 16px;
|
||||||
|
line-height: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0 4px 0 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.movies.list_list .data .quality,
|
||||||
|
.movies.mass_edit_list .data .quality {
|
||||||
|
text-align: right;
|
||||||
|
right: 0;
|
||||||
|
margin-right: 60px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .data .quality .available,
|
||||||
|
.movies .data .quality .snatched {
|
||||||
|
opacity: 1;
|
||||||
|
box-shadow: 1px 1px 0 rgba(0,0,0,0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .data .quality .available { background-color: #578bc3; }
|
||||||
|
.movies .data .quality .snatched { background-color: #369545; }
|
||||||
|
.movies .data .quality .done {
|
||||||
|
background-color: #369545;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.movies .data .quality .finish {
|
||||||
|
background-image: url('../images/sprite.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 2px;
|
||||||
|
padding-left: 14px;
|
||||||
|
background-size: 14px
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .data .actions {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
line-height: 0;
|
||||||
|
top: 0;
|
||||||
|
opacity: 0;
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.movies .data .actions {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie:hover .data .actions {
|
||||||
|
opacity: 1;
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.details_list .data .actions {
|
||||||
|
top: auto;
|
||||||
|
bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie:hover .actions {
|
||||||
|
opacity: 1;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.movies.thumbs_list .data .actions {
|
||||||
|
bottom: 2px;
|
||||||
|
right: 10px;
|
||||||
|
top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie:hover .action { opacity: 0.6; }
|
||||||
|
.movies .movie:hover .action:hover { opacity: 1; }
|
||||||
|
|
||||||
.movies .data .action {
|
.movies .data .action {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@@ -196,13 +452,20 @@
|
|||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
opacity: 0;
|
}
|
||||||
|
|
||||||
|
.movies.mass_edit_list .movie .data .actions {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions {
|
.movies.list_list .movie:not(.details_view):hover .actions,
|
||||||
margin: -34px 2px 0 0;
|
.movies.mass_edit_list .movie:hover .actions {
|
||||||
|
margin: 0;
|
||||||
background: #4e5969;
|
background: #4e5969;
|
||||||
position: relative;
|
top: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
right: 5px;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .delete_container {
|
.movies .delete_container {
|
||||||
@@ -211,7 +474,8 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 70px 0 0;
|
padding: 70px 0 0;
|
||||||
width: 100%;
|
left: 120px;
|
||||||
|
right: 0;
|
||||||
}
|
}
|
||||||
.movies .delete_container .cancel {
|
.movies .delete_container .cancel {
|
||||||
}
|
}
|
||||||
@@ -229,8 +493,8 @@
|
|||||||
|
|
||||||
.movies .options {
|
.movies .options {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 120px;
|
right: 0;
|
||||||
width: 840px;
|
left: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .options .form {
|
.movies .options .form {
|
||||||
@@ -253,7 +517,6 @@
|
|||||||
.movies .options .table .item.ignored span {
|
.movies .options .table .item.ignored span {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: rgba(255,255,255,0.4);
|
color: rgba(255,255,255,0.4);
|
||||||
text-shadow: none;
|
|
||||||
}
|
}
|
||||||
.movies .options .table .item.ignored .delete {
|
.movies .options .table .item.ignored .delete {
|
||||||
background-image: url('../images/icon.undo.png');
|
background-image: url('../images/icon.undo.png');
|
||||||
@@ -284,9 +547,10 @@
|
|||||||
.movies .options .table .provider {
|
.movies .options .table .provider {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.movies .options .table .name {
|
.movies .options .table .name {
|
||||||
width: 350px;
|
width: 340px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
@@ -320,6 +584,9 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.movies .movie .trailer_container.hide {
|
.movies .movie .trailer_container.hide {
|
||||||
height: 0 !important;
|
height: 0 !important;
|
||||||
@@ -335,11 +602,12 @@
|
|||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
background: #4e5969;
|
background: #4e5969;
|
||||||
border-radius: 0 0 2px 2px;
|
border-radius: 0 0 2px 2px;
|
||||||
transition: all .6s cubic-bezier(0.9,0,0.1,1) .2s;
|
transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s;
|
||||||
}
|
z-index: 11;
|
||||||
.movies .movie .hide_trailer.hide {
|
|
||||||
top: -30px;
|
|
||||||
}
|
}
|
||||||
|
.movies .movie .hide_trailer.hide {
|
||||||
|
top: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .movie .try_container {
|
.movies .movie .try_container {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
@@ -366,6 +634,54 @@
|
|||||||
.movies .movie .releases .last_release > :first-child {
|
.movies .movie .releases .last_release > :first-child {
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
}
|
}
|
||||||
|
.movies .movie .trynext {
|
||||||
|
display: inline;
|
||||||
|
position: absolute;
|
||||||
|
right: 135px;
|
||||||
|
z-index: 2;
|
||||||
|
opacity: 0;
|
||||||
|
background: #4e5969;
|
||||||
|
min-width: 300px;
|
||||||
|
text-align: right;
|
||||||
|
height: 100%;
|
||||||
|
padding: 3px 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.movies .movie .trynext {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.movies.mass_edit_list .trynext { display: none; }
|
||||||
|
.wanted .movies .movie .trynext {
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
.movies .movie:hover .trynext {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.details_list .movie .trynext {
|
||||||
|
background: #47515f;
|
||||||
|
padding: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 35px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .trynext a {
|
||||||
|
background-position: 5px center;
|
||||||
|
padding: 0 5px 0 25px;
|
||||||
|
margin-right: 10px;
|
||||||
|
color: #FFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.movies .movie .trynext a:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.movies .movie .trynext a:hover {
|
||||||
|
background-color: #369545;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .load_more {
|
.movies .load_more {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -379,24 +695,32 @@
|
|||||||
|
|
||||||
.movies .alph_nav {
|
.movies .alph_nav {
|
||||||
transition: box-shadow .4s linear;
|
transition: box-shadow .4s linear;
|
||||||
position: fixed;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 4;
|
||||||
top: 0;
|
top: 0px;
|
||||||
padding: 100px 60px 7px;
|
right: 0;
|
||||||
width: 1080px;
|
margin: 0 auto;
|
||||||
margin: 0 -60px;
|
width: 100%;
|
||||||
box-shadow: 0 20px 20px -22px rgba(0,0,0,0.1);
|
padding: 10px 0;
|
||||||
background: #4e5969;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav.float {
|
@media all and (max-width: 480px) {
|
||||||
box-shadow: 0 30px 30px -32px rgba(0,0,0,0.5);
|
.movies .alph_nav {
|
||||||
border-radius: 0;
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav ul.numbers,
|
.movies .alph_nav > div {
|
||||||
|
position: relative;
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav .numbers,
|
||||||
.movies .alph_nav .counter,
|
.movies .alph_nav .counter,
|
||||||
.movies .alph_nav ul.actions {
|
.movies .alph_nav .actions {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0 0 1px;
|
padding: 0 0 1px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -405,48 +729,62 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav .counter {
|
.movies .alph_nav .counter {
|
||||||
width: 60px;
|
text-align: right;
|
||||||
text-align: center;
|
position: absolute;
|
||||||
|
right: 270px;
|
||||||
|
background: #4e5969;
|
||||||
|
padding: 4px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav .numbers li, .movies .alph_nav .actions li {
|
.movies .alph_nav .numbers li,
|
||||||
|
.movies .alph_nav .actions li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
width: 20px;
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
line-height: 26px;
|
line-height: 23px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: rgba(255,255,255,0.2);
|
color: rgba(255,255,255,0.2);
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
transition: all 0.1s ease-in-out;
|
transition: all 0.1s ease-in-out;
|
||||||
text-shadow: none;
|
|
||||||
}
|
}
|
||||||
.movies .alph_nav .numbers li:first-child {
|
|
||||||
width: 43px;
|
@media all and (max-width: 900px) {
|
||||||
}
|
.movies .alph_nav .numbers {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav .numbers li {
|
||||||
|
width: auto;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .alph_nav li.available {
|
.movies .alph_nav li.available {
|
||||||
color: rgba(255,255,255,0.8);
|
color: #FFF;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
|
|
||||||
}
|
}
|
||||||
.movies .alph_nav li.active.available, .movies .alph_nav li.available:hover {
|
.movies .alph_nav li.active.available,
|
||||||
color: #fff;
|
.movies .alph_nav li.available:hover {
|
||||||
font-size: 20px;
|
background: rgba(255,255,255,.1);
|
||||||
line-height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav input {
|
.movies .alph_nav .search {
|
||||||
padding: 6px 5px;
|
padding: 6px 5px;
|
||||||
margin: 0 0 0 6px;
|
margin: 0 0 0 20px;
|
||||||
float: left;
|
position: absolute;
|
||||||
width: 155px;
|
right: 30px;
|
||||||
|
width: 154px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
|
transition: all 0.6s cubic-bezier(0.9,0,0.1,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav .actions {
|
.movies .alph_nav .actions {
|
||||||
margin: 0 6px 0 0;
|
margin: 0 6px 0 0;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 183px;
|
||||||
}
|
}
|
||||||
.movies .alph_nav .actions li {
|
.movies .alph_nav .actions li {
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
@@ -472,7 +810,7 @@
|
|||||||
background-position: 3px -95px;
|
background-position: 3px -95px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav .actions li.thumbs span {
|
.movies .alph_nav .actions li.details span {
|
||||||
background-position: 3px -74px;
|
background-position: 3px -74px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,10 +868,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav .more_menu {
|
.movies .alph_nav .more_menu {
|
||||||
margin-left: 48px;
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav .more_menu > a {
|
.movies .alph_nav .more_menu > a {
|
||||||
|
background-color: #4e5969;
|
||||||
background-position: center -158px;
|
background-position: center -158px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,7 +930,6 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-shadow: none;
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
border-left: 1px solid rgba(255, 255, 255, .2);
|
border-left: 1px solid rgba(255, 255, 255, .2);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ var Movie = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.data = data;
|
self.data = data;
|
||||||
self.view = options.view || 'thumbs';
|
self.view = options.view || 'details';
|
||||||
self.list = list;
|
self.list = list;
|
||||||
|
|
||||||
self.el = new Element('div.movie.inlay');
|
self.el = new Element('div.movie.inlay');
|
||||||
@@ -22,7 +22,10 @@ var Movie = new Class({
|
|||||||
addEvents: function(){
|
addEvents: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
App.addEvent('movie.update.'+self.data.id, self.update.bind(self));
|
App.addEvent('movie.update.'+self.data.id, function(notification){
|
||||||
|
self.busy(false)
|
||||||
|
self.update.delay(2000, self, notification);
|
||||||
|
});
|
||||||
|
|
||||||
['movie.busy', 'searcher.started'].each(function(listener){
|
['movie.busy', 'searcher.started'].each(function(listener){
|
||||||
App.addEvent(listener+'.'+self.data.id, function(notification){
|
App.addEvent(listener+'.'+self.data.id, function(notification){
|
||||||
@@ -57,22 +60,23 @@ var Movie = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!set_busy){
|
if(!set_busy){
|
||||||
if(self.spinner){
|
setTimeout(function(){
|
||||||
self.mask.fade('out');
|
if(self.spinner){
|
||||||
setTimeout(function(){
|
self.mask.fade('out');
|
||||||
if(self.mask)
|
setTimeout(function(){
|
||||||
self.mask.destroy();
|
if(self.mask)
|
||||||
if(self.spinner)
|
self.mask.destroy();
|
||||||
self.spinner.el.destroy();
|
if(self.spinner)
|
||||||
self.spinner = null;
|
self.spinner.el.destroy();
|
||||||
self.mask = null;
|
self.spinner = null;
|
||||||
}, 400);
|
self.mask = null;
|
||||||
}
|
}, 400);
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
else if(!self.spinner) {
|
else if(!self.spinner) {
|
||||||
self.createMask();
|
self.createMask();
|
||||||
self.spinner = createSpinner(self.mask);
|
self.spinner = createSpinner(self.mask);
|
||||||
self.positionMask();
|
|
||||||
self.mask.fade('in');
|
self.mask.fade('in');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -81,10 +85,9 @@ var Movie = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
self.mask = new Element('div.mask', {
|
self.mask = new Element('div.mask', {
|
||||||
'styles': {
|
'styles': {
|
||||||
'z-index': '1'
|
'z-index': 4
|
||||||
}
|
}
|
||||||
}).inject(self.el, 'top').fade('hide');
|
}).inject(self.el, 'top').fade('hide');
|
||||||
self.positionMask();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
positionMask: function(){
|
positionMask: function(){
|
||||||
@@ -103,7 +106,7 @@ var Movie = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.data = notification.data;
|
self.data = notification.data;
|
||||||
self.container.destroy();
|
self.el.empty();
|
||||||
|
|
||||||
self.profile = Quality.getProfile(self.data.profile_id) || {};
|
self.profile = Quality.getProfile(self.data.profile_id) || {};
|
||||||
self.create();
|
self.create();
|
||||||
@@ -114,52 +117,52 @@ var Movie = new Class({
|
|||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
var s = Status.get(self.get('status_id'));
|
||||||
|
self.el.addClass('status_'+s.identifier);
|
||||||
|
|
||||||
self.el.adopt(
|
self.el.adopt(
|
||||||
self.container = new Element('div.movie_container').adopt(
|
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
'events': {
|
||||||
'events': {
|
'change': function(){
|
||||||
'change': function(){
|
self.fireEvent('select')
|
||||||
self.fireEvent('select')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
}),
|
||||||
self.data_container = new Element('div.data.inlay.light', {
|
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||||
'tween': {
|
self.data_container = new Element('div.data.inlay.light').adopt(
|
||||||
duration: 400,
|
self.info_container = new Element('div.info').adopt(
|
||||||
transition: 'quint:in:out',
|
new Element('div.title').adopt(
|
||||||
onComplete: self.fireEvent.bind(self, 'slideEnd')
|
self.title = new Element('span', {
|
||||||
}
|
|
||||||
}).adopt(
|
|
||||||
self.info_container = new Element('div.info').adopt(
|
|
||||||
self.title = new Element('div.title', {
|
|
||||||
'text': self.getTitle() || 'n/a'
|
'text': self.getTitle() || 'n/a'
|
||||||
}),
|
}),
|
||||||
self.year = new Element('div.year', {
|
self.year = new Element('div.year', {
|
||||||
'text': self.data.library.year || 'n/a'
|
'text': self.data.library.year || 'n/a'
|
||||||
}),
|
|
||||||
self.rating = new Element('div.rating.icon', {
|
|
||||||
'text': self.data.library.rating
|
|
||||||
}),
|
|
||||||
self.description = new Element('div.description', {
|
|
||||||
'text': self.data.library.plot
|
|
||||||
}),
|
|
||||||
self.quality = new Element('div.quality', {
|
|
||||||
'events': {
|
|
||||||
'click': function(e){
|
|
||||||
var releases = self.el.getElement('.actions .releases');
|
|
||||||
if(releases)
|
|
||||||
releases.fireEvent('click', [e])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
self.actions = new Element('div.actions')
|
self.rating = new Element('div.rating.icon', {
|
||||||
)
|
'text': self.data.library.rating
|
||||||
|
}),
|
||||||
|
self.description = new Element('div.description', {
|
||||||
|
'text': self.data.library.plot
|
||||||
|
}),
|
||||||
|
self.quality = new Element('div.quality', {
|
||||||
|
'events': {
|
||||||
|
'click': function(e){
|
||||||
|
var releases = self.el.getElement('.actions .releases');
|
||||||
|
if(releases)
|
||||||
|
releases.fireEvent('click', [e])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
self.actions = new Element('div.actions')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
self.changeView(self.view);
|
if(self.thumbnail.empty)
|
||||||
|
self.el.addClass('no_thumbnail');
|
||||||
|
|
||||||
|
//self.changeView(self.view);
|
||||||
self.select_checkbox_class = new Form.Check(self.select_checkbox);
|
self.select_checkbox_class = new Form.Check(self.select_checkbox);
|
||||||
|
|
||||||
// Add profile
|
// Add profile
|
||||||
@@ -174,7 +177,7 @@ var Movie = new Class({
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add done releases
|
// Add releases
|
||||||
self.data.releases.each(function(release){
|
self.data.releases.each(function(release){
|
||||||
|
|
||||||
var q = self.quality.getElement('.q_id'+ release.quality_id),
|
var q = self.quality.getElement('.q_id'+ release.quality_id),
|
||||||
@@ -241,23 +244,23 @@ var Movie = new Class({
|
|||||||
|
|
||||||
if(direction == 'in'){
|
if(direction == 'in'){
|
||||||
self.temp_view = self.view;
|
self.temp_view = self.view;
|
||||||
self.changeView('thumbs')
|
self.changeView('details')
|
||||||
|
|
||||||
self.el.addEvent('outerClick', function(){
|
self.el.addEvent('outerClick', function(){
|
||||||
self.changeView(self.temp_view)
|
self.removeView()
|
||||||
self.slide('out')
|
self.slide('out')
|
||||||
})
|
})
|
||||||
el.show();
|
el.show();
|
||||||
self.data_container.tween('right', 0, -840);
|
self.data_container.addClass('hide_right');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.el.removeEvents('outerClick')
|
self.el.removeEvents('outerClick')
|
||||||
|
|
||||||
self.addEvent('slideEnd:once', function(){
|
setTimeout(function(){
|
||||||
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
|
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
|
||||||
});
|
}, 600);
|
||||||
|
|
||||||
self.data_container.tween('right', -840, 0);
|
self.data_container.removeClass('hide_right');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -271,6 +274,12 @@ var Movie = new Class({
|
|||||||
self.view = new_view;
|
self.view = new_view;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeView: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el.removeClass(self.view+'_view')
|
||||||
|
},
|
||||||
|
|
||||||
get: function(attr){
|
get: function(attr){
|
||||||
return this.data[attr] || this.data.library[attr]
|
return this.data[attr] || this.data.library[attr]
|
||||||
},
|
},
|
||||||
@@ -288,388 +297,4 @@ var Movie = new Class({
|
|||||||
return this.el;
|
return this.el;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var MovieAction = new Class({
|
|
||||||
|
|
||||||
class_name: 'action icon',
|
|
||||||
|
|
||||||
initialize: function(movie){
|
|
||||||
var self = this;
|
|
||||||
self.movie = movie;
|
|
||||||
|
|
||||||
self.create();
|
|
||||||
if(self.el)
|
|
||||||
self.el.addClass(self.class_name)
|
|
||||||
},
|
|
||||||
|
|
||||||
create: function(){},
|
|
||||||
|
|
||||||
disable: function(){
|
|
||||||
this.el.addClass('disable')
|
|
||||||
},
|
|
||||||
|
|
||||||
enable: function(){
|
|
||||||
this.el.removeClass('disable')
|
|
||||||
},
|
|
||||||
|
|
||||||
createMask: function(){
|
|
||||||
var self = this;
|
|
||||||
self.mask = new Element('div.mask', {
|
|
||||||
'styles': {
|
|
||||||
'z-index': '1'
|
|
||||||
}
|
|
||||||
}).inject(self.movie, 'top').fade('hide');
|
|
||||||
self.positionMask();
|
|
||||||
},
|
|
||||||
|
|
||||||
positionMask: function(){
|
|
||||||
var self = this,
|
|
||||||
movie = $(self.movie),
|
|
||||||
s = movie.getSize()
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
return self.mask.setStyles({
|
|
||||||
'width': s.x,
|
|
||||||
'height': s.y
|
|
||||||
}).position({
|
|
||||||
'relativeTo': movie
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
toElement: function(){
|
|
||||||
return this.el || null
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var IMDBAction = new Class({
|
|
||||||
|
|
||||||
Extends: MovieAction,
|
|
||||||
id: null,
|
|
||||||
|
|
||||||
create: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.id = self.movie.get('identifier');
|
|
||||||
|
|
||||||
self.el = new Element('a.imdb', {
|
|
||||||
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
|
|
||||||
'href': 'http://www.imdb.com/title/'+self.id+'/',
|
|
||||||
'target': '_blank'
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!self.id) self.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var ReleaseAction = new Class({
|
|
||||||
|
|
||||||
Extends: MovieAction,
|
|
||||||
|
|
||||||
create: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var buttons_done = false;
|
|
||||||
|
|
||||||
self.movie.data.releases.sortBy('-info.score').each(function(release){
|
|
||||||
if(buttons_done) return;
|
|
||||||
|
|
||||||
var status = Status.get(release.status_id);
|
|
||||||
|
|
||||||
if((self.next_release && (status.identifier == 'ignored' || status.identifier == 'failed')) || (!self.next_release && status.identifier == 'available')){
|
|
||||||
self.hide_on_click = false;
|
|
||||||
self.show();
|
|
||||||
buttons_done = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function(e){
|
|
||||||
var self = this;
|
|
||||||
if(e)
|
|
||||||
(e).preventDefault();
|
|
||||||
|
|
||||||
if(!self.options_container){
|
|
||||||
self.options_container = new Element('div.options').adopt(
|
|
||||||
self.release_container = new Element('div.releases.table').adopt(
|
|
||||||
self.trynext_container = new Element('div.buttons.try_container')
|
|
||||||
)
|
|
||||||
).inject(self.movie, 'top');
|
|
||||||
|
|
||||||
// Header
|
|
||||||
new Element('div.item.head').adopt(
|
|
||||||
new Element('span.name', {'text': 'Release name'}),
|
|
||||||
new Element('span.status', {'text': 'Status'}),
|
|
||||||
new Element('span.quality', {'text': 'Quality'}),
|
|
||||||
new Element('span.size', {'text': 'Size'}),
|
|
||||||
new Element('span.age', {'text': 'Age'}),
|
|
||||||
new Element('span.score', {'text': 'Score'}),
|
|
||||||
new Element('span.provider', {'text': 'Provider'})
|
|
||||||
).inject(self.release_container)
|
|
||||||
|
|
||||||
self.movie.data.releases.sortBy('-info.score').each(function(release){
|
|
||||||
|
|
||||||
var status = Status.get(release.status_id),
|
|
||||||
quality = Quality.getProfile(release.quality_id) || {},
|
|
||||||
info = release.info;
|
|
||||||
release.status = status;
|
|
||||||
|
|
||||||
// Create release
|
|
||||||
new Element('div', {
|
|
||||||
'class': 'item '+status.identifier,
|
|
||||||
'id': 'release_'+release.id
|
|
||||||
}).adopt(
|
|
||||||
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
|
||||||
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
|
||||||
new Element('span.quality', {'text': quality.get('label') || 'n/a'}),
|
|
||||||
new Element('span.size', {'text': release.info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
|
|
||||||
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')}),
|
|
||||||
release.info['detail_url'] ? new Element('a.info.icon', {
|
|
||||||
'href': release.info['detail_url'],
|
|
||||||
'target': '_blank'
|
|
||||||
}) : null,
|
|
||||||
new Element('a.download.icon', {
|
|
||||||
'events': {
|
|
||||||
'click': function(e){
|
|
||||||
(e).preventDefault();
|
|
||||||
if(!this.hasClass('completed'))
|
|
||||||
self.download(release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new Element('a.delete.icon', {
|
|
||||||
'events': {
|
|
||||||
'click': function(e){
|
|
||||||
(e).preventDefault();
|
|
||||||
self.ignore(release);
|
|
||||||
this.getParent('.item').toggleClass('ignored')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
).inject(self.release_container)
|
|
||||||
|
|
||||||
if(status.identifier == 'ignored' || status.identifier == 'failed' || status.identifier == 'snatched'){
|
|
||||||
if(!self.last_release || (self.last_release && self.last_release.status.identifier != 'snatched' && status.identifier == 'snatched'))
|
|
||||||
self.last_release = release;
|
|
||||||
}
|
|
||||||
else if(!self.next_release && status.identifier == 'available'){
|
|
||||||
self.next_release = release;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(self.last_release){
|
|
||||||
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self.next_release){
|
|
||||||
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self.next_release || self.last_release){
|
|
||||||
|
|
||||||
self.trynext_container.adopt(
|
|
||||||
new Element('span.or', {
|
|
||||||
'text': 'This movie is snatched, if anything went wrong, download'
|
|
||||||
}),
|
|
||||||
self.last_release ? new Element('a.button.orange', {
|
|
||||||
'text': 'the same release again',
|
|
||||||
'events': {
|
|
||||||
'click': self.trySameRelease.bind(self)
|
|
||||||
}
|
|
||||||
}) : null,
|
|
||||||
self.next_release && self.last_release ? new Element('span.or', {
|
|
||||||
'text': ','
|
|
||||||
}) : null,
|
|
||||||
self.next_release ? [new Element('a.button.green', {
|
|
||||||
'text': self.last_release ? 'another release' : 'the best release',
|
|
||||||
'events': {
|
|
||||||
'click': self.tryNextRelease.bind(self)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new Element('span.or', {
|
|
||||||
'text': 'or pick one below'
|
|
||||||
})] : null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
self.movie.slide('in', self.options_container);
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function(release, type){
|
|
||||||
return release.info[type] || 'n/a'
|
|
||||||
},
|
|
||||||
|
|
||||||
download: function(release){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var release_el = self.release_container.getElement('#release_'+release.id),
|
|
||||||
icon = release_el.getElement('.download.icon');
|
|
||||||
|
|
||||||
icon.addClass('spinner');
|
|
||||||
|
|
||||||
Api.request('release.download', {
|
|
||||||
'data': {
|
|
||||||
'id': release.id
|
|
||||||
},
|
|
||||||
'onComplete': function(json){
|
|
||||||
icon.removeClass('spinner')
|
|
||||||
if(json.success)
|
|
||||||
icon.addClass('completed');
|
|
||||||
else
|
|
||||||
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
ignore: function(release){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
Api.request('release.ignore', {
|
|
||||||
'data': {
|
|
||||||
'id': release.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
tryNextRelease: function(movie_id){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(self.last_release)
|
|
||||||
self.ignore(self.last_release);
|
|
||||||
|
|
||||||
if(self.next_release)
|
|
||||||
self.download(self.next_release);
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
trySameRelease: function(movie_id){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(self.last_release)
|
|
||||||
self.download(self.last_release);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var TrailerAction = new Class({
|
|
||||||
|
|
||||||
Extends: MovieAction,
|
|
||||||
id: null,
|
|
||||||
|
|
||||||
create: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.el = new Element('a.trailer', {
|
|
||||||
'title': 'Watch the trailer of ' + self.movie.getTitle(),
|
|
||||||
'events': {
|
|
||||||
'click': self.watch.bind(self)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: function(offset){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
|
|
||||||
var url = data_url.substitute({
|
|
||||||
'title': encodeURI(self.movie.getTitle()),
|
|
||||||
'year': self.movie.get('year'),
|
|
||||||
'offset': offset || 1
|
|
||||||
}),
|
|
||||||
size = $(self.movie).getSize(),
|
|
||||||
height = (size.x/16)*9,
|
|
||||||
id = 'trailer-'+randomString();
|
|
||||||
|
|
||||||
self.player_container = new Element('div[id='+id+']');
|
|
||||||
self.container = new Element('div.hide.trailer_container')
|
|
||||||
.adopt(self.player_container)
|
|
||||||
.inject(self.movie.container, 'top');
|
|
||||||
|
|
||||||
self.container.setStyle('height', 0);
|
|
||||||
self.container.removeClass('hide');
|
|
||||||
|
|
||||||
self.close_button = new Element('a.hide.hide_trailer', {
|
|
||||||
'text': 'Hide trailer',
|
|
||||||
'events': {
|
|
||||||
'click': self.stop.bind(self)
|
|
||||||
}
|
|
||||||
}).inject(self.movie);
|
|
||||||
|
|
||||||
setTimeout(function(){
|
|
||||||
$(self.movie).setStyle('max-height', height);
|
|
||||||
self.container.setStyle('height', height);
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
new Request.JSONP({
|
|
||||||
'url': url,
|
|
||||||
'onComplete': function(json){
|
|
||||||
var video_url = json.feed.entry[0].id.$t.split('/'),
|
|
||||||
video_id = video_url[video_url.length-1];
|
|
||||||
|
|
||||||
self.player = new YT.Player(id, {
|
|
||||||
'height': height,
|
|
||||||
'width': size.x,
|
|
||||||
'videoId': video_id,
|
|
||||||
'playerVars': {
|
|
||||||
'autoplay': 1,
|
|
||||||
'showsearch': 0,
|
|
||||||
'wmode': 'transparent',
|
|
||||||
'iv_load_policy': 3
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.close_button.removeClass('hide');
|
|
||||||
|
|
||||||
var quality_set = false;
|
|
||||||
var change_quality = function(state){
|
|
||||||
if(!quality_set && (state.data == 1 || state.data || 2)){
|
|
||||||
try {
|
|
||||||
self.player.setPlaybackQuality('hd720');
|
|
||||||
quality_set = true;
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.player.addEventListener('onStateChange', change_quality);
|
|
||||||
|
|
||||||
}
|
|
||||||
}).send()
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
stop: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.player.stopVideo();
|
|
||||||
self.container.addClass('hide');
|
|
||||||
self.close_button.addClass('hide');
|
|
||||||
|
|
||||||
setTimeout(function(){
|
|
||||||
self.container.destroy()
|
|
||||||
self.close_button.destroy();
|
|
||||||
}, 1800)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -1,113 +1,153 @@
|
|||||||
.search_form {
|
.search_form {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 25%;
|
position: absolute;
|
||||||
|
right: 105px;
|
||||||
|
top: 0;
|
||||||
|
text-align: right;
|
||||||
|
height: 100%;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-width: 0 0 4px;
|
||||||
}
|
}
|
||||||
|
.search_form:hover {
|
||||||
.search_form input {
|
border-color: #047792;
|
||||||
padding: 4px 20px 4px 4px;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
width: 100%;
|
|
||||||
height: 24px;
|
|
||||||
}
|
}
|
||||||
.search_form input:focus {
|
|
||||||
padding-right: 83px;
|
@media all and (max-width: 480px) {
|
||||||
|
.search_form {
|
||||||
|
right: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search_form.focused,
|
||||||
|
.search_form.shown {
|
||||||
|
border-color: #04bce6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search_form .input {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 45px;
|
||||||
|
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search_form.focused .input,
|
||||||
|
.search_form.shown .input {
|
||||||
|
width: 380px;
|
||||||
|
background: #4e5969;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .input .enter {
|
.search_form .input input {
|
||||||
background: #369545 url('../images/sprite.png') right -188px no-repeat;
|
border-radius: 0;
|
||||||
padding: 0 20px 0 4px;
|
display: block;
|
||||||
border-radius: 2px;
|
width: 100%;
|
||||||
text-transform: uppercase;
|
border: 0;
|
||||||
font-size: 10px;
|
background: rgba(255,255,255,.08);
|
||||||
margin-left: -78px;
|
color: #FFF;
|
||||||
display: inline-block;
|
font-size: 25px;
|
||||||
opacity: 0;
|
height: 100%;
|
||||||
position: relative;
|
padding: 10px;
|
||||||
top: -2px;
|
width: 100%;
|
||||||
cursor: pointer;
|
opacity: 0;
|
||||||
vertical-align: middle;
|
padding: 0 40px 0 10px;
|
||||||
visibility: hidden;
|
transition: all .4s ease-in-out .2s;
|
||||||
}
|
|
||||||
.search_form.focused .input .enter {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
}
|
||||||
.search_form.focused.filled .input .enter {
|
.search_form.focused .input input,
|
||||||
opacity: 1;
|
.search_form.shown .input input {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.search_form .input input {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search_form.focused .input,
|
||||||
|
.search_form.shown .input {
|
||||||
|
width: 277px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .input a {
|
.search_form .input a {
|
||||||
width: 17px;
|
position: absolute;
|
||||||
height: 20px;
|
top: 0;
|
||||||
display: inline-block;
|
right: 0;
|
||||||
margin: -2px 0 0 2px;
|
width: 44px;
|
||||||
top: 4px;
|
height: 100%;
|
||||||
right: 5px;
|
cursor: pointer;
|
||||||
background: url('../images/sprite.png') left -37px no-repeat;
|
vertical-align: middle;
|
||||||
cursor: pointer;
|
text-align: center;
|
||||||
opacity: 0;
|
line-height: 66px;
|
||||||
transition: all 0.2s ease-in-out;
|
font-size: 15px;
|
||||||
vertical-align: middle;
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search_form .input a:after {
|
||||||
|
content: "\e03e";
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form.filled .input a {
|
.search_form.shown.filled .input a:after {
|
||||||
opacity: 1;
|
content: "\e04e";
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.search_form .input a {
|
||||||
|
line-height: 44px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .results_container {
|
.search_form .results_container {
|
||||||
|
text-align: left;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: #5c697b;
|
background: #5c697b;
|
||||||
margin: 6px 0 0 -230px;
|
margin: 4px 0 0;
|
||||||
width: 470px;
|
width: 470px;
|
||||||
min-height: 140px;
|
min-height: 140px;
|
||||||
border-radius: 3px;
|
|
||||||
box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55);
|
box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55);
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
.search_form .results_container {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search_form.focused.filled .results_container,
|
||||||
.search_form.shown.filled .results_container {
|
.search_form.shown.filled .results_container {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .results_container:before {
|
|
||||||
content: ' ';
|
|
||||||
height: 0;
|
|
||||||
position: relative;
|
|
||||||
width: 0;
|
|
||||||
border: 10px solid transparent;
|
|
||||||
border-bottom-color: #5c697b;
|
|
||||||
display: block;
|
|
||||||
top: -20px;
|
|
||||||
left: 346px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search_form .results {
|
.search_form .results {
|
||||||
max-height: 570px;
|
max-height: 570px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 10px 0;
|
|
||||||
margin-top: -18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result {
|
.movie_result {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 140px;
|
height: 50px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .options {
|
.movie_result .options {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 30px;
|
||||||
|
right: 0;
|
||||||
|
padding: 13px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-width: 1px 0;
|
border-width: 1px 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadow: inset 0 1px 8px rgba(0,0,0,0.25);
|
box-shadow: inset 0 1px 8px rgba(0,0,0,0.25);
|
||||||
}
|
}
|
||||||
|
.movie_result .options > .in_library_wanted {
|
||||||
|
margin-top: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
.movie_result .options > div {
|
.movie_result .options > div {
|
||||||
padding: 0 15px;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +162,13 @@
|
|||||||
}
|
}
|
||||||
.movie_result .options select[name=title] { width: 180px; }
|
.movie_result .options select[name=title] { width: 180px; }
|
||||||
.movie_result .options select[name=profile] { width: 90px; }
|
.movie_result .options select[name=profile] { width: 90px; }
|
||||||
|
|
||||||
|
@media all and (max-width: 480px) {
|
||||||
|
|
||||||
|
.movie_result .options select[name=title] { width: 90px; }
|
||||||
|
.movie_result .options select[name=profile] { width: 60px; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.movie_result .options .button {
|
.movie_result .options .button {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@@ -130,25 +177,21 @@
|
|||||||
|
|
||||||
.movie_result .options .message {
|
.movie_result .options .message {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: 140px;
|
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
text-align: center;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .data {
|
.movie_result .data {
|
||||||
padding: 0 15px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 30px;
|
||||||
|
right: 0;
|
||||||
background: #5c697b;
|
background: #5c697b;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-top: 1px solid rgba(255,255,255, 0.08);
|
||||||
border-bottom: 1px solid #333;
|
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||||
border-top: 1px solid rgba(255,255,255, 0.15);
|
|
||||||
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
|
||||||
}
|
}
|
||||||
.movie_result .data.open {
|
.movie_result .data.open {
|
||||||
left: 100%;
|
left: 100%;
|
||||||
@@ -158,50 +201,46 @@
|
|||||||
|
|
||||||
.movie_result .in_wanted, .movie_result .in_library {
|
.movie_result .in_wanted, .movie_result .in_library {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 105px;
|
bottom: 2px;
|
||||||
|
left: 14px;
|
||||||
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .thumbnail {
|
.movie_result .thumbnail {
|
||||||
width: 17%;
|
width: 34px;
|
||||||
display: inline-block;
|
min-height: 100%;
|
||||||
margin: 15px 3% 15px 0;
|
display: block;
|
||||||
|
margin: 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
border-radius: 3px;
|
|
||||||
box-shadow: 0 0 3px rgba(0,0,0,0.35);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .info {
|
.movie_result .info {
|
||||||
width: 80%;
|
position: absolute;
|
||||||
display: inline-block;
|
top: 20%;
|
||||||
vertical-align: top;
|
left: 15px;
|
||||||
padding: 15px 0;
|
right: 60px;
|
||||||
height: 120px;
|
vertical-align: middle;
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movie_result .info .tagline {
|
|
||||||
max-height: 70px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movie_result .add +.info {
|
|
||||||
margin-left: 20%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .info h2 {
|
.movie_result .info h2 {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 20px;
|
||||||
|
display: block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .info h2 span {
|
.movie_result .info h2 span {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
|
position: absolute;
|
||||||
|
right: -60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .info h2 span:before { content: "("; }
|
.search_form .mask,
|
||||||
.movie_result .info h2 span:after { content: ")"; }
|
.movie_result .mask {
|
||||||
|
|
||||||
.search_form .mask {
|
|
||||||
border-radius: 3px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -7,33 +7,30 @@ Block.Search = new Class({
|
|||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
var focus_timer = 0;
|
||||||
self.el = new Element('div.search_form').adopt(
|
self.el = new Element('div.search_form').adopt(
|
||||||
new Element('div.input').adopt(
|
new Element('div.input').adopt(
|
||||||
self.input = new Element('input.inlay', {
|
self.input = new Element('input', {
|
||||||
'placeholder': 'Search & add a new movie',
|
'placeholder': 'Search & add a new movie',
|
||||||
'events': {
|
'events': {
|
||||||
'keyup': self.keyup.bind(self),
|
'keyup': self.keyup.bind(self),
|
||||||
'focus': function(){
|
'focus': function(){
|
||||||
|
if(focus_timer) clearTimeout(focus_timer);
|
||||||
self.el.addClass('focused')
|
self.el.addClass('focused')
|
||||||
if(this.get('value'))
|
if(this.get('value'))
|
||||||
self.hideResults(false)
|
self.hideResults(false)
|
||||||
},
|
},
|
||||||
'blur': function(){
|
'blur': function(){
|
||||||
(function(){
|
focus_timer = (function(){
|
||||||
self.el.removeClass('focused')
|
self.el.removeClass('focused')
|
||||||
}).delay(2000);
|
}).delay(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new Element('span.enter', {
|
new Element('a.icon2', {
|
||||||
'events': {
|
'events': {
|
||||||
'click': self.keyup.bind(self)
|
'click': self.clear.bind(self),
|
||||||
},
|
'touchend': self.clear.bind(self)
|
||||||
'text':'Enter'
|
|
||||||
}),
|
|
||||||
new Element('a', {
|
|
||||||
'events': {
|
|
||||||
'click': self.clear.bind(self)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@@ -59,13 +56,21 @@ Block.Search = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
(e).preventDefault();
|
(e).preventDefault();
|
||||||
|
|
||||||
self.last_q = '';
|
if(self.last_q === ''){
|
||||||
self.input.set('value', '');
|
self.input.blur()
|
||||||
self.input.focus()
|
self.last_q = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
self.movies = []
|
self.last_q = '';
|
||||||
self.results.empty()
|
self.input.set('value', '');
|
||||||
self.el.removeClass('filled')
|
self.input.focus()
|
||||||
|
|
||||||
|
self.movies = []
|
||||||
|
self.results.empty()
|
||||||
|
self.el.removeClass('filled')
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hideResults: function(bool){
|
hideResults: function(bool){
|
||||||
@@ -92,8 +97,10 @@ Block.Search = new Class({
|
|||||||
|
|
||||||
self.el[self.q() ? 'addClass' : 'removeClass']('filled')
|
self.el[self.q() ? 'addClass' : 'removeClass']('filled')
|
||||||
|
|
||||||
if(self.q() != self.last_q && (['enter'].indexOf(e.key) > -1 || e.type == 'click'))
|
if(self.q() != self.last_q){
|
||||||
self.autocomplete()
|
if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer)
|
||||||
|
self.autocomplete_timer = self.autocomplete.delay(300, self)
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -197,6 +204,11 @@ Block.Search.Item = new Class({
|
|||||||
self.el = new Element('div.movie_result', {
|
self.el = new Element('div.movie_result', {
|
||||||
'id': info.imdb
|
'id': info.imdb
|
||||||
}).adopt(
|
}).adopt(
|
||||||
|
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||||
|
'src': info.images.poster[0],
|
||||||
|
'height': null,
|
||||||
|
'width': null
|
||||||
|
}) : null,
|
||||||
self.options_el = new Element('div.options.inlay'),
|
self.options_el = new Element('div.options.inlay'),
|
||||||
self.data_container = new Element('div.data', {
|
self.data_container = new Element('div.data', {
|
||||||
'tween': {
|
'tween': {
|
||||||
@@ -207,11 +219,6 @@ Block.Search.Item = new Class({
|
|||||||
'click': self.showOptions.bind(self)
|
'click': self.showOptions.bind(self)
|
||||||
}
|
}
|
||||||
}).adopt(
|
}).adopt(
|
||||||
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
|
||||||
'src': info.images.poster[0],
|
|
||||||
'height': null,
|
|
||||||
'width': null
|
|
||||||
}) : null,
|
|
||||||
new Element('div.info').adopt(
|
new Element('div.info').adopt(
|
||||||
self.title = new Element('h2', {
|
self.title = new Element('h2', {
|
||||||
'text': info.titles[0]
|
'text': info.titles[0]
|
||||||
@@ -219,28 +226,11 @@ Block.Search.Item = new Class({
|
|||||||
self.year = info.year ? new Element('span.year', {
|
self.year = info.year ? new Element('span.year', {
|
||||||
'text': info.year
|
'text': info.year
|
||||||
}) : null
|
}) : null
|
||||||
),
|
)
|
||||||
self.tagline = new Element('span.tagline', {
|
|
||||||
'text': info.tagline ? info.tagline : info.plot,
|
|
||||||
'title': info.tagline ? info.tagline : info.plot
|
|
||||||
}),
|
|
||||||
self.director = self.info.director ? new Element('span.director', {
|
|
||||||
'text': 'Director:' + info.director
|
|
||||||
}) : null,
|
|
||||||
self.starring = info.actors ? new Element('span.actors', {
|
|
||||||
'text': 'Starring:'
|
|
||||||
}) : null
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if(info.actors){
|
|
||||||
Object.each(info.actors, function(actor){
|
|
||||||
new Element('span', {
|
|
||||||
'text': actor
|
|
||||||
}).inject(self.starring)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
info.titles.each(function(title){
|
info.titles.each(function(title){
|
||||||
self.alternativeTitle({
|
self.alternativeTitle({
|
||||||
@@ -319,12 +309,9 @@ Block.Search.Item = new Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.options_el.grab(
|
self.options_el.grab(
|
||||||
new Element('div').adopt(
|
new Element('div', {
|
||||||
self.thumbnail = (info.images && info.images.poster.length > 0) ? new Element('img.thumbnail', {
|
'class': self.info.in_wanted && self.info.in_wanted.profile || in_library ? 'in_library_wanted' : ''
|
||||||
'src': info.images.poster[0],
|
}).adopt(
|
||||||
'height': null,
|
|
||||||
'width': null
|
|
||||||
}) : null,
|
|
||||||
self.info.in_wanted && self.info.in_wanted.profile ? new Element('span.in_wanted', {
|
self.info.in_wanted && self.info.in_wanted.profile ? new Element('span.in_wanted', {
|
||||||
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
||||||
}) : (in_library ? new Element('span.in_library', {
|
}) : (in_library ? new Element('span.in_library', {
|
||||||
@@ -366,7 +353,7 @@ Block.Search.Item = new Class({
|
|||||||
loadingMask: function(){
|
loadingMask: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.mask = new Element('span.mask').inject(self.el).fade('hide')
|
self.mask = new Element('div.mask').inject(self.el).fade('hide')
|
||||||
|
|
||||||
createSpinner(self.mask)
|
createSpinner(self.mask)
|
||||||
self.mask.fade('in')
|
self.mask.fade('in')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from couchpotato.core.helpers.encoding import toUnicode
|
|||||||
from couchpotato.core.helpers.request import jsonified, getParams, getParam
|
from couchpotato.core.helpers.request import jsonified, getParams, getParam
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Profile, ProfileType
|
from couchpotato.core.settings.model import Profile, ProfileType, Movie
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -30,6 +30,21 @@ class ProfilePlugin(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
addEvent('app.initialize', self.fill, priority = 90)
|
addEvent('app.initialize', self.fill, priority = 90)
|
||||||
|
addEvent('app.load', self.forceDefaults)
|
||||||
|
|
||||||
|
def forceDefaults(self):
|
||||||
|
|
||||||
|
# Get all active movies without profile
|
||||||
|
active_status = fireEvent('status.get', 'active', single = True)
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
movies = db.query(Movie).filter(Movie.status_id == active_status.get('id'), Movie.profile == None).all()
|
||||||
|
|
||||||
|
if len(movies) > 0:
|
||||||
|
default_profile = self.default()
|
||||||
|
for movie in movies:
|
||||||
|
movie.profile_id = default_profile.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
def allView(self):
|
def allView(self):
|
||||||
|
|
||||||
@@ -129,6 +144,9 @@ class ProfilePlugin(Plugin):
|
|||||||
db.delete(p)
|
db.delete(p)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
# Force defaults on all empty profile movies
|
||||||
|
self.forceDefaults()
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
message = log.error('Failed deleting Profile: %s', e)
|
message = log.error('Failed deleting Profile: %s', e)
|
||||||
|
|||||||
@@ -6,15 +6,25 @@
|
|||||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile { border-bottom: 1px solid rgba(255,255,255,0.2) }
|
.profile {
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.profile > .delete {
|
.profile > .delete {
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 690px;
|
padding: 25px 20px;
|
||||||
padding: 14px;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
right: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.profile > .delete:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .ctrlHolder:hover {
|
||||||
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .qualities {
|
.profile .qualities {
|
||||||
@@ -34,7 +44,8 @@
|
|||||||
|
|
||||||
.profile .wait_for {
|
.profile .wait_for {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: -45px 0 0 437px;
|
right: 60px;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .wait_for input {
|
.profile .wait_for input {
|
||||||
@@ -61,6 +72,10 @@
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile .type .check {
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
.profile .quality_type select {
|
.profile .quality_type select {
|
||||||
width: 186px;
|
width: 186px;
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
@@ -71,13 +86,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile .types .type .handle {
|
.profile .types .type .handle {
|
||||||
background: url('./handle.png') center;
|
background: url('../../static/profile_plugin/handle.png') center;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
cursor: grab;
|
|
||||||
cursor: -moz-grab;
|
|
||||||
cursor: -webkit-grab;
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +120,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#profile_ordering li {
|
#profile_ordering li {
|
||||||
cursor: grab;
|
|
||||||
cursor: -moz-grab;
|
|
||||||
cursor: -webkit-grab;
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
@@ -126,7 +141,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#profile_ordering li .handle {
|
#profile_ordering li .handle {
|
||||||
background: url('./handle.png') center;
|
background: url('../../static/profile_plugin/handle.png') center;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
qualities = [
|
qualities = [
|
||||||
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
|
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
|
||||||
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
|
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
|
||||||
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
|
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
|
||||||
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']},
|
||||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
{'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', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
||||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], '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': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
|
|||||||
@@ -41,12 +41,15 @@ class Release(Plugin):
|
|||||||
addEvent('release.clean', self.clean)
|
addEvent('release.clean', self.clean)
|
||||||
|
|
||||||
def add(self, group):
|
def add(self, group):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier'])
|
identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier'])
|
||||||
|
|
||||||
|
|
||||||
|
done_status, snatched_status = fireEvent('status.get', ['done', 'snatched'], single = True)
|
||||||
|
|
||||||
# Add movie
|
# Add movie
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
|
||||||
movie = db.query(Movie).filter_by(library_id = group['library'].get('id')).first()
|
movie = db.query(Movie).filter_by(library_id = group['library'].get('id')).first()
|
||||||
if not movie:
|
if not movie:
|
||||||
movie = Movie(
|
movie = Movie(
|
||||||
@@ -58,7 +61,6 @@ class Release(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# Add Release
|
# Add Release
|
||||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
|
||||||
rel = db.query(Relea).filter(
|
rel = db.query(Relea).filter(
|
||||||
or_(
|
or_(
|
||||||
Relea.identifier == identifier,
|
Relea.identifier == identifier,
|
||||||
@@ -76,15 +78,19 @@ class Release(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# Add each file type
|
# Add each file type
|
||||||
|
added_files = []
|
||||||
for type in group['files']:
|
for type in group['files']:
|
||||||
for cur_file in group['files'][type]:
|
for cur_file in group['files'][type]:
|
||||||
added_file = self.saveFile(cur_file, type = type, include_media_info = type is 'movie')
|
added_file = self.saveFile(cur_file, type = type, include_media_info = type is 'movie')
|
||||||
try:
|
added_files.append(added_file.get('id'))
|
||||||
added_file = db.query(File).filter_by(id = added_file.get('id')).one()
|
|
||||||
rel.files.append(added_file)
|
# Add the release files in batch
|
||||||
db.commit()
|
try:
|
||||||
except Exception, e:
|
added_files = db.query(File).filter(or_(*[File.id == x for x in added_files])).all()
|
||||||
log.debug('Failed to attach "%s" to release: %s', (cur_file, e))
|
rel.files.extend(added_files)
|
||||||
|
db.commit()
|
||||||
|
except Exception, e:
|
||||||
|
log.debug('Failed to attach "%s" to release: %s', (cur_file, e))
|
||||||
|
|
||||||
fireEvent('movie.restatus', movie.id)
|
fireEvent('movie.restatus', movie.id)
|
||||||
|
|
||||||
@@ -147,8 +153,7 @@ class Release(Plugin):
|
|||||||
|
|
||||||
rel = db.query(Relea).filter_by(id = id).first()
|
rel = db.query(Relea).filter_by(id = id).first()
|
||||||
if rel:
|
if rel:
|
||||||
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
ignored_status, available_status = fireEvent('status.get', ['ignored', 'available'], single = True)
|
||||||
available_status = fireEvent('status.get', 'available', single = True)
|
|
||||||
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
|
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@@ -160,7 +165,8 @@ class Release(Plugin):
|
|||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
id = getParam('id')
|
id = getParam('id')
|
||||||
status_snatched = fireEvent('status.add', 'snatched', single = True)
|
|
||||||
|
snatched_status, done_status = fireEvent('status.get', ['snatched', 'done'], single = True)
|
||||||
|
|
||||||
rel = db.query(Relea).filter_by(id = id).first()
|
rel = db.query(Relea).filter_by(id = id).first()
|
||||||
if rel:
|
if rel:
|
||||||
@@ -168,6 +174,8 @@ class Release(Plugin):
|
|||||||
for info in rel.info:
|
for info in rel.info:
|
||||||
item[info.identifier] = info.value
|
item[info.identifier] = info.value
|
||||||
|
|
||||||
|
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Snatching "%s"' % item['name'])
|
||||||
|
|
||||||
# Get matching provider
|
# Get matching provider
|
||||||
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
||||||
|
|
||||||
@@ -182,8 +190,14 @@ class Release(Plugin):
|
|||||||
}), manual = True, single = True)
|
}), manual = True, single = True)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
rel.status_id = status_snatched.get('id')
|
db.expunge_all()
|
||||||
db.commit()
|
rel = db.query(Relea).filter_by(id = id).first() # Get release again
|
||||||
|
|
||||||
|
if rel.status_id != done_status.get('id'):
|
||||||
|
rel.status_id = snatched_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
fireEvent('notify.frontend', type = 'release.download', data = True, message = 'Successfully snatched "%s"' % item['name'])
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': success
|
'success': success
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from couchpotato.core.plugins.renamer.main import Renamer
|
from couchpotato.core.plugins.renamer.main import Renamer
|
||||||
|
import os
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
return Renamer()
|
return Renamer()
|
||||||
@@ -12,7 +13,7 @@ rename_options = {
|
|||||||
'thename': 'The Moviename',
|
'thename': 'The Moviename',
|
||||||
'year': 'Year (2011)',
|
'year': 'Year (2011)',
|
||||||
'first': 'First letter (M)',
|
'first': 'First letter (M)',
|
||||||
'quality': 'Quality (720P)',
|
'quality': 'Quality (720p)',
|
||||||
'video': 'Video (x264)',
|
'video': 'Video (x264)',
|
||||||
'audio': 'Audio (DTS)',
|
'audio': 'Audio (DTS)',
|
||||||
'group': 'Releasegroup name',
|
'group': 'Releasegroup name',
|
||||||
@@ -111,6 +112,24 @@ config = [{
|
|||||||
'label': 'Separator',
|
'label': 'Separator',
|
||||||
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
|
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'file_action',
|
||||||
|
'label': 'Torrent File Action',
|
||||||
|
'default': 'move',
|
||||||
|
'type': 'dropdown',
|
||||||
|
'values': [('Move', 'move'), ('Copy', 'copy'), ('Hard link', 'hardlink'), ('Sym link', 'symlink'), ('Move & Sym link', 'move_symlink')],
|
||||||
|
'description': 'Define which kind of file operation you want to use for torrents. Before you start using <a href="http://en.wikipedia.org/wiki/Hard_link">hard links</a> or <a href="http://en.wikipedia.org/wiki/Sym_link">sym links</a>, PLEASE read about their possible drawbacks.',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
|
'name': 'ntfs_permission',
|
||||||
|
'label': 'NTFS Permission',
|
||||||
|
'type': 'bool',
|
||||||
|
'hidden': os.name != 'nt',
|
||||||
|
'description': 'Set permission of moved files to that of destination folder (Windows NTFS only).',
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
'tab': 'renamer',
|
'tab': 'renamer',
|
||||||
|
|||||||
@@ -2,22 +2,23 @@ from couchpotato import get_session
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, ss
|
from couchpotato.core.helpers.encoding import toUnicode, ss
|
||||||
from couchpotato.core.helpers.request import jsonified
|
from couchpotato.core.helpers.request import getParams, jsonified
|
||||||
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \
|
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \
|
||||||
getImdb
|
getImdb, link, symlink
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Library, File, Profile, Release
|
from couchpotato.core.settings.model import Library, File, Profile, Release, \
|
||||||
|
ReleaseInfo
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Renamer(Plugin):
|
class Renamer(Plugin):
|
||||||
|
|
||||||
renaming_started = False
|
renaming_started = False
|
||||||
@@ -26,7 +27,12 @@ class Renamer(Plugin):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
addApiView('renamer.scan', self.scanView, docs = {
|
addApiView('renamer.scan', self.scanView, docs = {
|
||||||
'desc': 'For the renamer to check for new files to rename',
|
'desc': 'For the renamer to check for new files to rename in a folder',
|
||||||
|
'params': {
|
||||||
|
'movie_folder': {'desc': 'Optional: The folder of the movie to scan. Keep empty for default renamer folder.'},
|
||||||
|
'downloader' : {'desc': 'Optional: The downloader this movie has been downloaded with'},
|
||||||
|
'download_id': {'desc': 'Optional: The downloader\'s nzb/torrent ID'},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
addEvent('renamer.scan', self.scan)
|
addEvent('renamer.scan', self.scan)
|
||||||
@@ -34,22 +40,42 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
addEvent('app.load', self.scan)
|
addEvent('app.load', self.scan)
|
||||||
addEvent('app.load', self.checkSnatched)
|
addEvent('app.load', self.checkSnatched)
|
||||||
|
addEvent('app.load', self.setCrons)
|
||||||
|
|
||||||
if self.conf('run_every') > 0:
|
# Enable / disable interval
|
||||||
fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
|
addEvent('setting.save.renamer.enabled.after', self.setCrons)
|
||||||
|
addEvent('setting.save.renamer.run_every.after', self.setCrons)
|
||||||
|
addEvent('setting.save.renamer.force_every.after', self.setCrons)
|
||||||
|
|
||||||
if self.conf('force_every') > 0:
|
def setCrons(self):
|
||||||
fireEvent('schedule.interval', 'renamer.check_snatched_forced', self.scan, hours = self.conf('force_every'))
|
|
||||||
|
fireEvent('schedule.remove', 'renamer.check_snatched')
|
||||||
|
if self.isEnabled() and self.conf('run_every') > 0:
|
||||||
|
fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every'), single = True)
|
||||||
|
|
||||||
|
fireEvent('schedule.remove', 'renamer.check_snatched_forced')
|
||||||
|
if self.isEnabled() and self.conf('force_every') > 0:
|
||||||
|
fireEvent('schedule.interval', 'renamer.check_snatched_forced', self.scan, hours = self.conf('force_every'), single = True)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def scanView(self):
|
def scanView(self):
|
||||||
|
|
||||||
fireEventAsync('renamer.scan')
|
params = getParams()
|
||||||
|
movie_folder = params.get('movie_folder', None)
|
||||||
|
downloader = params.get('downloader', None)
|
||||||
|
download_id = params.get('download_id', None)
|
||||||
|
|
||||||
|
fireEventAsync('renamer.scan',
|
||||||
|
movie_folder = movie_folder,
|
||||||
|
download_info = {'id': download_id, 'downloader': downloader} if download_id else None
|
||||||
|
)
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
|
|
||||||
def scan(self):
|
def scan(self, movie_folder = None, download_info = None):
|
||||||
|
|
||||||
if self.isDisabled():
|
if self.isDisabled():
|
||||||
return
|
return
|
||||||
@@ -59,17 +85,42 @@ class Renamer(Plugin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Check to see if the "to" folder is inside the "from" folder.
|
# Check to see if the "to" folder is inside the "from" folder.
|
||||||
if not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')):
|
if movie_folder and not os.path.isdir(movie_folder) or not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')):
|
||||||
log.debug('"To" and "From" have to exist.')
|
l = log.debug if movie_folder else log.error
|
||||||
|
l('Both the "To" and "From" have to exist.')
|
||||||
return
|
return
|
||||||
elif self.conf('from') in self.conf('to'):
|
elif self.conf('from') in self.conf('to'):
|
||||||
log.error('The "to" can\'t be inside of the "from" folder. You\'ll get an infinite loop.')
|
log.error('The "to" can\'t be inside of the "from" folder. You\'ll get an infinite loop.')
|
||||||
return
|
return
|
||||||
|
elif (movie_folder and movie_folder in [self.conf('to'), self.conf('from')]):
|
||||||
groups = fireEvent('scanner.scan', folder = self.conf('from'), single = True)
|
log.error('The "to" and "from" folders can\'t be inside of or the same as the provided movie folder.')
|
||||||
|
return
|
||||||
|
|
||||||
self.renaming_started = True
|
self.renaming_started = True
|
||||||
|
|
||||||
|
# make sure the movie folder name is included in the search
|
||||||
|
folder = None
|
||||||
|
files = []
|
||||||
|
if movie_folder:
|
||||||
|
log.info('Scanning movie folder %s...', movie_folder)
|
||||||
|
movie_folder = movie_folder.rstrip(os.path.sep)
|
||||||
|
folder = os.path.dirname(movie_folder)
|
||||||
|
|
||||||
|
# Get all files from the specified folder
|
||||||
|
try:
|
||||||
|
for root, folders, names in os.walk(movie_folder):
|
||||||
|
files.extend([os.path.join(root, name) for name in names])
|
||||||
|
except:
|
||||||
|
log.error('Failed getting files from %s: %s', (movie_folder, traceback.format_exc()))
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
|
||||||
|
# Extend the download info with info stored in the downloaded release
|
||||||
|
download_info = self.extendDownloadInfo(download_info)
|
||||||
|
|
||||||
|
groups = fireEvent('scanner.scan', folder = folder if folder else self.conf('from'),
|
||||||
|
files = files, download_info = download_info, return_ignored = False, single = True)
|
||||||
|
|
||||||
destination = self.conf('to')
|
destination = self.conf('to')
|
||||||
folder_name = self.conf('folder_name')
|
folder_name = self.conf('folder_name')
|
||||||
file_name = self.conf('file_name')
|
file_name = self.conf('file_name')
|
||||||
@@ -78,12 +129,8 @@ class Renamer(Plugin):
|
|||||||
separator = self.conf('separator')
|
separator = self.conf('separator')
|
||||||
|
|
||||||
# Statusses
|
# Statusses
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
done_status, active_status, downloaded_status, snatched_status = \
|
||||||
active_status = fireEvent('status.get', 'active', single = True)
|
fireEvent('status.get', ['done', 'active', 'downloaded', 'snatched'], single = True)
|
||||||
downloaded_status = fireEvent('status.get', 'downloaded', single = True)
|
|
||||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
|
||||||
|
|
||||||
db = get_session()
|
|
||||||
|
|
||||||
for group_identifier in groups:
|
for group_identifier in groups:
|
||||||
|
|
||||||
@@ -170,15 +217,15 @@ class Renamer(Plugin):
|
|||||||
replacements['cd_nr'] = cd if multiple else ''
|
replacements['cd_nr'] = cd if multiple else ''
|
||||||
|
|
||||||
# Naming
|
# Naming
|
||||||
final_folder_name = self.doReplace(folder_name, replacements)
|
final_folder_name = self.doReplace(folder_name, replacements).lstrip('. ')
|
||||||
final_file_name = self.doReplace(file_name, replacements)
|
final_file_name = self.doReplace(file_name, replacements).lstrip('. ')
|
||||||
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
|
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
|
||||||
|
|
||||||
# Meta naming
|
# Meta naming
|
||||||
if file_type is 'trailer':
|
if file_type is 'trailer':
|
||||||
final_file_name = self.doReplace(trailer_name, replacements, remove_multiple = True)
|
final_file_name = self.doReplace(trailer_name, replacements, remove_multiple = True).lstrip('. ')
|
||||||
elif file_type is 'nfo':
|
elif file_type is 'nfo':
|
||||||
final_file_name = self.doReplace(nfo_name, replacements, remove_multiple = True)
|
final_file_name = self.doReplace(nfo_name, replacements, remove_multiple = True).lstrip('. ')
|
||||||
|
|
||||||
# Seperator replace
|
# Seperator replace
|
||||||
if separator:
|
if separator:
|
||||||
@@ -275,6 +322,7 @@ class Renamer(Plugin):
|
|||||||
for profile_type in movie.profile.types:
|
for profile_type in movie.profile.types:
|
||||||
if profile_type.quality_id == group['meta_data']['quality']['id'] and profile_type.finish:
|
if profile_type.quality_id == group['meta_data']['quality']['id'] and profile_type.finish:
|
||||||
movie.status_id = done_status.get('id')
|
movie.status_id = done_status.get('id')
|
||||||
|
movie.last_edit = int(time.time())
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
|
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
|
||||||
@@ -302,7 +350,7 @@ class Renamer(Plugin):
|
|||||||
else:
|
else:
|
||||||
log.info('Better quality release already exists for %s, with quality %s', (movie.library.titles[0].title, release.quality.label))
|
log.info('Better quality release already exists for %s, with quality %s', (movie.library.titles[0].title, release.quality.label))
|
||||||
|
|
||||||
# Add _EXISTS_ to the parent dir
|
# Add exists tag to the .ignore file
|
||||||
self.tagDir(group, 'exists')
|
self.tagDir(group, 'exists')
|
||||||
|
|
||||||
# Notify on rename fail
|
# Notify on rename fail
|
||||||
@@ -316,12 +364,15 @@ class Renamer(Plugin):
|
|||||||
log.debug('Marking release as downloaded')
|
log.debug('Marking release as downloaded')
|
||||||
try:
|
try:
|
||||||
release.status_id = downloaded_status.get('id')
|
release.status_id = downloaded_status.get('id')
|
||||||
|
release.last_edit = int(time.time())
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed marking release as finished: %s %s', (e, traceback.format_exc()))
|
log.error('Failed marking release as finished: %s %s', (e, traceback.format_exc()))
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# Remove leftover files
|
# Remove leftover files
|
||||||
if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers:
|
if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers and \
|
||||||
|
not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)):
|
||||||
log.debug('Removing leftover files')
|
log.debug('Removing leftover files')
|
||||||
for current_file in group['files']['leftover']:
|
for current_file in group['files']['leftover']:
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
@@ -346,7 +397,7 @@ class Renamer(Plugin):
|
|||||||
os.remove(src)
|
os.remove(src)
|
||||||
|
|
||||||
parent_dir = os.path.normpath(os.path.dirname(src))
|
parent_dir = os.path.normpath(os.path.dirname(src))
|
||||||
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and destination != parent_dir:
|
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and not parent_dir in [destination, movie_folder] and not self.conf('from') in parent_dir:
|
||||||
delete_folders.append(parent_dir)
|
delete_folders.append(parent_dir)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
@@ -371,12 +422,15 @@ class Renamer(Plugin):
|
|||||||
self.makeDir(os.path.dirname(dst))
|
self.makeDir(os.path.dirname(dst))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.moveFile(src, dst)
|
self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(download_info))
|
||||||
group['renamed_files'].append(dst)
|
group['renamed_files'].append(dst)
|
||||||
except:
|
except:
|
||||||
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
||||||
self.tagDir(group, 'failed_rename')
|
self.tagDir(group, 'failed_rename')
|
||||||
|
|
||||||
|
if self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info):
|
||||||
|
self.tagDir(group, 'renamed already')
|
||||||
|
|
||||||
# Remove matching releases
|
# Remove matching releases
|
||||||
for release in remove_releases:
|
for release in remove_releases:
|
||||||
log.debug('Removing release %s', release.identifier)
|
log.debug('Removing release %s', release.identifier)
|
||||||
@@ -422,39 +476,45 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
return rename_files
|
return rename_files
|
||||||
|
|
||||||
|
# This adds a file to ignore / tag a release so it is ignored later
|
||||||
def tagDir(self, group, tag):
|
def tagDir(self, group, tag):
|
||||||
|
|
||||||
rename_files = {}
|
ignore_file = None
|
||||||
|
for movie_file in sorted(list(group['files']['movie'])):
|
||||||
|
ignore_file = '%s.ignore' % os.path.splitext(movie_file)[0]
|
||||||
|
break
|
||||||
|
|
||||||
if group['dirname']:
|
text = """This file is from CouchPotato
|
||||||
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_%s_%s' % (tag.upper(), group['dirname']))
|
It has marked this release as "%s"
|
||||||
else: # Add it to filename
|
This file hides the release from the renamer
|
||||||
for file_type in group['files']:
|
Remove it if you want it to be renamed (again, or at least let it try again)
|
||||||
for rename_me in group['files'][file_type]:
|
""" % tag
|
||||||
filename = os.path.basename(rename_me)
|
|
||||||
rename_files[rename_me] = rename_me.replace(filename, '_%s_%s' % (tag.upper(), filename))
|
|
||||||
|
|
||||||
for src in rename_files:
|
if ignore_file:
|
||||||
if rename_files[src]:
|
self.createFile(ignore_file, text)
|
||||||
dst = rename_files[src]
|
|
||||||
log.info('Renaming "%s" to "%s"', (src, dst))
|
|
||||||
|
|
||||||
# Create dir
|
|
||||||
self.makeDir(os.path.dirname(dst))
|
|
||||||
|
|
||||||
try:
|
def moveFile(self, old, dest, forcemove = False):
|
||||||
self.moveFile(src, dst)
|
|
||||||
except:
|
|
||||||
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
|
||||||
raise
|
|
||||||
|
|
||||||
def moveFile(self, old, dest):
|
|
||||||
dest = ss(dest)
|
dest = ss(dest)
|
||||||
try:
|
try:
|
||||||
shutil.move(old, dest)
|
if forcemove:
|
||||||
|
shutil.move(old, dest)
|
||||||
|
elif self.conf('file_action') == 'hardlink':
|
||||||
|
link(old, dest)
|
||||||
|
elif self.conf('file_action') == 'symlink':
|
||||||
|
symlink(old, dest)
|
||||||
|
elif self.conf('file_action') == 'copy':
|
||||||
|
shutil.copy(old, dest)
|
||||||
|
elif self.conf('file_action') == 'move_symlink':
|
||||||
|
shutil.move(old, dest)
|
||||||
|
symlink(dest, old)
|
||||||
|
else:
|
||||||
|
shutil.move(old, dest)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.chmod(dest, Env.getPermission('file'))
|
os.chmod(dest, Env.getPermission('file'))
|
||||||
|
if os.name == 'nt' and self.conf('ntfs_permission'):
|
||||||
|
os.popen('icacls "' + dest + '"* /reset /T')
|
||||||
except:
|
except:
|
||||||
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
||||||
|
|
||||||
@@ -468,7 +528,7 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
||||||
raise Exception
|
raise
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -518,16 +578,14 @@ class Renamer(Plugin):
|
|||||||
loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
|
loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
|
||||||
|
|
||||||
def checkSnatched(self):
|
def checkSnatched(self):
|
||||||
|
|
||||||
if self.checking_snatched:
|
if self.checking_snatched:
|
||||||
log.debug('Already checking snatched')
|
log.debug('Already checking snatched')
|
||||||
|
|
||||||
self.checking_snatched = True
|
self.checking_snatched = True
|
||||||
|
|
||||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
snatched_status, ignored_status, failed_status, done_status = \
|
||||||
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done'], single = True)
|
||||||
failed_status = fireEvent('status.get', 'failed', single = True)
|
|
||||||
|
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
|
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
|
||||||
@@ -554,6 +612,7 @@ class Renamer(Plugin):
|
|||||||
if rel.movie.status_id == done_status.get('id'):
|
if rel.movie.status_id == done_status.get('id'):
|
||||||
log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
|
log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
|
||||||
rel.status_id = ignored_status.get('id')
|
rel.status_id = ignored_status.get('id')
|
||||||
|
rel.last_edit = int(time.time())
|
||||||
db.commit()
|
db.commit()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -564,8 +623,16 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
found = False
|
found = False
|
||||||
for item in statuses:
|
for item in statuses:
|
||||||
if item['name'] == nzbname or getImdb(item['name']) == movie_dict['library']['identifier']:
|
found_release = False
|
||||||
|
if rel_dict['info'].get('download_id'):
|
||||||
|
if item['id'] == rel_dict['info']['download_id'] and item['downloader'] == rel_dict['info']['download_downloader']:
|
||||||
|
log.debug('Found release by id: %s', item['id'])
|
||||||
|
found_release = True
|
||||||
|
else:
|
||||||
|
if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']:
|
||||||
|
found_release = True
|
||||||
|
|
||||||
|
if found_release:
|
||||||
timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft']
|
timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft']
|
||||||
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
|
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
|
||||||
|
|
||||||
@@ -573,15 +640,18 @@ class Renamer(Plugin):
|
|||||||
pass
|
pass
|
||||||
elif item['status'] == 'failed':
|
elif item['status'] == 'failed':
|
||||||
fireEvent('download.remove_failed', item, single = True)
|
fireEvent('download.remove_failed', item, single = True)
|
||||||
|
rel.status_id = failed_status.get('id')
|
||||||
|
rel.last_edit = int(time.time())
|
||||||
|
db.commit()
|
||||||
|
|
||||||
if self.conf('next_on_failed'):
|
if self.conf('next_on_failed'):
|
||||||
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
|
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
|
||||||
else:
|
|
||||||
rel.status_id = failed_status.get('id')
|
|
||||||
db.commit()
|
|
||||||
elif item['status'] == 'completed':
|
elif item['status'] == 'completed':
|
||||||
log.info('Download of %s completed!', item['name'])
|
log.info('Download of %s completed!', item['name'])
|
||||||
scan_required = True
|
if item['id'] and item['downloader'] and item['folder']:
|
||||||
|
fireEventAsync('renamer.scan', movie_folder = item['folder'], download_info = item)
|
||||||
|
else:
|
||||||
|
scan_required = True
|
||||||
|
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
@@ -598,3 +668,38 @@ class Renamer(Plugin):
|
|||||||
self.checking_snatched = False
|
self.checking_snatched = False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def extendDownloadInfo(self, download_info):
|
||||||
|
|
||||||
|
rls = None
|
||||||
|
|
||||||
|
if download_info and download_info.get('id') and download_info.get('downloader'):
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
|
||||||
|
rlsnfo_dwnlds = db.query(ReleaseInfo).filter_by(identifier = 'download_downloader', value = download_info.get('downloader')).all()
|
||||||
|
rlsnfo_ids = db.query(ReleaseInfo).filter_by(identifier = 'download_id', value = download_info.get('id')).all()
|
||||||
|
|
||||||
|
for rlsnfo_dwnld in rlsnfo_dwnlds:
|
||||||
|
for rlsnfo_id in rlsnfo_ids:
|
||||||
|
if rlsnfo_id.release == rlsnfo_dwnld.release:
|
||||||
|
rls = rlsnfo_id.release
|
||||||
|
break
|
||||||
|
if rls: break
|
||||||
|
|
||||||
|
if not rls:
|
||||||
|
log.error('Download ID %s from downloader %s not found in releases', (download_info.get('id'), download_info.get('downloader')))
|
||||||
|
|
||||||
|
if rls:
|
||||||
|
|
||||||
|
rls_dict = rls.to_dict({'info':{}})
|
||||||
|
download_info.update({
|
||||||
|
'imdb_id': rls.movie.library.identifier,
|
||||||
|
'quality': rls.quality.identifier,
|
||||||
|
'type': rls_dict.get('info', {}).get('type')
|
||||||
|
})
|
||||||
|
|
||||||
|
return download_info
|
||||||
|
|
||||||
|
def downloadIsTorrent(self, download_info):
|
||||||
|
return download_info and download_info.get('type') in ['torrent', 'torrent_magnet']
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from subliminal.videos import Video
|
|||||||
import enzyme
|
import enzyme
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ class Scanner(Plugin):
|
|||||||
'media': 314572800, # 300MB
|
'media': 314572800, # 300MB
|
||||||
'trailer': 1048576, # 1MB
|
'trailer': 1048576, # 1MB
|
||||||
}
|
}
|
||||||
ignored_in_path = ['extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
ignored_in_path = [os.path.sep + 'extracted' + os.path.sep, 'extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.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', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
||||||
extensions = {
|
extensions = {
|
||||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
|
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
|
||||||
@@ -74,7 +75,7 @@ class Scanner(Plugin):
|
|||||||
'hdtv': ['hdtv']
|
'hdtv': ['hdtv']
|
||||||
}
|
}
|
||||||
|
|
||||||
clean = '[ _\,\.\(\)\[\]\-](french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
clean = '[ _\,\.\(\)\[\]\-](extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
||||||
multipart_regex = [
|
multipart_regex = [
|
||||||
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
||||||
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
||||||
@@ -100,7 +101,7 @@ class Scanner(Plugin):
|
|||||||
addEvent('scanner.name_year', self.getReleaseNameYear)
|
addEvent('scanner.name_year', self.getReleaseNameYear)
|
||||||
addEvent('scanner.partnumber', self.getPartNumber)
|
addEvent('scanner.partnumber', self.getPartNumber)
|
||||||
|
|
||||||
def scan(self, folder = None, files = None, simple = False, newer_than = 0, on_found = None):
|
def scan(self, folder = None, files = None, download_info = None, simple = False, newer_than = 0, return_ignored = True, on_found = None):
|
||||||
|
|
||||||
folder = ss(os.path.normpath(folder))
|
folder = ss(os.path.normpath(folder))
|
||||||
|
|
||||||
@@ -118,8 +119,7 @@ class Scanner(Plugin):
|
|||||||
try:
|
try:
|
||||||
files = []
|
files = []
|
||||||
for root, dirs, walk_files in os.walk(folder):
|
for root, dirs, walk_files in os.walk(folder):
|
||||||
for filename in walk_files:
|
files.extend(os.path.join(root, filename) for filename in walk_files)
|
||||||
files.append(os.path.join(root, filename))
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed getting files from %s: %s', (folder, traceback.format_exc()))
|
log.error('Failed getting files from %s: %s', (folder, traceback.format_exc()))
|
||||||
else:
|
else:
|
||||||
@@ -177,17 +177,25 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
|
|
||||||
# Group files minus extension
|
# Group files minus extension
|
||||||
|
ignored_identifiers = []
|
||||||
for identifier, group in movie_files.iteritems():
|
for identifier, group in movie_files.iteritems():
|
||||||
if identifier not in group['identifiers'] and len(identifier) > 0: group['identifiers'].append(identifier)
|
if identifier not in group['identifiers'] and len(identifier) > 0: group['identifiers'].append(identifier)
|
||||||
|
|
||||||
log.debug('Grouping files: %s', identifier)
|
log.debug('Grouping files: %s', identifier)
|
||||||
|
|
||||||
|
has_ignored = 0
|
||||||
for file_path in group['unsorted_files']:
|
for file_path in group['unsorted_files']:
|
||||||
wo_ext = file_path[:-(len(getExt(file_path)) + 1)]
|
ext = getExt(file_path)
|
||||||
|
wo_ext = file_path[:-(len(ext) + 1)]
|
||||||
found_files = set([i for i in leftovers if wo_ext in i])
|
found_files = set([i for i in leftovers if wo_ext in i])
|
||||||
group['unsorted_files'].extend(found_files)
|
group['unsorted_files'].extend(found_files)
|
||||||
leftovers = leftovers - found_files
|
leftovers = leftovers - found_files
|
||||||
|
|
||||||
|
has_ignored += 1 if ext == 'ignore' else 0
|
||||||
|
|
||||||
|
if has_ignored > 0:
|
||||||
|
ignored_identifiers.append(identifier)
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
@@ -313,6 +321,11 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
del movie_files
|
del movie_files
|
||||||
|
|
||||||
|
# Make sure only one movie was found if a download ID is provided
|
||||||
|
if download_info and not len(valid_files) == 1:
|
||||||
|
log.info('Download ID provided (%s), but more than one group found (%s). Ignoring Download ID...', (download_info.get('imdb_id'), len(valid_files)))
|
||||||
|
download_info = None
|
||||||
|
|
||||||
# Determine file types
|
# Determine file types
|
||||||
processed_movies = {}
|
processed_movies = {}
|
||||||
total_found = len(valid_files)
|
total_found = len(valid_files)
|
||||||
@@ -322,15 +335,17 @@ class Scanner(Plugin):
|
|||||||
except:
|
except:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if return_ignored is False and identifier in ignored_identifiers:
|
||||||
|
log.debug('Ignore file found, ignoring release: %s' % identifier)
|
||||||
|
continue
|
||||||
|
|
||||||
# Group extra (and easy) files first
|
# Group extra (and easy) files first
|
||||||
# images = self.getImages(group['unsorted_files'])
|
|
||||||
group['files'] = {
|
group['files'] = {
|
||||||
'movie_extra': self.getMovieExtras(group['unsorted_files']),
|
'movie_extra': self.getMovieExtras(group['unsorted_files']),
|
||||||
'subtitle': self.getSubtitles(group['unsorted_files']),
|
'subtitle': self.getSubtitles(group['unsorted_files']),
|
||||||
'subtitle_extra': self.getSubtitlesExtras(group['unsorted_files']),
|
'subtitle_extra': self.getSubtitlesExtras(group['unsorted_files']),
|
||||||
'nfo': self.getNfo(group['unsorted_files']),
|
'nfo': self.getNfo(group['unsorted_files']),
|
||||||
'trailer': self.getTrailers(group['unsorted_files']),
|
'trailer': self.getTrailers(group['unsorted_files']),
|
||||||
#'backdrop': images['backdrop'],
|
|
||||||
'leftover': set(group['unsorted_files']),
|
'leftover': set(group['unsorted_files']),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +360,7 @@ class Scanner(Plugin):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
log.debug('Getting metadata for %s', identifier)
|
log.debug('Getting metadata for %s', identifier)
|
||||||
group['meta_data'] = self.getMetaData(group, folder = folder)
|
group['meta_data'] = self.getMetaData(group, folder = folder, download_info = download_info)
|
||||||
|
|
||||||
# Subtitle meta
|
# Subtitle meta
|
||||||
group['subtitle_language'] = self.getSubtitleLanguage(group) if not simple else {}
|
group['subtitle_language'] = self.getSubtitleLanguage(group) if not simple else {}
|
||||||
@@ -375,7 +390,7 @@ class Scanner(Plugin):
|
|||||||
del group['unsorted_files']
|
del group['unsorted_files']
|
||||||
|
|
||||||
# Determine movie
|
# Determine movie
|
||||||
group['library'] = self.determineMovie(group)
|
group['library'] = self.determineMovie(group, download_info = download_info)
|
||||||
if not group['library']:
|
if not group['library']:
|
||||||
log.error('Unable to determine movie: %s', group['identifiers'])
|
log.error('Unable to determine movie: %s', group['identifiers'])
|
||||||
else:
|
else:
|
||||||
@@ -388,6 +403,11 @@ class Scanner(Plugin):
|
|||||||
if on_found:
|
if on_found:
|
||||||
on_found(group, total_found, total_found - len(processed_movies))
|
on_found(group, total_found, total_found - len(processed_movies))
|
||||||
|
|
||||||
|
# Wait for all the async events calm down a bit
|
||||||
|
while threading.activeCount() > 100 and not self.shuttingDown():
|
||||||
|
log.debug('Too many threads active, waiting a few seconds')
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
if len(processed_movies) > 0:
|
if len(processed_movies) > 0:
|
||||||
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
||||||
else:
|
else:
|
||||||
@@ -395,7 +415,7 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
return processed_movies
|
return processed_movies
|
||||||
|
|
||||||
def getMetaData(self, group, folder = ''):
|
def getMetaData(self, group, folder = '', download_info = None):
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
files = list(group['files']['movie'])
|
files = list(group['files']['movie'])
|
||||||
@@ -417,9 +437,13 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
if data.get('audio'): break
|
if data.get('audio'): break
|
||||||
|
|
||||||
|
# Use the quality guess first, if that failes use the quality we wanted to download
|
||||||
data['quality'] = fireEvent('quality.guess', files = files, extra = data, single = True)
|
data['quality'] = fireEvent('quality.guess', files = files, extra = data, single = True)
|
||||||
if not data['quality']:
|
if not data['quality']:
|
||||||
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
if download_info and download_info.get('quality'):
|
||||||
|
data['quality'] = fireEvent('quality.single', download_info.get('quality'), single = True)
|
||||||
|
else:
|
||||||
|
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
||||||
|
|
||||||
data['quality_type'] = 'HD' if data.get('resolution_width', 0) >= 1280 or data['quality'].get('hd') else 'SD'
|
data['quality_type'] = 'HD' if data.get('resolution_width', 0) >= 1280 or data['quality'].get('hd') else 'SD'
|
||||||
|
|
||||||
@@ -495,17 +519,22 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
return detected_languages
|
return detected_languages
|
||||||
|
|
||||||
def determineMovie(self, group):
|
def determineMovie(self, group, download_info = None):
|
||||||
imdb_id = None
|
|
||||||
|
# Get imdb id from downloader
|
||||||
|
imdb_id = download_info and download_info.get('imdb_id')
|
||||||
|
if imdb_id:
|
||||||
|
log.debug('Found movie via imdb id from it\'s download id: %s', download_info.get('imdb_id'))
|
||||||
|
|
||||||
files = group['files']
|
files = group['files']
|
||||||
|
|
||||||
# Check for CP(imdb_id) string in the file paths
|
# Check for CP(imdb_id) string in the file paths
|
||||||
for cur_file in files['movie']:
|
if not imdb_id:
|
||||||
imdb_id = self.getCPImdb(cur_file)
|
for cur_file in files['movie']:
|
||||||
if imdb_id:
|
imdb_id = self.getCPImdb(cur_file)
|
||||||
log.debug('Found movie via CP tag: %s', cur_file)
|
if imdb_id:
|
||||||
break
|
log.debug('Found movie via CP tag: %s', cur_file)
|
||||||
|
break
|
||||||
|
|
||||||
# Check and see if nfo contains the imdb-id
|
# Check and see if nfo contains the imdb-id
|
||||||
if not imdb_id:
|
if not imdb_id:
|
||||||
|
|||||||
@@ -116,13 +116,13 @@ def sizeScore(size):
|
|||||||
|
|
||||||
|
|
||||||
def providerScore(provider):
|
def providerScore(provider):
|
||||||
if provider in ['OMGWTFNZBs', 'PassThePopcorn', 'SceneAccess', 'TorrentLeech']:
|
|
||||||
return 20
|
|
||||||
|
|
||||||
if provider in ['Newznab']:
|
try:
|
||||||
return 10
|
score = tryInt(Env.setting('extra_score', section = provider.lower(), default = 0))
|
||||||
|
except:
|
||||||
|
score = 0
|
||||||
|
|
||||||
return 0
|
return score
|
||||||
|
|
||||||
|
|
||||||
def duplicateScore(nzb_name, movie_name):
|
def duplicateScore(nzb_name, movie_name):
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ config = [{
|
|||||||
'label': 'Required words',
|
'label': 'Required words',
|
||||||
'default': '',
|
'default': '',
|
||||||
'placeholder': 'Example: DTS, AC3 & English',
|
'placeholder': 'Example: DTS, AC3 & English',
|
||||||
'description': 'Ignore releases that don\'t contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
|
'description': 'A release should contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'ignored_words',
|
'name': 'ignored_words',
|
||||||
'label': 'Ignored words',
|
'label': 'Ignored words',
|
||||||
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
|
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
|
||||||
|
'description': 'Ignores releases that match any of these sets. (Works like explained above)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'preferred_method',
|
'name': 'preferred_method',
|
||||||
@@ -40,6 +41,14 @@ config = [{
|
|||||||
'type': 'dropdown',
|
'type': 'dropdown',
|
||||||
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
|
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'always_search',
|
||||||
|
'default': False,
|
||||||
|
'advanced': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'label': 'Always search',
|
||||||
|
'description': 'Search for movies even before there is a ETA. Enabling this will probably get you a lot of fakes.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class Searcher(Plugin):
|
|||||||
addEvent('searcher.correct_movie', self.correctMovie)
|
addEvent('searcher.correct_movie', self.correctMovie)
|
||||||
addEvent('searcher.download', self.download)
|
addEvent('searcher.download', self.download)
|
||||||
addEvent('searcher.try_next_release', self.tryNextRelease)
|
addEvent('searcher.try_next_release', self.tryNextRelease)
|
||||||
|
addEvent('searcher.could_be_released', self.couldBeReleased)
|
||||||
|
|
||||||
addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
|
addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
|
||||||
'desc': 'Marks the snatched results as ignored and try the next best release',
|
'desc': 'Marks the snatched results as ignored and try the next best release',
|
||||||
@@ -49,7 +50,12 @@ class Searcher(Plugin):
|
|||||||
}"""},
|
}"""},
|
||||||
})
|
})
|
||||||
|
|
||||||
# Schedule cronjob
|
addEvent('app.load', self.setCrons)
|
||||||
|
addEvent('setting.save.searcher.cron_day.after', self.setCrons)
|
||||||
|
addEvent('setting.save.searcher.cron_hour.after', self.setCrons)
|
||||||
|
addEvent('setting.save.searcher.cron_minute.after', self.setCrons)
|
||||||
|
|
||||||
|
def setCrons(self):
|
||||||
fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
||||||
|
|
||||||
def allMoviesView(self):
|
def allMoviesView(self):
|
||||||
@@ -140,8 +146,7 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
pre_releases = fireEvent('quality.pre_releases', single = True)
|
pre_releases = fireEvent('quality.pre_releases', single = True)
|
||||||
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
||||||
available_status = fireEvent('status.get', 'available', single = True)
|
available_status, ignored_status = fireEvent('status.get', ['available', 'ignored'], single = True)
|
||||||
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
|
||||||
|
|
||||||
found_releases = []
|
found_releases = []
|
||||||
|
|
||||||
@@ -156,7 +161,7 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
ret = False
|
ret = False
|
||||||
for quality_type in movie['profile']['types']:
|
for quality_type in movie['profile']['types']:
|
||||||
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
|
if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates):
|
||||||
log.info('Too early to search for %s, %s', (quality_type['quality']['identifier'], default_title))
|
log.info('Too early to search for %s, %s', (quality_type['quality']['identifier'], default_title))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -208,6 +213,7 @@ class Searcher(Plugin):
|
|||||||
db.add(rls)
|
db.add(rls)
|
||||||
else:
|
else:
|
||||||
[db.delete(old_info) for old_info in rls.info]
|
[db.delete(old_info) for old_info in rls.info]
|
||||||
|
rls.last_edit = int(time.time())
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@@ -283,16 +289,28 @@ class Searcher(Plugin):
|
|||||||
if filedata == 'try_next':
|
if filedata == 'try_next':
|
||||||
return filedata
|
return filedata
|
||||||
|
|
||||||
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||||
|
log.debug('Downloader result: %s', download_result)
|
||||||
if successful:
|
|
||||||
|
|
||||||
|
if download_result:
|
||||||
try:
|
try:
|
||||||
# Mark release as snatched
|
# Mark release as snatched
|
||||||
db = get_session()
|
db = get_session()
|
||||||
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
|
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
|
||||||
if rls:
|
if rls:
|
||||||
rls.status_id = snatched_status.get('id')
|
renamer_enabled = Env.setting('enabled', 'renamer')
|
||||||
|
|
||||||
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
|
||||||
|
|
||||||
|
# Save download-id info if returned
|
||||||
|
if isinstance(download_result, dict):
|
||||||
|
for key in download_result:
|
||||||
|
rls_info = ReleaseInfo(
|
||||||
|
identifier = 'download_%s' % key,
|
||||||
|
value = toUnicode(download_result.get(key))
|
||||||
|
)
|
||||||
|
rls.info.append(rls_info)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
||||||
@@ -300,33 +318,35 @@ class Searcher(Plugin):
|
|||||||
log.info(snatch_message)
|
log.info(snatch_message)
|
||||||
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
||||||
|
|
||||||
# If renamer isn't used, mark movie done
|
# If renamer isn't used, mark movie done
|
||||||
if not Env.setting('enabled', 'renamer'):
|
if not renamer_enabled:
|
||||||
active_status = fireEvent('status.get', 'active', single = True)
|
active_status = fireEvent('status.get', 'active', single = True)
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
try:
|
try:
|
||||||
if movie['status_id'] == active_status.get('id'):
|
if movie['status_id'] == active_status.get('id'):
|
||||||
for profile_type in movie['profile']['types']:
|
for profile_type in movie['profile']['types']:
|
||||||
if rls and profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
||||||
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
||||||
|
|
||||||
# Mark release done
|
# Mark release done
|
||||||
rls.status_id = done_status.get('id')
|
rls.status_id = done_status.get('id')
|
||||||
db.commit()
|
rls.last_edit = int(time.time())
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# Mark movie done
|
# Mark movie done
|
||||||
mvie = db.query(Movie).filter_by(id = movie['id']).first()
|
mvie = db.query(Movie).filter_by(id = movie['id']).first()
|
||||||
mvie.status_id = done_status.get('id')
|
mvie.status_id = done_status.get('id')
|
||||||
db.commit()
|
mvie.last_edit = int(time.time())
|
||||||
except:
|
db.commit()
|
||||||
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
|
except:
|
||||||
|
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed marking movie finished: %s', traceback.format_exc())
|
log.error('Failed marking movie finished: %s', traceback.format_exc())
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
log.info('Tried to download, but none of the "%s" downloaders are enabled', (data.get('type', '')))
|
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('type', '')))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -350,7 +370,7 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
return search_types
|
return search_types
|
||||||
|
|
||||||
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
|
def correctMovie(self, nzb = None, movie = None, quality = None, **kwargs):
|
||||||
|
|
||||||
imdb_results = kwargs.get('imdb_results', False)
|
imdb_results = kwargs.get('imdb_results', False)
|
||||||
retention = Env.setting('retention', section = 'nzb')
|
retention = Env.setting('retention', section = 'nzb')
|
||||||
@@ -363,8 +383,9 @@ class Searcher(Plugin):
|
|||||||
movie_words = re.split('\W+', simplifyString(movie_name))
|
movie_words = re.split('\W+', simplifyString(movie_name))
|
||||||
nzb_name = simplifyString(nzb['name'])
|
nzb_name = simplifyString(nzb['name'])
|
||||||
nzb_words = re.split('\W+', nzb_name)
|
nzb_words = re.split('\W+', nzb_name)
|
||||||
required_words = splitString(self.conf('required_words').lower())
|
|
||||||
|
|
||||||
|
# Make sure it has required words
|
||||||
|
required_words = splitString(self.conf('required_words').lower())
|
||||||
req_match = 0
|
req_match = 0
|
||||||
for req_set in required_words:
|
for req_set in required_words:
|
||||||
req = splitString(req_set, '&')
|
req = splitString(req_set, '&')
|
||||||
@@ -374,19 +395,24 @@ class Searcher(Plugin):
|
|||||||
log.info2("Wrong: Required word missing: %s" % nzb['name'])
|
log.info2("Wrong: Required word missing: %s" % nzb['name'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Ignore releases
|
||||||
ignored_words = splitString(self.conf('ignored_words').lower())
|
ignored_words = splitString(self.conf('ignored_words').lower())
|
||||||
blacklisted = list(set(nzb_words) & set(ignored_words) - set(movie_words))
|
ignored_match = 0
|
||||||
if self.conf('ignored_words') and blacklisted:
|
for ignored_set in ignored_words:
|
||||||
log.info2("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
|
ignored = splitString(ignored_set, '&')
|
||||||
|
ignored_match += len(list(set(nzb_words) & set(ignored))) == len(ignored)
|
||||||
|
|
||||||
|
if self.conf('ignored_words') and ignored_match:
|
||||||
|
log.info2("Wrong: '%s' contains 'ignored words'" % (nzb['name']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Ignore porn stuff
|
||||||
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
|
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
|
||||||
pron_words = list(set(nzb_words) & set(pron_tags) - set(movie_words))
|
pron_words = list(set(nzb_words) & set(pron_tags) - set(movie_words))
|
||||||
if pron_words:
|
if pron_words:
|
||||||
log.info('Wrong: %s, probably pr0n', (nzb['name']))
|
log.info('Wrong: %s, probably pr0n', (nzb['name']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#qualities = fireEvent('quality.all', single = True)
|
|
||||||
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
@@ -526,7 +552,7 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def couldBeReleased(self, wanted_quality, dates, pre_releases):
|
def couldBeReleased(self, is_pre_release, dates):
|
||||||
|
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
|
|
||||||
@@ -538,7 +564,7 @@ class Searcher(Plugin):
|
|||||||
if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
|
if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if wanted_quality in pre_releases:
|
if is_pre_release:
|
||||||
# Prerelease 1 week before theaters
|
# Prerelease 1 week before theaters
|
||||||
if dates.get('theater') - 604800 < now:
|
if dates.get('theater') - 604800 < now:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -23,14 +23,16 @@ class StatusPlugin(Plugin):
|
|||||||
'deleted': 'Deleted',
|
'deleted': 'Deleted',
|
||||||
'ignored': 'Ignored',
|
'ignored': 'Ignored',
|
||||||
'available': 'Available',
|
'available': 'Available',
|
||||||
|
'suggest': 'Suggest',
|
||||||
}
|
}
|
||||||
|
status_cached = {}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('status.add', self.add)
|
addEvent('status.get', self.get)
|
||||||
addEvent('status.get', self.add) # Alias for .add
|
|
||||||
addEvent('status.get_by_id', self.getById)
|
addEvent('status.get_by_id', self.getById)
|
||||||
addEvent('status.all', self.all)
|
addEvent('status.all', self.all)
|
||||||
addEvent('app.initialize', self.fill)
|
addEvent('app.initialize', self.fill)
|
||||||
|
addEvent('app.load', self.all) # Cache all statuses
|
||||||
|
|
||||||
addApiView('status.list', self.list, docs = {
|
addApiView('status.list', self.list, docs = {
|
||||||
'desc': 'Check for available update',
|
'desc': 'Check for available update',
|
||||||
@@ -66,26 +68,40 @@ class StatusPlugin(Plugin):
|
|||||||
s = status.to_dict()
|
s = status.to_dict()
|
||||||
temp.append(s)
|
temp.append(s)
|
||||||
|
|
||||||
#db.close()
|
# Update cache
|
||||||
|
self.status_cached[status.identifier] = s
|
||||||
|
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def add(self, identifier):
|
def get(self, identifiers):
|
||||||
|
|
||||||
|
if not isinstance(identifiers, (list)):
|
||||||
|
identifiers = [identifiers]
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
return_list = []
|
||||||
|
|
||||||
s = db.query(Status).filter_by(identifier = identifier).first()
|
for identifier in identifiers:
|
||||||
if not s:
|
|
||||||
s = Status(
|
|
||||||
identifier = identifier,
|
|
||||||
label = toUnicode(identifier.capitalize())
|
|
||||||
)
|
|
||||||
db.add(s)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
status_dict = s.to_dict()
|
if self.status_cached.get(identifier):
|
||||||
|
return_list.append(self.status_cached.get(identifier))
|
||||||
|
continue
|
||||||
|
|
||||||
#db.close()
|
s = db.query(Status).filter_by(identifier = identifier).first()
|
||||||
return status_dict
|
if not s:
|
||||||
|
s = Status(
|
||||||
|
identifier = identifier,
|
||||||
|
label = toUnicode(identifier.capitalize())
|
||||||
|
)
|
||||||
|
db.add(s)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
status_dict = s.to_dict()
|
||||||
|
|
||||||
|
self.status_cached[identifier] = status_dict
|
||||||
|
return_list.append(status_dict)
|
||||||
|
|
||||||
|
return return_list if len(identifiers) > 1 else return_list[0]
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'languages',
|
'name': 'languages',
|
||||||
'description': 'Comma separated, 2 letter country code. Example: en, nl',
|
'description': 'Comma separated, 2 letter country code. Example: en, nl. See the codes at <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">on Wikipedia</a>',
|
||||||
},
|
},
|
||||||
# {
|
# {
|
||||||
# 'name': 'automatic',
|
# 'name': 'automatic',
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
|
from couchpotato.api import addApiView
|
||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
|
||||||
class Suggestion(Plugin):
|
class Suggestion(Plugin):
|
||||||
|
|
||||||
pass
|
def __init__(self):
|
||||||
|
|
||||||
|
addApiView('suggestion.view', self.getView)
|
||||||
|
|
||||||
|
def getView(self):
|
||||||
|
|
||||||
|
limit_offset = getParam('limit_offset', None)
|
||||||
|
total_movies, movies = fireEvent('movie.list', status = 'suggest', limit_offset = limit_offset, single = True)
|
||||||
|
|
||||||
|
return jsonified({
|
||||||
|
'success': True,
|
||||||
|
'empty': len(movies) == 0,
|
||||||
|
'total': total_movies,
|
||||||
|
'movies': movies,
|
||||||
|
})
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ config = [{
|
|||||||
'name': 'quality',
|
'name': 'quality',
|
||||||
'default': '720p',
|
'default': '720p',
|
||||||
'type': 'dropdown',
|
'type': 'dropdown',
|
||||||
'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')],
|
'values': [('1080p', '1080p'), ('720p', '720p'), ('480P', '480p')],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'label': 'Naming',
|
'label': 'Naming',
|
||||||
'default': '<filename>-trailer',
|
'default': '<filename>-trailer',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Use <filename> to use above settings.'
|
'description': 'Use <strong><filename></strong> to use above settings.'
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ class Trailer(Plugin):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
for trailer in trailers.get(self.conf('quality'), []):
|
for trailer in trailers.get(self.conf('quality'), []):
|
||||||
filename = self.conf('name').replace('<filename>', group['filename']) + ('.%s' % getExt(trailer))
|
|
||||||
|
ext = getExt(trailer)
|
||||||
|
filename = self.conf('name').replace('<filename>', group['filename']) + ('.%s' % ('mp4' if len(ext) > 5 else ext))
|
||||||
destination = os.path.join(group['destination_dir'], filename)
|
destination = os.path.join(group['destination_dir'], filename)
|
||||||
if not os.path.isfile(destination):
|
if not os.path.isfile(destination):
|
||||||
fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
trailer_file = fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
||||||
|
if os.path.getsize(trailer_file) < (1024 * 1024): # Don't trust small trailers (1MB), try next one
|
||||||
|
os.unlink(trailer_file)
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
log.debug('Trailer already exists: %s', destination)
|
log.debug('Trailer already exists: %s', destination)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.userscript .frame.loading {
|
.page.userscript .frame.loading {
|
||||||
@@ -12,3 +13,26 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page.userscript .movie_result {
|
||||||
|
height: 140px;
|
||||||
|
}
|
||||||
|
.page.userscript .movie_result .thumbnail {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
.page.userscript .movie_result .options {
|
||||||
|
left: 90px;
|
||||||
|
padding: 54px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.userscript .movie_result .year {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.userscript .movie_result .options select[name="title"] {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.userscript .movie_result .options select[name="profile"] {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,28 +63,19 @@ var UserscriptSettingTab = new Class({
|
|||||||
self.settings = App.getPage('Settings')
|
self.settings = App.getPage('Settings')
|
||||||
self.settings.addEvent('create', function(){
|
self.settings.addEvent('create', function(){
|
||||||
|
|
||||||
// See if userscript can be installed
|
|
||||||
var userscript = false;
|
|
||||||
try {
|
|
||||||
if(Components.interfaces.gmIGreasemonkeyService)
|
|
||||||
userscript = true
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
userscript = Browser.chrome === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var host_url = window.location.protocol + '//' + window.location.host;
|
var host_url = window.location.protocol + '//' + window.location.host;
|
||||||
|
|
||||||
self.settings.createGroup({
|
self.settings.createGroup({
|
||||||
'name': 'userscript',
|
'name': 'userscript',
|
||||||
'label': 'Install the bookmarklet' + (userscript ? ' or userscript' : ''),
|
'label': 'Install the bookmarklet or userscript',
|
||||||
'description': 'Easily add movies via imdb.com, appletrailers and more'
|
'description': 'Easily add movies via imdb.com, appletrailers and more'
|
||||||
}).inject(self.settings.tabs.automation.content, 'top').adopt(
|
}).inject(self.settings.tabs.automation.content, 'top').adopt(
|
||||||
(userscript ? [new Element('a.userscript.button', {
|
new Element('a.userscript.button', {
|
||||||
'text': 'Install userscript',
|
'text': 'Install userscript',
|
||||||
'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js',
|
'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js',
|
||||||
'target': '_self'
|
'target': '_blank'
|
||||||
}), new Element('span.or[text=or]')] : null),
|
}),
|
||||||
|
new Element('span.or[text=or]'),
|
||||||
new Element('span.bookmarklet').adopt(
|
new Element('span.bookmarklet').adopt(
|
||||||
new Element('a.button.green', {
|
new Element('a.button.green', {
|
||||||
'text': '+CouchPotato',
|
'text': '+CouchPotato',
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
|
//
|
||||||
|
// If you can read this, you need to enable or install the Greasemonkey add-on for firefox
|
||||||
|
// If you are using Chrome, download this file and drag it to the extensions tab
|
||||||
|
// Other browsers, use the bookmarklet
|
||||||
|
//
|
||||||
// @name CouchPotato UserScript
|
// @name CouchPotato UserScript
|
||||||
// @description Add movies like a real CouchPotato
|
// @description Add movies like a real CouchPotato
|
||||||
// @grant none
|
// @grant none
|
||||||
|
|||||||
@@ -1,49 +1,60 @@
|
|||||||
.page.wizard .uniForm {
|
.page.wizard .uniForm {
|
||||||
width: 80%;
|
margin: 0 0 30px;
|
||||||
margin: 0 auto 30px;
|
width: 83%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.wizard h1 {
|
.page.wizard h1 {
|
||||||
padding: 10px 30px;
|
padding: 10px 0;
|
||||||
margin: 0;
|
margin: 0 5px;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
margin-top: 80px;
|
margin-top: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.wizard .description {
|
.page.wizard .description {
|
||||||
padding: 10px 30px;
|
padding: 10px 5px;
|
||||||
font-size: 18px;
|
font-size: 1.45em;
|
||||||
|
line-height: 1.4em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.wizard .tab_wrapper {
|
.page.wizard .tab_wrapper {
|
||||||
background: #5c697b;
|
background: #5c697b;
|
||||||
padding: 10px 0;
|
height: 65px;
|
||||||
font-size: 18px;
|
font-size: 1.75em;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 960px;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.wizard .tab_wrapper .tabs {
|
.page.wizard .tab_wrapper .tabs {
|
||||||
text-align: center;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 960px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.wizard .tabs li {
|
.page.wizard .tabs li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
.page.wizard .tabs li a {
|
.page.wizard .tabs li a {
|
||||||
padding: 20px 10px;
|
padding: 20px 10px;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
color: #FFF;
|
||||||
|
font-weight: normal;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page.wizard .tabs li:hover a { border-color: #047792; }
|
||||||
|
.page.wizard .tabs li.done a { border-color: #04bce6; }
|
||||||
|
|
||||||
.page.wizard .tab_wrapper .pointer {
|
.page.wizard .tab_wrapper .pointer {
|
||||||
border-right: 10px solid transparent;
|
border-right: 10px solid transparent;
|
||||||
@@ -61,27 +72,13 @@
|
|||||||
.page.wizard form > div {
|
.page.wizard form > div {
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
.page.wizard .wgroup_finish {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
.page.wizard .wgroup_finish h1 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.page.wizard .wgroup_finish .wizard_support,
|
|
||||||
.page.wizard .wgroup_finish .description {
|
|
||||||
font-size: 25px;
|
|
||||||
line-height: 120%;
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wizard .button.green {
|
.page.wizard .button.green {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
margin: 10px 30px 80px;
|
margin: 10px 0 80px;
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.page.wizard .tab_nzb_providers {
|
.page.wizard .tab_nzb_providers {
|
||||||
margin: 20px 0 0 0;
|
margin: 20px 0 0 0;
|
||||||
|
|||||||
@@ -9,27 +9,12 @@ Page.Wizard = new Class({
|
|||||||
headers: {
|
headers: {
|
||||||
'welcome': {
|
'welcome': {
|
||||||
'title': 'Welcome to the new CouchPotato',
|
'title': 'Welcome to the new CouchPotato',
|
||||||
'description': 'To get started, fill in each of the following settings as much as you can. <br />Maybe first start with importing your movies from the previous CouchPotato',
|
'description': 'To get started, fill in each of the following settings as much as you can.',
|
||||||
'content': new Element('div', {
|
'content': new Element('div', {
|
||||||
'styles': {
|
'styles': {
|
||||||
'margin': '0 0 0 30px'
|
'margin': '0 0 0 30px'
|
||||||
}
|
}
|
||||||
}).adopt(
|
})
|
||||||
new Element('div', {
|
|
||||||
'html': 'Select the <strong>data.db</strong>. It should be in your CouchPotato root directory.'
|
|
||||||
}),
|
|
||||||
self.import_iframe = new Element('iframe', {
|
|
||||||
'styles': {
|
|
||||||
'height': 40,
|
|
||||||
'width': 300,
|
|
||||||
'border': 0,
|
|
||||||
'overflow': 'hidden'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
),
|
|
||||||
'event': function(){
|
|
||||||
self.import_iframe.set('src', Api.createUrl('v1.import'))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'general': {
|
'general': {
|
||||||
'title': 'General',
|
'title': 'General',
|
||||||
@@ -82,7 +67,7 @@ Page.Wizard = new Class({
|
|||||||
'target': self.el
|
'target': self.el
|
||||||
},
|
},
|
||||||
'onComplete': function(){
|
'onComplete': function(){
|
||||||
window.location = App.createUrl();
|
window.location = App.createUrl('wanted');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -178,7 +163,7 @@ Page.Wizard = new Class({
|
|||||||
'href': App.createUrl('wizard/'+group),
|
'href': App.createUrl('wizard/'+group),
|
||||||
'text': (self.headers[group].label || group).capitalize()
|
'text': (self.headers[group].label || group).capitalize()
|
||||||
})
|
})
|
||||||
).inject(tabs);
|
).inject(tabs)
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -214,13 +199,7 @@ Page.Wizard = new Class({
|
|||||||
self.el.getElement('.t_searcher').hide();
|
self.el.getElement('.t_searcher').hide();
|
||||||
|
|
||||||
// Add pointer
|
// Add pointer
|
||||||
new Element('.tab_wrapper').wraps(tabs).adopt(
|
new Element('.tab_wrapper').wraps(tabs);
|
||||||
self.pointer = new Element('.pointer', {
|
|
||||||
'tween': {
|
|
||||||
'transition': 'quint:in:out'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add nav
|
// Add nav
|
||||||
var minimum = self.el.getSize().y-window.getSize().y;
|
var minimum = self.el.getSize().y-window.getSize().y;
|
||||||
@@ -232,16 +211,18 @@ Page.Wizard = new Class({
|
|||||||
if(!t) return;
|
if(!t) return;
|
||||||
|
|
||||||
var func = function(){
|
var func = function(){
|
||||||
var ct = t.getCoordinates();
|
// Activate all previous ones
|
||||||
self.pointer.tween('left', ct.left+(ct.width/2)-(self.pointer.getWidth()/2));
|
self.groups.each(function(groups2, nr2){
|
||||||
|
var t2 = self.el.getElement('.t_'+groups2);
|
||||||
|
t2[nr2 > nr ? 'removeClass' : 'addClass' ]('done');
|
||||||
|
})
|
||||||
g.tween('opacity', 1);
|
g.tween('opacity', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nr == 0)
|
if(nr == 0)
|
||||||
func();
|
func();
|
||||||
|
|
||||||
|
new ScrollSpy( {
|
||||||
var ss = new ScrollSpy( {
|
|
||||||
min: function(){
|
min: function(){
|
||||||
var c = g.getCoordinates();
|
var c = g.getCoordinates();
|
||||||
var top = c.top-(window.getSize().y/2);
|
var top = c.top-(window.getSize().y/2);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from couchpotato.core.event import addEvent, fireEvent
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import Provider
|
from couchpotato.core.providers.base import Provider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
from couchpotato.core.helpers.variable import splitString
|
||||||
import time
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -59,7 +60,26 @@ class Automation(Provider):
|
|||||||
type_value = movie.get(minimal_type, 0)
|
type_value = movie.get(minimal_type, 0)
|
||||||
type_min = self.getMinimal(minimal_type)
|
type_min = self.getMinimal(minimal_type)
|
||||||
if type_value < type_min:
|
if type_value < type_min:
|
||||||
log.info('%s too low for %s, need %s has %s', (minimal_type, movie['imdb'], type_min, type_value))
|
log.info('%s too low for %s, need %s has %s', (minimal_type, movie['original_title'], type_min, type_value))
|
||||||
|
return False
|
||||||
|
|
||||||
|
movie_genres = [genre.lower() for genre in movie['genres']]
|
||||||
|
required_genres = splitString(self.getMinimal('required_genres').lower())
|
||||||
|
ignored_genres = splitString(self.getMinimal('ignored_genres').lower())
|
||||||
|
|
||||||
|
req_match = 0
|
||||||
|
for req_set in required_genres:
|
||||||
|
req = splitString(req_set, '&')
|
||||||
|
req_match += len(list(set(movie_genres) & set(req))) == len(req)
|
||||||
|
|
||||||
|
if self.getMinimal('required_genres') and req_match == 0:
|
||||||
|
log.info2("Required genre(s) missing for %s" % movie['original_title'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
for ign_set in ignored_genres:
|
||||||
|
ign = splitString(ign_set, '&')
|
||||||
|
if len(list(set(movie_genres) & set(ign))) == len(ign):
|
||||||
|
log.info2("%s has blacklisted genre(s): %s" % (movie['original_title'], ign))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from .main import Goodfilms
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Goodfilms()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'goodfilms',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'list': 'watchlist_providers',
|
||||||
|
'name': 'goodfilms_automation',
|
||||||
|
'label': 'Goodfilms',
|
||||||
|
'description': 'import movies from your <a href="http://goodfil.ms">Goodfilms</a> queue',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_username',
|
||||||
|
'label': 'Username',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Goodfilms(Automation):
|
||||||
|
|
||||||
|
url = 'http://goodfil.ms/%s/queue'
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
if not self.conf('automation_username'):
|
||||||
|
log.error('Please fill in your username')
|
||||||
|
return []
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
|
||||||
|
for movie in self.getWatchlist():
|
||||||
|
imdb_id = self.search(movie.get('title'), movie.get('year'), imdb_only = True)
|
||||||
|
movies.append(imdb_id)
|
||||||
|
|
||||||
|
return movies
|
||||||
|
|
||||||
|
def getWatchlist(self):
|
||||||
|
|
||||||
|
url = self.url % self.conf('automation_username')
|
||||||
|
soup = BeautifulSoup(self.getHTMLData(url))
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
|
||||||
|
for movie in soup.find_all('div', attrs = { 'class': 'movie', 'data-film-title': True }):
|
||||||
|
movies.append({ 'title': movie['data-film-title'], 'year': movie['data-film-year'] })
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from .main import Letterboxd
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Letterboxd()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'letterboxd',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'list': 'watchlist_providers',
|
||||||
|
'name': 'letterboxd_automation',
|
||||||
|
'label': 'Letterboxd',
|
||||||
|
'description': 'Import movies from any public <a href="http://letterboxd.com/">Letterboxd</a> watchlist',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls_use',
|
||||||
|
'label': 'Use',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls',
|
||||||
|
'label': 'Username',
|
||||||
|
'type': 'combined',
|
||||||
|
'combine': ['automation_urls_use', 'automation_urls'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
|
import re
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Letterboxd(Automation):
|
||||||
|
|
||||||
|
url = 'http://letterboxd.com/%s/watchlist/'
|
||||||
|
pattern = re.compile(r'(.*)\((\d*)\)')
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
urls = splitString(self.conf('automation_urls'))
|
||||||
|
|
||||||
|
if len(urls) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
|
||||||
|
for movie in self.getWatchlist():
|
||||||
|
imdb_id = self.search(movie.get('title'), movie.get('year'), imdb_only = True)
|
||||||
|
movies.append(imdb_id)
|
||||||
|
|
||||||
|
return movies
|
||||||
|
|
||||||
|
def getWatchlist(self):
|
||||||
|
|
||||||
|
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||||
|
urls = splitString(self.conf('automation_urls'))
|
||||||
|
|
||||||
|
index = -1
|
||||||
|
movies = []
|
||||||
|
for username in urls:
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
if not enablers[index]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
soup = BeautifulSoup(self.getHTMLData(self.url % username))
|
||||||
|
|
||||||
|
for movie in soup.find_all('a', attrs = { 'class': 'frame' }):
|
||||||
|
match = filter(None, self.pattern.split(movie['title']))
|
||||||
|
movies.append({'title': match[0], 'year': match[1] })
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -46,7 +46,8 @@ class Provider(Plugin):
|
|||||||
|
|
||||||
def getJsonData(self, url, **kwargs):
|
def getJsonData(self, url, **kwargs):
|
||||||
|
|
||||||
data = self.getCache(md5(url), url, **kwargs)
|
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
|
||||||
|
data = self.getCache(cache_key, url, **kwargs)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
try:
|
try:
|
||||||
@@ -58,7 +59,8 @@ class Provider(Plugin):
|
|||||||
|
|
||||||
def getRSSData(self, url, item_path = 'channel/item', **kwargs):
|
def getRSSData(self, url, item_path = 'channel/item', **kwargs):
|
||||||
|
|
||||||
data = self.getCache(md5(url), url, **kwargs)
|
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
|
||||||
|
data = self.getCache(cache_key, url, **kwargs)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
try:
|
try:
|
||||||
@@ -70,7 +72,9 @@ class Provider(Plugin):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def getHTMLData(self, url, **kwargs):
|
def getHTMLData(self, url, **kwargs):
|
||||||
return self.getCache(md5(url), url, **kwargs)
|
|
||||||
|
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
|
||||||
|
return self.getCache(cache_key, url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class YarrProvider(Provider):
|
class YarrProvider(Provider):
|
||||||
@@ -100,6 +104,7 @@ class YarrProvider(Provider):
|
|||||||
try:
|
try:
|
||||||
cookiejar = cookielib.CookieJar()
|
cookiejar = cookielib.CookieJar()
|
||||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
|
||||||
|
opener.addheaders = []
|
||||||
urllib2.install_opener(opener)
|
urllib2.install_opener(opener)
|
||||||
log.info2('Logging into %s', self.urls['login'])
|
log.info2('Logging into %s', self.urls['login'])
|
||||||
f = opener.open(self.urls['login'], self.getLoginParams())
|
f = opener.open(self.urls['login'], self.getLoginParams())
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ class MovieResultModifier(Plugin):
|
|||||||
if l:
|
if l:
|
||||||
|
|
||||||
# Statuses
|
# Statuses
|
||||||
active_status = fireEvent('status.get', 'active', single = True)
|
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
|
||||||
|
|
||||||
for movie in l.movies:
|
for movie in l.movies:
|
||||||
if movie.status_id == active_status['id']:
|
if movie.status_id == active_status['id']:
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ from couchpotato import get_session
|
|||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams
|
from couchpotato.core.helpers.request import jsonified, getParams
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.movie.base import MovieProvider
|
from couchpotato.core.providers.movie.base import MovieProvider
|
||||||
from couchpotato.core.settings.model import Movie
|
from couchpotato.core.settings.model import Movie
|
||||||
from flask.helpers import json
|
from couchpotato.environment import Env
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -17,70 +17,78 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
urls = {
|
urls = {
|
||||||
'search': 'https://couchpota.to/api/search/%s/',
|
'search': 'https://couchpota.to/api/search/%s/',
|
||||||
'info': 'https://couchpota.to/api/info/%s/',
|
'info': 'https://couchpota.to/api/info/%s/',
|
||||||
|
'is_movie': 'https://couchpota.to/api/ismovie/%s/',
|
||||||
'eta': 'https://couchpota.to/api/eta/%s/',
|
'eta': 'https://couchpota.to/api/eta/%s/',
|
||||||
'suggest': 'https://couchpota.to/api/suggest/%s/%s/',
|
'suggest': 'https://couchpota.to/api/suggest/',
|
||||||
|
'updater': 'https://couchpota.to/api/updater/?%s',
|
||||||
|
'messages': 'https://couchpota.to/api/messages/?%s',
|
||||||
}
|
}
|
||||||
http_time_between_calls = 0
|
http_time_between_calls = 0
|
||||||
api_version = 1
|
api_version = 1
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
#addApiView('movie.suggest', self.suggestView)
|
|
||||||
|
|
||||||
addEvent('movie.info', self.getInfo, priority = 1)
|
addEvent('movie.info', self.getInfo, priority = 1)
|
||||||
addEvent('movie.search', self.search, priority = 1)
|
addEvent('movie.search', self.search, priority = 1)
|
||||||
addEvent('movie.release_date', self.getReleaseDate)
|
addEvent('movie.release_date', self.getReleaseDate)
|
||||||
|
addEvent('movie.suggest', self.suggest)
|
||||||
|
addEvent('movie.is_movie', self.isMovie)
|
||||||
|
|
||||||
|
addEvent('cp.source_url', self.getSourceUrl)
|
||||||
|
addEvent('cp.messages', self.getMessages)
|
||||||
|
|
||||||
|
def getMessages(self, last_check = 0):
|
||||||
|
|
||||||
|
data = self.getJsonData(self.urls['messages'] % tryUrlencode({
|
||||||
|
'last_check': last_check,
|
||||||
|
}), headers = self.getRequestHeaders(), cache_timeout = 10)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def getSourceUrl(self, repo = None, repo_name = None, branch = None):
|
||||||
|
return self.getJsonData(self.urls['updater'] % tryUrlencode({
|
||||||
|
'repo': repo,
|
||||||
|
'name': repo_name,
|
||||||
|
'branch': branch,
|
||||||
|
}), headers = self.getRequestHeaders())
|
||||||
|
|
||||||
def search(self, q, limit = 12):
|
def search(self, q, limit = 12):
|
||||||
|
return self.getJsonData(self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders())
|
||||||
|
|
||||||
cache_key = 'cpapi.cache.%s' % q
|
def isMovie(self, identifier = None):
|
||||||
cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders())
|
|
||||||
|
|
||||||
if cached:
|
if not identifier:
|
||||||
try:
|
return
|
||||||
movies = json.loads(cached)
|
|
||||||
return movies
|
|
||||||
except:
|
|
||||||
log.error('Failed parsing search results: %s', traceback.format_exc())
|
|
||||||
|
|
||||||
return []
|
data = self.getJsonData(self.urls['is_movie'] % identifier, headers = self.getRequestHeaders())
|
||||||
|
if data:
|
||||||
|
return data.get('is_movie', True)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def getInfo(self, identifier = None):
|
def getInfo(self, identifier = None):
|
||||||
|
|
||||||
if not identifier:
|
if not identifier:
|
||||||
return
|
return
|
||||||
|
|
||||||
cache_key = 'cpapi.cache.info.%s' % identifier
|
result = self.getJsonData(self.urls['info'] % identifier, headers = self.getRequestHeaders())
|
||||||
cached = self.getCache(cache_key, self.urls['info'] % identifier, headers = self.getRequestHeaders())
|
if result: return result
|
||||||
|
|
||||||
if cached:
|
|
||||||
try:
|
|
||||||
movie = json.loads(cached)
|
|
||||||
return movie
|
|
||||||
except:
|
|
||||||
log.error('Failed parsing info results: %s', traceback.format_exc())
|
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def getReleaseDate(self, identifier = None):
|
def getReleaseDate(self, identifier = None):
|
||||||
|
|
||||||
if identifier is None: return {}
|
if identifier is None: return {}
|
||||||
try:
|
|
||||||
data = self.urlopen(self.urls['eta'] % identifier, headers = self.getRequestHeaders())
|
|
||||||
dates = json.loads(data)
|
|
||||||
log.debug('Found ETA for %s: %s', (identifier, dates))
|
|
||||||
return dates
|
|
||||||
except Exception, e:
|
|
||||||
log.error('Error getting ETA for %s: %s', (identifier, e))
|
|
||||||
|
|
||||||
return {}
|
dates = self.getJsonData(self.urls['eta'] % identifier, headers = self.getRequestHeaders())
|
||||||
|
log.debug('Found ETA for %s: %s', (identifier, dates))
|
||||||
|
|
||||||
|
return dates
|
||||||
|
|
||||||
def suggest(self, movies = [], ignore = []):
|
def suggest(self, movies = [], ignore = []):
|
||||||
try:
|
suggestions = self.getJsonData(self.urls['suggest'], params = {
|
||||||
data = self.urlopen(self.urls['suggest'] % (','.join(movies), ','.join(ignore)))
|
'movies': ','.join(movies),
|
||||||
suggestions = json.loads(data)
|
#'ignore': ','.join(ignore),
|
||||||
log.info('Found Suggestions for %s', (suggestions))
|
})
|
||||||
except Exception, e:
|
log.info('Found Suggestions for %s', (suggestions))
|
||||||
log.error('Error getting suggestions for %s: %s', (movies, e))
|
|
||||||
|
|
||||||
return suggestions
|
return suggestions
|
||||||
|
|
||||||
@@ -108,4 +116,5 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
'X-CP-Version': fireEvent('app.version', single = True),
|
'X-CP-Version': fireEvent('app.version', single = True),
|
||||||
'X-CP-API': self.api_version,
|
'X-CP-API': self.api_version,
|
||||||
'X-CP-Time': time.time(),
|
'X-CP-Time': time.time(),
|
||||||
|
'X-CP-Identifier': '+%s' % Env.setting('api_key', 'core')[:10], # Use first 10 as identifier, so we don't need to use IP address in api stats
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ config = [{
|
|||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ config = [{
|
|||||||
'default': '',
|
'default': '',
|
||||||
'type': 'password',
|
'type': 'password',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ config = [{
|
|||||||
'list': 'nzb_providers',
|
'list': 'nzb_providers',
|
||||||
'name': 'newznab',
|
'name': 'newznab',
|
||||||
'order': 10,
|
'order': 10,
|
||||||
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab providers</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
|
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
|
||||||
<a href="https://nzbs.org" target="_blank">NZBs.org</a>, <a href="http://dognzb.cr/" target="_blank">DOGnzb.cr</a>, \
|
<a href="https://nzbs.org" target="_blank">NZBs.org</a>, <a href="http://dognzb.cr/" target="_blank">DOGnzb.cr</a>, \
|
||||||
<a href="https://github.com/spotweb/spotweb" target="_blank">Spotweb</a> or <a href="https://nzbgeek.info/" target="_blank">NZBGeek</a>',
|
<a href="https://github.com/spotweb/spotweb" target="_blank">Spotweb</a>, <a href="https://nzbgeek.info/" target="_blank">NZBGeek</a>, \
|
||||||
|
<a href="https://smackdownonyou.com" target="_blank">SmackDown</a>, <a href="https://www.nzbfinder.ws" target="_blank">NZBFinder</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
@@ -23,20 +24,27 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'use',
|
'name': 'use',
|
||||||
'default': '0,0,0,0'
|
'default': '0,0,0,0,0,0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'nzb.su,dognzb.cr,nzbs.org,https://index.nzbgeek.info',
|
'default': 'nzb.su,dognzb.cr,nzbs.org,https://index.nzbgeek.info, https://smackdownonyou.com, https://www.nzbfinder.ws',
|
||||||
'description': 'The hostname of your newznab provider',
|
'description': 'The hostname of your newznab provider',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'default': '0,0,0,0,0,0',
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'api_key',
|
'name': 'api_key',
|
||||||
'default': ',,,',
|
'default': ',,,,,',
|
||||||
'label': 'Api Key',
|
'label': 'Api Key',
|
||||||
'description': 'Can be found on your profile page',
|
'description': 'Can be found on your profile page',
|
||||||
'type': 'combined',
|
'type': 'combined',
|
||||||
'combine': ['use', 'host', 'api_key'],
|
'combine': ['use', 'host', 'api_key', 'extra_score'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import cleanHost, splitString
|
from couchpotato.core.helpers.variable import cleanHost, splitString, tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import ResultList
|
from couchpotato.core.providers.base import ResultList
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
@@ -69,13 +69,14 @@ class Newznab(NZBProvider, RSS):
|
|||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': nzb_id,
|
'id': nzb_id,
|
||||||
'provider_extra': host['host'],
|
'provider_extra': urlparse(host['host']).hostname or host['host'],
|
||||||
'name': self.getTextElement(nzb, 'title'),
|
'name': self.getTextElement(nzb, 'title'),
|
||||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||||
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
||||||
'url': (self.getUrl(host['host'], self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
'url': (self.getUrl(host['host'], self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
||||||
'detail_url': '%sdetails/%s' % (cleanHost(host['host']), tryUrlencode(nzb_id)),
|
'detail_url': '%sdetails/%s' % (cleanHost(host['host']), tryUrlencode(nzb_id)),
|
||||||
'content': self.getTextElement(nzb, 'description'),
|
'content': self.getTextElement(nzb, 'description'),
|
||||||
|
'score': host['extra_score'],
|
||||||
})
|
})
|
||||||
|
|
||||||
def getHosts(self):
|
def getHosts(self):
|
||||||
@@ -83,13 +84,15 @@ class Newznab(NZBProvider, RSS):
|
|||||||
uses = splitString(str(self.conf('use')))
|
uses = splitString(str(self.conf('use')))
|
||||||
hosts = splitString(self.conf('host'))
|
hosts = splitString(self.conf('host'))
|
||||||
api_keys = splitString(self.conf('api_key'))
|
api_keys = splitString(self.conf('api_key'))
|
||||||
|
extra_score = splitString(self.conf('extra_score'))
|
||||||
|
|
||||||
list = []
|
list = []
|
||||||
for nr in range(len(hosts)):
|
for nr in range(len(hosts)):
|
||||||
list.append({
|
list.append({
|
||||||
'use': uses[nr],
|
'use': uses[nr],
|
||||||
'host': hosts[nr],
|
'host': hosts[nr],
|
||||||
'api_key': api_keys[nr]
|
'api_key': api_keys[nr],
|
||||||
|
'extra_score': tryInt(extra_score[nr]) if len(extra_score) > nr else 0
|
||||||
})
|
})
|
||||||
|
|
||||||
return list
|
return list
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ config = [{
|
|||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ config = [{
|
|||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': True,
|
'default': True,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ config = [{
|
|||||||
'label': 'English only',
|
'label': 'English only',
|
||||||
'description': 'Only search for English spoken movies on Nzbsrus',
|
'description': 'Only search for English spoken movies on Nzbsrus',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ config = [{
|
|||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': True,
|
'default': True,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ config = [{
|
|||||||
'label': 'Api Key',
|
'label': 'Api Key',
|
||||||
'default': '',
|
'default': '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'default': 20,
|
||||||
|
'type': 'int',
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
from .main import IPTorrents
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return IPTorrents()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'iptorrents',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'searcher',
|
||||||
|
'subtab': 'providers',
|
||||||
|
'list': 'torrent_providers',
|
||||||
|
'name': 'IPTorrents',
|
||||||
|
'description': 'See <a href="http://www.iptorrents.com">IPTorrents</a>',
|
||||||
|
'wizard': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'type': 'enabler',
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'username',
|
||||||
|
'default': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'password',
|
||||||
|
'default': '',
|
||||||
|
'type': 'password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'freeleech',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'description': 'Only search for [FreeLeech] torrents.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IPTorrents(TorrentProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'test' : 'http://www.iptorrents.com/',
|
||||||
|
'base_url' : 'http://www.iptorrents.com',
|
||||||
|
'login' : 'http://www.iptorrents.com/torrents/',
|
||||||
|
'search' : 'http://www.iptorrents.com/torrents/?l%d=1%s&q=%s&qf=ti',
|
||||||
|
}
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([48], ['720p', '1080p', 'bd50']),
|
||||||
|
([72], ['cam', 'ts', 'tc', 'r5', 'scr']),
|
||||||
|
([7], ['dvdrip', 'brrip']),
|
||||||
|
([6], ['dvdr']),
|
||||||
|
]
|
||||||
|
|
||||||
|
http_time_between_calls = 1 #seconds
|
||||||
|
cat_backup_id = None
|
||||||
|
|
||||||
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
|
freeleech = '' if not self.conf('freeleech') else '&free=on'
|
||||||
|
|
||||||
|
url = self.urls['search'] % (self.getCatId(quality['identifier'])[0], freeleech, tryUrlencode('%s %s' % (title.replace(':', ''), movie['library']['year'])))
|
||||||
|
data = self.getHTMLData(url, opener = self.login_opener)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
html = BeautifulSoup(data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result_table = html.find('table', attrs = {'class' : 'torrents'})
|
||||||
|
|
||||||
|
if not result_table or 'nothing found!' in data.lower():
|
||||||
|
return
|
||||||
|
|
||||||
|
entries = result_table.find_all('tr')
|
||||||
|
|
||||||
|
for result in entries[1:]:
|
||||||
|
|
||||||
|
torrent = result.find_all('td')[1].find('a')
|
||||||
|
|
||||||
|
torrent_id = torrent['href'].replace('/details.php?id=', '')
|
||||||
|
torrent_name = torrent.string
|
||||||
|
torrent_download_url = self.urls['base_url'] + (result.find_all('td')[3].find('a'))['href'].replace(' ', '.')
|
||||||
|
torrent_details_url = self.urls['base_url'] + torrent['href']
|
||||||
|
torrent_size = self.parseSize(result.find_all('td')[5].string)
|
||||||
|
torrent_seeders = tryInt(result.find('td', attrs = {'class' : 'ac t_seeders'}).string)
|
||||||
|
torrent_leechers = tryInt(result.find('td', attrs = {'class' : 'ac t_leechers'}).string)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'id': torrent_id,
|
||||||
|
'name': torrent_name,
|
||||||
|
'url': torrent_download_url,
|
||||||
|
'detail_url': torrent_details_url,
|
||||||
|
'download': self.loginDownload,
|
||||||
|
'size': torrent_size,
|
||||||
|
'seeders': torrent_seeders,
|
||||||
|
'leechers': torrent_leechers,
|
||||||
|
})
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
def loginSuccess(self, output):
|
||||||
|
return 'don\'t have an account' not in output.lower()
|
||||||
|
|
||||||
|
def getLoginParams(self):
|
||||||
|
return tryUrlencode({
|
||||||
|
'username': self.conf('username'),
|
||||||
|
'password': self.conf('password'),
|
||||||
|
'login': 'submit',
|
||||||
|
})
|
||||||
@@ -19,6 +19,14 @@ config = [{
|
|||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': True,
|
'default': True,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'passkey',
|
'name': 'passkey',
|
||||||
'default': '',
|
'default': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'extra_score',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Extra Score',
|
||||||
|
'type': 'int',
|
||||||
|
'default': 20,
|
||||||
|
'description': 'Starting score for each release found via this provider.',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user