Compare commits
95 Commits
build/2.0.
...
build/2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a314cfbc4 | ||
|
|
5941d0bf77 | ||
|
|
d326c1c25c | ||
|
|
7e6234298d | ||
|
|
d4da206f93 | ||
|
|
985a168724 | ||
|
|
173c6194ed | ||
|
|
bcd23ad10c | ||
|
|
898e6f487d | ||
|
|
96472a9a8f | ||
|
|
27252561e2 | ||
|
|
6618c3927c | ||
|
|
c9e732651f | ||
|
|
7849e7170d | ||
|
|
087894eb4e | ||
|
|
4b58b40226 | ||
|
|
3ecc826629 | ||
|
|
25f1b8c7a7 | ||
|
|
e71da1f14d | ||
|
|
938b14ba18 | ||
|
|
d6522d8f38 | ||
|
|
78eab890e7 | ||
|
|
1a56191f83 | ||
|
|
41c0f34d95 | ||
|
|
37bf205d7a | ||
|
|
32fe3796e4 | ||
|
|
359d1aaafa | ||
|
|
fb5d336351 | ||
|
|
eb30dff986 | ||
|
|
9312336962 | ||
|
|
aa1fa3eb9a | ||
|
|
0e2f8a612c | ||
|
|
ade4338ea6 | ||
|
|
55b20324c0 | ||
|
|
465e7b2abc | ||
|
|
578fb45785 | ||
|
|
c0fb28301d | ||
|
|
96995bbbe5 | ||
|
|
4cfdafebbc | ||
|
|
f9c2503f81 | ||
|
|
b97acb8ef5 | ||
|
|
5b4cdf05b1 | ||
|
|
d68d2dfdb6 | ||
|
|
39b269a454 | ||
|
|
ac081d3e10 | ||
|
|
5d4efb60cf | ||
|
|
6f25a6bdfd | ||
|
|
23427e95f7 | ||
|
|
cc408b980c | ||
|
|
90a09e573b | ||
|
|
e1d7440b9d | ||
|
|
59590b3ac9 | ||
|
|
ff759dacf3 | ||
|
|
a328e44130 | ||
|
|
7924cac5f9 | ||
|
|
1cef3b0c93 | ||
|
|
3cd59edc8b | ||
|
|
0d624af01d | ||
|
|
a09132570c | ||
|
|
ee3fc38432 | ||
|
|
dbf0192c8e | ||
|
|
6962cfc3f5 | ||
|
|
e096ec3b5b | ||
|
|
b30a74ae0c | ||
|
|
978eeb16c9 | ||
|
|
e5c9d91657 | ||
|
|
fa81c3a07a | ||
|
|
9cdd520d41 | ||
|
|
55d7898771 | ||
|
|
b8256bef97 | ||
|
|
5be9dc0b4a | ||
|
|
7d0be0cefb | ||
|
|
f7ce1edb13 | ||
|
|
5ad9280b60 | ||
|
|
2b353f1b20 | ||
|
|
75ab90b87b | ||
|
|
0219296120 | ||
|
|
20032b3a31 | ||
|
|
ea9e9a8c90 | ||
|
|
f7b0ee145b | ||
|
|
cc866738ee | ||
|
|
eadccf6e33 | ||
|
|
b70b66e567 | ||
|
|
5b6792dc20 | ||
|
|
f498e7343a | ||
|
|
6962f441e6 | ||
|
|
1def62b1b1 | ||
|
|
a4a4a6a185 | ||
|
|
d4c9469c1a | ||
|
|
3e2d4c5d7b | ||
|
|
d03f711d69 | ||
|
|
44dd8d9b96 | ||
|
|
549a3be0d8 | ||
|
|
1bb2edf8ec | ||
|
|
84c6f36315 |
@@ -62,6 +62,7 @@ class Loader(object):
|
||||
self.log.logger.addHandler(hdlr)
|
||||
|
||||
def addSignals(self):
|
||||
|
||||
signal.signal(signal.SIGINT, self.onExit)
|
||||
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
|
||||
|
||||
@@ -73,7 +74,7 @@ class Loader(object):
|
||||
|
||||
def onExit(self, signal, frame):
|
||||
from couchpotato.core.event import fireEvent
|
||||
fireEvent('app.shutdown', single = True)
|
||||
fireEvent('app.crappy_shutdown', single = True)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
231
Desktop.py
Normal file
231
Desktop.py
Normal file
@@ -0,0 +1,231 @@
|
||||
from esky.util import appdir_from_executable #@UnresolvedImport
|
||||
from threading import Thread
|
||||
from version import VERSION
|
||||
from wx.lib.softwareupdate import SoftwareUpdate
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import webbrowser
|
||||
import wx
|
||||
|
||||
# Include proper dirs
|
||||
if hasattr(sys, 'frozen'):
|
||||
import libs
|
||||
base_path = os.path.dirname(os.path.dirname(os.path.abspath(libs.__file__)))
|
||||
else:
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
lib_dir = os.path.join(base_path, 'libs')
|
||||
|
||||
sys.path.insert(0, base_path)
|
||||
sys.path.insert(0, lib_dir)
|
||||
|
||||
from couchpotato.environment import Env
|
||||
|
||||
class TaskBarIcon(wx.TaskBarIcon):
|
||||
|
||||
TBMENU_OPEN = wx.NewId()
|
||||
TBMENU_SETTINGS = wx.NewId()
|
||||
TBMENU_EXIT = wx.ID_EXIT
|
||||
|
||||
closed = False
|
||||
menu = False
|
||||
enabled = False
|
||||
|
||||
def __init__(self, frame):
|
||||
wx.TaskBarIcon.__init__(self)
|
||||
self.frame = frame
|
||||
|
||||
icon = wx.Icon('icon.png', wx.BITMAP_TYPE_PNG)
|
||||
self.SetIcon(icon)
|
||||
|
||||
self.Bind(wx.EVT_TASKBAR_LEFT_UP, self.OnTaskBarClick)
|
||||
self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTaskBarClick)
|
||||
|
||||
self.Bind(wx.EVT_MENU, self.onOpen, id = self.TBMENU_OPEN)
|
||||
self.Bind(wx.EVT_MENU, self.onSettings, id = self.TBMENU_SETTINGS)
|
||||
self.Bind(wx.EVT_MENU, self.onTaskBarClose, id = self.TBMENU_EXIT)
|
||||
|
||||
def OnTaskBarClick(self, evt):
|
||||
menu = self.CreatePopupMenu()
|
||||
self.PopupMenu(menu)
|
||||
menu.Destroy()
|
||||
|
||||
def enable(self):
|
||||
self.enabled = True
|
||||
|
||||
if self.menu:
|
||||
self.open_menu.Enable(True)
|
||||
self.setting_menu.Enable(True)
|
||||
|
||||
self.open_menu.SetText('Open')
|
||||
|
||||
def CreatePopupMenu(self):
|
||||
|
||||
if not self.menu:
|
||||
self.menu = wx.Menu()
|
||||
self.open_menu = self.menu.Append(self.TBMENU_OPEN, 'Open')
|
||||
self.setting_menu = self.menu.Append(self.TBMENU_SETTINGS, 'About')
|
||||
self.exit_menu = self.menu.Append(self.TBMENU_EXIT, 'Quit')
|
||||
|
||||
if not self.enabled:
|
||||
self.open_menu.Enable(False)
|
||||
self.setting_menu.Enable(False)
|
||||
|
||||
self.open_menu.SetText('Loading...')
|
||||
|
||||
return self.menu
|
||||
|
||||
def onOpen(self, event):
|
||||
url = self.frame.parent.getSetting('base_url')
|
||||
webbrowser.open(url)
|
||||
|
||||
def onSettings(self, event):
|
||||
url = self.frame.parent.getSetting('base_url') + '/settings/'
|
||||
webbrowser.open(url)
|
||||
|
||||
def onTaskBarClose(self, evt):
|
||||
if self.closed:
|
||||
return
|
||||
|
||||
self.closed = True
|
||||
|
||||
self.RemoveIcon()
|
||||
wx.CallAfter(self.frame.Close)
|
||||
|
||||
|
||||
def makeIcon(self, img):
|
||||
if "wxMSW" in wx.PlatformInfo:
|
||||
img = img.Scale(16, 16)
|
||||
elif "wxGTK" in wx.PlatformInfo:
|
||||
img = img.Scale(22, 22)
|
||||
|
||||
icon = wx.IconFromBitmap(img.CopyFromBitmap())
|
||||
return icon
|
||||
|
||||
|
||||
class MainFrame(wx.Frame):
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Frame.__init__(self, None, style = wx.FRAME_NO_TASKBAR)
|
||||
|
||||
self.parent = parent
|
||||
self.tbicon = TaskBarIcon(self)
|
||||
|
||||
|
||||
class WorkerThread(Thread):
|
||||
|
||||
def __init__(self, desktop):
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self._desktop = desktop
|
||||
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
|
||||
# Get options via arg
|
||||
from couchpotato.runner import getOptions
|
||||
args = ['--quiet']
|
||||
self.options = getOptions(base_path, args)
|
||||
|
||||
# Load settings
|
||||
settings = Env.get('settings')
|
||||
settings.setFile(self.options.config_file)
|
||||
|
||||
# Create data dir if needed
|
||||
self.data_dir = os.path.expanduser(Env.setting('data_dir'))
|
||||
if self.data_dir == '':
|
||||
from couchpotato.core.helpers.variable import getDataDir
|
||||
self.data_dir = getDataDir()
|
||||
|
||||
if not os.path.isdir(self.data_dir):
|
||||
os.makedirs(self.data_dir)
|
||||
|
||||
# Create logging dir
|
||||
self.log_dir = os.path.join(self.data_dir, 'logs');
|
||||
if not os.path.isdir(self.log_dir):
|
||||
os.mkdir(self.log_dir)
|
||||
|
||||
try:
|
||||
from couchpotato.runner import runCouchPotato
|
||||
runCouchPotato(self.options, base_path, args, data_dir = self.data_dir, log_dir = self.log_dir, Env = Env, desktop = self._desktop)
|
||||
except:
|
||||
pass
|
||||
|
||||
self._desktop.frame.Close()
|
||||
|
||||
|
||||
class CouchPotatoApp(wx.App, SoftwareUpdate):
|
||||
|
||||
settings = {}
|
||||
events = {}
|
||||
restart = False
|
||||
closing = False
|
||||
|
||||
def OnInit(self):
|
||||
|
||||
# Updater
|
||||
base_url = 'http://couchpota.to/updates/%s/' % VERSION
|
||||
self.InitUpdates(base_url, base_url + 'changelog.html',
|
||||
icon = wx.Icon('icon.png'))
|
||||
|
||||
self.frame = MainFrame(self)
|
||||
self.frame.Bind(wx.EVT_CLOSE, self.onClose)
|
||||
|
||||
# CouchPotato thread
|
||||
self.worker = WorkerThread(self)
|
||||
|
||||
return True
|
||||
|
||||
def onAppLoad(self):
|
||||
self.frame.tbicon.enable()
|
||||
|
||||
def setSettings(self, settings = {}):
|
||||
self.settings = settings
|
||||
|
||||
def getSetting(self, name):
|
||||
return self.settings.get(name)
|
||||
|
||||
def addEvents(self, events = {}):
|
||||
for name in events.iterkeys():
|
||||
self.events[name] = events[name]
|
||||
|
||||
def onClose(self, event):
|
||||
|
||||
if not self.closing:
|
||||
self.closing = True
|
||||
self.frame.tbicon.onTaskBarClose(event)
|
||||
|
||||
onClose = self.events.get('onClose')
|
||||
onClose(event)
|
||||
|
||||
def afterShutdown(self, restart = False):
|
||||
self.frame.Destroy()
|
||||
self.restart = restart
|
||||
self.ExitMainLoop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
app = CouchPotatoApp(redirect = False)
|
||||
app.MainLoop()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
if app.restart:
|
||||
|
||||
def appexe_from_executable(exepath):
|
||||
appdir = appdir_from_executable(exepath)
|
||||
exename = os.path.basename(exepath)
|
||||
|
||||
if sys.platform == "darwin":
|
||||
if os.path.isdir(os.path.join(appdir, "Contents", "MacOS")):
|
||||
return os.path.join(appdir, "Contents", "MacOS", exename)
|
||||
|
||||
return os.path.join(appdir, exename)
|
||||
|
||||
exe = appexe_from_executable(sys.executable)
|
||||
os.chdir(os.path.dirname(exe))
|
||||
|
||||
os.execv(exe, [exe] + sys.argv[1:])
|
||||
@@ -5,10 +5,9 @@
|
||||
* Search through the existing (and closed) issues first. See if you can get your answer there.
|
||||
* Double check the result manually, because it could be an external issue.
|
||||
* Post logs! Without seeing what is going on, I can't reproduce the error.
|
||||
* What is the movie + quality you are searching for.
|
||||
* What are you settings for the specific problem.
|
||||
* What providers are you using. (While your logs include these, scanning through hundred of lines of log isn't my hobby).
|
||||
* Give me a short step by step of how to reproduce.
|
||||
* What are you settings for the specific problem
|
||||
* What providers are you using. (While your logs include these, scanning through hundred of lines of log isn't my hobby)
|
||||
* Give me a short step by step of how to reproduce
|
||||
* What hardware / OS are you using and what are the limits? NAS can be slow and maybe have a different python installed then when you use CP on OSX or Windows for example.
|
||||
* I will mark issues with the "can't reproduce" tag. Don't go asking me "why closed" if it clearly says the issue in the tag ;)
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ def page_not_found(error):
|
||||
r = '%s%s' % (request.url.rstrip('/'), index_url + '#' + url)
|
||||
return redirect(r)
|
||||
else:
|
||||
if not Env.get('dev'):
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.1)
|
||||
return 'Wrong API key used', 404
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from flask.blueprints import Blueprint
|
||||
from flask.helpers import url_for
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import RequestHandler, asynchronous
|
||||
from werkzeug.utils import redirect
|
||||
|
||||
@@ -10,11 +11,7 @@ api_nonblock = {}
|
||||
|
||||
|
||||
class NonBlockHandler(RequestHandler):
|
||||
|
||||
def __init__(self, application, request, **kwargs):
|
||||
cls = NonBlockHandler
|
||||
cls.stoppers = []
|
||||
super(NonBlockHandler, self).__init__(application, request, **kwargs)
|
||||
stoppers = []
|
||||
|
||||
@asynchronous
|
||||
def get(self, route):
|
||||
|
||||
@@ -23,22 +23,20 @@ config = [{
|
||||
'default': '',
|
||||
'type': 'password',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'advanced': True,
|
||||
'default': '0.0.0.0',
|
||||
'hidden': True,
|
||||
'label': 'IP',
|
||||
'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.',
|
||||
},
|
||||
{
|
||||
'name': 'port',
|
||||
'default': 5050,
|
||||
'type': 'int',
|
||||
'description': 'The port I should listen to.',
|
||||
},
|
||||
{
|
||||
'name': 'ssl_cert',
|
||||
'description': 'Path to SSL server.crt',
|
||||
'advanced': True,
|
||||
},
|
||||
{
|
||||
'name': 'ssl_key',
|
||||
'description': 'Path to SSL server.key',
|
||||
'advanced': True,
|
||||
},
|
||||
{
|
||||
'name': 'launch_browser',
|
||||
'default': True,
|
||||
|
||||
@@ -152,7 +152,7 @@ class Core(Plugin):
|
||||
|
||||
def createBaseUrl(self):
|
||||
host = Env.setting('host')
|
||||
if host == '0.0.0.0' or host == '':
|
||||
if host == '0.0.0.0':
|
||||
host = 'localhost'
|
||||
port = Env.setting('port')
|
||||
|
||||
@@ -176,10 +176,8 @@ class Core(Plugin):
|
||||
})
|
||||
|
||||
def signalHandler(self):
|
||||
if Env.get('daemonized'): return
|
||||
|
||||
def signal_handler(signal, frame):
|
||||
fireEvent('app.shutdown', single = True)
|
||||
fireEvent('app.do_shutdown')
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
@@ -1,60 +1,15 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from minify.cssmin import cssmin
|
||||
from minify.jsmin import jsmin
|
||||
import os
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class ClientScript(Plugin):
|
||||
|
||||
core_static = {
|
||||
'style': [
|
||||
'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/prefix_free.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'
|
||||
urls = {
|
||||
'style': {},
|
||||
'script': {},
|
||||
}
|
||||
|
||||
html = {
|
||||
@@ -69,66 +24,6 @@ class ClientScript(Plugin):
|
||||
addEvent('clientscript.get_styles', self.getStyles)
|
||||
addEvent('clientscript.get_scripts', self.getScripts)
|
||||
|
||||
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 = cssmin(f)
|
||||
data = data.replace('../images/', '../static/images/')
|
||||
|
||||
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, 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):
|
||||
return self.get('style', *args, **kwargs)
|
||||
|
||||
@@ -140,30 +35,22 @@ class ClientScript(Plugin):
|
||||
data = '' if as_html else []
|
||||
|
||||
try:
|
||||
try:
|
||||
if not Env.get('dev'):
|
||||
return self.minified[type][location]
|
||||
except:
|
||||
pass
|
||||
|
||||
return self.urls[type][location]
|
||||
except:
|
||||
log.error('Error getting minified %s, %s: %s', (type, location, traceback.format_exc()))
|
||||
except Exception, e:
|
||||
log.error(e)
|
||||
|
||||
return data
|
||||
|
||||
def registerStyle(self, api_path, file_path, position = 'head'):
|
||||
self.register(api_path, file_path, 'style', position)
|
||||
def registerStyle(self, path, position = 'head'):
|
||||
self.register(path, 'style', position)
|
||||
|
||||
def registerScript(self, api_path, file_path, position = 'head'):
|
||||
self.register(api_path, file_path, 'script', position)
|
||||
def registerScript(self, path, position = 'head'):
|
||||
self.register(path, 'script', position)
|
||||
|
||||
def register(self, api_path, file_path, type, location):
|
||||
def register(self, filepath, type, location):
|
||||
|
||||
if not self.urls[type].get(location):
|
||||
self.urls[type][location] = []
|
||||
self.urls[type][location].append(api_path)
|
||||
|
||||
if not self.paths[type].get(location):
|
||||
self.paths[type][location] = []
|
||||
self.paths[type][location].append(file_path)
|
||||
filePath = filepath
|
||||
self.urls[type][location].append(filePath)
|
||||
|
||||
@@ -2,6 +2,7 @@ from apscheduler.scheduler import Scheduler as Sched
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
import logging
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
config = {
|
||||
'name': 'download_providers',
|
||||
'groups': [
|
||||
{
|
||||
'label': 'Downloaders',
|
||||
'description': 'You can select different downloaders for each type (usenet / torrent)',
|
||||
'type': 'list',
|
||||
'name': 'download_providers',
|
||||
'tab': 'downloaders',
|
||||
'options': [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
from base64 import b32decode, b16encode
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.base import Provider
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
import random
|
||||
import re
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Downloader(Provider):
|
||||
class Downloader(Plugin):
|
||||
|
||||
type = []
|
||||
http_time_between_calls = 0
|
||||
|
||||
torrent_sources = [
|
||||
'http://torrage.com/torrent/%s.torrent',
|
||||
@@ -33,44 +32,18 @@ class Downloader(Provider):
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
addEvent('download', self._download)
|
||||
addEvent('download.enabled', self._isEnabled)
|
||||
addEvent('download.enabled_types', self.getEnabledDownloadType)
|
||||
addEvent('download.status', self._getAllDownloadStatus)
|
||||
addEvent('download.remove_failed', self._removeFailed)
|
||||
addEvent('download', self.download)
|
||||
addEvent('download.status', self.getAllDownloadStatus)
|
||||
addEvent('download.remove_failed', self.removeFailed)
|
||||
|
||||
def getEnabledDownloadType(self):
|
||||
for download_type in self.type:
|
||||
if self.isEnabled(manual = True, data = {'type': download_type}):
|
||||
return self.type
|
||||
|
||||
return []
|
||||
|
||||
def _download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||
if self.isDisabled(manual, data):
|
||||
return
|
||||
return self.download(data = data, movie = movie, filedata = filedata)
|
||||
|
||||
def _getAllDownloadStatus(self):
|
||||
if self.isDisabled(manual = True, data = {}):
|
||||
return
|
||||
|
||||
return self.getAllDownloadStatus()
|
||||
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||
pass
|
||||
|
||||
def getAllDownloadStatus(self):
|
||||
return
|
||||
|
||||
def _removeFailed(self, item):
|
||||
if self.isDisabled(manual = True, data = {}):
|
||||
return
|
||||
|
||||
if self.conf('delete_failed', default = True):
|
||||
return self.removeFailed(item)
|
||||
|
||||
return False
|
||||
|
||||
def removeFailed(self, item):
|
||||
return
|
||||
def removeFailed(self, name = {}, nzo_id = {}):
|
||||
return False
|
||||
|
||||
def isCorrectType(self, item_type):
|
||||
is_correct = item_type in self.type
|
||||
@@ -103,16 +76,9 @@ class Downloader(Provider):
|
||||
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
|
||||
return False
|
||||
|
||||
def isDisabled(self, manual, data):
|
||||
return not self.isEnabled(manual, data)
|
||||
def isDisabled(self, manual):
|
||||
return not self.isEnabled(manual)
|
||||
|
||||
def _isEnabled(self, manual, data = {}):
|
||||
if not self.isEnabled(manual, data):
|
||||
return
|
||||
return True
|
||||
|
||||
def isEnabled(self, manual, data = {}):
|
||||
def isEnabled(self, manual):
|
||||
d_manual = self.conf('manual', default = False)
|
||||
return super(Downloader, self).isEnabled() and \
|
||||
((d_manual and manual) or (d_manual is False)) and \
|
||||
(not data or self.isCorrectType(data.get('type')))
|
||||
return super(Downloader, self).isEnabled() and ((d_manual and manual) or (d_manual is False))
|
||||
|
||||
@@ -10,7 +10,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'blackhole',
|
||||
'label': 'Black hole',
|
||||
'description': 'Download the NZB/Torrent to a specific folder.',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import with_statement
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
import os
|
||||
import traceback
|
||||
|
||||
@@ -11,7 +10,11 @@ class Blackhole(Downloader):
|
||||
|
||||
type = ['nzb', 'torrent', 'torrent_magnet']
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||
if self.isDisabled(manual) or \
|
||||
(not self.isCorrectType(data.get('type')) or \
|
||||
(not self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')])):
|
||||
return
|
||||
|
||||
directory = self.conf('directory')
|
||||
if not directory or not os.path.isdir(directory):
|
||||
@@ -37,7 +40,6 @@ class Blackhole(Downloader):
|
||||
log.info('Downloading %s to %s.', (data.get('type'), fullPath))
|
||||
with open(fullPath, 'wb') as f:
|
||||
f.write(filedata)
|
||||
os.chmod(fullPath, Env.getPermission('file'))
|
||||
return True
|
||||
else:
|
||||
log.info('File %s already exists.', fullPath)
|
||||
@@ -50,23 +52,4 @@ class Blackhole(Downloader):
|
||||
except:
|
||||
log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc()))
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def getEnabledDownloadType(self):
|
||||
if self.conf('use_for') == 'both':
|
||||
return super(Blackhole, self).getEnabledDownloadType()
|
||||
elif self.conf('use_for') == 'torrent':
|
||||
return ['torrent', 'torrent_magnet']
|
||||
else:
|
||||
return ['nzb']
|
||||
|
||||
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 \
|
||||
((self.conf('use_for') in for_type))
|
||||
|
||||
@@ -8,10 +8,9 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'nzbget',
|
||||
'label': 'NZBGet',
|
||||
'description': 'Use <a href="http://nzbget.sourceforge.net/Main_Page" target="_blank">NZBGet</a> to download NZBs.',
|
||||
'description': 'Send NZBs to your NZBGet installation.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
@@ -26,7 +25,6 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'type': 'password',
|
||||
'description': 'Default NZBGet password is <i>tegbzn6789</i>',
|
||||
},
|
||||
{
|
||||
@@ -34,13 +32,6 @@ config = [{
|
||||
'default': 'Movies',
|
||||
'description': 'The category CP places the nzb in. Like <strong>movies</strong> or <strong>couchpotato</strong>',
|
||||
},
|
||||
{
|
||||
'name': 'priority',
|
||||
'default': '0',
|
||||
'type': 'dropdown',
|
||||
'values': [('Very Low', -100), ('Low', -50), ('Normal', 0), ('High', 50), ('Very High', 100)],
|
||||
'description': 'Only change this if you are using NZBget 9.0 or higher',
|
||||
},
|
||||
{
|
||||
'name': 'manual',
|
||||
'default': 0,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from base64 import standard_b64encode
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.encoding import ss
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
import re
|
||||
from inspect import isfunction
|
||||
import socket
|
||||
import traceback
|
||||
import xmlrpclib
|
||||
@@ -16,7 +14,10 @@ class NZBGet(Downloader):
|
||||
|
||||
url = 'http://nzbget:%(password)s@%(host)s/xmlrpc'
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||
|
||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||
return
|
||||
|
||||
if not filedata:
|
||||
log.error('Unable to get NZB file: %s', traceback.format_exc())
|
||||
@@ -25,7 +26,7 @@ class NZBGet(Downloader):
|
||||
log.info('Sending "%s" to NZBGet.', data.get('name'))
|
||||
|
||||
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
|
||||
nzb_name = ss('%s.nzb' % self.createNzbName(data, movie))
|
||||
nzb_name = '%s.nzb' % self.createNzbName(data, movie)
|
||||
|
||||
rpc = xmlrpclib.ServerProxy(url)
|
||||
try:
|
||||
@@ -43,12 +44,7 @@ class NZBGet(Downloader):
|
||||
log.error('Protocol Error: %s', e)
|
||||
return False
|
||||
|
||||
if re.search(r"^0", rpc.version()):
|
||||
xml_response = rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip()))
|
||||
else:
|
||||
xml_response = rpc.append(nzb_name, self.conf('category'), tryInt(self.conf('priority')), False, standard_b64encode(filedata.strip()))
|
||||
|
||||
if xml_response:
|
||||
if rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip())):
|
||||
log.info('NZB sent successfully to NZBGet')
|
||||
return True
|
||||
else:
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
from .main import NZBVortex
|
||||
|
||||
def start():
|
||||
return NZBVortex()
|
||||
|
||||
config = [{
|
||||
'name': 'nzbvortex',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'nzbvortex',
|
||||
'label': 'NZBVortex',
|
||||
'description': 'Use <a href="http://www.nzbvortex.com/landing/" target="_blank">NZBVortex</a> to download NZBs.',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': 0,
|
||||
'type': 'enabler',
|
||||
'radio_group': 'nzb',
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'https://localhost:4321',
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
'label': 'Api Key',
|
||||
},
|
||||
{
|
||||
'name': 'manual',
|
||||
'default': False,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'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,170 +0,0 @@
|
||||
from base64 import b64encode
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||
from couchpotato.core.helpers.variable import cleanHost
|
||||
from couchpotato.core.logger import CPLog
|
||||
from urllib2 import URLError
|
||||
from uuid import uuid4
|
||||
import hashlib
|
||||
import httplib
|
||||
import json
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
import traceback
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class NZBVortex(Downloader):
|
||||
|
||||
type = ['nzb']
|
||||
api_level = None
|
||||
session_id = None
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
|
||||
# Send the nzb
|
||||
try:
|
||||
nzb_filename = self.createFileName(data, filedata, movie)
|
||||
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True)
|
||||
|
||||
return True
|
||||
except:
|
||||
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
||||
return False
|
||||
|
||||
def getAllDownloadStatus(self):
|
||||
|
||||
raw_statuses = self.call('nzb')
|
||||
|
||||
statuses = []
|
||||
for item in raw_statuses.get('nzbs', []):
|
||||
|
||||
# Check status
|
||||
status = 'busy'
|
||||
if item['state'] == 20:
|
||||
status = 'completed'
|
||||
elif item['state'] in [21, 22, 24]:
|
||||
status = 'failed'
|
||||
|
||||
statuses.append({
|
||||
'id': item['id'],
|
||||
'name': item['uiTitle'],
|
||||
'status': status,
|
||||
'original_status': item['state'],
|
||||
'timeleft':-1,
|
||||
})
|
||||
|
||||
return statuses
|
||||
|
||||
def removeFailed(self, item):
|
||||
|
||||
log.info('%s failed downloading, deleting...', item['name'])
|
||||
|
||||
try:
|
||||
self.call('nzb/%s/cancel' % item['id'])
|
||||
except:
|
||||
log.error('Failed deleting: %s', traceback.format_exc(0))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def login(self):
|
||||
|
||||
nonce = self.call('auth/nonce', auth = False).get('authNonce')
|
||||
cnonce = uuid4().hex
|
||||
hashed = b64encode(hashlib.sha256('%s:%s:%s' % (nonce, cnonce, self.conf('api_key'))).digest())
|
||||
|
||||
params = {
|
||||
'nonce': nonce,
|
||||
'cnonce': cnonce,
|
||||
'hash': hashed
|
||||
}
|
||||
|
||||
login_data = self.call('auth/login', parameters = params, auth = False)
|
||||
|
||||
# Save for later
|
||||
if login_data.get('loginResult') == 'successful':
|
||||
self.session_id = login_data.get('sessionID')
|
||||
return True
|
||||
|
||||
log.error('Login failed, please check you api-key')
|
||||
return False
|
||||
|
||||
|
||||
def call(self, call, parameters = {}, repeat = False, auth = True, *args, **kwargs):
|
||||
|
||||
# Login first
|
||||
if not self.session_id and auth:
|
||||
self.login()
|
||||
|
||||
# Always add session id to request
|
||||
if self.session_id:
|
||||
parameters['sessionid'] = self.session_id
|
||||
|
||||
params = tryUrlencode(parameters)
|
||||
|
||||
url = cleanHost(self.conf('host')) + 'api/' + call
|
||||
url_opener = urllib2.build_opener(HTTPSHandler())
|
||||
|
||||
try:
|
||||
data = self.urlopen('%s?%s' % (url, params), opener = url_opener, *args, **kwargs)
|
||||
|
||||
if data:
|
||||
return json.loads(data)
|
||||
except URLError, e:
|
||||
if hasattr(e, 'code') and e.code == 403:
|
||||
# Try login and do again
|
||||
if not repeat:
|
||||
self.login()
|
||||
return self.call(call, parameters = parameters, repeat = True, *args, **kwargs)
|
||||
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
return {}
|
||||
|
||||
def getApiLevel(self):
|
||||
|
||||
if not self.api_level:
|
||||
|
||||
url = cleanHost(self.conf('host')) + 'api/app/apilevel'
|
||||
url_opener = urllib2.build_opener(HTTPSHandler())
|
||||
|
||||
try:
|
||||
data = self.urlopen(url, opener = url_opener, show_error = False)
|
||||
self.api_level = float(json.loads(data).get('apilevel'))
|
||||
except URLError, e:
|
||||
if hasattr(e, 'code') and e.code == 403:
|
||||
log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher')
|
||||
else:
|
||||
log.error('NZBVortex doesn\'t seem to be running or maybe the remote option isn\'t enabled yet: %s', traceback.format_exc(1))
|
||||
|
||||
return self.api_level
|
||||
|
||||
def isEnabled(self, manual, data):
|
||||
return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel()
|
||||
|
||||
|
||||
class HTTPSConnection(httplib.HTTPSConnection):
|
||||
def __init__(self, *args, **kwargs):
|
||||
httplib.HTTPSConnection.__init__(self, *args, **kwargs)
|
||||
|
||||
def connect(self):
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||
if sys.version_info < (2, 6, 7):
|
||||
if hasattr(self, '_tunnel_host'):
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
else:
|
||||
if self._tunnel_host:
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
|
||||
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version = ssl.PROTOCOL_TLSv1)
|
||||
|
||||
class HTTPSHandler(urllib2.HTTPSHandler):
|
||||
def https_open(self, req):
|
||||
return self.do_open(HTTPSConnection, req)
|
||||
@@ -9,10 +9,9 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'pneumatic',
|
||||
'label': 'Pneumatic',
|
||||
'description': 'Use <a href="http://forum.xbmc.org/showthread.php?tid=97657" target="_blank">Pneumatic</a> to download .strm files.',
|
||||
'description': 'Download the .strm file to a specific folder.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -11,7 +11,9 @@ class Pneumatic(Downloader):
|
||||
type = ['nzb']
|
||||
strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s'
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||
if self.isDisabled(manual) or (not self.isCorrectType(data.get('type'))):
|
||||
return
|
||||
|
||||
directory = self.conf('directory')
|
||||
if not directory or not os.path.isdir(directory):
|
||||
|
||||
@@ -8,10 +8,9 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'sabnzbd',
|
||||
'label': 'Sabnzbd',
|
||||
'description': 'Use <a href="http://sabnzbd.org/" target="_blank">SABnzbd</a> to download NZBs.',
|
||||
'description': 'Send NZBs to your Sabnzbd installation.',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
from urllib2 import URLError
|
||||
import json
|
||||
import traceback
|
||||
@@ -13,7 +12,10 @@ class Sabnzbd(Downloader):
|
||||
|
||||
type = ['nzb']
|
||||
|
||||
def download(self, data = {}, movie = {}, filedata = None):
|
||||
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||
|
||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||
return
|
||||
|
||||
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
||||
|
||||
@@ -39,9 +41,9 @@ class Sabnzbd(Downloader):
|
||||
|
||||
try:
|
||||
if params.get('mode') is 'addfile':
|
||||
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
|
||||
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (nzb_filename, filedata)}, multipart = True, show_error = False)
|
||||
else:
|
||||
sab = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
|
||||
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||
except URLError:
|
||||
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
|
||||
return False
|
||||
@@ -63,6 +65,8 @@ class Sabnzbd(Downloader):
|
||||
return False
|
||||
|
||||
def getAllDownloadStatus(self):
|
||||
if self.isDisabled(manual = False):
|
||||
return False
|
||||
|
||||
log.debug('Checking SABnzbd download status.')
|
||||
|
||||
@@ -118,6 +122,9 @@ class Sabnzbd(Downloader):
|
||||
|
||||
def removeFailed(self, item):
|
||||
|
||||
if not self.conf('delete_failed', default = True):
|
||||
return False
|
||||
|
||||
log.info('%s failed downloading, deleting...', item['name'])
|
||||
|
||||
try:
|
||||
@@ -140,7 +147,7 @@ class Sabnzbd(Downloader):
|
||||
'output': 'json'
|
||||
}))
|
||||
|
||||
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
|
||||
data = self.urlopen(url, timeout = 60, show_error = False)
|
||||
if use_json:
|
||||
d = json.loads(data)
|
||||
if d.get('error'):
|
||||
|
||||
@@ -8,10 +8,9 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'synology',
|
||||
'label': 'Synology',
|
||||
'description': 'Use <a href="http://www.synology.com/dsm/home_home_applications_download_station.php" target="_blank">Synology Download Station</a> to download.',
|
||||
'description': 'Send torrents to Synology\'s Download Station.',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -14,7 +14,10 @@ class Synology(Downloader):
|
||||
type = ['torrent_magnet']
|
||||
log = CPLog(__name__)
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data, movie, manual = False, filedata = None):
|
||||
|
||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||
return
|
||||
|
||||
log.error('Sending "%s" (%s) to Synology.', (data.get('name'), data.get('type')))
|
||||
|
||||
|
||||
@@ -8,10 +8,9 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'transmission',
|
||||
'label': 'Transmission',
|
||||
'description': 'Use <a href="http://www.transmissionbt.com/" target="_blank">Transmission</a> to download torrents.',
|
||||
'description': 'Send torrents to Transmission.',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -16,7 +16,10 @@ class Transmission(Downloader):
|
||||
type = ['torrent', 'torrent_magnet']
|
||||
log = CPLog(__name__)
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data, movie, manual = False, filedata = None):
|
||||
|
||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||
return
|
||||
|
||||
log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
|
||||
|
||||
@@ -38,12 +41,10 @@ class Transmission(Downloader):
|
||||
'download-dir': folder_path
|
||||
}
|
||||
|
||||
torrent_params = {}
|
||||
if self.conf('ratio'):
|
||||
torrent_params = {
|
||||
'seedRatioLimit': self.conf('ratio'),
|
||||
'seedRatioMode': self.conf('ratio')
|
||||
}
|
||||
torrent_params = {
|
||||
'seedRatioLimit': self.conf('ratio'),
|
||||
'seedRatioMode': (0 if self.conf('ratio') else 1)
|
||||
}
|
||||
|
||||
if not filedata and data.get('type') == 'torrent':
|
||||
log.error('Failed sending torrent, no data')
|
||||
@@ -59,8 +60,7 @@ class Transmission(Downloader):
|
||||
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
|
||||
|
||||
# Change settings of added torrents
|
||||
if torrent_params:
|
||||
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
||||
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
||||
|
||||
return True
|
||||
except Exception, err:
|
||||
|
||||
@@ -8,10 +8,9 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'downloaders',
|
||||
'list': 'download_providers',
|
||||
'name': 'utorrent',
|
||||
'label': 'uTorrent',
|
||||
'description': 'Use <a href="http://www.utorrent.com/" target="_blank">uTorrent</a> to download torrents.',
|
||||
'description': 'Send torrents to uTorrent.',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from base64 import b16encode, b32decode
|
||||
from bencode import bencode, bdecode
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.encoding import isInt, ss
|
||||
from couchpotato.core.helpers.encoding import isInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from hashlib import sha1
|
||||
from multipartpost import MultipartPostHandler
|
||||
import cookielib
|
||||
import httplib
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib
|
||||
@@ -22,7 +20,10 @@ class uTorrent(Downloader):
|
||||
type = ['torrent', 'torrent_magnet']
|
||||
utorrent_api = None
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data, movie, manual = False, filedata = None):
|
||||
|
||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||
return
|
||||
|
||||
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type')))
|
||||
|
||||
@@ -39,7 +40,6 @@ class uTorrent(Downloader):
|
||||
if not filedata and data.get('type') == 'torrent':
|
||||
log.error('Failed sending torrent, no data')
|
||||
return False
|
||||
|
||||
if data.get('type') == 'torrent_magnet':
|
||||
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)
|
||||
@@ -48,10 +48,6 @@ class uTorrent(Downloader):
|
||||
torrent_hash = sha1(bencode(info)).hexdigest().upper()
|
||||
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
|
||||
try:
|
||||
if not self.utorrent_api:
|
||||
@@ -71,59 +67,6 @@ class uTorrent(Downloader):
|
||||
log.error('Failed to send torrent to uTorrent: %s', err)
|
||||
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 = []
|
||||
|
||||
# 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'
|
||||
|
||||
statuses.append({
|
||||
'id': item[0],
|
||||
'name': item[2],
|
||||
'status': status,
|
||||
'original_status': item[1],
|
||||
'timeleft': item[10],
|
||||
})
|
||||
|
||||
return statuses
|
||||
|
||||
|
||||
|
||||
class uTorrentAPI(object):
|
||||
|
||||
@@ -154,7 +97,9 @@ class uTorrentAPI(object):
|
||||
try:
|
||||
open_request = self.opener.open(request)
|
||||
response = open_request.read()
|
||||
log.debug('response: %s', response)
|
||||
if response:
|
||||
log.debug('uTorrent action successfull')
|
||||
return response
|
||||
else:
|
||||
log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response)
|
||||
@@ -180,7 +125,7 @@ class uTorrentAPI(object):
|
||||
|
||||
def add_torrent_file(self, filename, filedata):
|
||||
action = "action=add-file"
|
||||
return self._request(action, {"torrent_file": (ss(filename), filedata)})
|
||||
return self._request(action, {"torrent_file": (filename, filedata)})
|
||||
|
||||
def set_torrent(self, hash, params):
|
||||
action = "action=setprops&hash=%s" % hash
|
||||
@@ -191,7 +136,3 @@ class uTorrentAPI(object):
|
||||
def pause_torrent(self, hash):
|
||||
action = "action=pause&hash=%s" % hash
|
||||
return self._request(action)
|
||||
|
||||
def get_status(self):
|
||||
action = "list=1"
|
||||
return self._request(action)
|
||||
|
||||
@@ -104,8 +104,6 @@ def fireEvent(name, *args, **kwargs):
|
||||
|
||||
# Merge
|
||||
if options['merge'] and len(results) > 0:
|
||||
results.reverse() # Priority 1 is higher then 100
|
||||
|
||||
# Dict
|
||||
if isinstance(results[0], dict):
|
||||
merged = {}
|
||||
@@ -117,8 +115,7 @@ def fireEvent(name, *args, **kwargs):
|
||||
elif isinstance(results[0], list):
|
||||
merged = []
|
||||
for result in results:
|
||||
if result not in merged:
|
||||
merged += result
|
||||
merged += result
|
||||
|
||||
results = merged
|
||||
|
||||
|
||||
@@ -168,4 +168,4 @@ def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
|
||||
return ''.join(random.choice(chars) for x in range(size))
|
||||
|
||||
def splitString(str, split_on = ','):
|
||||
return [x.strip() for x in str.split(split_on)] if str else []
|
||||
return [x.strip() for x in str.split(split_on)]
|
||||
|
||||
@@ -67,18 +67,6 @@ class Loader(object):
|
||||
|
||||
def addFromDir(self, plugin_type, priority, module, dir_name):
|
||||
|
||||
# Load dir module
|
||||
try:
|
||||
m = __import__(module)
|
||||
splitted = module.split('.')
|
||||
for sub in splitted[1:]:
|
||||
m = getattr(m, sub)
|
||||
|
||||
if hasattr(m, 'config'):
|
||||
fireEvent('settings.options', splitted[-1] + '_config', getattr(m, 'config'))
|
||||
except:
|
||||
raise
|
||||
|
||||
for cur_file in glob.glob(os.path.join(dir_name, '*')):
|
||||
name = os.path.basename(cur_file)
|
||||
if os.path.isdir(os.path.join(dir_name, name)):
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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
|
||||
@@ -1,13 +0,0 @@
|
||||
config = {
|
||||
'name': 'notification_providers',
|
||||
'groups': [
|
||||
{
|
||||
'label': 'Notifications',
|
||||
'description': 'Notify when movies are done or snatched',
|
||||
'type': 'list',
|
||||
'name': 'notification_providers',
|
||||
'tab': 'notifications',
|
||||
'options': [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'boxcar',
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -178,14 +178,11 @@ var NotificationBase = new Class({
|
||||
},
|
||||
|
||||
addTestButton: function(fieldset, plugin_name){
|
||||
var self = this,
|
||||
button_name = self.testButtonName(fieldset);
|
||||
|
||||
if(button_name.contains('Notifications')) return;
|
||||
var self = this;
|
||||
|
||||
new Element('.ctrlHolder.test_button').adopt(
|
||||
new Element('a.button', {
|
||||
'text': button_name,
|
||||
'text': self.testButtonName(fieldset),
|
||||
'events': {
|
||||
'click': function(){
|
||||
var button = fieldset.getElement('.test_button .button');
|
||||
@@ -194,7 +191,7 @@ var NotificationBase = new Class({
|
||||
Api.request('notify.'+plugin_name+'.test', {
|
||||
'onComplete': function(json){
|
||||
|
||||
button.set('text', button_name);
|
||||
button.set('text', self.testButtonName(fieldset));
|
||||
|
||||
if(json.success){
|
||||
var message = new Element('span.success', {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
from .main import Email
|
||||
|
||||
def start():
|
||||
return Email()
|
||||
|
||||
config = [{
|
||||
'name': 'email',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'email',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': 0,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'from',
|
||||
'label': 'Send e-mail from',
|
||||
},
|
||||
{
|
||||
'name': 'to',
|
||||
'label': 'Send e-mail to',
|
||||
},
|
||||
{
|
||||
'name': 'smtp_server',
|
||||
'label': 'SMTP server',
|
||||
},
|
||||
{
|
||||
'name': 'ssl',
|
||||
'label': 'Enable SSL',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
},
|
||||
{
|
||||
'name': 'smtp_user',
|
||||
'label': 'SMTP user',
|
||||
},
|
||||
{
|
||||
'name': 'smtp_pass',
|
||||
'label': 'SMTP password',
|
||||
'type': 'password',
|
||||
},
|
||||
{
|
||||
'name': 'on_snatch',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Also send message when movie is snatched.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -1,55 +0,0 @@
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import splitString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
from email.mime.text import MIMEText
|
||||
import smtplib
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Email(Notification):
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
if self.isDisabled(): return
|
||||
|
||||
# Extract all the settings from settings
|
||||
from_address = self.conf('from')
|
||||
to_address = self.conf('to')
|
||||
ssl = self.conf('ssl')
|
||||
smtp_server = self.conf('smtp_server')
|
||||
smtp_user = self.conf('smtp_user')
|
||||
smtp_pass = self.conf('smtp_pass')
|
||||
|
||||
# Make the basic message
|
||||
message = MIMEText(toUnicode(message))
|
||||
message['Subject'] = self.default_title
|
||||
message['From'] = from_address
|
||||
message['To'] = to_address
|
||||
|
||||
try:
|
||||
# Open the SMTP connection, via SSL if requested
|
||||
log.debug("SMTP over SSL %s", ("enabled" if ssl == 1 else "disabled"))
|
||||
mailserver = smtplib.SMTP_SSL(smtp_server) if ssl == 1 else smtplib.SMTP(smtp_server)
|
||||
|
||||
# Check too see if an login attempt should be attempted
|
||||
if len(smtp_user) > 0:
|
||||
log.debug("Logging on to SMTP server using username \'%s\'%s", (smtp_user, " and a password" if len(smtp_pass) > 0 else ""))
|
||||
mailserver.login(smtp_user, smtp_pass)
|
||||
|
||||
# Send the e-mail
|
||||
log.debug("Sending the email")
|
||||
mailserver.sendmail(from_address, splitString(to_address), message.as_string())
|
||||
|
||||
# Close the SMTP connection
|
||||
mailserver.quit()
|
||||
|
||||
log.info('Email notification sent')
|
||||
|
||||
return True
|
||||
except:
|
||||
log.error('E-mail failed: %s', traceback.format_exc())
|
||||
return False
|
||||
|
||||
return False
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'growl',
|
||||
'description': 'Version 1.4+',
|
||||
'options': [
|
||||
|
||||
@@ -37,11 +37,8 @@ class Growl(Notification):
|
||||
)
|
||||
self.growl.register()
|
||||
self.registered = True
|
||||
except Exception, e:
|
||||
if 'timed out' in str(e):
|
||||
self.registered = True
|
||||
else:
|
||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||
except:
|
||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
if self.isDisabled(): return
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'nmj',
|
||||
'label': 'NMJ',
|
||||
'options': [
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'notifo',
|
||||
'description': 'Keep in mind that Notifo service will end soon.',
|
||||
'options': [
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'notifymyandroid',
|
||||
'label': 'Notify My Android',
|
||||
'options': [
|
||||
|
||||
@@ -8,9 +8,8 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'notifymywp',
|
||||
'label': 'Windows Phone',
|
||||
'label': 'Notify My Windows Phone',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'plex',
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'prowl',
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -1,37 +0,0 @@
|
||||
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):
|
||||
if self.isDisabled(): return
|
||||
|
||||
data = {
|
||||
'AuthorizationToken': self.conf('auth_token'),
|
||||
'Title': self.default_title,
|
||||
'Body': toUnicode(message),
|
||||
'LinkTitle': toUnicode("CouchPotato"),
|
||||
'link': toUnicode("https://couchpota.to/"),
|
||||
'IsImportant': self.conf('important'),
|
||||
'IsSilent': self.conf('silent'),
|
||||
}
|
||||
|
||||
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
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'pushover',
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'synoindex',
|
||||
'description': 'Automaticly adds index to Synology Media Server.',
|
||||
'options': [
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
from .main import Toasty
|
||||
|
||||
def start():
|
||||
return Toasty()
|
||||
|
||||
config = [{
|
||||
'name': 'toasty',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'toasty',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': 0,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'api_key',
|
||||
'label': 'Device ID',
|
||||
},
|
||||
{
|
||||
'name': 'on_snatch',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Also send message when movie is snatched.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
@@ -1,30 +0,0 @@
|
||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class Toasty(Notification):
|
||||
|
||||
urls = {
|
||||
'api': 'http://api.supertoasty.com/notify/%s?%s'
|
||||
}
|
||||
|
||||
def notify(self, message = '', data = {}, listener = None):
|
||||
if self.isDisabled(): return
|
||||
|
||||
data = {
|
||||
'title': self.default_title,
|
||||
'text': toUnicode(message),
|
||||
'sender': toUnicode("CouchPotato"),
|
||||
'image': 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/homescreen.png',
|
||||
}
|
||||
|
||||
try:
|
||||
self.urlopen(self.urls['api'] % (self.conf('api_key'), tryUrlencode(data)), show_error = False)
|
||||
return True
|
||||
except:
|
||||
log.error('Toasty failed: %s', traceback.format_exc())
|
||||
|
||||
return False
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'twitter',
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -8,10 +8,8 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'xbmc',
|
||||
'label': 'XBMC',
|
||||
'description': 'v11 (Eden) and v12 (Frodo)',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -4,7 +4,6 @@ from couchpotato.core.notifications.base import Notification
|
||||
from flask.helpers import json
|
||||
import base64
|
||||
import traceback
|
||||
import urllib
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -12,148 +11,27 @@ log = CPLog(__name__)
|
||||
class XBMC(Notification):
|
||||
|
||||
listen_to = ['renamer.after']
|
||||
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):
|
||||
if self.isDisabled(): return
|
||||
|
||||
hosts = splitString(self.conf('host'))
|
||||
|
||||
successful = 0
|
||||
for host in hosts:
|
||||
|
||||
if self.use_json_notifications.get(host) is None:
|
||||
self.getXBMCJSONversion(host, message = message)
|
||||
|
||||
if self.use_json_notifications.get(host):
|
||||
response = self.request(host, [
|
||||
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.couch_logo_url}),
|
||||
('VideoLibrary.Scan', {}),
|
||||
])
|
||||
else:
|
||||
response = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
|
||||
response += self.request(host, [('VideoLibrary.Scan', {})])
|
||||
response = self.request(host, [
|
||||
('GUI.ShowNotification', {"title":"CouchPotato", "message":message}),
|
||||
('VideoLibrary.Scan', {}),
|
||||
])
|
||||
|
||||
try:
|
||||
for result in response:
|
||||
if (result.get('result') and result['result'] == 'OK'):
|
||||
if result['result'] == "OK":
|
||||
successful += 1
|
||||
elif (result.get('error')):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
|
||||
except:
|
||||
log.error('Failed parsing results: %s', traceback.format_exc())
|
||||
|
||||
return successful == len(hosts) * 2
|
||||
|
||||
def getXBMCJSONversion(self, host, message = ''):
|
||||
|
||||
success = False
|
||||
|
||||
# XBMC JSON-RPC version request
|
||||
response = self.request(host, [
|
||||
('JSONRPC.Version', {})
|
||||
])
|
||||
for result in response:
|
||||
if (result.get('result') and type(result['result']['version']).__name__ == 'int'):
|
||||
# only v2 and v4 return an int object
|
||||
# v6 (as of XBMC v12(Frodo)) is required to send notifications
|
||||
xbmc_rpc_version = str(result['result']['version'])
|
||||
|
||||
log.debug('XBMC JSON-RPC Version: %s ; Notifications by JSON-RPC only supported for v6 [as of XBMC v12(Frodo)]', xbmc_rpc_version)
|
||||
|
||||
# disable JSON use
|
||||
self.use_json_notifications[host] = False
|
||||
|
||||
# send the text message
|
||||
resp = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
|
||||
for result in resp:
|
||||
if (result.get('result') and result['result'] == 'OK'):
|
||||
log.debug('Message delivered successfully!')
|
||||
success = True
|
||||
break
|
||||
elif (result.get('error')):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
break
|
||||
|
||||
elif (result.get('result') and type(result['result']['version']).__name__ == 'dict'):
|
||||
# XBMC JSON-RPC v6 returns an array object containing
|
||||
# major, minor and patch number
|
||||
xbmc_rpc_version = str(result['result']['version']['major'])
|
||||
xbmc_rpc_version += '.' + str(result['result']['version']['minor'])
|
||||
xbmc_rpc_version += '.' + str(result['result']['version']['patch'])
|
||||
|
||||
log.debug('XBMC JSON-RPC Version: %s', xbmc_rpc_version)
|
||||
|
||||
# ok, XBMC version is supported
|
||||
self.use_json_notifications[host] = True
|
||||
|
||||
# send the text message
|
||||
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image':self.couch_logo_url})])
|
||||
for result in resp:
|
||||
if (result.get('result') and result['result'] == 'OK'):
|
||||
log.debug('Message delivered successfully!')
|
||||
success = True
|
||||
break
|
||||
elif (result.get('error')):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
break
|
||||
|
||||
# error getting version info (we do have contact with XBMC though)
|
||||
elif (result.get('error')):
|
||||
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||
|
||||
log.debug('Use JSON notifications: %s ', self.use_json_notifications)
|
||||
|
||||
return success
|
||||
|
||||
def notifyXBMCnoJSON(self, host, data):
|
||||
|
||||
server = 'http://%s/xbmcCmds/' % host
|
||||
|
||||
# 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))
|
||||
server += cmd
|
||||
|
||||
# I have no idea what to set to, just tried text/plain and seems to be working :)
|
||||
headers = {
|
||||
'Content-Type': 'text/plain',
|
||||
}
|
||||
|
||||
# authentication support
|
||||
if self.conf('password'):
|
||||
base64string = base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password'))).replace('\n', '')
|
||||
headers['Authorization'] = 'Basic %s' % base64string
|
||||
|
||||
try:
|
||||
log.debug('Sending non-JSON-type request to %s: %s', (host, data))
|
||||
|
||||
# response wil either be 'OK':
|
||||
# <html>
|
||||
# <li>OK
|
||||
# </html>
|
||||
#
|
||||
# or 'Error':
|
||||
# <html>
|
||||
# <li>Error:<message>
|
||||
# </html>
|
||||
#
|
||||
response = self.urlopen(server, headers = headers)
|
||||
|
||||
if 'OK' in response:
|
||||
log.debug('Returned from non-JSON-type request %s: %s', (host, response))
|
||||
# manually fake expected response array
|
||||
return [{'result': 'OK'}]
|
||||
else:
|
||||
log.error('Returned from non-JSON-type request %s: %s', (host, response))
|
||||
# manually fake expected response array
|
||||
return [{'result': 'Error'}]
|
||||
|
||||
except:
|
||||
log.error('Failed sending non-JSON-type request to XBMC: %s', traceback.format_exc())
|
||||
return [{'result': 'Error'}]
|
||||
|
||||
def request(self, host, requests):
|
||||
server = 'http://%s/jsonrpc' % host
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class Plugin(object):
|
||||
for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')):
|
||||
ext = getExt(f)
|
||||
if ext in ['js', 'css']:
|
||||
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
|
||||
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f))
|
||||
|
||||
def showStatic(self, filename):
|
||||
d = os.path.join(self.plugin_path, 'static')
|
||||
@@ -78,7 +78,7 @@ class Plugin(object):
|
||||
self.makeDir(os.path.dirname(path))
|
||||
|
||||
try:
|
||||
f = open(path, 'w+' if not binary else 'w+b')
|
||||
f = open(path, 'w' if not binary else 'wb')
|
||||
f.write(content)
|
||||
f.close()
|
||||
os.chmod(path, Env.getPermission('file'))
|
||||
@@ -98,7 +98,6 @@ class Plugin(object):
|
||||
|
||||
# http request
|
||||
def urlopen(self, url, timeout = 30, params = None, headers = None, opener = None, multipart = False, show_error = True):
|
||||
url = ss(url)
|
||||
|
||||
if not headers: headers = {}
|
||||
if not params: params = {}
|
||||
@@ -130,11 +129,8 @@ class Plugin(object):
|
||||
log.info('Opening multipart url: %s, params: %s', (url, [x for x in params.iterkeys()] if isinstance(params, dict) else 'with data'))
|
||||
request = urllib2.Request(url, params, headers)
|
||||
|
||||
if opener:
|
||||
opener.add_handler(MultipartPostHandler())
|
||||
else:
|
||||
cookies = cookielib.CookieJar()
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
||||
cookies = cookielib.CookieJar()
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
||||
|
||||
response = opener.open(request, timeout = timeout)
|
||||
else:
|
||||
@@ -240,6 +236,7 @@ class Plugin(object):
|
||||
del kwargs['cache_timeout']
|
||||
|
||||
data = self.urlopen(url, **kwargs)
|
||||
|
||||
if data:
|
||||
self.setCache(cache_key, data, timeout = cache_timeout)
|
||||
return data
|
||||
|
||||
@@ -15,7 +15,7 @@ if os.name == 'nt':
|
||||
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/");
|
||||
else:
|
||||
import win32file #@UnresolvedImport
|
||||
import win32file
|
||||
|
||||
class FileBrowser(Plugin):
|
||||
|
||||
@@ -98,7 +98,7 @@ class FileBrowser(Plugin):
|
||||
|
||||
def has_hidden_attribute(self, filepath):
|
||||
try:
|
||||
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath)) #@UndefinedVariable
|
||||
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath))
|
||||
assert attrs != -1
|
||||
result = bool(attrs & 2)
|
||||
except (AttributeError, AssertionError):
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
from .main import Dashboard
|
||||
|
||||
def start():
|
||||
return Dashboard()
|
||||
|
||||
config = []
|
||||
@@ -1,134 +0,0 @@
|
||||
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
|
||||
@@ -71,7 +71,7 @@ class FileManager(Plugin):
|
||||
db = get_session()
|
||||
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
|
||||
for filename in walk_files:
|
||||
if root == python_cache or 'minified' in filename: continue
|
||||
if root == python_cache: continue
|
||||
file_path = os.path.join(root, filename)
|
||||
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
|
||||
if not f:
|
||||
|
||||
@@ -4,7 +4,6 @@ var File = new Class({
|
||||
var self = this;
|
||||
|
||||
if(!file){
|
||||
self.empty = true;
|
||||
self.el = new Element('div');
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class LibraryPlugin(Plugin):
|
||||
|
||||
title = LibraryTitle(
|
||||
title = toUnicode(attrs.get('title')),
|
||||
simple_title = self.simplifyTitle(attrs.get('title')),
|
||||
simple_title = self.simplifyTitle(attrs.get('title'))
|
||||
)
|
||||
|
||||
l.titles.append(title)
|
||||
@@ -96,7 +96,6 @@ class LibraryPlugin(Plugin):
|
||||
|
||||
titles = info.get('titles', [])
|
||||
log.debug('Adding titles: %s', titles)
|
||||
counter = 0
|
||||
for title in titles:
|
||||
if not title:
|
||||
continue
|
||||
@@ -104,10 +103,9 @@ class LibraryPlugin(Plugin):
|
||||
t = LibraryTitle(
|
||||
title = title,
|
||||
simple_title = self.simplifyTitle(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)
|
||||
default = title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
|
||||
)
|
||||
library.titles.append(t)
|
||||
counter += 1
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@ from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import ss
|
||||
from couchpotato.core.helpers.request import jsonified, getParam
|
||||
from couchpotato.core.helpers.variable import splitString, getTitle
|
||||
from couchpotato.core.helpers.variable import getTitle, splitString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
@@ -24,7 +22,6 @@ class Manage(Plugin):
|
||||
fireEvent('scheduler.interval', identifier = 'manage.update_library', handle = self.updateLibrary, hours = 2)
|
||||
|
||||
addEvent('manage.update', self.updateLibrary)
|
||||
addEvent('manage.diskspace', self.getDiskSpace)
|
||||
|
||||
# Add files after renaming
|
||||
def after_rename(message = None, group = {}):
|
||||
@@ -138,6 +135,7 @@ class Manage(Plugin):
|
||||
already_used = used_files.get(release_file['path'])
|
||||
|
||||
if already_used:
|
||||
print already_used, release['id']
|
||||
if already_used < release['id']:
|
||||
fireEvent('release.delete', release['id'], single = True) # delete this one
|
||||
else:
|
||||
@@ -195,19 +193,15 @@ class Manage(Plugin):
|
||||
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
|
||||
total = self.in_progress[folder]['total']
|
||||
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']))
|
||||
|
||||
return afterUpdate
|
||||
|
||||
def directories(self):
|
||||
try:
|
||||
if self.conf('library', default = '').strip():
|
||||
return splitString(self.conf('library', default = ''), '::')
|
||||
return splitString(self.conf('library', default = ''), '::')
|
||||
except:
|
||||
pass
|
||||
|
||||
return []
|
||||
return []
|
||||
|
||||
def scanFilesToLibrary(self, folder = None, files = None):
|
||||
|
||||
@@ -218,31 +212,3 @@ class Manage(Plugin):
|
||||
for group in groups.itervalues():
|
||||
if group['library'] and group['library'].get('identifier'):
|
||||
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,13 +6,11 @@ from couchpotato.core.helpers.request import getParams, jsonified, getParam
|
||||
from couchpotato.core.helpers.variable import getImdb, splitString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, Movie, \
|
||||
Release
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, Movie
|
||||
from couchpotato.environment import Env
|
||||
from sqlalchemy.orm import joinedload_all
|
||||
from sqlalchemy.sql.expression import or_, asc, not_, desc
|
||||
from sqlalchemy.sql.expression import or_, asc, not_
|
||||
from string import ascii_lowercase
|
||||
import time
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -43,7 +41,6 @@ class MoviePlugin(Plugin):
|
||||
'desc': 'List movies in wanted list',
|
||||
'params': {
|
||||
'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"'},
|
||||
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'},
|
||||
'search': {'desc': 'Search movie title'},
|
||||
@@ -97,34 +94,6 @@ class MoviePlugin(Plugin):
|
||||
addEvent('movie.list', self.list)
|
||||
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 = fireEvent('status.get', 'done', single = True)
|
||||
available_status = fireEvent('status.get', 'available', single = True)
|
||||
snatched_status = fireEvent('status.get', '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):
|
||||
|
||||
movie_id = getParam('id')
|
||||
@@ -152,29 +121,20 @@ class MoviePlugin(Plugin):
|
||||
|
||||
return results
|
||||
|
||||
def list(self, status = None, release_status = None, limit_offset = None, starts_with = None, search = None, order = None):
|
||||
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
|
||||
|
||||
db = get_session()
|
||||
|
||||
# Make a list from string
|
||||
if status and not isinstance(status, (list, tuple)):
|
||||
if not isinstance(status, (list, tuple)):
|
||||
status = [status]
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
|
||||
q = db.query(Movie) \
|
||||
.outerjoin(Movie.releases, Movie.library, Library.titles) \
|
||||
.join(Movie.library, Library.titles) \
|
||||
.filter(LibraryTitle.default == True) \
|
||||
.filter(or_(*[Movie.status.has(identifier = s) for s in status])) \
|
||||
.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()
|
||||
|
||||
filter_or = []
|
||||
@@ -194,10 +154,7 @@ class MoviePlugin(Plugin):
|
||||
if filter_or:
|
||||
q = q.filter(or_(*filter_or))
|
||||
|
||||
if order == 'release_order':
|
||||
q = q.order_by(desc(Release.last_edit))
|
||||
else:
|
||||
q = q.order_by(asc(LibraryTitle.simple_title))
|
||||
q = q.order_by(asc(LibraryTitle.simple_title))
|
||||
|
||||
q = q.subquery()
|
||||
q2 = db.query(Movie).join((q, q.c.id == Movie.id)) \
|
||||
@@ -209,7 +166,7 @@ class MoviePlugin(Plugin):
|
||||
.options(joinedload_all('files'))
|
||||
|
||||
if limit_offset:
|
||||
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||
splt = splitString(limit_offset)
|
||||
limit = splt[0]
|
||||
offset = 0 if len(splt) is 1 else splt[1]
|
||||
q2 = q2.limit(limit).offset(offset)
|
||||
@@ -228,7 +185,7 @@ class MoviePlugin(Plugin):
|
||||
#db.close()
|
||||
return (total_count, movies)
|
||||
|
||||
def availableChars(self, status = None, release_status = None):
|
||||
def availableChars(self, status = ['active']):
|
||||
|
||||
chars = ''
|
||||
|
||||
@@ -237,20 +194,11 @@ class MoviePlugin(Plugin):
|
||||
# Make a list from string
|
||||
if not isinstance(status, (list, tuple)):
|
||||
status = [status]
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
|
||||
q = db.query(Movie) \
|
||||
.outerjoin(Movie.releases, Movie.library, Library.titles, Movie.status) \
|
||||
.options(joinedload_all('library.titles'))
|
||||
|
||||
# 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]))
|
||||
.join(Movie.library, Library.titles, Movie.status) \
|
||||
.options(joinedload_all('library.titles')) \
|
||||
.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
|
||||
|
||||
results = q.all()
|
||||
|
||||
@@ -258,29 +206,20 @@ class MoviePlugin(Plugin):
|
||||
char = movie.library.titles[0].simple_title[0]
|
||||
char = char if char in ascii_lowercase else '#'
|
||||
if char not in chars:
|
||||
chars += str(char)
|
||||
chars += char
|
||||
|
||||
#db.close()
|
||||
return ''.join(sorted(chars, key = str.lower))
|
||||
return chars
|
||||
|
||||
def listView(self):
|
||||
|
||||
params = getParams()
|
||||
status = splitString(params.get('status', None))
|
||||
release_status = splitString(params.get('release_status', None))
|
||||
status = params.get('status', ['active'])
|
||||
limit_offset = params.get('limit_offset', None)
|
||||
starts_with = params.get('starts_with', None)
|
||||
search = params.get('search', None)
|
||||
order = params.get('order', None)
|
||||
|
||||
total_movies, movies = self.list(
|
||||
status = status,
|
||||
release_status = release_status,
|
||||
limit_offset = limit_offset,
|
||||
starts_with = starts_with,
|
||||
search = search,
|
||||
order = order
|
||||
)
|
||||
total_movies, movies = self.list(status = status, limit_offset = limit_offset, starts_with = starts_with, search = search)
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
@@ -292,9 +231,8 @@ class MoviePlugin(Plugin):
|
||||
def charView(self):
|
||||
|
||||
params = getParams()
|
||||
status = splitString(params.get('status', None))
|
||||
release_status = splitString(params.get('release_status', None))
|
||||
chars = self.availableChars(status, release_status)
|
||||
status = params.get('status', ['active'])
|
||||
chars = self.availableChars(status)
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
@@ -345,7 +283,7 @@ class MoviePlugin(Plugin):
|
||||
'movies': movies,
|
||||
})
|
||||
|
||||
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
|
||||
def add(self, params = {}, force_readd = True, search_after = True, update_library = False):
|
||||
|
||||
if not params.get('identifier'):
|
||||
msg = 'Can\'t add movie without imdb identifier.'
|
||||
@@ -354,8 +292,9 @@ class MoviePlugin(Plugin):
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), single = True)
|
||||
if not is_movie:
|
||||
url = 'http://thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=%s' % params.get('identifier')
|
||||
tvdb = self.getCache('thetvdb.%s' % params.get('identifier'), url = url, show_error = False)
|
||||
if tvdb and 'series' in tvdb.lower():
|
||||
msg = 'Can\'t add movie, seems to be a TV show.'
|
||||
log.error(msg)
|
||||
fireEvent('notify.frontend', type = 'movie.is_tvshow', message = msg)
|
||||
@@ -368,9 +307,7 @@ class MoviePlugin(Plugin):
|
||||
|
||||
# Status
|
||||
status_active = fireEvent('status.add', 'active', single = True)
|
||||
snatched_status = fireEvent('status.add', 'snatched', single = True)
|
||||
ignored_status = fireEvent('status.add', 'ignored', single = True)
|
||||
downloaded_status = fireEvent('status.add', 'downloaded', single = True)
|
||||
status_snatched = fireEvent('status.add', 'snatched', single = True)
|
||||
|
||||
default_profile = fireEvent('profile.default', single = True)
|
||||
|
||||
@@ -382,7 +319,7 @@ class MoviePlugin(Plugin):
|
||||
m = Movie(
|
||||
library_id = library.get('id'),
|
||||
profile_id = params.get('profile_id', default_profile.get('id')),
|
||||
status_id = status_id if status_id else status_active.get('id'),
|
||||
status_id = status_active.get('id'),
|
||||
)
|
||||
db.add(m)
|
||||
db.commit()
|
||||
@@ -394,14 +331,10 @@ class MoviePlugin(Plugin):
|
||||
fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
|
||||
search_after = False
|
||||
elif force_readd:
|
||||
|
||||
# Clean snatched history
|
||||
for release in m.releases:
|
||||
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id')]:
|
||||
if params.get('ignore_previous', False):
|
||||
release.status_id = ignored_status.get('id')
|
||||
else:
|
||||
fireEvent('release.delete', release.id, single = True)
|
||||
if release.status_id == status_snatched.get('id'):
|
||||
release.delete()
|
||||
|
||||
m.profile_id = params.get('profile_id', default_profile.get('id'))
|
||||
else:
|
||||
@@ -409,8 +342,7 @@ class MoviePlugin(Plugin):
|
||||
added = False
|
||||
|
||||
if force_readd:
|
||||
m.status_id = status_id if status_id else status_active.get('id')
|
||||
m.last_edit = int(time.time())
|
||||
m.status_id = status_active.get('id')
|
||||
do_search = True
|
||||
|
||||
db.commit()
|
||||
@@ -516,7 +448,7 @@ class MoviePlugin(Plugin):
|
||||
total_deleted = 0
|
||||
new_movie_status = None
|
||||
for release in movie.releases:
|
||||
if delete_from in ['wanted', 'snatched']:
|
||||
if delete_from == 'wanted':
|
||||
if release.status_id != done_status.get('id'):
|
||||
db.delete(release)
|
||||
total_deleted += 1
|
||||
|
||||
@@ -5,7 +5,6 @@ var MovieList = new Class({
|
||||
options: {
|
||||
navigation: true,
|
||||
limit: 50,
|
||||
load_more: true,
|
||||
menu: [],
|
||||
add_new: false
|
||||
},
|
||||
@@ -13,37 +12,25 @@ var MovieList = new Class({
|
||||
movies: [],
|
||||
movies_added: {},
|
||||
letters: {},
|
||||
filter: null,
|
||||
filter: {
|
||||
'startswith': null,
|
||||
'search': null
|
||||
},
|
||||
|
||||
initialize: function(options){
|
||||
var self = this;
|
||||
self.setOptions(options);
|
||||
|
||||
self.offset = 0;
|
||||
self.filter = self.options.filter || {
|
||||
'startswith': null,
|
||||
'search': null
|
||||
}
|
||||
|
||||
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.load_more = self.options.load_more ? new Element('a.load_more', {
|
||||
self.load_more = new Element('a.load_more', {
|
||||
'events': {
|
||||
'click': self.loadMore.bind(self)
|
||||
}
|
||||
}) : null
|
||||
})
|
||||
);
|
||||
|
||||
self.changeView(self.getSavedView() || self.options.view || 'details');
|
||||
|
||||
self.getMovies();
|
||||
|
||||
App.addEvent('movie.added', self.movieAdded.bind(self))
|
||||
@@ -83,14 +70,22 @@ var MovieList = new Class({
|
||||
if(self.options.navigation)
|
||||
self.createNavigation();
|
||||
|
||||
if(self.options.load_more)
|
||||
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.movie_list.addEvents({
|
||||
'mouseenter:relay(.movie)': function(e, el){
|
||||
el.addClass('hover');
|
||||
},
|
||||
'mouseleave:relay(.movie)': function(e, el){
|
||||
el.removeClass('hover');
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
},
|
||||
@@ -101,7 +96,7 @@ var MovieList = new Class({
|
||||
if(!self.created) self.create();
|
||||
|
||||
// do scrollspy
|
||||
if(movies.length < self.options.limit && self.scrollspy){
|
||||
if(movies.length < self.options.limit){
|
||||
self.load_more.hide();
|
||||
self.scrollspy.stop();
|
||||
}
|
||||
@@ -126,14 +121,18 @@ var MovieList = new Class({
|
||||
|
||||
createMovie: function(movie, inject_at){
|
||||
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, {
|
||||
'actions': self.options.actions,
|
||||
'actions': actions,
|
||||
'view': self.current_view,
|
||||
'onSelect': self.calculateSelected.bind(self)
|
||||
}, movie);
|
||||
|
||||
$(m).inject(self.movie_list, inject_at || 'bottom');
|
||||
|
||||
m.fireEvent('injected');
|
||||
|
||||
self.movies.include(m)
|
||||
@@ -217,7 +216,7 @@ var MovieList = new Class({
|
||||
});
|
||||
|
||||
// Actions
|
||||
['mass_edit', 'details', 'list'].each(function(view){
|
||||
['mass_edit', 'thumbs', 'list'].each(function(view){
|
||||
self.navigation_actions.adopt(
|
||||
new Element('li.'+view+(self.current_view == view ? '.active' : '')+'[data-view='+view+']', {
|
||||
'events': {
|
||||
@@ -399,16 +398,11 @@ var MovieList = new Class({
|
||||
var self = this;
|
||||
|
||||
self.movies = []
|
||||
if(self.mass_edit_select)
|
||||
self.calculateSelected()
|
||||
if(self.navigation_alpha)
|
||||
self.navigation_alpha.getElements('.active').removeClass('active')
|
||||
|
||||
self.calculateSelected()
|
||||
self.navigation_alpha.getElements('.active').removeClass('active')
|
||||
self.offset = 0;
|
||||
if(self.scrollspy){
|
||||
self.load_more.show();
|
||||
self.scrollspy.start();
|
||||
}
|
||||
self.load_more.show();
|
||||
self.scrollspy.start();
|
||||
},
|
||||
|
||||
activateLetter: function(letter){
|
||||
@@ -424,6 +418,10 @@ var MovieList = new Class({
|
||||
changeView: function(new_view){
|
||||
var self = this;
|
||||
|
||||
self.movies.each(function(movie){
|
||||
movie.changeView(new_view)
|
||||
});
|
||||
|
||||
self.el
|
||||
.removeClass(self.current_view+'_list')
|
||||
.addClass(new_view+'_list')
|
||||
@@ -434,7 +432,7 @@ var MovieList = new Class({
|
||||
|
||||
getSavedView: function(){
|
||||
var self = this;
|
||||
return Cookie.read(self.options.identifier+'_view') || 'details';
|
||||
return Cookie.read(self.options.identifier+'_view') || 'thumbs';
|
||||
},
|
||||
|
||||
search: function(){
|
||||
@@ -470,12 +468,9 @@ var MovieList = new Class({
|
||||
getMovies: function(){
|
||||
var self = this;
|
||||
|
||||
if(self.scrollspy){
|
||||
self.scrollspy.stop();
|
||||
self.load_more.set('text', 'loading...');
|
||||
}
|
||||
|
||||
Api.request(self.options.api_call || 'movie.list', {
|
||||
if(self.scrollspy) self.scrollspy.stop();
|
||||
self.load_more.set('text', 'loading...');
|
||||
Api.request('movie.list', {
|
||||
'data': Object.merge({
|
||||
'status': self.options.status,
|
||||
'limit_offset': self.options.limit + ',' + self.offset
|
||||
@@ -483,10 +478,8 @@ var MovieList = new Class({
|
||||
'onComplete': function(json){
|
||||
self.store(json.movies);
|
||||
self.addMovies(json.movies, json.total);
|
||||
if(self.scrollspy) {
|
||||
self.load_more.set('text', 'load more movies');
|
||||
self.scrollspy.start();
|
||||
}
|
||||
self.load_more.set('text', 'load more movies');
|
||||
if(self.scrollspy) self.scrollspy.start();
|
||||
|
||||
self.checkIfEmpty()
|
||||
}
|
||||
@@ -509,13 +502,7 @@ var MovieList = new Class({
|
||||
checkIfEmpty: function(){
|
||||
var self = this;
|
||||
|
||||
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[is_empty ? 'hide' : 'show']()
|
||||
var is_empty = self.movies.length == 0 && self.total_movies == 0;
|
||||
|
||||
if(is_empty && self.options.on_empty_element){
|
||||
self.el.grab(self.options.on_empty_element);
|
||||
|
||||
@@ -1,699 +0,0 @@
|
||||
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 {
|
||||
|
||||
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,
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
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,33 +1,7 @@
|
||||
.movies {
|
||||
padding: 60px 0 20px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.movies h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.movies > .description {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
right: 0;
|
||||
font-style: italic;
|
||||
text-shadow: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.movies:hover > .description {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.movies.thumbs_list {
|
||||
padding: 20px 0 20px;
|
||||
}
|
||||
|
||||
.home .movies {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.movies.mass_edit_list {
|
||||
padding-top: 90px;
|
||||
}
|
||||
@@ -38,58 +12,33 @@
|
||||
margin: 10px 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
|
||||
.movies.list_list .movie:not(.details_view),
|
||||
.movies.mass_edit_list .movie {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.movies.thumbs_list .movie {
|
||||
width: 153px;
|
||||
height: 230px;
|
||||
display: inline-block;
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
.movies.thumbs_list .movie:nth-child(6n+6) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.movies .movie.list_view, .movies .movie.mass_edit_view {
|
||||
margin: 1px 0;
|
||||
border-radius: 0;
|
||||
background: no-repeat;
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.movies.list_list .movie:hover:not(.details_view),
|
||||
.movies.mass_edit_list .movie {
|
||||
.movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover {
|
||||
background: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.movies .movie_container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.movies .data {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
height: 180px;
|
||||
width: 840px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
position: relative;
|
||||
float: right;
|
||||
border-radius: 0;
|
||||
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
.movies.list_list .movie:not(.details_view) .data,
|
||||
.movies.mass_edit_list .movie .data {
|
||||
.movies .list_view .data, .movies .mass_edit_view .data {
|
||||
height: 30px;
|
||||
padding: 3px 0 3px 10px;
|
||||
width: 938px;
|
||||
@@ -97,148 +46,79 @@
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.movies.thumbs_list .data {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
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 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.movies.mass_edit_list .movie .check {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
float: left;
|
||||
display: block;
|
||||
margin: 7px 0 0 5px;
|
||||
}
|
||||
|
||||
.movies .poster {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
float: left;
|
||||
width: 120px;
|
||||
line-height: 0;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
height: 180px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
||||
transition: all 0.2s linear;
|
||||
|
||||
}
|
||||
.movies.list_list .movie:not(.details_view) .poster,
|
||||
.movies.mass_edit_list .poster {
|
||||
.movies .list_view .poster, .movies .mass_edit_view .poster {
|
||||
width: 20px;
|
||||
height: 30px;
|
||||
border-radius: 1px 0 0 1px;
|
||||
}
|
||||
.movies.mass_edit_list .poster {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.movies.thumbs_list .poster {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.movies .poster img,
|
||||
.options .poster img {
|
||||
.movies .poster img, .options .poster img {
|
||||
width: 101%;
|
||||
height: 101%;
|
||||
}
|
||||
|
||||
.movies .info {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.movies .info .title {
|
||||
display: inline;
|
||||
position: absolute;
|
||||
font-size: 28px;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
float: left;
|
||||
width: 90%;
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
.movies.list_list .movie:not(.details_view) .info .title,
|
||||
.movies.mass_edit_list .info .title {
|
||||
.movies .list_view .info .title, .movies .mass_edit_view .info .title {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
text-overflow: ellipsis;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
.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;
|
||||
text-shadow: 0 0 10px #000;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.movies .info .year {
|
||||
position: absolute;
|
||||
font-size: 30px;
|
||||
margin-bottom: 10px;
|
||||
float: right;
|
||||
color: #bbb;
|
||||
width: 10%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
text-align: right;
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
.movies.list_list .movie:not(.details_view) .info .year,
|
||||
.movies.mass_edit_list .info .year {
|
||||
.movies .list_view .info .year, .movies .mass_edit_view .info .year {
|
||||
font-size: 16px;
|
||||
width: 6%;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.movies.thumbs_list .info .year {
|
||||
font-size: 23px;
|
||||
margin: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: auto;
|
||||
right: auto;
|
||||
color: #FFF;
|
||||
text-shadow: none;
|
||||
text-shadow: 0 0 6px #000;
|
||||
}
|
||||
|
||||
.movies .info .rating {
|
||||
font-size: 30px;
|
||||
margin-bottom: 10px;
|
||||
color: #444;
|
||||
float: left;
|
||||
width: 5%;
|
||||
padding: 0 0 0 3%;
|
||||
}
|
||||
|
||||
.movies .info .description {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
clear: both;
|
||||
height: 80px;
|
||||
overflow: hidden;
|
||||
@@ -246,82 +126,63 @@
|
||||
.movies .data:hover .description {
|
||||
overflow: auto;
|
||||
}
|
||||
.movies.list_list .movie:not(.details_view) .info .description,
|
||||
.movies.mass_edit_list .info .description,
|
||||
.movies.thumbs_list .info .description {
|
||||
.movies .list_view .info .description, .movies .mass_edit_view .info .description {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.movies .data .quality {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
min-height: 20px;
|
||||
vertical-align: mid;
|
||||
}
|
||||
|
||||
.movies .status_suggest .data .quality,
|
||||
.movies.thumbs_list .data .quality {
|
||||
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;
|
||||
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 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;
|
||||
text-shadow: none;
|
||||
font-weight: normal;
|
||||
margin: 0 2px;
|
||||
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: 50px;
|
||||
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 .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;
|
||||
clear: both;
|
||||
float: right;
|
||||
margin-top: -25px;
|
||||
}
|
||||
.movies.thumbs_list .data .actions {
|
||||
bottom: 8px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.movies .data:hover .action { opacity: 0.6; }
|
||||
.movies .data:hover .action:hover { opacity: 1; }
|
||||
.movies.mass_edit_list .data .actions {
|
||||
@@ -338,14 +199,10 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.movies.list_list .movie:not(.details_view) .data:hover .actions,
|
||||
.movies.mass_edit_list .data:hover .actions {
|
||||
margin: 0;
|
||||
.movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions {
|
||||
margin: -34px 2px 0 0;
|
||||
background: #4e5969;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
right: 5px;
|
||||
z-index: 3;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.movies .delete_container {
|
||||
@@ -427,7 +284,6 @@
|
||||
.movies .options .table .provider {
|
||||
width: 120px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.movies .options .table .name {
|
||||
width: 350px;
|
||||
@@ -479,11 +335,11 @@
|
||||
padding: 3px 10px;
|
||||
background: #4e5969;
|
||||
border-radius: 0 0 2px 2px;
|
||||
transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s;
|
||||
transition: all .6s cubic-bezier(0.9,0,0.1,1) .2s;
|
||||
}
|
||||
.movies .movie .hide_trailer.hide {
|
||||
top: -30px;
|
||||
}
|
||||
.movies .movie .hide_trailer.hide {
|
||||
top: -30px;
|
||||
}
|
||||
|
||||
.movies .movie .try_container {
|
||||
padding: 5px 10px;
|
||||
@@ -524,7 +380,7 @@
|
||||
.movies .alph_nav {
|
||||
transition: box-shadow .4s linear;
|
||||
position: fixed;
|
||||
z-index: 4;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
padding: 100px 60px 7px;
|
||||
width: 1080px;
|
||||
@@ -553,8 +409,7 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.movies .alph_nav .numbers li,
|
||||
.movies .alph_nav .actions li {
|
||||
.movies .alph_nav .numbers li, .movies .alph_nav .actions li {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 20px;
|
||||
@@ -617,7 +472,7 @@
|
||||
background-position: 3px -95px;
|
||||
}
|
||||
|
||||
.movies .alph_nav .actions li.details span {
|
||||
.movies .alph_nav .actions li.thumbs span {
|
||||
background-position: 3px -74px;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ var Movie = new Class({
|
||||
var self = this;
|
||||
|
||||
self.data = data;
|
||||
self.view = options.view || 'details';
|
||||
self.view = options.view || 'thumbs';
|
||||
self.list = list;
|
||||
|
||||
self.el = new Element('div.movie.inlay');
|
||||
@@ -72,6 +72,7 @@ var Movie = new Class({
|
||||
else if(!self.spinner) {
|
||||
self.createMask();
|
||||
self.spinner = createSpinner(self.mask);
|
||||
self.positionMask();
|
||||
self.mask.fade('in');
|
||||
}
|
||||
},
|
||||
@@ -80,9 +81,10 @@ var Movie = new Class({
|
||||
var self = this;
|
||||
self.mask = new Element('div.mask', {
|
||||
'styles': {
|
||||
'z-index': 4
|
||||
'z-index': '1'
|
||||
}
|
||||
}).inject(self.el, 'top').fade('hide');
|
||||
self.positionMask();
|
||||
},
|
||||
|
||||
positionMask: function(){
|
||||
@@ -101,7 +103,7 @@ var Movie = new Class({
|
||||
var self = this;
|
||||
|
||||
self.data = notification.data;
|
||||
self.el.empty();
|
||||
self.container.destroy();
|
||||
|
||||
self.profile = Quality.getProfile(self.data.profile_id) || {};
|
||||
self.create();
|
||||
@@ -112,50 +114,52 @@ var Movie = new Class({
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
var s = Status.get(self.get('status_id'));
|
||||
self.el.addClass('status_'+s.identifier);
|
||||
|
||||
self.el.adopt(
|
||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': function(){
|
||||
self.fireEvent('select')
|
||||
}
|
||||
}
|
||||
}),
|
||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||
self.data_container = new Element('div.data.inlay.light').adopt(
|
||||
self.info_container = new Element('div.info').adopt(
|
||||
self.title = new Element('div.title', {
|
||||
'text': self.getTitle() || 'n/a'
|
||||
}),
|
||||
self.year = new Element('div.year', {
|
||||
'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.container = new Element('div.movie_container').adopt(
|
||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': function(){
|
||||
self.fireEvent('select')
|
||||
}
|
||||
})
|
||||
),
|
||||
self.actions = new Element('div.actions')
|
||||
}
|
||||
}),
|
||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||
self.data_container = new Element('div.data.inlay.light', {
|
||||
'tween': {
|
||||
duration: 400,
|
||||
transition: 'quint:in:out',
|
||||
onComplete: self.fireEvent.bind(self, 'slideEnd')
|
||||
}
|
||||
}).adopt(
|
||||
self.info_container = new Element('div.info').adopt(
|
||||
self.title = new Element('div.title', {
|
||||
'text': self.getTitle() || 'n/a'
|
||||
}),
|
||||
self.year = new Element('div.year', {
|
||||
'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')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if(self.thumbnail.empty)
|
||||
self.el.addClass('no_thumbnail');
|
||||
|
||||
//self.changeView(self.view);
|
||||
self.changeView(self.view);
|
||||
self.select_checkbox_class = new Form.Check(self.select_checkbox);
|
||||
|
||||
// Add profile
|
||||
@@ -170,7 +174,7 @@ var Movie = new Class({
|
||||
|
||||
});
|
||||
|
||||
// Add releases
|
||||
// Add done releases
|
||||
self.data.releases.each(function(release){
|
||||
|
||||
var q = self.quality.getElement('.q_id'+ release.quality_id),
|
||||
@@ -237,23 +241,23 @@ var Movie = new Class({
|
||||
|
||||
if(direction == 'in'){
|
||||
self.temp_view = self.view;
|
||||
self.changeView('details')
|
||||
self.changeView('thumbs')
|
||||
|
||||
self.el.addEvent('outerClick', function(){
|
||||
self.removeView()
|
||||
self.changeView(self.temp_view)
|
||||
self.slide('out')
|
||||
})
|
||||
el.show();
|
||||
self.data_container.addClass('hide_right');
|
||||
self.data_container.tween('right', 0, -840);
|
||||
}
|
||||
else {
|
||||
self.el.removeEvents('outerClick')
|
||||
|
||||
setTimeout(function(){
|
||||
self.addEvent('slideEnd:once', function(){
|
||||
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
|
||||
}, 600);
|
||||
});
|
||||
|
||||
self.data_container.removeClass('hide_right');
|
||||
self.data_container.tween('right', -840, 0);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -267,12 +271,6 @@ var Movie = new Class({
|
||||
self.view = new_view;
|
||||
},
|
||||
|
||||
removeView: function(){
|
||||
var self = this;
|
||||
|
||||
self.el.removeClass(self.view+'_view')
|
||||
},
|
||||
|
||||
get: function(attr){
|
||||
return this.data[attr] || this.data.library[attr]
|
||||
},
|
||||
@@ -290,4 +288,388 @@ var Movie = new Class({
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
@@ -191,8 +191,6 @@
|
||||
|
||||
.movie_result .info h2 {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.movie_result .info h2 span {
|
||||
@@ -202,8 +200,7 @@
|
||||
.movie_result .info h2 span:before { content: "("; }
|
||||
.movie_result .info h2 span:after { content: ")"; }
|
||||
|
||||
.search_form .mask,
|
||||
.movie_result .mask {
|
||||
.search_form .mask {
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
||||
@@ -366,7 +366,7 @@ Block.Search.Item = new Class({
|
||||
loadingMask: function(){
|
||||
var self = this;
|
||||
|
||||
self.mask = new Element('div.mask').inject(self.el).fade('hide')
|
||||
self.mask = new Element('span.mask').inject(self.el).fade('hide')
|
||||
|
||||
createSpinner(self.mask)
|
||||
self.mask.fade('in')
|
||||
|
||||
@@ -21,7 +21,7 @@ class QualityPlugin(Plugin):
|
||||
{'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': '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', '1080p'], 'ext':['avi']},
|
||||
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', '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': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from couchpotato.core.plugins.renamer.main import Renamer
|
||||
import os
|
||||
|
||||
def start():
|
||||
return Renamer()
|
||||
@@ -112,15 +111,6 @@ config = [{
|
||||
'label': 'Separator',
|
||||
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
|
||||
},
|
||||
{
|
||||
'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',
|
||||
|
||||
@@ -13,7 +13,6 @@ import errno
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -34,7 +33,6 @@ class Renamer(Plugin):
|
||||
addEvent('renamer.check_snatched', self.checkSnatched)
|
||||
|
||||
addEvent('app.load', self.scan)
|
||||
addEvent('app.load', self.checkSnatched)
|
||||
|
||||
if self.conf('run_every') > 0:
|
||||
fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
|
||||
@@ -171,15 +169,15 @@ class Renamer(Plugin):
|
||||
replacements['cd_nr'] = cd if multiple else ''
|
||||
|
||||
# Naming
|
||||
final_folder_name = self.doReplace(folder_name, replacements).lstrip('. ')
|
||||
final_file_name = self.doReplace(file_name, replacements).lstrip('. ')
|
||||
final_folder_name = self.doReplace(folder_name, replacements)
|
||||
final_file_name = self.doReplace(file_name, replacements)
|
||||
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
|
||||
|
||||
# Meta naming
|
||||
if file_type is 'trailer':
|
||||
final_file_name = self.doReplace(trailer_name, replacements, remove_multiple = True).lstrip('. ')
|
||||
final_file_name = self.doReplace(trailer_name, replacements, remove_multiple = True)
|
||||
elif file_type is 'nfo':
|
||||
final_file_name = self.doReplace(nfo_name, replacements, remove_multiple = True).lstrip('. ')
|
||||
final_file_name = self.doReplace(nfo_name, replacements, remove_multiple = True)
|
||||
|
||||
# Seperator replace
|
||||
if separator:
|
||||
@@ -276,7 +274,6 @@ class Renamer(Plugin):
|
||||
for profile_type in movie.profile.types:
|
||||
if profile_type.quality_id == group['meta_data']['quality']['id'] and profile_type.finish:
|
||||
movie.status_id = done_status.get('id')
|
||||
movie.last_edit = int(time.time())
|
||||
db.commit()
|
||||
except Exception, e:
|
||||
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
|
||||
@@ -318,10 +315,8 @@ class Renamer(Plugin):
|
||||
log.debug('Marking release as downloaded')
|
||||
try:
|
||||
release.status_id = downloaded_status.get('id')
|
||||
release.last_edit = int(time.time())
|
||||
except Exception, e:
|
||||
log.error('Failed marking release as finished: %s %s', (e, traceback.format_exc()))
|
||||
|
||||
db.commit()
|
||||
|
||||
# Remove leftover files
|
||||
@@ -459,8 +454,6 @@ class Renamer(Plugin):
|
||||
|
||||
try:
|
||||
os.chmod(dest, Env.getPermission('file'))
|
||||
if os.name == 'nt' and self.conf('ntfs_permission'):
|
||||
os.popen('icacls "' + dest + '"* /reset /T')
|
||||
except:
|
||||
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
||||
|
||||
@@ -474,7 +467,7 @@ class Renamer(Plugin):
|
||||
|
||||
except:
|
||||
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
||||
raise
|
||||
raise Exception
|
||||
|
||||
return True
|
||||
|
||||
@@ -560,7 +553,6 @@ class Renamer(Plugin):
|
||||
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)
|
||||
rel.status_id = ignored_status.get('id')
|
||||
rel.last_edit = int(time.time())
|
||||
db.commit()
|
||||
continue
|
||||
|
||||
@@ -571,7 +563,7 @@ class Renamer(Plugin):
|
||||
|
||||
found = False
|
||||
for item in statuses:
|
||||
if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']:
|
||||
if item['name'] == nzbname or getImdb(item['name']) == movie_dict['library']['identifier']:
|
||||
|
||||
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))
|
||||
@@ -585,7 +577,6 @@ class Renamer(Plugin):
|
||||
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
|
||||
else:
|
||||
rel.status_id = failed_status.get('id')
|
||||
rel.last_edit = int(time.time())
|
||||
db.commit()
|
||||
elif item['status'] == 'completed':
|
||||
log.info('Download of %s completed!', item['name'])
|
||||
|
||||
@@ -23,7 +23,7 @@ class Scanner(Plugin):
|
||||
'media': 314572800, # 300MB
|
||||
'trailer': 1048576, # 1MB
|
||||
}
|
||||
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
|
||||
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
|
||||
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
||||
extensions = {
|
||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
|
||||
@@ -89,7 +89,7 @@ class Scanner(Plugin):
|
||||
'()([ab])(\.....?)$' #*a.mkv
|
||||
]
|
||||
|
||||
cp_imdb = '(.cp.(?P<id>tt[0-9{7}]+).)'
|
||||
cp_imdb = '(\.cp\((?P<id>tt[0-9{7}]+)\))'
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -341,7 +341,7 @@ class Scanner(Plugin):
|
||||
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
||||
|
||||
if len(group['files']['movie']) == 0:
|
||||
log.error('Couldn\'t find any movie files for %s', identifier)
|
||||
log.error('Couldn\t find any movie files for %s', identifier)
|
||||
continue
|
||||
|
||||
log.debug('Getting metadata for %s', identifier)
|
||||
|
||||
@@ -12,7 +12,6 @@ from couchpotato.environment import Env
|
||||
from inspect import ismethod, isfunction
|
||||
from sqlalchemy.exc import InterfaceError
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
@@ -30,7 +29,6 @@ class Searcher(Plugin):
|
||||
addEvent('searcher.correct_movie', self.correctMovie)
|
||||
addEvent('searcher.download', self.download)
|
||||
addEvent('searcher.try_next_release', self.tryNextRelease)
|
||||
addEvent('searcher.could_be_released', self.couldBeReleased)
|
||||
|
||||
addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
|
||||
'desc': 'Marks the snatched results as ignored and try the next best release',
|
||||
@@ -85,51 +83,37 @@ class Searcher(Plugin):
|
||||
movies = db.query(Movie).filter(
|
||||
Movie.status.has(identifier = 'active')
|
||||
).all()
|
||||
random.shuffle(movies)
|
||||
|
||||
self.in_progress = {
|
||||
'total': len(movies),
|
||||
'to_go': len(movies),
|
||||
}
|
||||
|
||||
try:
|
||||
search_types = self.getSearchTypes()
|
||||
for movie in movies:
|
||||
movie_dict = movie.to_dict({
|
||||
'profile': {'types': {'quality': {}}},
|
||||
'releases': {'status': {}, 'quality': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {}
|
||||
})
|
||||
|
||||
for movie in movies:
|
||||
movie_dict = movie.to_dict({
|
||||
'profile': {'types': {'quality': {}}},
|
||||
'releases': {'status': {}, 'quality': {}},
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {}
|
||||
})
|
||||
try:
|
||||
self.single(movie_dict)
|
||||
except IndexError:
|
||||
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
|
||||
fireEvent('library.update', movie_dict['library']['identifier'], force = True)
|
||||
except:
|
||||
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
|
||||
|
||||
try:
|
||||
self.single(movie_dict, search_types)
|
||||
except IndexError:
|
||||
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
|
||||
fireEvent('library.update', movie_dict['library']['identifier'], force = True)
|
||||
except:
|
||||
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
|
||||
self.in_progress['to_go'] -= 1
|
||||
|
||||
self.in_progress['to_go'] -= 1
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
except SearchSetupError:
|
||||
pass
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
self.in_progress = False
|
||||
|
||||
def single(self, movie, search_types = None):
|
||||
|
||||
# Find out search type
|
||||
try:
|
||||
if not search_types:
|
||||
search_types = self.getSearchTypes()
|
||||
except SearchSetupError:
|
||||
return
|
||||
def single(self, movie):
|
||||
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
|
||||
@@ -144,8 +128,6 @@ class Searcher(Plugin):
|
||||
available_status = fireEvent('status.get', 'available', single = True)
|
||||
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
||||
|
||||
found_releases = []
|
||||
|
||||
default_title = getTitle(movie['library'])
|
||||
if not default_title:
|
||||
log.error('No proper info found for movie, removing it from library to cause it from having more issues.')
|
||||
@@ -154,10 +136,9 @@ class Searcher(Plugin):
|
||||
|
||||
fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title)
|
||||
|
||||
|
||||
ret = False
|
||||
for quality_type in movie['profile']['types']:
|
||||
if not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates):
|
||||
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
|
||||
log.info('Too early to search for %s, %s', (quality_type['quality']['identifier'], default_title))
|
||||
continue
|
||||
|
||||
@@ -165,7 +146,7 @@ class Searcher(Plugin):
|
||||
|
||||
# See if better quality is available
|
||||
for release in movie['releases']:
|
||||
if release['quality']['order'] < quality_type['quality']['order'] and release['status_id'] not in [available_status.get('id'), ignored_status.get('id')]:
|
||||
if release['quality']['order'] <= quality_type['quality']['order'] and release['status_id'] not in [available_status.get('id'), ignored_status.get('id')]:
|
||||
has_better_quality += 1
|
||||
|
||||
# Don't search for quality lower then already available.
|
||||
@@ -174,11 +155,7 @@ class Searcher(Plugin):
|
||||
log.info('Search for %s in %s', (default_title, quality_type['quality']['label']))
|
||||
quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True)
|
||||
|
||||
results = []
|
||||
for search_type in search_types:
|
||||
type_results = fireEvent('%s.search' % search_type, movie, quality, merge = True)
|
||||
if type_results:
|
||||
results += type_results
|
||||
results = fireEvent('yarr.search', movie, quality, merge = True)
|
||||
|
||||
sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
|
||||
if len(sorted_results) == 0:
|
||||
@@ -195,13 +172,10 @@ class Searcher(Plugin):
|
||||
# Add them to this movie releases list
|
||||
for nzb in sorted_results:
|
||||
|
||||
nzb_identifier = md5(nzb['url'])
|
||||
found_releases.append(nzb_identifier)
|
||||
|
||||
rls = db.query(Release).filter_by(identifier = nzb_identifier).first()
|
||||
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
|
||||
if not rls:
|
||||
rls = Release(
|
||||
identifier = nzb_identifier,
|
||||
identifier = md5(nzb['url']),
|
||||
movie_id = movie.get('id'),
|
||||
quality_id = quality_type.get('quality_id'),
|
||||
status_id = available_status.get('id')
|
||||
@@ -209,7 +183,6 @@ class Searcher(Plugin):
|
||||
db.add(rls)
|
||||
else:
|
||||
[db.delete(old_info) for old_info in rls.info]
|
||||
rls.last_edit = int(time.time())
|
||||
|
||||
db.commit()
|
||||
|
||||
@@ -250,12 +223,6 @@ class Searcher(Plugin):
|
||||
break
|
||||
elif downloaded != 'try_next':
|
||||
break
|
||||
|
||||
# Remove releases that aren't found anymore
|
||||
for release in movie.get('releases', []):
|
||||
if release.get('status_id') == available_status.get('id') and release.get('identifier') not in found_releases:
|
||||
fireEvent('release.delete', release.get('id'), single = True)
|
||||
|
||||
else:
|
||||
log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title))
|
||||
fireEvent('movie.restatus', movie['id'])
|
||||
@@ -271,92 +238,61 @@ class Searcher(Plugin):
|
||||
|
||||
def download(self, data, movie, manual = False):
|
||||
|
||||
# Test to see if any downloaders are enabled for this type
|
||||
downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
|
||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||
|
||||
if downloader_enabled:
|
||||
# Download movie to temp
|
||||
filedata = None
|
||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
if filedata == 'try_next':
|
||||
return filedata
|
||||
|
||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||
|
||||
# Download movie to temp
|
||||
filedata = None
|
||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
if filedata == 'try_next':
|
||||
return filedata
|
||||
if successful:
|
||||
|
||||
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||
try:
|
||||
# Mark release as snatched
|
||||
db = get_session()
|
||||
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
|
||||
if rls:
|
||||
rls.status_id = snatched_status.get('id')
|
||||
db.commit()
|
||||
|
||||
if successful:
|
||||
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
||||
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
||||
log.info(snatch_message)
|
||||
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
||||
|
||||
try:
|
||||
# Mark release as snatched
|
||||
db = get_session()
|
||||
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
|
||||
if rls:
|
||||
renamer_enabled = Env.setting('enabled', 'renamer')
|
||||
# If renamer isn't used, mark movie done
|
||||
if not Env.setting('enabled', 'renamer'):
|
||||
active_status = fireEvent('status.get', 'active', single = True)
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
try:
|
||||
if movie['status_id'] == active_status.get('id'):
|
||||
for profile_type in movie['profile']['types']:
|
||||
if rls and profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
||||
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
||||
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
|
||||
db.commit()
|
||||
# Mark release done
|
||||
rls.status_id = done_status.get('id')
|
||||
db.commit()
|
||||
|
||||
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
||||
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
||||
log.info(snatch_message)
|
||||
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
||||
# Mark movie done
|
||||
mvie = db.query(Movie).filter_by(id = movie['id']).first()
|
||||
mvie.status_id = done_status.get('id')
|
||||
db.commit()
|
||||
except:
|
||||
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
|
||||
|
||||
# If renamer isn't used, mark movie done
|
||||
if not renamer_enabled:
|
||||
active_status = fireEvent('status.get', 'active', single = True)
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
try:
|
||||
if movie['status_id'] == active_status.get('id'):
|
||||
for profile_type in movie['profile']['types']:
|
||||
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
||||
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
||||
except:
|
||||
log.error('Failed marking movie finished: %s', traceback.format_exc())
|
||||
|
||||
# Mark release done
|
||||
rls.status_id = done_status.get('id')
|
||||
rls.last_edit = int(time.time())
|
||||
db.commit()
|
||||
|
||||
# Mark movie done
|
||||
mvie = db.query(Movie).filter_by(id = movie['id']).first()
|
||||
mvie.status_id = done_status.get('id')
|
||||
mvie.last_edit = int(time.time())
|
||||
db.commit()
|
||||
except:
|
||||
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
|
||||
|
||||
except:
|
||||
log.error('Failed marking movie finished: %s', traceback.format_exc())
|
||||
|
||||
return True
|
||||
|
||||
log.info('Tried to download, but none of the "%s" downloaders are enabled', (data.get('type', '')))
|
||||
return True
|
||||
|
||||
log.info('Tried to download, but none of the downloaders are enabled')
|
||||
return False
|
||||
|
||||
def getSearchTypes(self):
|
||||
|
||||
download_types = fireEvent('download.enabled_types', merge = True)
|
||||
provider_types = fireEvent('provider.enabled_types', merge = True)
|
||||
|
||||
if download_types and len(list(set(provider_types) & set(download_types))) == 0:
|
||||
log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_types))
|
||||
raise NoProviders
|
||||
|
||||
for useless_provider in list(set(provider_types) - set(download_types)):
|
||||
log.debug('Provider for "%s" enabled, but no downloader.', useless_provider)
|
||||
|
||||
search_types = download_types
|
||||
|
||||
if len(search_types) == 0:
|
||||
log.error('There aren\'t any downloaders enabled. Please pick one in settings.')
|
||||
raise NoDownloaders
|
||||
|
||||
return search_types
|
||||
|
||||
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
|
||||
|
||||
imdb_results = kwargs.get('imdb_results', False)
|
||||
@@ -533,7 +469,7 @@ class Searcher(Plugin):
|
||||
|
||||
return False
|
||||
|
||||
def couldBeReleased(self, is_pre_release, dates):
|
||||
def couldBeReleased(self, wanted_quality, dates, pre_releases):
|
||||
|
||||
now = int(time.time())
|
||||
|
||||
@@ -545,7 +481,7 @@ class Searcher(Plugin):
|
||||
if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
|
||||
return True
|
||||
|
||||
if is_pre_release:
|
||||
if wanted_quality in pre_releases:
|
||||
# Prerelease 1 week before theaters
|
||||
if dates.get('theater') - 604800 < now:
|
||||
return True
|
||||
@@ -600,12 +536,3 @@ class Searcher(Plugin):
|
||||
except:
|
||||
log.error('Failed searching for next release: %s', traceback.format_exc())
|
||||
return False
|
||||
|
||||
class SearchSetupError(Exception):
|
||||
pass
|
||||
|
||||
class NoDownloaders(SearchSetupError):
|
||||
pass
|
||||
|
||||
class NoProviders(SearchSetupError):
|
||||
pass
|
||||
|
||||
@@ -23,7 +23,6 @@ class StatusPlugin(Plugin):
|
||||
'deleted': 'Deleted',
|
||||
'ignored': 'Ignored',
|
||||
'available': 'Available',
|
||||
'suggest': 'Suggest',
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -49,7 +49,6 @@ class Subtitle(Plugin):
|
||||
available_languages = sum(group['subtitle_language'].itervalues(), [])
|
||||
downloaded = []
|
||||
files = [toUnicode(x) for x in group['files']['movie']]
|
||||
log.debug('Searching for subtitles for: %s', files)
|
||||
|
||||
for lang in self.getLanguages():
|
||||
if lang not in available_languages:
|
||||
@@ -58,7 +57,6 @@ class Subtitle(Plugin):
|
||||
downloaded.extend(download[subtitle])
|
||||
|
||||
for d_sub in downloaded:
|
||||
log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files))
|
||||
group['files']['subtitle'].add(d_sub.path)
|
||||
group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2]
|
||||
|
||||
|
||||
@@ -1,22 +1,6 @@
|
||||
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
|
||||
|
||||
class Suggestion(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
.page.wizard .uniForm {
|
||||
width: 80%;
|
||||
margin: 0 auto 30px;
|
||||
}
|
||||
|
||||
.page.wizard h1 {
|
||||
padding: 10px 30px;
|
||||
margin: 0;
|
||||
display: block;
|
||||
font-size: 30px;
|
||||
font-size: 40px;
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ Page.Wizard = new Class({
|
||||
},
|
||||
'providers': {
|
||||
'title': 'Are you registered at any of these sites?',
|
||||
'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.'
|
||||
'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.',
|
||||
'include': ['nzb_providers', 'torrent_providers']
|
||||
},
|
||||
'renamer': {
|
||||
'title': 'Move & rename the movies after downloading?',
|
||||
@@ -82,7 +83,7 @@ Page.Wizard = new Class({
|
||||
'target': self.el
|
||||
},
|
||||
'onComplete': function(){
|
||||
window.location = App.createUrl('wanted');
|
||||
window.location = App.createUrl();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -212,6 +213,8 @@ Page.Wizard = new Class({
|
||||
// Hide retention
|
||||
self.el.getElement('.tab_searcher').hide();
|
||||
self.el.getElement('.t_searcher').hide();
|
||||
self.el.getElement('.t_nzb_providers').hide();
|
||||
self.el.getElement('.t_torrent_providers').hide();
|
||||
|
||||
// Add pointer
|
||||
new Element('.tab_wrapper').wraps(tabs).adopt(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
config = {
|
||||
'name': 'automation_providers',
|
||||
'groups': [
|
||||
{
|
||||
'label': 'Watchlists',
|
||||
'description': 'Check watchlists for new movies',
|
||||
'type': 'list',
|
||||
'name': 'watchlist_providers',
|
||||
'tab': 'automation',
|
||||
'options': [],
|
||||
},
|
||||
{
|
||||
'label': 'Automated',
|
||||
'description': 'Uses minimal requirements',
|
||||
'type': 'list',
|
||||
'name': 'automation_providers',
|
||||
'tab': 'automation',
|
||||
'options': [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.base import Provider
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
import time
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Automation(Provider):
|
||||
class Automation(Plugin):
|
||||
|
||||
enabled_option = 'automation_enabled'
|
||||
|
||||
@@ -19,9 +19,6 @@ class Automation(Provider):
|
||||
|
||||
def _getMovies(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
if not self.canCheck():
|
||||
log.debug('Just checked, skipping %s', self.getName())
|
||||
return []
|
||||
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'automation_providers',
|
||||
'name': 'bluray_automation',
|
||||
'label': 'Blu-ray.com',
|
||||
'description': 'Imports movies from blu-ray.com. (uses minimal requirements)',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.helpers.variable import md5, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -13,24 +14,32 @@ class Bluray(Automation, RSS):
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
movies = []
|
||||
|
||||
rss_movies = self.getRSSData(self.rss_url)
|
||||
cache_key = 'bluray.%s' % md5(self.rss_url)
|
||||
rss_data = self.getCache(cache_key, self.rss_url)
|
||||
data = XMLTree.fromstring(rss_data)
|
||||
|
||||
for movie in rss_movies:
|
||||
name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip()
|
||||
year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip()
|
||||
if data is not None:
|
||||
rss_movies = self.getElements(data, 'channel/item')
|
||||
|
||||
if not name.find('/') == -1: # make sure it is not a double movie release
|
||||
continue
|
||||
for movie in rss_movies:
|
||||
name = self.getTextElement(movie, "title").lower().split("blu-ray")[0].strip("(").rstrip()
|
||||
year = self.getTextElement(movie, "description").split("|")[1].strip("(").strip()
|
||||
|
||||
if tryInt(year) < self.getMinimal('year'):
|
||||
continue
|
||||
if not name.find("/") == -1: # make sure it is not a double movie release
|
||||
continue
|
||||
|
||||
imdb = self.search(name, year)
|
||||
if tryInt(year) < self.getMinimal('year'):
|
||||
continue
|
||||
|
||||
if imdb:
|
||||
if self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
imdb = self.search(name, year)
|
||||
|
||||
if imdb:
|
||||
if self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
|
||||
return movies
|
||||
|
||||
@@ -8,4 +8,7 @@ class CP(Automation):
|
||||
|
||||
def getMovies(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
return []
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -1,36 +0,0 @@
|
||||
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
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'watchlist_providers',
|
||||
'name': 'imdb_automation',
|
||||
'label': 'IMDB',
|
||||
'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the RSS link.',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
|
||||
from couchpotato.core.helpers.variable import md5, getImdb, splitString, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
import traceback
|
||||
@@ -13,6 +13,9 @@ class IMDB(Automation, RSS):
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
movies = []
|
||||
|
||||
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||
@@ -26,8 +29,9 @@ class IMDB(Automation, RSS):
|
||||
continue
|
||||
|
||||
try:
|
||||
rss_data = self.getHTMLData(url)
|
||||
imdbs = getImdb(rss_data, multiple = True) if rss_data else []
|
||||
cache_key = 'imdb.rss.%s' % md5(url)
|
||||
rss_data = self.getCache(cache_key, url)
|
||||
imdbs = getImdb(rss_data, multiple = True)
|
||||
|
||||
for imdb in imdbs:
|
||||
movies.append(imdb)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from .main import ITunes
|
||||
|
||||
def start():
|
||||
return ITunes()
|
||||
|
||||
config = [{
|
||||
'name': 'itunes',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'automation_providers',
|
||||
'name': 'itunes_automation',
|
||||
'label': 'iTunes',
|
||||
'description': 'From any <a href="http://itunes.apple.com/rss">iTunes</a> Store feed. Url should be the RSS link. (uses minimal requirements)',
|
||||
'options': [
|
||||
{
|
||||
'name': 'automation_enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'automation_urls_use',
|
||||
'label': 'Use',
|
||||
'default': ',',
|
||||
},
|
||||
{
|
||||
'name': 'automation_urls',
|
||||
'label': 'url',
|
||||
'type': 'combined',
|
||||
'combine': ['automation_urls_use', 'automation_urls'],
|
||||
'default': 'https://itunes.apple.com/rss/topmovies/limit=25/xml,',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -1,63 +0,0 @@
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import md5, splitString, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
from xml.etree.ElementTree import QName
|
||||
import datetime
|
||||
import traceback
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class ITunes(Automation, RSS):
|
||||
|
||||
interval = 1800
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
movies = []
|
||||
|
||||
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||
urls = splitString(self.conf('automation_urls'))
|
||||
|
||||
namespace = 'http://www.w3.org/2005/Atom'
|
||||
namespaceIM = 'http://itunes.apple.com/rss'
|
||||
|
||||
index = -1
|
||||
for url in urls:
|
||||
|
||||
index += 1
|
||||
if not enablers[index]:
|
||||
continue
|
||||
|
||||
try:
|
||||
cache_key = 'itunes.rss.%s' % md5(url)
|
||||
rss_data = self.getCache(cache_key, url)
|
||||
|
||||
data = XMLTree.fromstring(rss_data)
|
||||
|
||||
if data is not None:
|
||||
entry_tag = str(QName(namespace, 'entry'))
|
||||
rss_movies = self.getElements(data, entry_tag)
|
||||
|
||||
for movie in rss_movies:
|
||||
name_tag = str(QName(namespaceIM, 'name'))
|
||||
name = self.getTextElement(movie, name_tag)
|
||||
|
||||
releaseDate_tag = str(QName(namespaceIM, 'releaseDate'))
|
||||
releaseDateText = self.getTextElement(movie, releaseDate_tag)
|
||||
year = datetime.datetime.strptime(releaseDateText, '%Y-%m-%dT00:00:00-07:00').strftime("%Y")
|
||||
|
||||
imdb = self.search(name, year)
|
||||
|
||||
if imdb and self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
|
||||
except:
|
||||
log.error('Failed loading iTunes rss feed: %s %s', (url, traceback.format_exc()))
|
||||
|
||||
return movies
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'automation_providers',
|
||||
'name': 'kinepolis_automation',
|
||||
'label': 'Kinepolis',
|
||||
'description': 'Imports movies from the current top 10 of kinepolis. (uses minimal requirements)',
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import md5
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
import datetime
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -13,17 +15,25 @@ class Kinepolis(Automation, RSS):
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
movies = []
|
||||
|
||||
rss_movies = self.getRSSData(self.rss_url)
|
||||
cache_key = 'kinepolis.%s' % md5(self.rss_url)
|
||||
rss_data = self.getCache(cache_key, self.rss_url)
|
||||
data = XMLTree.fromstring(rss_data)
|
||||
|
||||
for movie in rss_movies:
|
||||
name = self.getTextElement(movie, 'title')
|
||||
year = datetime.datetime.now().strftime('%Y')
|
||||
if data is not None:
|
||||
rss_movies = self.getElements(data, 'channel/item')
|
||||
|
||||
imdb = self.search(name, year)
|
||||
for movie in rss_movies:
|
||||
name = self.getTextElement(movie, "title")
|
||||
year = datetime.datetime.now().strftime("%Y")
|
||||
|
||||
if imdb and self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
imdb = self.search(name, year)
|
||||
|
||||
if imdb and self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
|
||||
return movies
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
from .main import Moviemeter
|
||||
|
||||
def start():
|
||||
return Moviemeter()
|
||||
|
||||
config = [{
|
||||
'name': 'moviemeter',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'automation_providers',
|
||||
'name': 'moviemeter_automation',
|
||||
'label': 'Moviemeter',
|
||||
'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)',
|
||||
'options': [
|
||||
{
|
||||
'name': 'automation_enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -1,28 +0,0 @@
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Moviemeter(Automation, RSS):
|
||||
|
||||
interval = 1800
|
||||
rss_url = 'http://www.moviemeter.nl/rss/cinema'
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
movies = []
|
||||
|
||||
rss_movies = self.getRSSData(self.rss_url)
|
||||
|
||||
for movie in rss_movies:
|
||||
|
||||
name_year = fireEvent('scanner.name_year', self.getTextElement(movie, 'title'), single = True)
|
||||
imdb = self.search(name_year.get('name'), name_year.get('year'))
|
||||
|
||||
if imdb and self.isMinimalMovie(imdb):
|
||||
movies.append(imdb['imdb'])
|
||||
|
||||
return movies
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'watchlist_providers',
|
||||
'name': 'moviesio',
|
||||
'label': 'Movies.IO',
|
||||
'description': 'Imports movies from <a href="http://movies.io">Movies.io</a> RSS watchlists',
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||
from couchpotato.core.helpers.variable import md5
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
from xml.etree.ElementTree import ParseError
|
||||
import traceback
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -13,27 +16,39 @@ class MoviesIO(Automation, RSS):
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
movies = []
|
||||
|
||||
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||
enablers = self.conf('automation_urls_use').split(',')
|
||||
|
||||
index = -1
|
||||
for rss_url in splitString(self.conf('automation_urls')):
|
||||
for rss_url in self.conf('automation_urls').split(','):
|
||||
|
||||
index += 1
|
||||
if not enablers[index]:
|
||||
continue
|
||||
|
||||
rss_movies = self.getRSSData(rss_url, headers = {'Referer': ''})
|
||||
try:
|
||||
cache_key = 'imdb.rss.%s' % md5(rss_url)
|
||||
|
||||
for movie in rss_movies:
|
||||
rss_data = self.getCache(cache_key, rss_url, headers = {'Referer': ''})
|
||||
data = XMLTree.fromstring(rss_data)
|
||||
rss_movies = self.getElements(data, 'channel/item')
|
||||
|
||||
nameyear = fireEvent('scanner.name_year', self.getTextElement(movie, 'title'), single = True)
|
||||
imdb = self.search(nameyear.get('name'), nameyear.get('year'), imdb_only = True)
|
||||
for movie in rss_movies:
|
||||
|
||||
if not imdb:
|
||||
continue
|
||||
nameyear = fireEvent('scanner.name_year', self.getTextElement(movie, "title"), single = True)
|
||||
imdb = self.search(nameyear.get('name'), nameyear.get('year'), imdb_only = True)
|
||||
|
||||
movies.append(imdb)
|
||||
if not imdb:
|
||||
continue
|
||||
|
||||
movies.append(imdb)
|
||||
except ParseError:
|
||||
log.debug('Failed loading Movies.io watchlist, probably empty: %s', (rss_url))
|
||||
except:
|
||||
log.error('Failed loading Movies.io watchlist: %s %s', (rss_url, traceback.format_exc()))
|
||||
|
||||
return movies
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
from .main import Rottentomatoes
|
||||
|
||||
def start():
|
||||
return Rottentomatoes()
|
||||
|
||||
config = [{
|
||||
'name': 'rottentomatoes',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'automation_providers',
|
||||
'name': 'rottentomatoes_automation',
|
||||
'label': 'Rottentomatoes',
|
||||
'description': 'Imports movies from the rottentomatoes "in theaters"-feed.',
|
||||
'options': [
|
||||
{
|
||||
'name': 'automation_enabled',
|
||||
'default': False,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'tomatometer_percent',
|
||||
'default': '80',
|
||||
'label': 'Tomatometer'
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
@@ -1,48 +0,0 @@
|
||||
from couchpotato.core.helpers.rss import RSS
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
from xml.etree.ElementTree import QName
|
||||
import datetime
|
||||
import re
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class Rottentomatoes(Automation, RSS):
|
||||
|
||||
interval = 1800
|
||||
urls = {
|
||||
'namespace': 'http://www.rottentomatoes.com/xmlns/rtmovie/',
|
||||
'theater': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml',
|
||||
}
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
movies = []
|
||||
|
||||
rss_movies = self.getRSSData(self.urls['theater'])
|
||||
rating_tag = str(QName(self.urls['namespace'], 'tomatometer_percent'))
|
||||
|
||||
for movie in rss_movies:
|
||||
|
||||
value = self.getTextElement(movie, "title")
|
||||
result = re.search('(?<=%\s).*', value)
|
||||
|
||||
if result:
|
||||
|
||||
log.info2('Something smells...')
|
||||
rating = tryInt(self.getTextElement(movie, rating_tag))
|
||||
name = result.group(0)
|
||||
|
||||
if rating < tryInt(self.conf('tomatometer_percent')):
|
||||
log.info2('%s seems to be rotten...' % name)
|
||||
else:
|
||||
|
||||
log.info2('Found %s fresh enough movies, enqueuing: %s' % (rating, name))
|
||||
year = datetime.datetime.now().strftime("%Y")
|
||||
imdb = self.search(name, year)
|
||||
|
||||
if imdb:
|
||||
movies.append(imdb['imdb'])
|
||||
|
||||
return movies
|
||||
@@ -8,7 +8,6 @@ config = [{
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'automation',
|
||||
'list': 'watchlist_providers',
|
||||
'name': 'trakt_automation',
|
||||
'label': 'Trakt',
|
||||
'description': 'import movies from your own watchlist',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.variable import sha1
|
||||
from couchpotato.core.helpers.variable import md5, sha1
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.automation.base import Automation
|
||||
import base64
|
||||
import json
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -24,6 +25,9 @@ class Trakt(Automation):
|
||||
|
||||
def getIMDBids(self):
|
||||
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
movies = []
|
||||
for movie in self.getWatchlist():
|
||||
movies.append(movie.get('imdb_id'))
|
||||
@@ -34,11 +38,22 @@ class Trakt(Automation):
|
||||
method = (self.urls['watchlist'] % self.conf('automation_api_key')) + self.conf('automation_username')
|
||||
return self.call(method)
|
||||
|
||||
|
||||
def call(self, method_url):
|
||||
|
||||
headers = {}
|
||||
if self.conf('automation_password'):
|
||||
headers['Authorization'] = 'Basic %s' % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
||||
try:
|
||||
if self.conf('automation_password'):
|
||||
headers = {
|
||||
'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
||||
}
|
||||
else:
|
||||
headers = {}
|
||||
|
||||
data = self.getJsonData(self.urls['base'] + method_url, headers = headers)
|
||||
return data if data else []
|
||||
cache_key = 'trakt.%s' % md5(method_url)
|
||||
json_string = self.getCache(cache_key, self.urls['base'] + method_url, headers = headers)
|
||||
if json_string:
|
||||
return json.loads(json_string)
|
||||
except:
|
||||
log.error('Failed to get data from trakt, check your login.')
|
||||
|
||||
return []
|
||||
|
||||
@@ -44,38 +44,6 @@ class Provider(Plugin):
|
||||
|
||||
return self.is_available.get(host, False)
|
||||
|
||||
def getJsonData(self, url, **kwargs):
|
||||
|
||||
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
|
||||
data = self.getCache(cache_key, url, **kwargs)
|
||||
|
||||
if data:
|
||||
try:
|
||||
return json.loads(data)
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
return []
|
||||
|
||||
def getRSSData(self, url, item_path = 'channel/item', **kwargs):
|
||||
|
||||
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
|
||||
data = self.getCache(cache_key, url, **kwargs)
|
||||
|
||||
if data:
|
||||
try:
|
||||
data = XMLTree.fromstring(data)
|
||||
return self.getElements(data, item_path)
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
return []
|
||||
|
||||
def getHTMLData(self, url, **kwargs):
|
||||
|
||||
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
|
||||
return self.getCache(cache_key, url, **kwargs)
|
||||
|
||||
|
||||
class YarrProvider(Provider):
|
||||
|
||||
@@ -88,16 +56,8 @@ class YarrProvider(Provider):
|
||||
login_opener = None
|
||||
|
||||
def __init__(self):
|
||||
addEvent('provider.enabled_types', self.getEnabledProviderType)
|
||||
addEvent('provider.belongs_to', self.belongsTo)
|
||||
addEvent('yarr.search', self.search)
|
||||
addEvent('%s.search' % self.type, self.search)
|
||||
|
||||
def getEnabledProviderType(self):
|
||||
if self.isEnabled():
|
||||
return self.type
|
||||
else:
|
||||
return []
|
||||
|
||||
def login(self):
|
||||
|
||||
@@ -107,20 +67,15 @@ class YarrProvider(Provider):
|
||||
urllib2.install_opener(opener)
|
||||
log.info2('Logging into %s', self.urls['login'])
|
||||
f = opener.open(self.urls['login'], self.getLoginParams())
|
||||
output = f.read()
|
||||
f.read()
|
||||
f.close()
|
||||
|
||||
if self.loginSuccess(output):
|
||||
self.login_opener = opener
|
||||
return True
|
||||
self.login_opener = opener
|
||||
return True
|
||||
except:
|
||||
log.error('Failed to login %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
return False
|
||||
|
||||
def loginSuccess(self, output):
|
||||
return True
|
||||
|
||||
def loginDownload(self, url = '', nzb_id = ''):
|
||||
try:
|
||||
if not self.login_opener and not self.login():
|
||||
@@ -151,11 +106,11 @@ class YarrProvider(Provider):
|
||||
return []
|
||||
|
||||
# Create result container
|
||||
imdb_results = hasattr(self, '_search')
|
||||
results = ResultList(self, movie, quality, imdb_results = imdb_results)
|
||||
imdb_result = hasattr(self, '_search')
|
||||
results = ResultList(self, movie, quality, imdb_result = imdb_result)
|
||||
|
||||
# Do search based on imdb id
|
||||
if imdb_results:
|
||||
if imdb_result:
|
||||
self._search(movie, quality, results)
|
||||
# Search possible titles
|
||||
else:
|
||||
@@ -210,6 +165,34 @@ class YarrProvider(Provider):
|
||||
|
||||
return [self.cat_backup_id]
|
||||
|
||||
def getJsonData(self, url, **kwargs):
|
||||
|
||||
data = self.getCache(md5(url), url, **kwargs)
|
||||
|
||||
if data:
|
||||
try:
|
||||
return json.loads(data)
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
return []
|
||||
|
||||
def getRSSData(self, url, **kwargs):
|
||||
|
||||
data = self.getCache(md5(url), url, **kwargs)
|
||||
|
||||
if data:
|
||||
try:
|
||||
data = XMLTree.fromstring(data)
|
||||
return self.getElements(data, 'channel/item')
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
return []
|
||||
|
||||
def getHTMLData(self, url, **kwargs):
|
||||
return self.getCache(md5(url), url, **kwargs)
|
||||
|
||||
|
||||
class ResultList(list):
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user