Merge branch 'refs/heads/develop' into desktop
Conflicts: couchpotato/core/_base/updater/main.py
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
|
*.pyc
|
||||||
|
/data/
|
||||||
/_source/
|
/_source/
|
||||||
+5
-2
@@ -35,7 +35,11 @@ class Loader(object):
|
|||||||
settings.setFile(self.options.config_file)
|
settings.setFile(self.options.config_file)
|
||||||
|
|
||||||
# Create data dir if needed
|
# Create data dir if needed
|
||||||
self.data_dir = os.path.expanduser(Env.setting('data_dir'))
|
if self.options.data_dir:
|
||||||
|
self.data_dir = self.options.data_dir
|
||||||
|
else:
|
||||||
|
self.data_dir = os.path.expanduser(Env.setting('data_dir'))
|
||||||
|
|
||||||
if self.data_dir == '':
|
if self.data_dir == '':
|
||||||
self.data_dir = getDataDir()
|
self.data_dir = getDataDir()
|
||||||
|
|
||||||
@@ -89,7 +93,6 @@ class Loader(object):
|
|||||||
if self.runAsDaemon():
|
if self.runAsDaemon():
|
||||||
try: self.daemon.stop()
|
try: self.daemon.stop()
|
||||||
except: pass
|
except: pass
|
||||||
self.daemon.delpid()
|
|
||||||
except:
|
except:
|
||||||
self.log.critical(traceback.format_exc())
|
self.log.critical(traceback.format_exc())
|
||||||
|
|
||||||
|
|||||||
@@ -69,10 +69,14 @@ def getApiKey():
|
|||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(error):
|
def page_not_found(error):
|
||||||
index_url = url_for('web.index')
|
index_url = url_for('web.index')
|
||||||
url = getattr(request, 'path')[len(index_url):]
|
url = request.path[len(index_url):]
|
||||||
|
|
||||||
if url[:3] != 'api':
|
if url[:3] != 'api':
|
||||||
return redirect(index_url + '#' + url)
|
if request.path != '/':
|
||||||
|
r = request.url.replace(request.path, index_url + '#' + url)
|
||||||
|
else:
|
||||||
|
r = '%s%s' % (request.url.rstrip('/'), index_url + '#' + url)
|
||||||
|
return redirect(r)
|
||||||
else:
|
else:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
return 'Wrong API key used', 404
|
return 'Wrong API key used', 404
|
||||||
|
|||||||
@@ -1,10 +1,39 @@
|
|||||||
from flask.blueprints import Blueprint
|
from flask.blueprints import Blueprint
|
||||||
from flask.helpers import url_for
|
from flask.helpers import url_for
|
||||||
|
from tornado.ioloop import IOLoop
|
||||||
|
from tornado.web import RequestHandler, asynchronous
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect
|
||||||
|
|
||||||
api = Blueprint('api', __name__)
|
api = Blueprint('api', __name__)
|
||||||
api_docs = {}
|
api_docs = {}
|
||||||
api_docs_missing = []
|
api_docs_missing = []
|
||||||
|
api_nonblock = {}
|
||||||
|
|
||||||
|
|
||||||
|
class NonBlockHandler(RequestHandler):
|
||||||
|
stoppers = []
|
||||||
|
|
||||||
|
@asynchronous
|
||||||
|
def get(self, route):
|
||||||
|
cls = NonBlockHandler
|
||||||
|
start, stop = api_nonblock[route]
|
||||||
|
cls.stoppers.append(stop)
|
||||||
|
|
||||||
|
start(self.onNewMessage, last_id = self.get_argument("last_id", None))
|
||||||
|
|
||||||
|
def onNewMessage(self, response):
|
||||||
|
if self.request.connection.stream.closed():
|
||||||
|
return
|
||||||
|
self.finish(response)
|
||||||
|
|
||||||
|
def on_connection_close(self):
|
||||||
|
cls = NonBlockHandler
|
||||||
|
|
||||||
|
for stop in cls.stoppers:
|
||||||
|
stop(self.onNewMessage)
|
||||||
|
|
||||||
|
cls.stoppers = []
|
||||||
|
|
||||||
|
|
||||||
def addApiView(route, func, static = False, docs = None, **kwargs):
|
def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||||
api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '::') if route else 'index', view_func = func, **kwargs)
|
api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '::') if route else 'index', view_func = func, **kwargs)
|
||||||
@@ -13,6 +42,14 @@ def addApiView(route, func, static = False, docs = None, **kwargs):
|
|||||||
else:
|
else:
|
||||||
api_docs_missing.append(route)
|
api_docs_missing.append(route)
|
||||||
|
|
||||||
|
def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
|
||||||
|
api_nonblock[route] = func_tuple
|
||||||
|
|
||||||
|
if docs:
|
||||||
|
api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
|
||||||
|
else:
|
||||||
|
api_docs_missing.append(route)
|
||||||
|
|
||||||
""" Api view """
|
""" Api view """
|
||||||
def index():
|
def index():
|
||||||
index_url = url_for('web.index')
|
index_url = url_for('web.index')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from couchpotato.core.helpers.variable import cleanHost, md5
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask import request
|
from tornado.ioloop import IOLoop
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
@@ -18,7 +18,10 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class Core(Plugin):
|
class Core(Plugin):
|
||||||
|
|
||||||
ignore_restart = ['Core.crappyRestart', 'Core.crappyShutdown']
|
ignore_restart = [
|
||||||
|
'Core.restart', 'Core.shutdown',
|
||||||
|
'Updater.check', 'Updater.autoUpdate',
|
||||||
|
]
|
||||||
shutdown_started = False
|
shutdown_started = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -37,12 +40,13 @@ class Core(Plugin):
|
|||||||
'desc': 'Get version.'
|
'desc': 'Get version.'
|
||||||
})
|
})
|
||||||
|
|
||||||
addEvent('app.crappy_shutdown', self.crappyShutdown)
|
addEvent('app.shutdown', self.shutdown)
|
||||||
addEvent('app.crappy_restart', self.crappyRestart)
|
addEvent('app.restart', self.restart)
|
||||||
addEvent('app.load', self.launchBrowser, priority = 1)
|
addEvent('app.load', self.launchBrowser, priority = 1)
|
||||||
addEvent('app.base_url', self.createBaseUrl)
|
addEvent('app.base_url', self.createBaseUrl)
|
||||||
addEvent('app.api_url', self.createApiUrl)
|
addEvent('app.api_url', self.createApiUrl)
|
||||||
addEvent('app.version', self.version)
|
addEvent('app.version', self.version)
|
||||||
|
addEvent('app.load', self.checkDataDir)
|
||||||
|
|
||||||
addEvent('setting.save.core.password', self.md5Password)
|
addEvent('setting.save.core.password', self.md5Password)
|
||||||
addEvent('setting.save.core.api_key', self.checkApikey)
|
addEvent('setting.save.core.api_key', self.checkApikey)
|
||||||
@@ -54,39 +58,35 @@ class Core(Plugin):
|
|||||||
def checkApikey(self, value):
|
def checkApikey(self, value):
|
||||||
return value if value and len(value) > 3 else uuid4().hex
|
return value if value and len(value) > 3 else uuid4().hex
|
||||||
|
|
||||||
|
def checkDataDir(self):
|
||||||
|
if Env.get('app_dir') in Env.get('data_dir'):
|
||||||
|
log.error('You should NOT use your CouchPotato directory to save your settings in. Files will get overwritten or be deleted.')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def available(self):
|
def available(self):
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'succes': True
|
'succes': True
|
||||||
})
|
})
|
||||||
|
|
||||||
def crappyShutdown(self):
|
|
||||||
if self.shutdown_started:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.urlopen('%s/app.shutdown' % self.createApiUrl(), show_error = False)
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
self.initShutdown()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def crappyRestart(self):
|
|
||||||
if self.shutdown_started:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.urlopen('%s/app.restart' % self.createApiUrl(), show_error = False)
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
self.initShutdown(restart = True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.initShutdown()
|
if self.shutdown_started:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
self.initShutdown()
|
||||||
|
IOLoop.instance().add_callback(shutdown)
|
||||||
|
|
||||||
return 'shutdown'
|
return 'shutdown'
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
self.initShutdown(restart = True)
|
if self.shutdown_started:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def restart():
|
||||||
|
self.initShutdown(restart = True)
|
||||||
|
IOLoop.instance().add_callback(restart)
|
||||||
|
|
||||||
return 'restarting'
|
return 'restarting'
|
||||||
|
|
||||||
def initShutdown(self, restart = False):
|
def initShutdown(self, restart = False):
|
||||||
@@ -102,17 +102,20 @@ class Core(Plugin):
|
|||||||
log.debug('Every plugin got shutdown event')
|
log.debug('Every plugin got shutdown event')
|
||||||
|
|
||||||
loop = True
|
loop = True
|
||||||
|
starttime = time.time()
|
||||||
while loop:
|
while loop:
|
||||||
log.debug('Asking who is running')
|
log.debug('Asking who is running')
|
||||||
still_running = fireEvent('plugin.running', merge = True)
|
still_running = fireEvent('plugin.running', merge = True)
|
||||||
log.debug('Still running: %s' % still_running)
|
log.debug('Still running: %s', still_running)
|
||||||
|
|
||||||
if len(still_running) == 0:
|
if len(still_running) == 0:
|
||||||
break
|
break
|
||||||
|
elif starttime < time.time() - 30: # Always force break after 30s wait
|
||||||
|
break
|
||||||
|
|
||||||
running = list(set(still_running) - set(self.ignore_restart))
|
running = list(set(still_running) - set(self.ignore_restart))
|
||||||
if len(running) > 0:
|
if len(running) > 0:
|
||||||
log.info('Waiting on plugins to finish: %s' % running)
|
log.info('Waiting on plugins to finish: %s', running)
|
||||||
else:
|
else:
|
||||||
loop = False
|
loop = False
|
||||||
|
|
||||||
@@ -121,11 +124,11 @@ class Core(Plugin):
|
|||||||
log.debug('Save to shutdown/restart')
|
log.debug('Save to shutdown/restart')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request.environ.get('werkzeug.server.shutdown')()
|
IOLoop.instance().stop()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
log.error('Failed shutting down the server: %s' % traceback.format_exc())
|
log.error('Failed shutting down the server: %s', traceback.format_exc())
|
||||||
|
|
||||||
fireEvent('app.after_shutdown', restart = restart)
|
fireEvent('app.after_shutdown', restart = restart)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ if Env.get('desktop'):
|
|||||||
addEvent('app.after_shutdown', desktop.afterShutdown)
|
addEvent('app.after_shutdown', desktop.afterShutdown)
|
||||||
|
|
||||||
def onClose(self, event):
|
def onClose(self, event):
|
||||||
return fireEvent('app.crappy_shutdown', single = True)
|
return fireEvent('app.shutdown', single = True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Scheduler(Plugin):
|
|||||||
for type in ['interval', 'cron']:
|
for type in ['interval', 'cron']:
|
||||||
try:
|
try:
|
||||||
self.sched.unschedule_job(getattr(self, type)[identifier]['job'])
|
self.sched.unschedule_job(getattr(self, type)[identifier]['job'])
|
||||||
log.debug('%s unscheduled %s' % (type.capitalize(), identifier))
|
log.debug('%s unscheduled %s', (type.capitalize(), identifier))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class Scheduler(Plugin):
|
|||||||
job = self.sched.add_cron_job(cron['handle'], day = cron['day'], hour = cron['hour'], minute = cron['minute'])
|
job = self.sched.add_cron_job(cron['handle'], day = cron['day'], hour = cron['hour'], minute = cron['minute'])
|
||||||
cron['job'] = job
|
cron['job'] = job
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
log.error("Failed adding cronjob: %s" % e)
|
log.error('Failed adding cronjob: %s', e)
|
||||||
|
|
||||||
# Intervals
|
# Intervals
|
||||||
for identifier in self.intervals:
|
for identifier in self.intervals:
|
||||||
@@ -55,7 +55,7 @@ class Scheduler(Plugin):
|
|||||||
job = self.sched.add_interval_job(interval['handle'], hours = interval['hours'], minutes = interval['minutes'], seconds = interval['seconds'])
|
job = self.sched.add_interval_job(interval['handle'], hours = interval['hours'], minutes = interval['minutes'], seconds = interval['seconds'])
|
||||||
interval['job'] = job
|
interval['job'] = job
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
log.error("Failed adding interval cronjob: %s" % e)
|
log.error('Failed adding interval cronjob: %s', e)
|
||||||
|
|
||||||
# Start it
|
# Start it
|
||||||
log.debug('Starting scheduler')
|
log.debug('Starting scheduler')
|
||||||
@@ -75,7 +75,7 @@ class Scheduler(Plugin):
|
|||||||
self.started = False
|
self.started = False
|
||||||
|
|
||||||
def cron(self, identifier = '', handle = None, day = '*', hour = '*', minute = '*'):
|
def cron(self, identifier = '', handle = None, day = '*', hour = '*', minute = '*'):
|
||||||
log.info('Scheduling "%s", cron: day = %s, hour = %s, minute = %s' % (identifier, day, hour, minute))
|
log.info('Scheduling "%s", cron: day = %s, hour = %s, minute = %s', (identifier, day, hour, minute))
|
||||||
|
|
||||||
self.remove(identifier)
|
self.remove(identifier)
|
||||||
self.crons[identifier] = {
|
self.crons[identifier] = {
|
||||||
@@ -86,7 +86,7 @@ class Scheduler(Plugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def interval(self, identifier = '', handle = None, hours = 0, minutes = 0, seconds = 0):
|
def interval(self, identifier = '', handle = None, hours = 0, minutes = 0, seconds = 0):
|
||||||
log.info('Scheduling %s, interval: hours = %s, minutes = %s, seconds = %s' % (identifier, hours, minutes, seconds))
|
log.info('Scheduling %s, interval: hours = %s, minutes = %s, seconds = %s', (identifier, hours, minutes, seconds))
|
||||||
|
|
||||||
self.remove(identifier)
|
self.remove(identifier)
|
||||||
self.intervals[identifier] = {
|
self.intervals[identifier] = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.request import jsonified
|
from couchpotato.core.helpers.request import jsonified
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
@@ -19,6 +20,8 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class Updater(Plugin):
|
class Updater(Plugin):
|
||||||
|
|
||||||
|
available_notified = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
if Env.get('desktop'):
|
if Env.get('desktop'):
|
||||||
@@ -28,7 +31,7 @@ class Updater(Plugin):
|
|||||||
else:
|
else:
|
||||||
self.updater = SourceUpdater()
|
self.updater = SourceUpdater()
|
||||||
|
|
||||||
fireEvent('schedule.interval', 'updater.check', self.check, hours = 6)
|
fireEvent('schedule.interval', 'updater.check', self.autoUpdate, hours = 6)
|
||||||
addEvent('app.load', self.check)
|
addEvent('app.load', self.check)
|
||||||
addEvent('updater.info', self.info)
|
addEvent('updater.info', self.info)
|
||||||
|
|
||||||
@@ -48,17 +51,36 @@ class Updater(Plugin):
|
|||||||
'return': {'type': 'see updater.info'}
|
'return': {'type': 'see updater.info'}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def autoUpdate(self):
|
||||||
|
if self.check() and self.conf('automatic') and not self.updater.update_failed:
|
||||||
|
if self.updater.doUpdate():
|
||||||
|
|
||||||
|
# Notify before restarting
|
||||||
|
try:
|
||||||
|
if self.conf('notification'):
|
||||||
|
info = self.updater.info()
|
||||||
|
version_date = datetime.fromtimestamp(info['update_version']['date'])
|
||||||
|
fireEvent('updater.updated', 'Updated to a new version with hash "%s", this version is from %s' % (info['update_version']['hash'], version_date), data = info)
|
||||||
|
except:
|
||||||
|
log.error('Failed notifying for update: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
fireEventAsync('app.restart')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
if self.isDisabled():
|
if self.isDisabled():
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.updater.check():
|
if self.updater.check():
|
||||||
if self.conf('automatic') and not self.updater.update_failed:
|
if not self.available_notified and self.conf('notification') and not self.conf('automatic'):
|
||||||
if self.updater.doUpdate():
|
fireEvent('updater.available', message = 'A new update is available', data = self.updater.info())
|
||||||
fireEventAsync('app.crappy_restart')
|
self.available_notified = True
|
||||||
else:
|
return True
|
||||||
if self.conf('notification'):
|
|
||||||
fireEvent('updater.available', message = 'A new update is available', data = self.updater.info())
|
return False
|
||||||
|
|
||||||
def info(self):
|
def info(self):
|
||||||
return self.updater.info()
|
return self.updater.info()
|
||||||
@@ -67,12 +89,22 @@ class Updater(Plugin):
|
|||||||
return jsonified(self.updater.info())
|
return jsonified(self.updater.info())
|
||||||
|
|
||||||
def checkView(self):
|
def checkView(self):
|
||||||
self.check()
|
return jsonified({
|
||||||
return self.updater.getInfo()
|
'update_available': self.check(),
|
||||||
|
'info': self.updater.info()
|
||||||
|
})
|
||||||
|
|
||||||
def doUpdateView(self):
|
def doUpdateView(self):
|
||||||
|
|
||||||
|
self.check()
|
||||||
|
if not self.updater.update_version:
|
||||||
|
log.error('Trying to update when no update is available.')
|
||||||
|
success = False
|
||||||
|
else:
|
||||||
|
success = self.updater.doUpdate()
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': self.updater.doUpdate()
|
'success': success
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -107,7 +139,7 @@ class BaseUpdater(Plugin):
|
|||||||
|
|
||||||
def deletePyc(self, only_excess = True):
|
def deletePyc(self, only_excess = True):
|
||||||
|
|
||||||
for root, dirs, files in os.walk(Env.get('app_dir')):
|
for root, dirs, files in os.walk(ss(Env.get('app_dir'))):
|
||||||
|
|
||||||
pyc_files = filter(lambda filename: filename.endswith('.pyc'), files)
|
pyc_files = filter(lambda filename: filename.endswith('.pyc'), files)
|
||||||
py_files = set(filter(lambda filename: filename.endswith('.py'), files))
|
py_files = set(filter(lambda filename: filename.endswith('.py'), files))
|
||||||
@@ -115,11 +147,11 @@ class BaseUpdater(Plugin):
|
|||||||
|
|
||||||
for excess_pyc_file in excess_pyc_files:
|
for excess_pyc_file in excess_pyc_files:
|
||||||
full_path = os.path.join(root, excess_pyc_file)
|
full_path = os.path.join(root, excess_pyc_file)
|
||||||
log.debug('Removing old PYC file: %s' % full_path)
|
log.debug('Removing old PYC file: %s', full_path)
|
||||||
try:
|
try:
|
||||||
os.remove(full_path)
|
os.remove(full_path)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t remove %s: %s' % (full_path, traceback.format_exc()))
|
log.error('Couldn\'t remove %s: %s', (full_path, traceback.format_exc()))
|
||||||
|
|
||||||
for dir_name in dirs:
|
for dir_name in dirs:
|
||||||
full_path = os.path.join(root, dir_name)
|
full_path = os.path.join(root, dir_name)
|
||||||
@@ -127,7 +159,7 @@ class BaseUpdater(Plugin):
|
|||||||
try:
|
try:
|
||||||
os.rmdir(full_path)
|
os.rmdir(full_path)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t remove empty directory %s: %s' % (full_path, traceback.format_exc()))
|
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -137,24 +169,20 @@ class GitUpdater(BaseUpdater):
|
|||||||
self.repo = LocalRepository(Env.get('app_dir'), command = git_command)
|
self.repo = LocalRepository(Env.get('app_dir'), command = git_command)
|
||||||
|
|
||||||
def doUpdate(self):
|
def doUpdate(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.debug('Stashing local changes')
|
log.debug('Stashing local changes')
|
||||||
self.repo.saveStash()
|
self.repo.saveStash()
|
||||||
|
|
||||||
log.info('Updating to latest version')
|
log.info('Updating to latest version')
|
||||||
info = self.info()
|
|
||||||
self.repo.pull()
|
self.repo.pull()
|
||||||
|
|
||||||
# Delete leftover .pyc files
|
# Delete leftover .pyc files
|
||||||
self.deletePyc()
|
self.deletePyc()
|
||||||
|
|
||||||
# Notify before returning and restarting
|
|
||||||
version_date = datetime.fromtimestamp(info['update_version']['date'])
|
|
||||||
fireEvent('updater.updated', 'Updated to a new version with hash "%s", this version is from %s' % (info['update_version']['hash'], version_date), data = info)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
log.error('Failed updating via GIT: %s' % traceback.format_exc())
|
log.error('Failed updating via GIT: %s', traceback.format_exc())
|
||||||
|
|
||||||
self.update_failed = True
|
self.update_failed = True
|
||||||
|
|
||||||
@@ -165,14 +193,14 @@ class GitUpdater(BaseUpdater):
|
|||||||
if not self.version:
|
if not self.version:
|
||||||
try:
|
try:
|
||||||
output = self.repo.getHead() # Yes, please
|
output = self.repo.getHead() # Yes, please
|
||||||
log.debug('Git version output: %s' % output.hash)
|
log.debug('Git version output: %s', output.hash)
|
||||||
self.version = {
|
self.version = {
|
||||||
'hash': output.hash[:8],
|
'hash': output.hash[:8],
|
||||||
'date': output.getDate(),
|
'date': output.getDate(),
|
||||||
'type': 'git',
|
'type': 'git',
|
||||||
}
|
}
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed using GIT updater, running from source, you need to have GIT installed. %s' % e)
|
log.error('Failed using GIT updater, running from source, you need to have GIT installed. %s', e)
|
||||||
return 'No GIT'
|
return 'No GIT'
|
||||||
|
|
||||||
return self.version
|
return self.version
|
||||||
@@ -182,7 +210,7 @@ class GitUpdater(BaseUpdater):
|
|||||||
if self.update_version:
|
if self.update_version:
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info('Checking for new version on github for %s' % self.repo_name)
|
log.info('Checking for new version on github for %s', self.repo_name)
|
||||||
if not Env.get('dev'):
|
if not Env.get('dev'):
|
||||||
self.repo.fetch()
|
self.repo.fetch()
|
||||||
|
|
||||||
@@ -194,7 +222,7 @@ class GitUpdater(BaseUpdater):
|
|||||||
local = self.repo.getHead()
|
local = self.repo.getHead()
|
||||||
remote = branch.getHead()
|
remote = branch.getHead()
|
||||||
|
|
||||||
log.info('Versions, local:%s, remote:%s' % (local.hash[:8], remote.hash[:8]))
|
log.info('Versions, local:%s, remote:%s', (local.hash[:8], remote.hash[:8]))
|
||||||
|
|
||||||
if local.getDate() < remote.getDate():
|
if local.getDate() < remote.getDate():
|
||||||
self.update_version = {
|
self.update_version = {
|
||||||
@@ -237,24 +265,24 @@ class SourceUpdater(BaseUpdater):
|
|||||||
tar.close()
|
tar.close()
|
||||||
os.remove(destination)
|
os.remove(destination)
|
||||||
|
|
||||||
self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0]))
|
if self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0])):
|
||||||
self.removeDir(extracted_path)
|
self.removeDir(extracted_path)
|
||||||
|
|
||||||
# Write update version to file
|
# Write update version to file
|
||||||
self.createFile(self.version_file, json.dumps(self.update_version))
|
self.createFile(self.version_file, json.dumps(self.update_version))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
log.error('Failed updating: %s' % traceback.format_exc())
|
log.error('Failed updating: %s', traceback.format_exc())
|
||||||
|
|
||||||
self.update_failed = True
|
self.update_failed = True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def replaceWith(self, path):
|
def replaceWith(self, path):
|
||||||
app_dir = Env.get('app_dir')
|
app_dir = ss(Env.get('app_dir'))
|
||||||
|
|
||||||
# Get list of files we want to overwrite
|
# Get list of files we want to overwrite
|
||||||
self.deletePyc(only_excess = False)
|
self.deletePyc()
|
||||||
existing_files = []
|
existing_files = []
|
||||||
for root, subfiles, filenames in os.walk(app_dir):
|
for root, subfiles, filenames in os.walk(app_dir):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
@@ -267,18 +295,30 @@ class SourceUpdater(BaseUpdater):
|
|||||||
|
|
||||||
if not Env.get('dev'):
|
if not Env.get('dev'):
|
||||||
try:
|
try:
|
||||||
os.remove(tofile)
|
if os.path.isfile(tofile):
|
||||||
except:
|
os.remove(tofile)
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
dirname = os.path.dirname(tofile)
|
||||||
os.renames(fromfile, tofile)
|
if not os.path.isdir(dirname):
|
||||||
|
self.makeDir(dirname)
|
||||||
|
|
||||||
|
os.rename(fromfile, tofile)
|
||||||
try:
|
try:
|
||||||
existing_files.remove(tofile)
|
existing_files.remove(tofile)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
except Exception, e:
|
except:
|
||||||
log.error('Failed overwriting file: %s' % e)
|
log.error('Failed overwriting file "%s": %s', (tofile, traceback.format_exc()))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if Env.get('app_dir') not in Env.get('data_dir'):
|
||||||
|
for still_exists in existing_files:
|
||||||
|
try:
|
||||||
|
os.remove(still_exists)
|
||||||
|
except:
|
||||||
|
log.error('Failed removing non-used file: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def removeDir(self, path):
|
def removeDir(self, path):
|
||||||
@@ -297,11 +337,11 @@ class SourceUpdater(BaseUpdater):
|
|||||||
output = json.loads(f.read())
|
output = json.loads(f.read())
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
log.debug('Source version output: %s' % output)
|
log.debug('Source version output: %s', output)
|
||||||
self.version = output
|
self.version = output
|
||||||
self.version['type'] = 'source'
|
self.version['type'] = 'source'
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed using source updater. %s' % e)
|
log.error('Failed using source updater. %s', e)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return self.version
|
return self.version
|
||||||
@@ -318,7 +358,7 @@ class SourceUpdater(BaseUpdater):
|
|||||||
|
|
||||||
self.last_check = time.time()
|
self.last_check = time.time()
|
||||||
except:
|
except:
|
||||||
log.error('Failed updating via source: %s' % traceback.format_exc())
|
log.error('Failed updating via source: %s', traceback.format_exc())
|
||||||
|
|
||||||
return self.update_version is not None
|
return self.update_version is not None
|
||||||
|
|
||||||
@@ -333,12 +373,12 @@ class SourceUpdater(BaseUpdater):
|
|||||||
'date': int(time.mktime(parse(commit['commit']['committer']['date']).timetuple())),
|
'date': int(time.mktime(parse(commit['commit']['committer']['date']).timetuple())),
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
log.error('Failed getting latest request from github: %s' % traceback.format_exc())
|
log.error('Failed getting latest request from github: %s', traceback.format_exc())
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
class DesktopUpdater(Plugin):
|
class DesktopUpdater(BaseUpdater):
|
||||||
|
|
||||||
version = None
|
version = None
|
||||||
update_failed = False
|
update_failed = False
|
||||||
@@ -350,9 +390,15 @@ class DesktopUpdater(Plugin):
|
|||||||
|
|
||||||
def doUpdate(self):
|
def doUpdate(self):
|
||||||
try:
|
try:
|
||||||
self.desktop.CheckForUpdate(silentUnlessUpdate = True)
|
def do_restart(e):
|
||||||
|
if e['status'] == 'done':
|
||||||
|
fireEventAsync('app.restart')
|
||||||
|
else:
|
||||||
|
log.error('Failed updating desktop: %s', e['exception'])
|
||||||
|
self.update_failed = True
|
||||||
|
|
||||||
|
self.desktop._esky.auto_update(callback = do_restart)
|
||||||
except:
|
except:
|
||||||
log.error('Failed updating desktop: %s' % traceback.format_exc())
|
|
||||||
self.update_failed = True
|
self.update_failed = True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -379,7 +425,7 @@ class DesktopUpdater(Plugin):
|
|||||||
|
|
||||||
self.last_check = time.time()
|
self.last_check = time.time()
|
||||||
except:
|
except:
|
||||||
log.error('Failed updating desktop: %s' % traceback.format_exc())
|
log.error('Failed updating desktop: %s', traceback.format_exc())
|
||||||
|
|
||||||
return self.update_version is not None
|
return self.update_version is not None
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,17 @@ var UpdaterBase = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
Api.request('updater.check', {
|
Api.request('updater.check', {
|
||||||
'onComplete': onComplete || Function.from()
|
'onComplete': function(json){
|
||||||
|
if(onComplete)
|
||||||
|
onComplete(json);
|
||||||
|
|
||||||
|
if(json.update_available)
|
||||||
|
self.doUpdate();
|
||||||
|
else {
|
||||||
|
App.unBlockPage();
|
||||||
|
App.fireEvent('message', 'No updates available');
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -52,7 +62,7 @@ var UpdaterBase = new Class({
|
|||||||
createMessage: function(data){
|
createMessage: function(data){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var changelog = 'https://github.com/'+data.repo_name+'/compare/'+data.version.hash+'...'+data.update_version.hash;
|
var changelog = 'https://github.com/'+data.repo_name+'/compare/'+data.version.hash+'...'+data.branch;
|
||||||
if(data.update_version.changelog)
|
if(data.update_version.changelog)
|
||||||
changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash
|
changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash
|
||||||
|
|
||||||
@@ -81,13 +91,19 @@ var UpdaterBase = new Class({
|
|||||||
Api.request('updater.update', {
|
Api.request('updater.update', {
|
||||||
'onComplete': function(json){
|
'onComplete': function(json){
|
||||||
if(json.success){
|
if(json.success){
|
||||||
App.restart('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
|
self.updating();
|
||||||
App.checkAvailable.delay(500, App);
|
|
||||||
if(self.message)
|
|
||||||
self.message.destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updating: function(){
|
||||||
|
App.blockPage('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
|
||||||
|
App.checkAvailable.delay(500, App, [1000, function(){
|
||||||
|
window.location.reload();
|
||||||
|
}]);
|
||||||
|
if(self.message)
|
||||||
|
self.message.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Downloader(Plugin):
|
|||||||
|
|
||||||
def createFileName(self, data, filedata, movie):
|
def createFileName(self, data, filedata, movie):
|
||||||
name = os.path.join(self.createNzbName(data, movie))
|
name = os.path.join(self.createNzbName(data, movie))
|
||||||
if data.get('type') == 'nzb' and "DOCTYPE nzb" not in filedata:
|
if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '</nzb>' not in filedata:
|
||||||
return '%s.%s' % (name, 'rar')
|
return '%s.%s' % (name, 'rar')
|
||||||
return '%s.%s' % (name, data.get('type'))
|
return '%s.%s' % (name, data.get('type'))
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ config = [{
|
|||||||
'label': 'Use for',
|
'label': 'Use for',
|
||||||
'default': 'both',
|
'default': 'both',
|
||||||
'type': 'dropdown',
|
'type': 'dropdown',
|
||||||
'values': [('nzbs & torrents', 'both'), ('nzb', 'nzb'), ('torrent', 'torrent')],
|
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrent', 'torrent')],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'manual',
|
'name': 'manual',
|
||||||
|
|||||||
@@ -10,38 +10,36 @@ class Blackhole(Downloader):
|
|||||||
|
|
||||||
type = ['nzb', 'torrent']
|
type = ['nzb', 'torrent']
|
||||||
|
|
||||||
def download(self, data = {}, movie = {}, manual = False):
|
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', data.get('type')])):
|
if self.isDisabled(manual) or (not self.isCorrectType(data.get('type')) or (not self.conf('use_for') in ['both', data.get('type')])):
|
||||||
return
|
return
|
||||||
|
|
||||||
directory = self.conf('directory')
|
directory = self.conf('directory')
|
||||||
if not directory or not os.path.isdir(directory):
|
if not directory or not os.path.isdir(directory):
|
||||||
log.error('No directory set for blackhole %s download.' % data.get('type'))
|
log.error('No directory set for blackhole %s download.', data.get('type'))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
if not filedata or len(filedata) < 50:
|
||||||
|
log.error('No nzb/torrent available!')
|
||||||
if len(filedata) < 50:
|
|
||||||
log.error('No nzb available!')
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
fullPath = os.path.join(directory, self.createFileName(data, filedata, movie))
|
fullPath = os.path.join(directory, self.createFileName(data, filedata, movie))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not os.path.isfile(fullPath):
|
if not os.path.isfile(fullPath):
|
||||||
log.info('Downloading %s to %s.' % (data.get('type'), fullPath))
|
log.info('Downloading %s to %s.', (data.get('type'), fullPath))
|
||||||
with open(fullPath, 'wb') as f:
|
with open(fullPath, 'wb') as f:
|
||||||
f.write(filedata)
|
f.write(filedata)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
log.info('File %s already exists.' % fullPath)
|
log.info('File %s already exists.', fullPath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed to download to blackhole %s' % traceback.format_exc())
|
log.error('Failed to download to blackhole %s', traceback.format_exc())
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.debug('Failed to download file %s: %s' % (data.get('name'), traceback.format_exc()))
|
log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc()))
|
||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ class NZBGet(Downloader):
|
|||||||
|
|
||||||
url = 'http://nzbget:%(password)s@%(host)s/xmlrpc'
|
url = 'http://nzbget:%(password)s@%(host)s/xmlrpc'
|
||||||
|
|
||||||
def download(self, data = {}, movie = {}, manual = False):
|
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||||
|
|
||||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info('Sending "%s" to NZBGet.' % data.get('name'))
|
if not filedata:
|
||||||
|
log.error('Unable to get NZB file: %s', traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|
||||||
|
log.info('Sending "%s" to NZBGet.', data.get('name'))
|
||||||
|
|
||||||
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
|
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
|
||||||
nzb_name = '%s.nzb' % self.createNzbName(data, movie)
|
nzb_name = '%s.nzb' % self.createNzbName(data, movie)
|
||||||
@@ -37,25 +41,12 @@ class NZBGet(Downloader):
|
|||||||
if e.errcode == 401:
|
if e.errcode == 401:
|
||||||
log.error('Password is incorrect.')
|
log.error('Password is incorrect.')
|
||||||
else:
|
else:
|
||||||
log.error('Protocol Error: %s' % e)
|
log.error('Protocol Error: %s', e)
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
if isfunction(data.get('download')):
|
|
||||||
filedata = data.get('download')()
|
|
||||||
if not filedata:
|
|
||||||
log.error('Failed download file: %s' % nzb_name)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
log.info('Downloading: %s' % data.get('url'))
|
|
||||||
filedata = self.urlopen(data.get('url'))
|
|
||||||
except:
|
|
||||||
log.error('Unable to get NZB file: %s' % traceback.format_exc())
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip())):
|
if rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip())):
|
||||||
log.info('NZB sent successfully to NZBGet')
|
log.info('NZB sent successfully to NZBGet')
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
log.error('NZBGet could not add %s to the queue.' % nzb_name)
|
log.error('NZBGet could not add %s to the queue.', nzb_name)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
from .main import Pneumatic
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Pneumatic()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'pneumatic',
|
||||||
|
'order': 30,
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'downloaders',
|
||||||
|
'name': 'pneumatic',
|
||||||
|
'label': 'Pneumatic',
|
||||||
|
'description': 'Download the .strm file to a specific folder.',
|
||||||
|
'wizard': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'directory',
|
||||||
|
'type': 'directory',
|
||||||
|
'description': 'Directory where the .strm file is saved to.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'manual',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
|
from couchpotato.core.downloaders.base import Downloader
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
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 = {}, 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):
|
||||||
|
log.error('No directory set for .strm downloads.')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if not filedata or len(filedata) < 50:
|
||||||
|
log.error('No nzb available!')
|
||||||
|
return False
|
||||||
|
|
||||||
|
fullPath = os.path.join(directory, self.createFileName(data, filedata, movie))
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not os.path.isfile(fullPath):
|
||||||
|
log.info('Downloading %s to %s.', (data.get('type'), fullPath))
|
||||||
|
with open(fullPath, 'wb') as f:
|
||||||
|
f.write(filedata)
|
||||||
|
|
||||||
|
nzb_name = self.createNzbName(data, movie)
|
||||||
|
strm_path = os.path.join(directory, nzb_name)
|
||||||
|
|
||||||
|
strm_file = open(strm_path + '.strm', 'wb')
|
||||||
|
strmContent = self.strm_syntax % (fullPath, nzb_name)
|
||||||
|
strm_file.write(strmContent)
|
||||||
|
strm_file.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
log.info('File %s already exists.', fullPath)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed to download .strm: %s', traceback.format_exc())
|
||||||
|
pass
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc()))
|
||||||
|
return False
|
||||||
|
return False
|
||||||
@@ -33,12 +33,6 @@ config = [{
|
|||||||
'label': 'Category',
|
'label': 'Category',
|
||||||
'description': 'The category CP places the nzb in. Like <strong>movies</strong> or <strong>couchpotato</strong>',
|
'description': 'The category CP places the nzb in. Like <strong>movies</strong> or <strong>couchpotato</strong>',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'advanced': True,
|
|
||||||
'name': 'pp_directory',
|
|
||||||
'type': 'directory',
|
|
||||||
'description': 'Your Post-Processing Script directory, set in Sabnzbd > Config > Directories.',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
'name': 'manual',
|
'name': 'manual',
|
||||||
'default': 0,
|
'default': 0,
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ from couchpotato.core.downloaders.base import Downloader
|
|||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from inspect import ismethod, isfunction
|
|
||||||
from tempfile import mkstemp
|
|
||||||
import base64
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -15,25 +10,12 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
type = ['nzb']
|
type = ['nzb']
|
||||||
|
|
||||||
def download(self, data = {}, movie = {}, manual = False):
|
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||||
|
|
||||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info("Sending '%s' to SABnzbd." % data.get('name'))
|
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
||||||
|
|
||||||
if self.conf('ppDir') and data.get('imdb_id'):
|
|
||||||
try:
|
|
||||||
pp_script_fn = self.buildPp(data.get('imdb_id'))
|
|
||||||
except:
|
|
||||||
log.info("Failed to create post-processing script.")
|
|
||||||
pp_script_fn = False
|
|
||||||
if not pp_script_fn:
|
|
||||||
pp = False
|
|
||||||
else:
|
|
||||||
pp = True
|
|
||||||
else:
|
|
||||||
pp = False
|
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'apikey': self.conf('api_key'),
|
'apikey': self.conf('api_key'),
|
||||||
@@ -42,29 +24,24 @@ class Sabnzbd(Downloader):
|
|||||||
'nzbname': self.createNzbName(data, movie),
|
'nzbname': self.createNzbName(data, movie),
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
if filedata:
|
||||||
nzb_file = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
if len(filedata) < 50:
|
||||||
|
log.error('No proper nzb available!')
|
||||||
if not nzb_file or len(nzb_file) < 50:
|
|
||||||
log.error('No nzb available!')
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
||||||
nzb_filename = self.createFileName(data, nzb_file, movie)
|
nzb_filename = self.createFileName(data, filedata, movie)
|
||||||
params['mode'] = 'addfile'
|
params['mode'] = 'addfile'
|
||||||
else:
|
else:
|
||||||
params['name'] = data.get('url')
|
params['name'] = data.get('url')
|
||||||
|
|
||||||
if pp:
|
|
||||||
params['script'] = pp_script_fn
|
|
||||||
|
|
||||||
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if params.get('mode') is 'addfile':
|
if params.get('mode') is 'addfile':
|
||||||
data = self.urlopen(url, params = {"nzbfile": (nzb_filename, nzb_file)}, multipart = True, show_error = False)
|
data = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False)
|
||||||
else:
|
else:
|
||||||
data = self.urlopen(url, show_error = False)
|
data = self.urlopen(url, timeout = 60, show_error = False)
|
||||||
except:
|
except:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
@@ -84,48 +61,3 @@ class Sabnzbd(Downloader):
|
|||||||
else:
|
else:
|
||||||
log.error("Unknown error: " + result[:40])
|
log.error("Unknown error: " + result[:40])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def buildPp(self, imdb_id):
|
|
||||||
|
|
||||||
pp_script_path = self.getPpFile()
|
|
||||||
|
|
||||||
scriptB64 = '''IyEvdXNyL2Jpbi9weXRob24KaW1wb3J0IG9zCmltcG9ydCBzeXMKcHJpbnQgIkNyZWF0aW5nIGNwLmNw
|
|
||||||
bmZvIGZvciAlcyIgJSBzeXMuYXJndlsxXQppbWRiSWQgPSB7W0lNREJJREhFUkVdfQpwYXRoID0gb3Mu
|
|
||||||
cGF0aC5qb2luKHN5cy5hcmd2WzFdLCAiY3AuY3BuZm8iKQp0cnk6CiBmID0gb3BlbihwYXRoLCAndycp
|
|
||||||
CmV4Y2VwdCBJT0Vycm9yOgogcHJpbnQgIlVuYWJsZSB0byBvcGVuICVzIGZvciB3cml0aW5nIiAlIHBh
|
|
||||||
dGgKIHN5cy5leGl0KDEpCnRyeToKIGYud3JpdGUob3MucGF0aC5iYXNlbmFtZShzeXMuYXJndlswXSkr
|
|
||||||
IlxuIitpbWRiSWQpCmV4Y2VwdDoKIHByaW50ICJVbmFibGUgdG8gd3JpdGUgdG8gZmlsZTogJXMiICUg
|
|
||||||
cGF0aAogc3lzLmV4aXQoMikKZi5jbG9zZSgpCnByaW50ICJXcm90ZSBpbWRiIGlkLCAlcywgdG8gZmls
|
|
||||||
ZTogJXMiICUgKGltZGJJZCwgcGF0aCkK'''
|
|
||||||
|
|
||||||
script = re.sub(r"\{\[IMDBIDHERE\]\}", "'%s'" % imdb_id, base64.b64decode(scriptB64))
|
|
||||||
|
|
||||||
try:
|
|
||||||
f = open(pp_script_path, 'wb')
|
|
||||||
except:
|
|
||||||
log.info("Unable to open post-processing script for writing. Check permissions: %s" % pp_script_path)
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
f.write(script)
|
|
||||||
f.close()
|
|
||||||
except:
|
|
||||||
log.info("Unable to write to post-processing script. Check permissions: %s" % pp_script_path)
|
|
||||||
return False
|
|
||||||
|
|
||||||
log.info("Wrote post-processing script to: %s" % pp_script_path)
|
|
||||||
|
|
||||||
return os.path.basename(pp_script_path)
|
|
||||||
|
|
||||||
def getPpFile(self):
|
|
||||||
|
|
||||||
pp_script_handle, pp_script_path = mkstemp(suffix = '.py', dir = self.conf('ppDir'))
|
|
||||||
pp_sh = os.fdopen(pp_script_handle)
|
|
||||||
pp_sh.close()
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.chmod(pp_script_path, int('777', 8))
|
|
||||||
except:
|
|
||||||
log.info("Unable to set post-processing script permissions to 777 (may still work correctly): %s" % pp_script_path)
|
|
||||||
|
|
||||||
return pp_script_path
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'paused',
|
'name': 'paused',
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
|
'default': False,
|
||||||
'description': 'Add the torrent paused.',
|
'description': 'Add the torrent paused.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,48 +2,139 @@ from base64 import b64encode
|
|||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader
|
||||||
from couchpotato.core.helpers.encoding import isInt
|
from couchpotato.core.helpers.encoding import isInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from libs import transmissionrpc
|
import httplib
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import urllib2
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Transmission(Downloader):
|
class Transmission(Downloader):
|
||||||
|
|
||||||
type = ['torrent']
|
type = ['torrent', 'torrent_magnet']
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
def download(self, data = {}, movie = {}, manual = False):
|
def download(self, data, movie, manual = False, filedata = None):
|
||||||
|
|
||||||
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info('Sending "%s" to Transmission.' % data.get('name'))
|
log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
|
||||||
|
|
||||||
# Load host from config and split out port.
|
# Load host from config and split out port.
|
||||||
host = self.conf('host').split(':')
|
host = self.conf('host').split(':')
|
||||||
if not isInt(host[1]):
|
if not isInt(host[1]):
|
||||||
log.error("Config properties are not filled in correctly, port is missing.")
|
log.error('Config properties are not filled in correctly, port is missing.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Set parameters for Transmission
|
# Set parameters for Transmission
|
||||||
params = {
|
params = {
|
||||||
'paused': self.conf('paused', default = 0),
|
'paused': self.conf('paused', default = 0),
|
||||||
'download_dir': self.conf('directory', default = None)
|
'download-dir': self.conf('directory', default = None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Send request to Transmission
|
||||||
try:
|
try:
|
||||||
tc = transmissionrpc.Client(host[0], port = host[1], user = self.conf('username'), password = self.conf('password'))
|
trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
|
||||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
if data.get('type') == 'torrent_magnet':
|
||||||
torrent = tc.add_torrent(b64encode(filedata), **params)
|
remote_torrent = trpc.add_torrent_uri(data.get('url'), arguments = params)
|
||||||
|
else:
|
||||||
|
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
|
||||||
|
|
||||||
# Change settings of added torrents
|
# Change settings of added torrents
|
||||||
try:
|
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
||||||
torrent.seed_ratio_limit = self.conf('ratio')
|
|
||||||
torrent.seed_ratio_mode = 'single' if self.conf('ratio') else 'global'
|
|
||||||
except transmissionrpc.TransmissionError, e:
|
|
||||||
log.error('Failed to change settings for transfer in transmission: %s' % e)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
except Exception, err:
|
||||||
except transmissionrpc.TransmissionError, e:
|
log.error('Failed to change settings for transfer: %s', err)
|
||||||
log.error('Failed to send link to transmission: %s' % e)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class TransmissionRPC(object):
|
||||||
|
|
||||||
|
"""TransmissionRPC lite library"""
|
||||||
|
|
||||||
|
def __init__(self, host = 'localhost', port = 9091, username = None, password = None):
|
||||||
|
|
||||||
|
super(TransmissionRPC, self).__init__()
|
||||||
|
|
||||||
|
self.url = 'http://' + host + ':' + str(port) + '/transmission/rpc'
|
||||||
|
self.tag = 0
|
||||||
|
self.session_id = 0
|
||||||
|
self.session = {}
|
||||||
|
if username and password:
|
||||||
|
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||||
|
password_manager.add_password(realm = None, uri = self.url, user = username, passwd = password)
|
||||||
|
opener = urllib2.build_opener(urllib2.HTTPBasicAuthHandler(password_manager), urllib2.HTTPDigestAuthHandler(password_manager))
|
||||||
|
opener.addheaders = [('User-agent', 'couchpotato-transmission-client/1.0')]
|
||||||
|
urllib2.install_opener(opener)
|
||||||
|
elif username or password:
|
||||||
|
log.debug('User or password missing, not using authentication.')
|
||||||
|
self.session = self.get_session()
|
||||||
|
|
||||||
|
def _request(self, ojson):
|
||||||
|
self.tag += 1
|
||||||
|
headers = {'x-transmission-session-id': str(self.session_id)}
|
||||||
|
request = urllib2.Request(self.url, json.dumps(ojson).encode('utf-8'), headers)
|
||||||
|
try:
|
||||||
|
open_request = urllib2.urlopen(request)
|
||||||
|
response = json.loads(open_request.read())
|
||||||
|
log.debug('response: %s', json.dumps(response))
|
||||||
|
if response['result'] == 'success':
|
||||||
|
log.debug('Transmission action successfull')
|
||||||
|
return response['arguments']
|
||||||
|
else:
|
||||||
|
log.debug('Unknown failure sending command to Transmission. Return text is: %s', response['result'])
|
||||||
|
return False
|
||||||
|
except httplib.InvalidURL, err:
|
||||||
|
log.error('Invalid Transmission host, check your config %s', err)
|
||||||
|
return False
|
||||||
|
except urllib2.HTTPError, err:
|
||||||
|
if err.code == 401:
|
||||||
|
log.error('Invalid Transmission Username or Password, check your config')
|
||||||
|
return False
|
||||||
|
elif err.code == 409:
|
||||||
|
msg = str(err.read())
|
||||||
|
try:
|
||||||
|
self.session_id = \
|
||||||
|
re.search('X-Transmission-Session-Id:\s*(\w+)', msg).group(1)
|
||||||
|
log.debug('X-Transmission-Session-Id: %s', self.session_id)
|
||||||
|
|
||||||
|
# #resend request with the updated header
|
||||||
|
|
||||||
|
return self._request(ojson)
|
||||||
|
except:
|
||||||
|
log.error('Unable to get Transmission Session-Id %s', err)
|
||||||
|
else:
|
||||||
|
log.error('TransmissionRPC HTTPError: %s', err)
|
||||||
|
except urllib2.URLError, err:
|
||||||
|
log.error('Unable to connect to Transmission %s', err)
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
post_data = {'method': 'session-get', 'tag': self.tag}
|
||||||
|
return self._request(post_data)
|
||||||
|
|
||||||
|
def add_torrent_uri(self, torrent, arguments):
|
||||||
|
arguments['filename'] = torrent
|
||||||
|
post_data = {'arguments': arguments, 'method': 'torrent-add', 'tag': self.tag}
|
||||||
|
return self._request(post_data)
|
||||||
|
|
||||||
|
def add_torrent_file(self, torrent, arguments):
|
||||||
|
arguments['metainfo'] = torrent
|
||||||
|
post_data = {'arguments': arguments, 'method': 'torrent-add', 'tag': self.tag}
|
||||||
|
return self._request(post_data)
|
||||||
|
|
||||||
|
def set_torrent(self, torrent_id, arguments):
|
||||||
|
arguments['ids'] = torrent_id
|
||||||
|
post_data = {'arguments': arguments, 'method': 'torrent-set', 'tag': self.tag}
|
||||||
|
return self._request(post_data)
|
||||||
|
|||||||
+20
-14
@@ -12,7 +12,7 @@ def runHandler(name, handler, *args, **kwargs):
|
|||||||
return handler(*args, **kwargs)
|
return handler(*args, **kwargs)
|
||||||
except:
|
except:
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
log.error('Error in event "%s", that wasn\'t caught: %s%s' % (name, traceback.format_exc(), Env.all()))
|
log.error('Error in event "%s", that wasn\'t caught: %s%s', (name, traceback.format_exc(), Env.all()))
|
||||||
|
|
||||||
def addEvent(name, handler, priority = 100):
|
def addEvent(name, handler, priority = 100):
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ def removeEvent(name, handler):
|
|||||||
|
|
||||||
def fireEvent(name, *args, **kwargs):
|
def fireEvent(name, *args, **kwargs):
|
||||||
if not events.get(name): return
|
if not events.get(name): return
|
||||||
#log.debug('Firing event %s' % name)
|
#log.debug('Firing event %s', name)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# Fire after event
|
# Fire after event
|
||||||
@@ -53,6 +53,13 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
is_after_event = True
|
is_after_event = True
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
# onComplete event
|
||||||
|
on_complete = False
|
||||||
|
try:
|
||||||
|
on_complete = kwargs['on_complete']
|
||||||
|
del kwargs['on_complete']
|
||||||
|
except: pass
|
||||||
|
|
||||||
# Return single handler
|
# Return single handler
|
||||||
single = False
|
single = False
|
||||||
try:
|
try:
|
||||||
@@ -93,7 +100,7 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
elif r[1]:
|
elif r[1]:
|
||||||
errorHandler(r[1])
|
errorHandler(r[1])
|
||||||
else:
|
else:
|
||||||
log.debug('Assume disabled eventhandler for: %s' % name)
|
log.debug('Assume disabled eventhandler for: %s', name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
results = []
|
results = []
|
||||||
@@ -123,30 +130,29 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
|
|
||||||
modified_results = fireEvent('result.modify.%s' % name, results, single = True)
|
modified_results = fireEvent('result.modify.%s' % name, results, single = True)
|
||||||
if modified_results:
|
if modified_results:
|
||||||
log.debug('Return modified results for %s' % name)
|
log.debug('Return modified results for %s', name)
|
||||||
results = modified_results
|
results = modified_results
|
||||||
|
|
||||||
if not is_after_event:
|
if not is_after_event:
|
||||||
fireEvent('%s.after' % name, is_after_event = True)
|
fireEvent('%s.after' % name, is_after_event = True)
|
||||||
|
|
||||||
|
if on_complete:
|
||||||
|
on_complete()
|
||||||
|
|
||||||
return results
|
return results
|
||||||
except KeyError, e:
|
except KeyError, e:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error('%s: %s' % (name, traceback.format_exc()))
|
log.error('%s: %s', (name, traceback.format_exc()))
|
||||||
|
|
||||||
def fireEventAsync(name, *args, **kwargs):
|
def fireEventAsync(*args, **kwargs):
|
||||||
#log.debug('Async "%s": %s, %s' % (name, args, kwargs))
|
|
||||||
try:
|
try:
|
||||||
e = events[name]
|
my_thread = threading.Thread(target = fireEvent, args = args, kwargs = kwargs)
|
||||||
e.lock.acquire()
|
my_thread.setDaemon(True)
|
||||||
e.asynchronous = True
|
my_thread.start()
|
||||||
e.error_handler = errorHandler
|
|
||||||
e(*args, **kwargs)
|
|
||||||
e.lock.release()
|
|
||||||
return True
|
return True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('%s: %s' % (name, e))
|
log.error('%s: %s', (args[0], e))
|
||||||
|
|
||||||
def errorHandler(error):
|
def errorHandler(error):
|
||||||
etype, value, tb = error
|
etype, value, tb = error
|
||||||
|
|||||||
@@ -31,10 +31,14 @@ def toUnicode(original, *args):
|
|||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
log.error('Unable to decode value: %s... ' % repr(original)[:20])
|
log.error('Unable to decode value: %s... ', repr(original)[:20])
|
||||||
ascii_text = str(original).encode('string_escape')
|
ascii_text = str(original).encode('string_escape')
|
||||||
return toUnicode(ascii_text)
|
return toUnicode(ascii_text)
|
||||||
|
|
||||||
|
def ss(original, *args):
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
return toUnicode(original, *args).encode(Env.get('encoding'))
|
||||||
|
|
||||||
def ek(original, *args):
|
def ek(original, *args):
|
||||||
if isinstance(original, (str, unicode)):
|
if isinstance(original, (str, unicode)):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.variable import natcmp
|
from couchpotato.core.helpers.variable import natcmp
|
||||||
from flask.globals import current_app
|
from flask.globals import current_app
|
||||||
from flask.helpers import json
|
from flask.helpers import json, make_response
|
||||||
from libs.werkzeug.urls import url_decode
|
from libs.werkzeug.urls import url_decode
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
import flask
|
import flask
|
||||||
@@ -26,7 +26,7 @@ def getParams():
|
|||||||
|
|
||||||
for item in nested:
|
for item in nested:
|
||||||
if item is nested[-1]:
|
if item is nested[-1]:
|
||||||
current[item] = toUnicode(unquote(value)).encode('utf-8')
|
current[item] = toUnicode(unquote(value))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
current[item]
|
current[item]
|
||||||
@@ -35,7 +35,7 @@ def getParams():
|
|||||||
|
|
||||||
current = current[item]
|
current = current[item]
|
||||||
else:
|
else:
|
||||||
temp[param] = toUnicode(unquote(value)).encode('utf-8')
|
temp[param] = toUnicode(unquote(value))
|
||||||
|
|
||||||
return dictToList(temp)
|
return dictToList(temp)
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ def dictToList(params):
|
|||||||
|
|
||||||
def getParam(attr, default = None):
|
def getParam(attr, default = None):
|
||||||
try:
|
try:
|
||||||
return toUnicode(unquote(getattr(flask.request, 'args').get(attr, default))).encode('utf-8')
|
return toUnicode(unquote(getattr(flask.request, 'args').get(attr, default)))
|
||||||
except:
|
except:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
@@ -70,9 +70,13 @@ def jsonify(mimetype, *args, **kwargs):
|
|||||||
return getattr(current_app, 'response_class')(content, mimetype = mimetype)
|
return getattr(current_app, 'response_class')(content, mimetype = mimetype)
|
||||||
|
|
||||||
def jsonified(*args, **kwargs):
|
def jsonified(*args, **kwargs):
|
||||||
from couchpotato.environment import Env
|
|
||||||
callback = getParam('callback_func', None)
|
callback = getParam('callback_func', None)
|
||||||
if callback:
|
if callback:
|
||||||
return padded_jsonify(callback, *args, **kwargs)
|
content = padded_jsonify(callback, *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
return jsonify('text/javascript' if Env.doDebug() else 'application/json', *args, **kwargs)
|
content = jsonify('application/json', *args, **kwargs)
|
||||||
|
|
||||||
|
response = make_response(content)
|
||||||
|
response.cache_control.no_cache = True
|
||||||
|
|
||||||
|
return response
|
||||||
|
|||||||
@@ -47,5 +47,5 @@ class RSS(object):
|
|||||||
try:
|
try:
|
||||||
return XMLTree.parse(data).findall(path)
|
return XMLTree.parse(data).findall(path)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Error parsing RSS. %s' % e)
|
log.error('Error parsing RSS. %s', e)
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ from couchpotato.core.logger import CPLog
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os.path
|
import os.path
|
||||||
import platform
|
import platform
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -20,6 +23,10 @@ def getDataDir():
|
|||||||
if 'darwin' in platform.platform().lower():
|
if 'darwin' in platform.platform().lower():
|
||||||
return os.path.join(user_dir, 'Library', 'Application Support', 'CouchPotato')
|
return os.path.join(user_dir, 'Library', 'Application Support', 'CouchPotato')
|
||||||
|
|
||||||
|
# FreeBSD
|
||||||
|
if 'freebsd' in sys.platform:
|
||||||
|
return os.path.join('/usr/local/', 'couchpotato', 'data')
|
||||||
|
|
||||||
# Linux
|
# Linux
|
||||||
return os.path.join(user_dir, '.couchpotato')
|
return os.path.join(user_dir, '.couchpotato')
|
||||||
|
|
||||||
@@ -77,9 +84,9 @@ def cleanHost(host):
|
|||||||
|
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def getImdb(txt):
|
def getImdb(txt, check_inside = True):
|
||||||
|
|
||||||
if os.path.isfile(txt):
|
if check_inside and os.path.isfile(txt):
|
||||||
output = open(txt, 'r')
|
output = open(txt, 'r')
|
||||||
txt = output.read()
|
txt = output.read()
|
||||||
output.close()
|
output.close()
|
||||||
@@ -94,7 +101,7 @@ def getImdb(txt):
|
|||||||
|
|
||||||
def tryInt(s):
|
def tryInt(s):
|
||||||
try: return int(s)
|
try: return int(s)
|
||||||
except: return s
|
except: return 0
|
||||||
|
|
||||||
def tryFloat(s):
|
def tryFloat(s):
|
||||||
try: return float(s) if '.' in s else tryInt(s)
|
try: return float(s) if '.' in s else tryInt(s)
|
||||||
@@ -111,9 +118,12 @@ def getTitle(library_dict):
|
|||||||
try:
|
try:
|
||||||
return library_dict['titles'][0]['title']
|
return library_dict['titles'][0]['title']
|
||||||
except:
|
except:
|
||||||
log.error('Could not get title for %s' % library_dict['identifier'])
|
log.error('Could not get title for %s', library_dict['identifier'])
|
||||||
return None
|
return None
|
||||||
except:
|
except:
|
||||||
log.error('Could not get title for library item: %s' % library_dict)
|
log.error('Could not get title for library item: %s', library_dict)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
|
||||||
|
return ''.join(random.choice(chars) for x in range(size))
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class Loader(object):
|
|||||||
try:
|
try:
|
||||||
m = getattr(self.loadModule(module_name), plugin.get('name'))
|
m = getattr(self.loadModule(module_name), plugin.get('name'))
|
||||||
|
|
||||||
log.info("Loading %s: %s" % (plugin['type'], plugin['name']))
|
log.info('Loading %s: %s', (plugin['type'], plugin['name']))
|
||||||
|
|
||||||
# Save default settings for plugin/provider
|
# Save default settings for plugin/provider
|
||||||
did_save += self.loadSettings(m, module_name, save = False)
|
did_save += self.loadSettings(m, module_name, save = False)
|
||||||
@@ -57,10 +57,10 @@ class Loader(object):
|
|||||||
log.error(e.message)
|
log.error(e.message)
|
||||||
pass
|
pass
|
||||||
# todo:: this needs to be more descriptive.
|
# todo:: this needs to be more descriptive.
|
||||||
log.error('Import error, remove the empty folder: %s' % plugin.get('module'))
|
log.error('Import error, remove the empty folder: %s', plugin.get('module'))
|
||||||
log.debug('Can\'t import %s: %s' % (module_name, traceback.format_exc()))
|
log.debug('Can\'t import %s: %s', (module_name, traceback.format_exc()))
|
||||||
except:
|
except:
|
||||||
log.error('Can\'t import %s: %s' % (module_name, traceback.format_exc()))
|
log.error('Can\'t import %s: %s', (module_name, traceback.format_exc()))
|
||||||
|
|
||||||
if did_save:
|
if did_save:
|
||||||
fireEvent('settings.save')
|
fireEvent('settings.save')
|
||||||
@@ -84,7 +84,7 @@ class Loader(object):
|
|||||||
fireEvent('settings.register', section_name = section['name'], options = options, save = save)
|
fireEvent('settings.register', section_name = section['name'], options = options, save = save)
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
log.debug("Failed loading settings for '%s': %s" % (name, traceback.format_exc()))
|
log.debug('Failed loading settings for "%s": %s', (name, traceback.format_exc()))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def loadPlugins(self, module, name):
|
def loadPlugins(self, module, name):
|
||||||
@@ -97,7 +97,7 @@ class Loader(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error("Failed loading plugin '%s': %s" % (module.__file__, traceback.format_exc()))
|
log.error('Failed loading plugin "%s": %s', (module.__file__, traceback.format_exc()))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def addModule(self, priority, plugin_type, module, name):
|
def addModule(self, priority, plugin_type, module, name):
|
||||||
|
|||||||
+29
-17
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
class CPLog(object):
|
class CPLog(object):
|
||||||
|
|
||||||
@@ -13,31 +14,42 @@ class CPLog(object):
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.logger = logging.getLogger()
|
self.logger = logging.getLogger()
|
||||||
|
|
||||||
def info(self, msg):
|
def info(self, msg, replace_tuple = ()):
|
||||||
self.logger.info(self.addContext(msg))
|
self.logger.info(self.addContext(msg, replace_tuple))
|
||||||
|
|
||||||
def debug(self, msg):
|
def debug(self, msg, replace_tuple = ()):
|
||||||
self.logger.debug(self.addContext(msg))
|
self.logger.debug(self.addContext(msg, replace_tuple))
|
||||||
|
|
||||||
def error(self, msg):
|
def error(self, msg, replace_tuple = ()):
|
||||||
self.logger.error(self.addContext(msg))
|
self.logger.error(self.addContext(msg, replace_tuple))
|
||||||
|
|
||||||
def warning(self, msg):
|
def warning(self, msg, replace_tuple = ()):
|
||||||
self.logger.warning(self.addContext(msg))
|
self.logger.warning(self.addContext(msg, replace_tuple))
|
||||||
|
|
||||||
def critical(self, msg):
|
def critical(self, msg, replace_tuple = ()):
|
||||||
self.logger.critical(self.addContext(msg), exc_info = 1)
|
self.logger.critical(self.addContext(msg, replace_tuple), exc_info = 1)
|
||||||
|
|
||||||
def addContext(self, msg):
|
def addContext(self, msg, replace_tuple = ()):
|
||||||
return '[%+25.25s] %s' % (self.context[-25:], self.removePrivateData(msg))
|
return '[%+25.25s] %s' % (self.context[-25:], self.safeMessage(msg, replace_tuple))
|
||||||
|
|
||||||
def removePrivateData(self, msg):
|
def safeMessage(self, msg, replace_tuple = ()):
|
||||||
try:
|
|
||||||
msg = unicode(msg)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
from couchpotato.core.helpers.encoding import ss
|
||||||
|
|
||||||
|
msg = ss(msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = msg % replace_tuple
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
if isinstance(replace_tuple, tuple):
|
||||||
|
msg = msg % tuple([ss(x) for x in list(replace_tuple)])
|
||||||
|
else:
|
||||||
|
msg = msg % ss(replace_tuple)
|
||||||
|
except:
|
||||||
|
self.logger.error(u'Failed encoding stuff to log: %s' % traceback.format_exc())
|
||||||
|
|
||||||
if not Env.get('dev'):
|
if not Env.get('dev'):
|
||||||
|
|
||||||
for replace in self.replace_private:
|
for replace in self.replace_private:
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ class Notification(Plugin):
|
|||||||
default_title = Env.get('appname')
|
default_title = Env.get('appname')
|
||||||
test_message = 'ZOMG Lazors Pewpewpew!'
|
test_message = 'ZOMG Lazors Pewpewpew!'
|
||||||
|
|
||||||
listen_to = ['movie.downloaded', 'movie.snatched', 'updater.available']
|
listen_to = [
|
||||||
|
'movie.downloaded', 'movie.snatched',
|
||||||
|
'updater.available', 'updater.updated',
|
||||||
|
]
|
||||||
dont_listen_to = []
|
dont_listen_to = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -41,7 +44,7 @@ class Notification(Plugin):
|
|||||||
|
|
||||||
test_type = self.testNotifyName()
|
test_type = self.testNotifyName()
|
||||||
|
|
||||||
log.info('Sending test to %s' % test_type)
|
log.info('Sending test to %s', test_type)
|
||||||
|
|
||||||
success = self.notify(
|
success = self.notify(
|
||||||
message = self.test_message,
|
message = self.test_message,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView, addNonBlockApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
@@ -8,14 +8,19 @@ from couchpotato.core.logger import CPLog
|
|||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
from couchpotato.core.settings.model import Notification as Notif
|
from couchpotato.core.settings.model import Notification as Notif
|
||||||
from sqlalchemy.sql.expression import or_
|
from sqlalchemy.sql.expression import or_
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CoreNotifier(Notification):
|
class CoreNotifier(Notification):
|
||||||
|
|
||||||
|
m_lock = threading.Lock()
|
||||||
messages = []
|
messages = []
|
||||||
|
listeners = []
|
||||||
|
|
||||||
listen_to = [
|
listen_to = [
|
||||||
'movie.downloaded', 'movie.snatched',
|
'movie.downloaded', 'movie.snatched',
|
||||||
'updater.available', 'updater.updated',
|
'updater.available', 'updater.updated',
|
||||||
@@ -30,7 +35,7 @@ class CoreNotifier(Notification):
|
|||||||
addApiView('notification.markread', self.markAsRead, docs = {
|
addApiView('notification.markread', self.markAsRead, docs = {
|
||||||
'desc': 'Mark notifications as read',
|
'desc': 'Mark notifications as read',
|
||||||
'params': {
|
'params': {
|
||||||
'ids': {'desc': 'Notification id you want to mark as read.', 'type': 'int (comma separated)'},
|
'ids': {'desc': 'Notification id you want to mark as read. All if ids is empty.', 'type': 'int (comma separated)'},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -46,22 +51,31 @@ class CoreNotifier(Notification):
|
|||||||
}"""}
|
}"""}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
addNonBlockApiView('notification.listener', (self.addListener, self.removeListener))
|
||||||
addApiView('notification.listener', self.listener)
|
addApiView('notification.listener', self.listener)
|
||||||
|
|
||||||
self.registerEvents()
|
addEvent('app.load', self.clean)
|
||||||
|
|
||||||
def registerEvents(self):
|
def clean(self):
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
db.query(Notif).filter(Notif.added <= (int(time.time()) - 2419200)).delete()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# Library update, frontend refresh
|
|
||||||
addEvent('library.update_finish', lambda data: fireEvent('notify.frontend', type = 'library.update', data = data))
|
|
||||||
|
|
||||||
def markAsRead(self):
|
def markAsRead(self):
|
||||||
ids = [x.strip() for x in getParam('ids').split(',')]
|
|
||||||
|
ids = None
|
||||||
|
if getParam('ids'):
|
||||||
|
ids = [x.strip() for x in getParam('ids').split(',')]
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
q = db.query(Notif) \
|
if ids:
|
||||||
.filter(or_(*[Notif.id == tryInt(s) for s in ids]))
|
q = db.query(Notif).filter(or_(*[Notif.id == tryInt(s) for s in ids]))
|
||||||
|
else:
|
||||||
|
q = db.query(Notif).filter_by(read = False)
|
||||||
|
|
||||||
q.update({Notif.read: True})
|
q.update({Notif.read: True})
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -83,6 +97,8 @@ class CoreNotifier(Notification):
|
|||||||
limit = splt[0]
|
limit = splt[0]
|
||||||
offset = 0 if len(splt) is 1 else splt[1]
|
offset = 0 if len(splt) is 1 else splt[1]
|
||||||
q = q.limit(limit).offset(offset)
|
q = q.limit(limit).offset(offset)
|
||||||
|
else:
|
||||||
|
q = q.limit(200)
|
||||||
|
|
||||||
results = q.all()
|
results = q.all()
|
||||||
notifications = []
|
notifications = []
|
||||||
@@ -114,25 +130,86 @@ class CoreNotifier(Notification):
|
|||||||
ndict = n.to_dict()
|
ndict = n.to_dict()
|
||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
ndict['time'] = time.time()
|
ndict['time'] = time.time()
|
||||||
self.messages.append(ndict)
|
|
||||||
|
self.frontend(type = listener, data = data)
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def frontend(self, type = 'notification', data = {}):
|
def frontend(self, type = 'notification', data = {}, message = None):
|
||||||
self.messages.append({
|
|
||||||
|
self.m_lock.acquire()
|
||||||
|
notification = {
|
||||||
|
'message_id': str(uuid.uuid4()),
|
||||||
'time': time.time(),
|
'time': time.time(),
|
||||||
'type': type,
|
'type': type,
|
||||||
'data': data,
|
'data': data,
|
||||||
})
|
'message': message,
|
||||||
|
}
|
||||||
|
self.messages.append(notification)
|
||||||
|
|
||||||
|
while len(self.listeners) > 0 and not self.shuttingDown():
|
||||||
|
try:
|
||||||
|
listener, last_id = self.listeners.pop()
|
||||||
|
listener({
|
||||||
|
'success': True,
|
||||||
|
'result': [notification],
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.m_lock.release()
|
||||||
|
self.cleanMessages()
|
||||||
|
|
||||||
|
def addListener(self, callback, last_id = None):
|
||||||
|
|
||||||
|
if last_id:
|
||||||
|
messages = self.getMessages(last_id)
|
||||||
|
if len(messages) > 0:
|
||||||
|
return callback({
|
||||||
|
'success': True,
|
||||||
|
'result': messages,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.listeners.append((callback, last_id))
|
||||||
|
|
||||||
|
|
||||||
|
def removeListener(self, callback):
|
||||||
|
|
||||||
|
for list_tuple in self.listeners:
|
||||||
|
try:
|
||||||
|
listener, last_id = list_tuple
|
||||||
|
if listener == callback:
|
||||||
|
self.listeners.remove(list_tuple)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cleanMessages(self):
|
||||||
|
self.m_lock.acquire()
|
||||||
|
|
||||||
|
for message in self.messages:
|
||||||
|
if message['time'] < (time.time() - 15):
|
||||||
|
self.messages.remove(message)
|
||||||
|
|
||||||
|
self.m_lock.release()
|
||||||
|
|
||||||
|
def getMessages(self, last_id):
|
||||||
|
self.m_lock.acquire()
|
||||||
|
|
||||||
|
recent = []
|
||||||
|
index = 0
|
||||||
|
for i in xrange(len(self.messages)):
|
||||||
|
index = len(self.messages) - i - 1
|
||||||
|
if self.messages[index]["message_id"] == last_id: break
|
||||||
|
recent = self.messages[index:]
|
||||||
|
|
||||||
|
self.m_lock.release()
|
||||||
|
|
||||||
|
return recent or []
|
||||||
|
|
||||||
def listener(self):
|
def listener(self):
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
for message in self.messages:
|
|
||||||
#delete message older then 15s
|
|
||||||
if message['time'] > (time.time() - 15):
|
|
||||||
messages.append(message)
|
|
||||||
|
|
||||||
# Get unread
|
# Get unread
|
||||||
if getParam('init'):
|
if getParam('init'):
|
||||||
@@ -146,9 +223,6 @@ class CoreNotifier(Notification):
|
|||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
messages.append(ndict)
|
messages.append(ndict)
|
||||||
|
|
||||||
#db.close()
|
|
||||||
|
|
||||||
self.messages = []
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'result': messages,
|
'result': messages,
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ var NotificationBase = new Class({
|
|||||||
self.setOptions(options);
|
self.setOptions(options);
|
||||||
|
|
||||||
// Listener
|
// Listener
|
||||||
App.addEvent('load', self.startInterval.bind(self));
|
App.addEvent('unload', self.stopPoll.bind(self));
|
||||||
App.addEvent('unload', self.stopTimer.bind(self));
|
App.addEvent('reload', self.startInterval.bind(self, [true]));
|
||||||
App.addEvent('notification', self.notify.bind(self));
|
App.addEvent('notification', self.notify.bind(self));
|
||||||
|
App.addEvent('message', self.showMessage.bind(self));
|
||||||
|
|
||||||
// Add test buttons to settings page
|
// Add test buttons to settings page
|
||||||
App.addEvent('load', self.addTestButtons.bind(self));
|
App.addEvent('load', self.addTestButtons.bind(self));
|
||||||
@@ -30,7 +31,11 @@ var NotificationBase = new Class({
|
|||||||
'href': App.createUrl('notifications'),
|
'href': App.createUrl('notifications'),
|
||||||
'text': 'Show older notifications'
|
'text': 'Show older notifications'
|
||||||
})); */
|
})); */
|
||||||
})
|
});
|
||||||
|
|
||||||
|
window.addEvent('load', function(){
|
||||||
|
self.startInterval.delay(Browser.safari ? 100 : 0, self)
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -73,9 +78,6 @@ var NotificationBase = new Class({
|
|||||||
|
|
||||||
if(ids.length > 0)
|
if(ids.length > 0)
|
||||||
Api.request('notification.markread', {
|
Api.request('notification.markread', {
|
||||||
'data': {
|
|
||||||
'ids': ids.join(',')
|
|
||||||
},
|
|
||||||
'onSuccess': function(){
|
'onSuccess': function(){
|
||||||
self.setBadge('')
|
self.setBadge('')
|
||||||
}
|
}
|
||||||
@@ -83,39 +85,88 @@ var NotificationBase = new Class({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startInterval: function(){
|
startInterval: function(force){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.request = Api.request('notification.listener', {
|
if(self.stopped && !force){
|
||||||
'initialDelay': 100,
|
self.stopped = false;
|
||||||
'delay': 3000,
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Api.request('notification.listener', {
|
||||||
'data': {'init':true},
|
'data': {'init':true},
|
||||||
'onSuccess': self.processData.bind(self)
|
'onSuccess': self.processData.bind(self)
|
||||||
})
|
}).send()
|
||||||
|
|
||||||
self.request.startTimer()
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startTimer: function(){
|
startPoll: function(){
|
||||||
if(this.request)
|
var self = this;
|
||||||
this.request.startTimer()
|
|
||||||
|
if(self.stopped || (self.request && self.request.isRunning()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
self.request = Api.request('nonblock/notification.listener', {
|
||||||
|
'onSuccess': self.processData.bind(self),
|
||||||
|
'data': {
|
||||||
|
'last_id': self.last_id
|
||||||
|
},
|
||||||
|
'onFailure': function(){
|
||||||
|
self.startPoll.delay(2000, self)
|
||||||
|
}
|
||||||
|
}).send()
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stopTimer: function(){
|
stopPoll: function(){
|
||||||
if(this.request)
|
if(this.request)
|
||||||
this.request.stopTimer()
|
this.request.cancel()
|
||||||
|
this.stopped = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
processData: function(json){
|
processData: function(json){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.request.options.data = {}
|
// Process data
|
||||||
Array.each(json.result, function(result){
|
if(json){
|
||||||
App.fireEvent(result.type, result)
|
Array.each(json.result, function(result){
|
||||||
})
|
App.fireEvent(result.type, result);
|
||||||
|
if(result.message && result.read === undefined)
|
||||||
|
self.showMessage(result.message);
|
||||||
|
})
|
||||||
|
|
||||||
|
if(json.result.length > 0)
|
||||||
|
self.last_id = json.result.getLast().message_id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart poll
|
||||||
|
self.startPoll()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showMessage: function(message){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(!self.message_container)
|
||||||
|
self.message_container = new Element('div.messages').inject(document.body);
|
||||||
|
|
||||||
|
var new_message = new Element('div.message', {
|
||||||
|
'text': message
|
||||||
|
}).inject(self.message_container);
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
new_message.addClass('show')
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
new_message.addClass('hide')
|
||||||
|
setTimeout(function(){
|
||||||
|
new_message.destroy();
|
||||||
|
}, 1000);
|
||||||
|
}, 4000);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// Notification setting tests
|
||||||
addTestButtons: function(){
|
addTestButtons: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from gntp import notifier
|
from gntp import notifier
|
||||||
import logging
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -17,7 +16,7 @@ class Growl(Notification):
|
|||||||
super(Growl, self).__init__()
|
super(Growl, self).__init__()
|
||||||
|
|
||||||
if self.isEnabled():
|
if self.isEnabled():
|
||||||
self.register()
|
addEvent('app.load', self.register)
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
if self.registered: return
|
if self.registered: return
|
||||||
@@ -39,7 +38,7 @@ class Growl(Notification):
|
|||||||
self.growl.register()
|
self.growl.register()
|
||||||
self.registered = True
|
self.registered = True
|
||||||
except:
|
except:
|
||||||
log.error('Failed register of growl: %s' % traceback.format_exc())
|
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
from .main import History
|
|
||||||
|
|
||||||
def start():
|
|
||||||
return History()
|
|
||||||
|
|
||||||
config = []
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
from couchpotato import get_session
|
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.notifications.base import Notification
|
|
||||||
from couchpotato.core.settings.model import History as Hist
|
|
||||||
import time
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class History(Notification):
|
|
||||||
|
|
||||||
listen_to = ['movie.downloaded', 'movie.snatched', 'renamer.canceled']
|
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
|
||||||
|
|
||||||
db = get_session()
|
|
||||||
history = Hist(
|
|
||||||
added = int(time.time()),
|
|
||||||
message = toUnicode(message),
|
|
||||||
release_id = data.get('id', 0)
|
|
||||||
)
|
|
||||||
db.add(history)
|
|
||||||
db.commit()
|
|
||||||
#db.close()
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -33,10 +33,10 @@ class NMJ(Notification):
|
|||||||
try:
|
try:
|
||||||
terminal = telnetlib.Telnet(host)
|
terminal = telnetlib.Telnet(host)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error('Warning: unable to get a telnet session to %s' % (host))
|
log.error('Warning: unable to get a telnet session to %s', (host))
|
||||||
return self.failed()
|
return self.failed()
|
||||||
|
|
||||||
log.debug('Connected to %s via telnet' % (host))
|
log.debug('Connected to %s via telnet', (host))
|
||||||
terminal.read_until('sh-3.00# ')
|
terminal.read_until('sh-3.00# ')
|
||||||
terminal.write('cat /tmp/source\n')
|
terminal.write('cat /tmp/source\n')
|
||||||
terminal.write('cat /tmp/netshare\n')
|
terminal.write('cat /tmp/netshare\n')
|
||||||
@@ -48,9 +48,9 @@ class NMJ(Notification):
|
|||||||
if match:
|
if match:
|
||||||
database = match.group(1)
|
database = match.group(1)
|
||||||
device = match.group(2)
|
device = match.group(2)
|
||||||
log.info('Found NMJ database %s on device %s' % (database, device))
|
log.info('Found NMJ database %s on device %s', (database, device))
|
||||||
else:
|
else:
|
||||||
log.error('Could not get current NMJ database on %s, NMJ is probably not running!' % (host))
|
log.error('Could not get current NMJ database on %s, NMJ is probably not running!', (host))
|
||||||
return self.failed()
|
return self.failed()
|
||||||
|
|
||||||
if device.startswith('NETWORK_SHARE/'):
|
if device.startswith('NETWORK_SHARE/'):
|
||||||
@@ -58,7 +58,7 @@ class NMJ(Notification):
|
|||||||
|
|
||||||
if match:
|
if match:
|
||||||
mount = match.group().replace('127.0.0.1', host)
|
mount = match.group().replace('127.0.0.1', host)
|
||||||
log.info('Found mounting url on the Popcorn Hour in configuration: %s' % (mount))
|
log.info('Found mounting url on the Popcorn Hour in configuration: %s', (mount))
|
||||||
else:
|
else:
|
||||||
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
|
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
|
||||||
return self.failed()
|
return self.failed()
|
||||||
@@ -77,7 +77,7 @@ class NMJ(Notification):
|
|||||||
database = self.conf('database')
|
database = self.conf('database')
|
||||||
|
|
||||||
if self.mount:
|
if self.mount:
|
||||||
log.debug('Try to mount network drive via url: %s' % (mount))
|
log.debug('Try to mount network drive via url: %s', (mount))
|
||||||
try:
|
try:
|
||||||
data = self.urlopen(mount)
|
data = self.urlopen(mount)
|
||||||
except:
|
except:
|
||||||
@@ -102,11 +102,11 @@ class NMJ(Notification):
|
|||||||
et = etree.fromstring(response)
|
et = etree.fromstring(response)
|
||||||
result = et.findtext('returnValue')
|
result = et.findtext('returnValue')
|
||||||
except SyntaxError, e:
|
except SyntaxError, e:
|
||||||
log.error('Unable to parse XML returned from the Popcorn Hour: %s' % (e))
|
log.error('Unable to parse XML returned from the Popcorn Hour: %s', (e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if int(result) > 0:
|
if int(result) > 0:
|
||||||
log.error('Popcorn Hour returned an errorcode: %s' % (result))
|
log.error('Popcorn Hour returned an errorcode: %s', (result))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
log.info('NMJ started background scan')
|
log.info('NMJ started background scan')
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class Notifo(Notification):
|
|||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Notification failed: %s' % traceback.format_exc())
|
log.error('Notification failed: %s', traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log.info('Notifo notification successful.')
|
log.info('Notifo notification successful.')
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ class NotifyMyAndroid(Notification):
|
|||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if not response[str(key)]['code'] == u'200':
|
if not response[str(key)]['code'] == u'200':
|
||||||
log.error('Could not send notification to NotifyMyAndroid (%s). %s' % (key, response[key]['message']))
|
log.error('Could not send notification to NotifyMyAndroid (%s). %s', (key, response[key]['message']))
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class NotifyMyWP(Notification):
|
|||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if not response[key]['Code'] == u'200':
|
if not response[key]['Code'] == u'200':
|
||||||
log.error('Could not send notification to NotifyMyWindowsPhone (%s). %s' % (key, response[key]['message']))
|
log.error('Could not send notification to NotifyMyWindowsPhone (%s). %s', (key, response[key]['message']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'localhost:32400',
|
'default': 'localhost',
|
||||||
'description': 'Default should be on localhost:32400',
|
'description': 'Default should be on localhost',
|
||||||
|
'advanced': True,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
@@ -11,13 +12,14 @@ log = CPLog(__name__)
|
|||||||
class Plex(Notification):
|
class Plex(Notification):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super(Plex, self).__init__()
|
||||||
addEvent('renamer.after', self.addToLibrary)
|
addEvent('renamer.after', self.addToLibrary)
|
||||||
|
|
||||||
def addToLibrary(self, group = {}):
|
def addToLibrary(self, group = {}):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
log.info('Sending notification to Plex')
|
log.info('Sending notification to Plex')
|
||||||
hosts = [cleanHost(x.strip()) for x in self.conf('host').split(",")]
|
hosts = [cleanHost(x.strip() + ':32400') for x in self.conf('host').split(",")]
|
||||||
|
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
|
|
||||||
@@ -36,7 +38,33 @@ class Plex(Notification):
|
|||||||
x = self.urlopen(url)
|
x = self.urlopen(url)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Plex library update failed for %s: %s' % (host, traceback.format_exc()))
|
log.error('Plex library update failed for %s: %s', (host, traceback.format_exc()))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
if self.isDisabled(): return
|
||||||
|
|
||||||
|
hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
|
||||||
|
successful = 0
|
||||||
|
for host in hosts:
|
||||||
|
if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host):
|
||||||
|
successful += 1
|
||||||
|
|
||||||
|
return successful == len(hosts)
|
||||||
|
|
||||||
|
def send(self, command, host):
|
||||||
|
|
||||||
|
url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, tryUrlencode(command))
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.urlopen(url, headers = headers, show_error = False)
|
||||||
|
except:
|
||||||
|
log.error("Couldn't sent command to Plex: %s", traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|
||||||
|
log.info('Plex notification to %s successful.', host)
|
||||||
|
return True
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class Prowl(Notification):
|
|||||||
log.info('Prowl notifications sent.')
|
log.info('Prowl notifications sent.')
|
||||||
return True
|
return True
|
||||||
elif request_status == 401:
|
elif request_status == 401:
|
||||||
log.error('Prowl auth failed: %s' % response.reason)
|
log.error('Prowl auth failed: %s', response.reason)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
log.error('Prowl notification failed.')
|
log.error('Prowl notification failed.')
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class Pushover(Notification):
|
|||||||
log.info('Pushover notifications sent.')
|
log.info('Pushover notifications sent.')
|
||||||
return True
|
return True
|
||||||
elif request_status == 401:
|
elif request_status == 401:
|
||||||
log.error('Pushover auth failed: %s' % response.reason)
|
log.error('Pushover auth failed: %s', response.reason)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
log.error('Pushover notification failed.')
|
log.error('Pushover notification failed.')
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ class Synoindex(Notification):
|
|||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
command = ['/usr/syno/bin/synoindex', '-A', group.get('destination_dir')]
|
command = ['/usr/syno/bin/synoindex', '-A', group.get('destination_dir')]
|
||||||
log.info(u'Executing synoindex command: %s ' % command)
|
log.info(u'Executing synoindex command: %s ', command)
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
|
p = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
|
||||||
out = p.communicate()
|
out = p.communicate()
|
||||||
log.info('Result from synoindex: %s' % str(out))
|
log.info('Result from synoindex: %s', str(out))
|
||||||
return True
|
return True
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
log.error('Unable to run synoindex: %s' % e)
|
log.error('Unable to run synoindex: %s', e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class Twitter(Notification):
|
|||||||
else:
|
else:
|
||||||
api.PostUpdate('[%s] %s' % (self.default_title, message))
|
api.PostUpdate('[%s] %s' % (self.default_title, message))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Error sending tweet: %s' % e)
|
log.error('Error sending tweet: %s', e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -71,7 +71,7 @@ class Twitter(Notification):
|
|||||||
resp, content = oauth_client.request(self.urls['request'], 'POST', body = tryUrlencode({'oauth_callback': callback_url}))
|
resp, content = oauth_client.request(self.urls['request'], 'POST', body = tryUrlencode({'oauth_callback': callback_url}))
|
||||||
|
|
||||||
if resp['status'] != '200':
|
if resp['status'] != '200':
|
||||||
log.error('Invalid response from Twitter requesting temp token: %s' % resp['status'])
|
log.error('Invalid response from Twitter requesting temp token: %s', resp['status'])
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': False,
|
'success': False,
|
||||||
})
|
})
|
||||||
@@ -80,7 +80,7 @@ class Twitter(Notification):
|
|||||||
|
|
||||||
auth_url = self.urls['authorize'] + ("?oauth_token=%s" % self.request_token['oauth_token'])
|
auth_url = self.urls['authorize'] + ("?oauth_token=%s" % self.request_token['oauth_token'])
|
||||||
|
|
||||||
log.info('Redirecting to "%s"' % auth_url)
|
log.info('Redirecting to "%s"', auth_url)
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'url': auth_url,
|
'url': auth_url,
|
||||||
@@ -100,10 +100,10 @@ class Twitter(Notification):
|
|||||||
access_token = dict(parse_qsl(content))
|
access_token = dict(parse_qsl(content))
|
||||||
|
|
||||||
if resp['status'] != '200':
|
if resp['status'] != '200':
|
||||||
log.error('The request for an access token did not succeed: %s' % resp['status'])
|
log.error('The request for an access token did not succeed: %s', resp['status'])
|
||||||
return 'Twitter auth failed'
|
return 'Twitter auth failed'
|
||||||
else:
|
else:
|
||||||
log.debug('Tokens: %s, %s' % (access_token['oauth_token'], access_token['oauth_token_secret']))
|
log.debug('Tokens: %s, %s', (access_token['oauth_token'], access_token['oauth_token_secret']))
|
||||||
|
|
||||||
self.conf('access_token_key', value = access_token['oauth_token'])
|
self.conf('access_token_key', value = access_token['oauth_token'])
|
||||||
self.conf('access_token_secret', value = access_token['oauth_token_secret'])
|
self.conf('access_token_secret', value = access_token['oauth_token_secret'])
|
||||||
|
|||||||
@@ -35,5 +35,5 @@ class XBMC(Notification):
|
|||||||
log.error("Couldn't sent command to XBMC")
|
log.error("Couldn't sent command to XBMC")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log.info('XBMC notification to %s successful.' % host)
|
log.info('XBMC notification to %s successful.', host)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'rating',
|
'name': 'rating',
|
||||||
'default': 6.0,
|
'default': 7.0,
|
||||||
'type': 'float',
|
'type': 'float',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,4 +19,8 @@ class Automation(Plugin):
|
|||||||
|
|
||||||
movies = fireEvent('automation.get_movies', merge = True)
|
movies = fireEvent('automation.get_movies', merge = True)
|
||||||
for imdb_id in movies:
|
for imdb_id in movies:
|
||||||
fireEvent('movie.add', params = {'identifier': imdb_id}, force_readd = False)
|
prop_name = 'automation.added.%s' % imdb_id
|
||||||
|
added = Env.prop(prop_name, default = False)
|
||||||
|
if not added:
|
||||||
|
fireEvent('movie.add', params = {'identifier': imdb_id}, force_readd = False)
|
||||||
|
Env.prop(prop_name, True)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from couchpotato import addView
|
from couchpotato import addView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss
|
||||||
from couchpotato.core.helpers.variable import getExt
|
from couchpotato.core.helpers.variable import getExt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -71,6 +71,7 @@ class Plugin(object):
|
|||||||
return send_from_directory(d, filename)
|
return send_from_directory(d, filename)
|
||||||
|
|
||||||
def createFile(self, path, content, binary = False):
|
def createFile(self, path, content, binary = False):
|
||||||
|
path = ss(path)
|
||||||
|
|
||||||
self.makeDir(os.path.dirname(path))
|
self.makeDir(os.path.dirname(path))
|
||||||
|
|
||||||
@@ -80,20 +81,21 @@ class Plugin(object):
|
|||||||
f.close()
|
f.close()
|
||||||
os.chmod(path, Env.getPermission('file'))
|
os.chmod(path, Env.getPermission('file'))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Unable writing to file "%s": %s' % (path, e))
|
log.error('Unable writing to file "%s": %s', (path, e))
|
||||||
|
|
||||||
def makeDir(self, path):
|
def makeDir(self, path):
|
||||||
|
path = ss(path)
|
||||||
try:
|
try:
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
os.makedirs(path, Env.getPermission('folder'))
|
os.makedirs(path, Env.getPermission('folder'))
|
||||||
return True
|
return True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Unable to create folder "%s": %s' % (path, e))
|
log.error('Unable to create folder "%s": %s', (path, e))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# http request
|
# http request
|
||||||
def urlopen(self, url, timeout = 10, params = {}, headers = {}, multipart = False, show_error = True):
|
def urlopen(self, url, timeout = 30, params = {}, headers = {}, opener = None, multipart = False, show_error = True):
|
||||||
|
|
||||||
# Fill in some headers
|
# Fill in some headers
|
||||||
if not headers.get('Referer'):
|
if not headers.get('Referer'):
|
||||||
@@ -106,7 +108,7 @@ class Plugin(object):
|
|||||||
# Don't try for failed requests
|
# Don't try for failed requests
|
||||||
if self.http_failed_disabled.get(host, 0) > 0:
|
if self.http_failed_disabled.get(host, 0) > 0:
|
||||||
if self.http_failed_disabled[host] > (time.time() - 900):
|
if self.http_failed_disabled[host] > (time.time() - 900):
|
||||||
log.info('Disabled calls to %s for 15 minutes because so many failed requests.' % host)
|
log.info('Disabled calls to %s for 15 minutes because so many failed requests.', host)
|
||||||
raise Exception
|
raise Exception
|
||||||
else:
|
else:
|
||||||
del self.http_failed_request[host]
|
del self.http_failed_request[host]
|
||||||
@@ -116,7 +118,7 @@ class Plugin(object):
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
if multipart:
|
if multipart:
|
||||||
log.info('Opening multipart url: %s, params: %s' % (url, [x for x in params.iterkeys()]))
|
log.info('Opening multipart url: %s, params: %s', (url, [x for x in params.iterkeys()]))
|
||||||
request = urllib2.Request(url, params, headers)
|
request = urllib2.Request(url, params, headers)
|
||||||
|
|
||||||
cookies = cookielib.CookieJar()
|
cookies = cookielib.CookieJar()
|
||||||
@@ -124,16 +126,19 @@ class Plugin(object):
|
|||||||
|
|
||||||
data = opener.open(request, timeout = timeout).read()
|
data = opener.open(request, timeout = timeout).read()
|
||||||
else:
|
else:
|
||||||
log.info('Opening url: %s, params: %s' % (url, [x for x in params.iterkeys()]))
|
log.info('Opening url: %s, params: %s', (url, [x for x in params.iterkeys()]))
|
||||||
data = tryUrlencode(params) if len(params) > 0 else None
|
data = tryUrlencode(params) if len(params) > 0 else None
|
||||||
request = urllib2.Request(url, data, headers)
|
request = urllib2.Request(url, data, headers)
|
||||||
|
|
||||||
data = urllib2.urlopen(request, timeout = timeout).read()
|
if opener:
|
||||||
|
data = opener.open(request, timeout = timeout).read()
|
||||||
|
else:
|
||||||
|
data = urllib2.urlopen(request, timeout = timeout).read()
|
||||||
|
|
||||||
self.http_failed_request[host] = 0
|
self.http_failed_request[host] = 0
|
||||||
except IOError:
|
except IOError:
|
||||||
if show_error:
|
if show_error:
|
||||||
log.error('Failed opening url in %s: %s %s' % (self.getName(), url, traceback.format_exc(1)))
|
log.error('Failed opening url in %s: %s %s', (self.getName(), url, traceback.format_exc(1)))
|
||||||
|
|
||||||
# Save failed requests by hosts
|
# Save failed requests by hosts
|
||||||
try:
|
try:
|
||||||
@@ -147,7 +152,7 @@ class Plugin(object):
|
|||||||
self.http_failed_disabled[host] = time.time()
|
self.http_failed_disabled[host] = time.time()
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.debug('Failed logging failed requests for %s: %s' % (url, traceback.format_exc()))
|
log.debug('Failed logging failed requests for %s: %s', (url, traceback.format_exc()))
|
||||||
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -163,7 +168,7 @@ class Plugin(object):
|
|||||||
wait = math.ceil(last_use - now + self.http_time_between_calls)
|
wait = math.ceil(last_use - now + self.http_time_between_calls)
|
||||||
|
|
||||||
if wait > 0:
|
if wait > 0:
|
||||||
log.debug('Waiting for %s, %d seconds' % (self.getName(), wait))
|
log.debug('Waiting for %s, %d seconds', (self.getName(), wait))
|
||||||
time.sleep(last_use - now + self.http_time_between_calls)
|
time.sleep(last_use - now + self.http_time_between_calls)
|
||||||
|
|
||||||
def beforeCall(self, handler):
|
def beforeCall(self, handler):
|
||||||
@@ -199,9 +204,10 @@ class Plugin(object):
|
|||||||
|
|
||||||
|
|
||||||
def getCache(self, cache_key, url = None, **kwargs):
|
def getCache(self, cache_key, url = None, **kwargs):
|
||||||
|
cache_key = simplifyString(cache_key)
|
||||||
cache = Env.get('cache').get(cache_key)
|
cache = Env.get('cache').get(cache_key)
|
||||||
if cache:
|
if cache:
|
||||||
if not Env.get('dev'): log.debug('Getting cache %s' % cache_key)
|
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
@@ -213,13 +219,15 @@ class Plugin(object):
|
|||||||
del kwargs['cache_timeout']
|
del kwargs['cache_timeout']
|
||||||
|
|
||||||
data = self.urlopen(url, **kwargs)
|
data = self.urlopen(url, **kwargs)
|
||||||
self.setCache(cache_key, data, timeout = cache_timeout)
|
|
||||||
|
if data:
|
||||||
|
self.setCache(cache_key, data, timeout = cache_timeout)
|
||||||
return data
|
return data
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def setCache(self, cache_key, value, timeout = 300):
|
def setCache(self, cache_key, value, timeout = 300):
|
||||||
log.debug('Setting cache %s' % cache_key)
|
log.debug('Setting cache %s', cache_key)
|
||||||
Env.get('cache').set(cache_key, value, timeout)
|
Env.get('cache').set(cache_key, value, timeout)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|||||||
@@ -62,13 +62,15 @@ class FileBrowser(Plugin):
|
|||||||
|
|
||||||
def view(self):
|
def view(self):
|
||||||
|
|
||||||
|
path = getParam('path', '/')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dirs = self.getDirectories(path = getParam('path', '/'), show_hidden = getParam('show_hidden', True))
|
dirs = self.getDirectories(path = path, show_hidden = getParam('show_hidden', True))
|
||||||
except:
|
except:
|
||||||
dirs = []
|
dirs = []
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'is_root': getParam('path', '/') == '/',
|
'is_root': path == '/' or not path,
|
||||||
'empty': len(dirs) == 0,
|
'empty': len(dirs) == 0,
|
||||||
'dirs': dirs,
|
'dirs': dirs,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from couchpotato.core.plugins.base import Plugin
|
|||||||
from couchpotato.core.settings.model import FileType, File
|
from couchpotato.core.settings.model import FileType, File
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import os.path
|
import os.path
|
||||||
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ class FileManager(Plugin):
|
|||||||
try:
|
try:
|
||||||
filedata = self.urlopen(url, **urlopen_kwargs)
|
filedata = self.urlopen(url, **urlopen_kwargs)
|
||||||
except:
|
except:
|
||||||
|
log.error('Failed downloading file %s: %s', (url, traceback.format_exc()))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.createFile(dest, filedata, binary = True)
|
self.createFile(dest, filedata, binary = True)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class LibraryPlugin(Plugin):
|
|||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
if not info or len(info) == 0:
|
if not info or len(info) == 0:
|
||||||
log.error('Could not update, no movie info to work with: %s' % identifier)
|
log.error('Could not update, no movie info to work with: %s', identifier)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Main info
|
# Main info
|
||||||
@@ -95,7 +95,7 @@ class LibraryPlugin(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
titles = info.get('titles', [])
|
titles = info.get('titles', [])
|
||||||
log.debug('Adding titles: %s' % titles)
|
log.debug('Adding titles: %s', titles)
|
||||||
for title in titles:
|
for title in titles:
|
||||||
if not title:
|
if not title:
|
||||||
continue
|
continue
|
||||||
@@ -117,26 +117,29 @@ class LibraryPlugin(Plugin):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
file_path = fireEvent('file.download', url = image, single = True)
|
file_path = fireEvent('file.download', url = image, single = True)
|
||||||
file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', type), single = True)
|
if file_path:
|
||||||
try:
|
file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', type), single = True)
|
||||||
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
|
try:
|
||||||
library.files.append(file_obj)
|
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
|
||||||
db.commit()
|
library.files.append(file_obj)
|
||||||
except:
|
db.commit()
|
||||||
log.debug('Failed to attach to library: %s' % traceback.format_exc())
|
except:
|
||||||
|
log.debug('Failed to attach to library: %s', traceback.format_exc())
|
||||||
|
|
||||||
library_dict = library.to_dict(self.default_dict)
|
library_dict = library.to_dict(self.default_dict)
|
||||||
|
|
||||||
fireEvent('library.update_finish', data = library_dict)
|
|
||||||
|
|
||||||
#db.close()
|
|
||||||
return library_dict
|
return library_dict
|
||||||
|
|
||||||
def updateReleaseDate(self, identifier):
|
def updateReleaseDate(self, identifier):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
library = db.query(Library).filter_by(identifier = identifier).first()
|
library = db.query(Library).filter_by(identifier = identifier).first()
|
||||||
dates = library.info.get('release_date')
|
|
||||||
|
if not library.info:
|
||||||
|
library_dict = self.update(identifier)
|
||||||
|
dates = library_dict.get('info', {}).get('release_dates')
|
||||||
|
else:
|
||||||
|
dates = library.info.get('release_date')
|
||||||
|
|
||||||
if dates and dates.get('expires', 0) < time.time():
|
if dates and dates.get('expires', 0) < time.time():
|
||||||
dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
|
dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam, getParams
|
from couchpotato.core.helpers.request import jsonified, getParam, getParams
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -71,14 +73,14 @@ class Logging(Plugin):
|
|||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'log': log,
|
'log': toUnicode(log),
|
||||||
'total': total,
|
'total': total,
|
||||||
})
|
})
|
||||||
|
|
||||||
def partial(self):
|
def partial(self):
|
||||||
|
|
||||||
log_type = getParam('type', 'all')
|
log_type = getParam('type', 'all')
|
||||||
total_lines = getParam('lines', 30)
|
total_lines = tryInt(getParam('lines', 30))
|
||||||
|
|
||||||
log_lines = []
|
log_lines = []
|
||||||
|
|
||||||
@@ -92,13 +94,12 @@ class Logging(Plugin):
|
|||||||
|
|
||||||
reversed_lines = []
|
reversed_lines = []
|
||||||
f = open(path, 'r')
|
f = open(path, 'r')
|
||||||
reversed_lines = f.read().split('[0m\n')
|
reversed_lines = toUnicode(f.read()).split('[0m\n')
|
||||||
reversed_lines.reverse()
|
reversed_lines.reverse()
|
||||||
|
|
||||||
brk = False
|
brk = False
|
||||||
for line in reversed_lines:
|
for line in reversed_lines:
|
||||||
|
|
||||||
#print '%s ' % log_type in line.lower()
|
|
||||||
if log_type == 'all' or '%s ' % log_type.upper() in line:
|
if log_type == 'all' or '%s ' % log_type.upper() in line:
|
||||||
log_lines.append(line)
|
log_lines.append(line)
|
||||||
|
|
||||||
@@ -149,7 +150,7 @@ class Logging(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error(log_message)
|
log.error(log_message)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t log via API: %s' % params)
|
log.error('Couldn\'t log via API: %s', params)
|
||||||
|
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
width: 14%;
|
width: 14%;
|
||||||
color: lightgrey;
|
color: lightgrey;
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.log .container .time:last-child { display: none; }
|
.page.log .container .time:last-child { display: none; }
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Page.Log = new Class({
|
|||||||
new Fx.Scroll(window, {'duration': 0}).toBottom();
|
new Fx.Scroll(window, {'duration': 0}).toBottom();
|
||||||
|
|
||||||
var nav = new Element('ul.nav').inject(self.log, 'top');
|
var nav = new Element('ul.nav').inject(self.log, 'top');
|
||||||
for (var i = 0; i < json.total; i++) {
|
for (var i = 0; i <= json.total; i++) {
|
||||||
new Element('li', {
|
new Element('li', {
|
||||||
'text': i+1,
|
'text': i+1,
|
||||||
'class': nr == i ? 'active': '',
|
'class': nr == i ? 'active': '',
|
||||||
@@ -78,11 +78,16 @@ Page.Log = new Class({
|
|||||||
addColors: function(text){
|
addColors: function(text){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
text = text.replace(/\u001b\[31m/gi, '</span><span class="error">')
|
text = text
|
||||||
text = text.replace(/\u001b\[36m/gi, '</span><span class="debug">')
|
.replace(/&/g, '&')
|
||||||
text = text.replace(/\u001b\[33m/gi, '</span><span class="debug">')
|
.replace(/</g, '<')
|
||||||
text = text.replace(/\u001b\[0m\n/gi, '</span><span class="time">')
|
.replace(/>/g, '>')
|
||||||
text = text.replace(/\u001b\[0m/gi, '</span><span>')
|
.replace(/"/g, '"')
|
||||||
|
.replace(/\u001b\[31m/gi, '</span><span class="error">')
|
||||||
|
.replace(/\u001b\[36m/gi, '</span><span class="debug">')
|
||||||
|
.replace(/\u001b\[33m/gi, '</span><span class="debug">')
|
||||||
|
.replace(/\u001b\[0m\n/gi, '</span><span class="time">')
|
||||||
|
.replace(/\u001b\[0m/gi, '</span><span>')
|
||||||
|
|
||||||
return '<span class="time">' + text + '</span>';
|
return '<span class="time">' + text + '</span>';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -25,13 +25,14 @@ class Manage(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if not Env.get('dev'):
|
if not Env.get('dev'):
|
||||||
addEvent('app.load', self.updateLibrary)
|
def updateLibrary():
|
||||||
|
self.updateLibrary(full = False)
|
||||||
|
addEvent('app.load', updateLibrary)
|
||||||
|
|
||||||
def updateLibraryView(self):
|
def updateLibraryView(self):
|
||||||
|
|
||||||
params = getParams()
|
full = getParam('full', default = 1)
|
||||||
|
fireEventAsync('manage.update', full = True if full == '1' else False)
|
||||||
fireEventAsync('manage.update', full = params.get('full', True))
|
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
@@ -51,11 +52,11 @@ class Manage(Plugin):
|
|||||||
|
|
||||||
if not os.path.isdir(directory):
|
if not os.path.isdir(directory):
|
||||||
if len(directory) > 0:
|
if len(directory) > 0:
|
||||||
log.error('Directory doesn\'t exist: %s' % directory)
|
log.error('Directory doesn\'t exist: %s', directory)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log.info('Updating manage library: %s' % directory)
|
log.info('Updating manage library: %s', directory)
|
||||||
identifiers = fireEvent('scanner.folder', folder = directory, newer_than = last_update, single = True)
|
identifiers = fireEvent('scanner.folder', folder = directory, newer_than = last_update if not full else 0, single = True)
|
||||||
if identifiers:
|
if identifiers:
|
||||||
added_identifiers.extend(identifiers)
|
added_identifiers.extend(identifiers)
|
||||||
|
|
||||||
@@ -67,11 +68,11 @@ class Manage(Plugin):
|
|||||||
if self.conf('cleanup') and full and not self.shuttingDown():
|
if self.conf('cleanup') and full and not self.shuttingDown():
|
||||||
|
|
||||||
# Get movies with done status
|
# Get movies with done status
|
||||||
done_movies = fireEvent('movie.list', status = 'done', single = True)
|
total_movies, done_movies = fireEvent('movie.list', status = 'done', single = True)
|
||||||
|
|
||||||
for done_movie in done_movies:
|
for done_movie in done_movies:
|
||||||
if done_movie['library']['identifier'] not in added_identifiers:
|
if done_movie['library']['identifier'] not in added_identifiers:
|
||||||
fireEvent('movie.delete', movie_id = done_movie['id'])
|
fireEvent('movie.delete', movie_id = done_movie['id'], delete_from = 'all')
|
||||||
|
|
||||||
Env.prop('manage.last_update', time.time())
|
Env.prop('manage.last_update', time.time())
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ class MoviePlugin(Plugin):
|
|||||||
.filter(or_(*[Movie.status.has(identifier = s) for s in status])) \
|
.filter(or_(*[Movie.status.has(identifier = s) for s in status])) \
|
||||||
.group_by(Movie.id)
|
.group_by(Movie.id)
|
||||||
|
|
||||||
|
total_count = q.count()
|
||||||
|
|
||||||
filter_or = []
|
filter_or = []
|
||||||
if starts_with:
|
if starts_with:
|
||||||
starts_with = toUnicode(starts_with.lower())
|
starts_with = toUnicode(starts_with.lower())
|
||||||
@@ -156,8 +158,7 @@ class MoviePlugin(Plugin):
|
|||||||
.options(joinedload_all('library.titles')) \
|
.options(joinedload_all('library.titles')) \
|
||||||
.options(joinedload_all('library.files')) \
|
.options(joinedload_all('library.files')) \
|
||||||
.options(joinedload_all('status')) \
|
.options(joinedload_all('status')) \
|
||||||
.options(joinedload_all('files')) \
|
.options(joinedload_all('files'))
|
||||||
|
|
||||||
|
|
||||||
if limit_offset:
|
if limit_offset:
|
||||||
splt = [x.strip() for x in limit_offset.split(',')]
|
splt = [x.strip() for x in limit_offset.split(',')]
|
||||||
@@ -165,7 +166,6 @@ class MoviePlugin(Plugin):
|
|||||||
offset = 0 if len(splt) is 1 else splt[1]
|
offset = 0 if len(splt) is 1 else splt[1]
|
||||||
q2 = q2.limit(limit).offset(offset)
|
q2 = q2.limit(limit).offset(offset)
|
||||||
|
|
||||||
|
|
||||||
results = q2.all()
|
results = q2.all()
|
||||||
movies = []
|
movies = []
|
||||||
for movie in results:
|
for movie in results:
|
||||||
@@ -178,7 +178,7 @@ class MoviePlugin(Plugin):
|
|||||||
movies.append(temp)
|
movies.append(temp)
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return movies
|
return (total_count, movies)
|
||||||
|
|
||||||
def availableChars(self, status = ['active']):
|
def availableChars(self, status = ['active']):
|
||||||
|
|
||||||
@@ -214,11 +214,12 @@ class MoviePlugin(Plugin):
|
|||||||
starts_with = params.get('starts_with', None)
|
starts_with = params.get('starts_with', None)
|
||||||
search = params.get('search', None)
|
search = params.get('search', None)
|
||||||
|
|
||||||
movies = self.list(status = status, limit_offset = limit_offset, starts_with = starts_with, search = search)
|
total_movies, movies = self.list(status = status, limit_offset = limit_offset, starts_with = starts_with, search = search)
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(movies) == 0,
|
'empty': len(movies) == 0,
|
||||||
|
'total': total_movies,
|
||||||
'movies': movies,
|
'movies': movies,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -241,14 +242,16 @@ class MoviePlugin(Plugin):
|
|||||||
for id in getParam('id').split(','):
|
for id in getParam('id').split(','):
|
||||||
movie = db.query(Movie).filter_by(id = id).first()
|
movie = db.query(Movie).filter_by(id = id).first()
|
||||||
|
|
||||||
# Get current selected title
|
|
||||||
default_title = ''
|
|
||||||
for title in movie.library.titles:
|
|
||||||
if title.default: default_title = title.title
|
|
||||||
|
|
||||||
if movie:
|
if movie:
|
||||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
|
||||||
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
|
# Get current selected title
|
||||||
|
default_title = ''
|
||||||
|
for title in movie.library.titles:
|
||||||
|
if title.default: default_title = title.title
|
||||||
|
|
||||||
|
fireEvent('notify.frontend', type = 'movie.busy.%s' % id, data = True, message = 'Updating "%s"' % default_title)
|
||||||
|
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id))
|
||||||
|
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
@@ -277,6 +280,10 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
def add(self, params = {}, force_readd = True, search_after = True):
|
def add(self, params = {}, force_readd = True, search_after = True):
|
||||||
|
|
||||||
|
if not params.get('identifier'):
|
||||||
|
log.error('Can\'t add movie without imdb identifier.')
|
||||||
|
return False
|
||||||
|
|
||||||
library = fireEvent('library.add', single = True, attrs = params, update_after = False)
|
library = fireEvent('library.add', single = True, attrs = params, update_after = False)
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
@@ -287,6 +294,7 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
m = db.query(Movie).filter_by(library_id = library.get('id')).first()
|
m = db.query(Movie).filter_by(library_id = library.get('id')).first()
|
||||||
|
added = True
|
||||||
do_search = False
|
do_search = False
|
||||||
if not m:
|
if not m:
|
||||||
m = Movie(
|
m = Movie(
|
||||||
@@ -295,8 +303,14 @@ class MoviePlugin(Plugin):
|
|||||||
status_id = status_active.get('id'),
|
status_id = status_active.get('id'),
|
||||||
)
|
)
|
||||||
db.add(m)
|
db.add(m)
|
||||||
fireEvent('library.update', params.get('identifier'), default_title = params.get('title', ''))
|
db.commit()
|
||||||
do_search = True
|
|
||||||
|
onComplete = None
|
||||||
|
if search_after:
|
||||||
|
onComplete = self.createOnComplete(m.id)
|
||||||
|
|
||||||
|
fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
|
||||||
|
search_after = False
|
||||||
elif force_readd:
|
elif force_readd:
|
||||||
# Clean snatched history
|
# Clean snatched history
|
||||||
for release in m.releases:
|
for release in m.releases:
|
||||||
@@ -305,10 +319,12 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
m.profile_id = params.get('profile_id', default_profile.get('id'))
|
m.profile_id = params.get('profile_id', default_profile.get('id'))
|
||||||
else:
|
else:
|
||||||
log.debug('Movie already exists, not updating: %s' % params)
|
log.debug('Movie already exists, not updating: %s', params)
|
||||||
|
added = False
|
||||||
|
|
||||||
if force_readd:
|
if force_readd:
|
||||||
m.status_id = status_active.get('id')
|
m.status_id = status_active.get('id')
|
||||||
|
do_search = True
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@@ -321,8 +337,12 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
movie_dict = m.to_dict(self.default_dict)
|
movie_dict = m.to_dict(self.default_dict)
|
||||||
|
|
||||||
if (force_readd or do_search) and search_after:
|
if do_search and search_after:
|
||||||
fireEventAsync('searcher.single', movie_dict)
|
onComplete = self.createOnComplete(m.id)
|
||||||
|
onComplete()
|
||||||
|
|
||||||
|
if added:
|
||||||
|
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = 'Successfully added "%s" to your wanted list.' % params.get('title', ''))
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return movie_dict
|
return movie_dict
|
||||||
@@ -336,7 +356,7 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'added': True,
|
'added': True if movie_dict else False,
|
||||||
'movie': movie_dict,
|
'movie': movie_dict,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -351,6 +371,9 @@ class MoviePlugin(Plugin):
|
|||||||
for movie_id in ids:
|
for movie_id in ids:
|
||||||
|
|
||||||
m = db.query(Movie).filter_by(id = movie_id).first()
|
m = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
|
||||||
m.profile_id = params.get('profile_id')
|
m.profile_id = params.get('profile_id')
|
||||||
|
|
||||||
# Remove releases
|
# Remove releases
|
||||||
@@ -369,7 +392,7 @@ class MoviePlugin(Plugin):
|
|||||||
fireEvent('movie.restatus', m.id)
|
fireEvent('movie.restatus', m.id)
|
||||||
|
|
||||||
movie_dict = m.to_dict(self.default_dict)
|
movie_dict = m.to_dict(self.default_dict)
|
||||||
fireEventAsync('searcher.single', movie_dict)
|
fireEventAsync('searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
@@ -421,6 +444,7 @@ class MoviePlugin(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
elif new_movie_status:
|
elif new_movie_status:
|
||||||
new_status = fireEvent('status.get', new_movie_status, single = True)
|
new_status = fireEvent('status.get', new_movie_status, single = True)
|
||||||
|
movie.profile_id = None
|
||||||
movie.status_id = new_status.get('id')
|
movie.status_id = new_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
else:
|
else:
|
||||||
@@ -441,7 +465,7 @@ class MoviePlugin(Plugin):
|
|||||||
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log.debug('Changing status for %s' % (m.library.titles[0].title))
|
log.debug('Changing status for %s', (m.library.titles[0].title))
|
||||||
if not m.profile:
|
if not m.profile:
|
||||||
m.status_id = done_status.get('id')
|
m.status_id = done_status.get('id')
|
||||||
else:
|
else:
|
||||||
@@ -458,3 +482,22 @@ class MoviePlugin(Plugin):
|
|||||||
#db.close()
|
#db.close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def createOnComplete(self, movie_id):
|
||||||
|
|
||||||
|
def onComplete():
|
||||||
|
db = get_session()
|
||||||
|
movie = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
|
fireEventAsync('searcher.single', movie.to_dict(self.default_dict), on_complete = self.createNotifyFront(movie_id))
|
||||||
|
|
||||||
|
return onComplete
|
||||||
|
|
||||||
|
|
||||||
|
def createNotifyFront(self, movie_id):
|
||||||
|
|
||||||
|
def notifyFront():
|
||||||
|
db = get_session()
|
||||||
|
movie = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
|
fireEvent('notify.frontend', type = 'movie.update.%s' % movie.id, data = movie.to_dict(self.default_dict))
|
||||||
|
|
||||||
|
return notifyFront
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ var MovieList = new Class({
|
|||||||
options: {
|
options: {
|
||||||
navigation: true,
|
navigation: true,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
menu: []
|
menu: [],
|
||||||
|
add_new: false
|
||||||
},
|
},
|
||||||
|
|
||||||
movies: [],
|
movies: [],
|
||||||
|
movies_added: {},
|
||||||
letters: {},
|
letters: {},
|
||||||
filter: {
|
filter: {
|
||||||
'startswith': null,
|
'startswith': null,
|
||||||
@@ -30,6 +32,17 @@ var MovieList = new Class({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
self.getMovies();
|
self.getMovies();
|
||||||
|
|
||||||
|
if(options.add_new)
|
||||||
|
App.addEvent('movie.added', self.movieAdded.bind(self))
|
||||||
|
},
|
||||||
|
|
||||||
|
movieAdded: function(notification){
|
||||||
|
var self = this;
|
||||||
|
window.scroll(0,0);
|
||||||
|
|
||||||
|
if(!self.movies_added[notification.data.id])
|
||||||
|
self.createMovie(notification.data, 'top');
|
||||||
},
|
},
|
||||||
|
|
||||||
create: function(){
|
create: function(){
|
||||||
@@ -59,7 +72,7 @@ var MovieList = new Class({
|
|||||||
self.created = true;
|
self.created = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
addMovies: function(movies){
|
addMovies: function(movies, total){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!self.created) self.create();
|
if(!self.created) self.create();
|
||||||
@@ -71,25 +84,41 @@ var MovieList = new Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.each(movies, function(movie){
|
Object.each(movies, function(movie){
|
||||||
|
self.createMovie(movie);
|
||||||
// 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': actions,
|
|
||||||
'view': self.current_view,
|
|
||||||
'onSelect': self.calculateSelected.bind(self)
|
|
||||||
}, movie);
|
|
||||||
$(m).inject(self.movie_list);
|
|
||||||
m.fireEvent('injected');
|
|
||||||
|
|
||||||
self.movies.include(m)
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.setCounter(total);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setCounter: function(count){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(!self.navigation_counter) return;
|
||||||
|
|
||||||
|
self.navigation_counter.set('text', (count || 0));
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
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': 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)
|
||||||
|
self.movies_added[movie.id] = true;
|
||||||
|
},
|
||||||
|
|
||||||
createNavigation: function(){
|
createNavigation: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -100,6 +129,7 @@ var MovieList = new Class({
|
|||||||
|
|
||||||
self.navigation = new Element('div.alph_nav').adopt(
|
self.navigation = new Element('div.alph_nav').adopt(
|
||||||
self.navigation_actions = new Element('ul.inlay.actions.reversed'),
|
self.navigation_actions = new Element('ul.inlay.actions.reversed'),
|
||||||
|
self.navigation_counter = new Element('span.counter[title=Total]'),
|
||||||
self.navigation_alpha = new Element('ul.numbers', {
|
self.navigation_alpha = new Element('ul.numbers', {
|
||||||
'events': {
|
'events': {
|
||||||
'click:relay(li)': function(e, el){
|
'click:relay(li)': function(e, el){
|
||||||
@@ -260,6 +290,7 @@ var MovieList = new Class({
|
|||||||
'events': {
|
'events': {
|
||||||
'click': function(e){
|
'click': function(e){
|
||||||
(e).preventDefault();
|
(e).preventDefault();
|
||||||
|
this.set('text', 'Deleting..')
|
||||||
Api.request('movie.delete', {
|
Api.request('movie.delete', {
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(','),
|
'id': ids.join(','),
|
||||||
@@ -268,14 +299,19 @@ var MovieList = new Class({
|
|||||||
'onSuccess': function(){
|
'onSuccess': function(){
|
||||||
qObj.close();
|
qObj.close();
|
||||||
|
|
||||||
|
var erase_movies = [];
|
||||||
self.movies.each(function(movie){
|
self.movies.each(function(movie){
|
||||||
if (movie.isSelected()){
|
if (movie.isSelected()){
|
||||||
$(movie).destroy()
|
$(movie).destroy()
|
||||||
self.movies.erase(movie)
|
erase_movies.include(movie)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.calculateSelected()
|
erase_movies.each(function(movie){
|
||||||
|
self.movies.erase(movie);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.calculateSelected();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -419,7 +455,7 @@ var MovieList = new Class({
|
|||||||
}, self.filter),
|
}, self.filter),
|
||||||
'onComplete': function(json){
|
'onComplete': function(json){
|
||||||
self.store(json.movies);
|
self.store(json.movies);
|
||||||
self.addMovies(json.movies);
|
self.addMovies(json.movies, json.total);
|
||||||
self.load_more.set('text', 'load more movies');
|
self.load_more.set('text', 'load more movies');
|
||||||
if(self.scrollspy) self.scrollspy.start();
|
if(self.scrollspy) self.scrollspy.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
.movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover {
|
.movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover {
|
||||||
background: rgba(255,255,255,0.03);
|
background: rgba(255,255,255,0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .movie_container {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .data {
|
.movies .data {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -32,7 +36,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
float: right;
|
float: right;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.2s linear;
|
transition: all 0.2s linear;
|
||||||
}
|
}
|
||||||
.movies .list_view .data, .movies .mass_edit_view .data {
|
.movies .list_view .data, .movies .mass_edit_view .data {
|
||||||
@@ -89,7 +92,7 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 64%;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .info .year {
|
.movies .info .year {
|
||||||
@@ -152,7 +155,6 @@
|
|||||||
.movies .list_view .data .quality, .movies .mass_edit_view .data .quality {
|
.movies .list_view .data .quality, .movies .mass_edit_view .data .quality {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
float: right;
|
float: right;
|
||||||
width: 30%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .data .quality .available, .movies .data .quality .snatched {
|
.movies .data .quality .available, .movies .data .quality .snatched {
|
||||||
@@ -200,6 +202,7 @@
|
|||||||
.movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions {
|
.movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions {
|
||||||
margin: -34px 2px 0 0;
|
margin: -34px 2px 0 0;
|
||||||
background: #4e5969;
|
background: #4e5969;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .delete_container {
|
.movies .delete_container {
|
||||||
@@ -310,6 +313,33 @@
|
|||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .movie .trailer_container {
|
||||||
|
width: 100%;
|
||||||
|
background: #000;
|
||||||
|
text-align: center;
|
||||||
|
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.movies .movie .trailer_container.hide {
|
||||||
|
height: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .hide_trailer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -50px;
|
||||||
|
width: 100px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: #4e5969;
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
transition: all .6s cubic-bezier(0.9,0,0.1,1) .2s;
|
||||||
|
}
|
||||||
|
.movies .movie .hide_trailer.hide {
|
||||||
|
top: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .load_more {
|
.movies .load_more {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -330,15 +360,17 @@
|
|||||||
width: 1080px;
|
width: 1080px;
|
||||||
margin: 0 -60px;
|
margin: 0 -60px;
|
||||||
box-shadow: 0 20px 20px -22px rgba(0,0,0,0.1);
|
box-shadow: 0 20px 20px -22px rgba(0,0,0,0.1);
|
||||||
|
background: #4e5969;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav.float {
|
.movies .alph_nav.float {
|
||||||
box-shadow: 0 30px 30px -32px rgba(0,0,0,0.5);
|
box-shadow: 0 30px 30px -32px rgba(0,0,0,0.5);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: #4e5969;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav ul.numbers, .movies .alph_nav ul.actions {
|
.movies .alph_nav ul.numbers,
|
||||||
|
.movies .alph_nav .counter,
|
||||||
|
.movies .alph_nav ul.actions {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0 0 1px;
|
padding: 0 0 1px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -346,10 +378,15 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav .counter {
|
||||||
|
width: 60px;
|
||||||
|
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;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
width: 22px;
|
width: 20px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -361,7 +398,6 @@
|
|||||||
}
|
}
|
||||||
.movies .alph_nav .numbers li:first-child {
|
.movies .alph_nav .numbers li:first-child {
|
||||||
width: 43px;
|
width: 43px;
|
||||||
margin-left: 7px;
|
|
||||||
}
|
}
|
||||||
.movies .alph_nav li.available {
|
.movies .alph_nav li.available {
|
||||||
color: rgba(255,255,255,0.8);
|
color: rgba(255,255,255,0.8);
|
||||||
@@ -370,8 +406,8 @@
|
|||||||
}
|
}
|
||||||
.movies .alph_nav li.active.available, .movies .alph_nav li.available:hover {
|
.movies .alph_nav li.active.available, .movies .alph_nav li.available:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
line-height: 24px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movies .alph_nav input {
|
.movies .alph_nav input {
|
||||||
|
|||||||
@@ -11,53 +11,124 @@ var Movie = new Class({
|
|||||||
self.view = options.view || 'thumbs';
|
self.view = options.view || 'thumbs';
|
||||||
self.list = list;
|
self.list = list;
|
||||||
|
|
||||||
|
self.el = new Element('div.movie.inlay');
|
||||||
|
|
||||||
self.profile = Quality.getProfile(data.profile_id) || {};
|
self.profile = Quality.getProfile(data.profile_id) || {};
|
||||||
self.parent(self, options);
|
self.parent(self, options);
|
||||||
|
|
||||||
|
App.addEvent('movie.update.'+data.id, self.update.bind(self));
|
||||||
|
|
||||||
|
['movie.busy', 'searcher.started'].each(function(listener){
|
||||||
|
App.addEvent(listener+'.'+data.id, function(notification){
|
||||||
|
if(notification.data)
|
||||||
|
self.busy(true)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
busy: function(set_busy){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(!set_busy){
|
||||||
|
if(self.spinner){
|
||||||
|
self.mask.fade('out');
|
||||||
|
setTimeout(function(){
|
||||||
|
if(self.mask)
|
||||||
|
self.mask.destroy();
|
||||||
|
if(self.spinner)
|
||||||
|
self.spinner.el.destroy();
|
||||||
|
self.spinner = null;
|
||||||
|
self.mask = null;
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(!self.spinner) {
|
||||||
|
self.createMask();
|
||||||
|
self.spinner = createSpinner(self.mask);
|
||||||
|
self.positionMask();
|
||||||
|
self.mask.fade('in');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createMask: function(){
|
||||||
|
var self = this;
|
||||||
|
self.mask = new Element('div.mask', {
|
||||||
|
'styles': {
|
||||||
|
'z-index': '1'
|
||||||
|
}
|
||||||
|
}).inject(self.el, 'top').fade('hide');
|
||||||
|
self.positionMask();
|
||||||
|
},
|
||||||
|
|
||||||
|
positionMask: function(){
|
||||||
|
var self = this,
|
||||||
|
s = self.el.getSize()
|
||||||
|
|
||||||
|
return self.mask.setStyles({
|
||||||
|
'width': s.x,
|
||||||
|
'height': s.y
|
||||||
|
}).position({
|
||||||
|
'relativeTo': self.el
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function(notification){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.data = notification.data;
|
||||||
|
self.container.destroy();
|
||||||
|
|
||||||
|
self.profile = Quality.getProfile(self.data.profile_id) || {};
|
||||||
|
self.create();
|
||||||
|
|
||||||
|
self.busy(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.el = new Element('div.movie.inlay').adopt(
|
self.el.adopt(
|
||||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
self.container = new Element('div.movie_container').adopt(
|
||||||
'events': {
|
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||||
'change': function(){
|
'events': {
|
||||||
self.fireEvent('select')
|
'change': function(){
|
||||||
}
|
self.fireEvent('select')
|
||||||
}
|
|
||||||
}),
|
|
||||||
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')
|
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')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -150,7 +221,7 @@ var Movie = new Class({
|
|||||||
self.el.removeEvents('outerClick')
|
self.el.removeEvents('outerClick')
|
||||||
|
|
||||||
self.addEvent('slideEnd:once', function(){
|
self.addEvent('slideEnd:once', function(){
|
||||||
self.el.getElements('> :not(.data):not(.poster)').hide();
|
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.data_container.tween('right', -840, 0);
|
self.data_container.tween('right', -840, 0);
|
||||||
@@ -178,6 +249,10 @@ var Movie = new Class({
|
|||||||
|
|
||||||
isSelected: function(){
|
isSelected: function(){
|
||||||
return this.select_checkbox.get('checked');
|
return this.select_checkbox.get('checked');
|
||||||
|
},
|
||||||
|
|
||||||
|
toElement: function(){
|
||||||
|
return this.el;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -290,7 +365,7 @@ var ReleaseAction = new Class({
|
|||||||
} catch(e){}
|
} catch(e){}
|
||||||
|
|
||||||
new Element('div', {
|
new Element('div', {
|
||||||
'class': 'item'
|
'class': 'item '+status.identifier
|
||||||
}).adopt(
|
}).adopt(
|
||||||
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
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.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
||||||
@@ -357,4 +432,109 @@ var ReleaseAction = new Class({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
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': 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -221,7 +221,9 @@ Block.Search.Item = new Class({
|
|||||||
}
|
}
|
||||||
}).adopt(
|
}).adopt(
|
||||||
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||||
'src': info.images.poster[0]
|
'src': info.images.poster[0],
|
||||||
|
'height': null,
|
||||||
|
'width': null
|
||||||
}) : null,
|
}) : null,
|
||||||
new Element('div.info').adopt(
|
new Element('div.info').adopt(
|
||||||
self.title = new Element('h2', {
|
self.title = new Element('h2', {
|
||||||
@@ -299,11 +301,11 @@ Block.Search.Item = new Class({
|
|||||||
'title': self.title_select.get('value'),
|
'title': self.title_select.get('value'),
|
||||||
'profile_id': self.profile_select.get('value')
|
'profile_id': self.profile_select.get('value')
|
||||||
},
|
},
|
||||||
'onComplete': function(){
|
'onComplete': function(json){
|
||||||
self.options.empty();
|
self.options.empty();
|
||||||
self.options.adopt(
|
self.options.adopt(
|
||||||
new Element('div.message', {
|
new Element('div.message', {
|
||||||
'text': 'Movie succesfully added.'
|
'text': json.added ? 'Movie succesfully added.' : 'Movie didn\'t add properly. Check logs'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -332,8 +334,10 @@ Block.Search.Item = new Class({
|
|||||||
|
|
||||||
self.options.adopt(
|
self.options.adopt(
|
||||||
new Element('div').adopt(
|
new Element('div').adopt(
|
||||||
self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
self.option_thumbnail = self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||||
'src': self.info.images.poster[0]
|
'src': self.info.images.poster[0],
|
||||||
|
'height': null,
|
||||||
|
'width': null
|
||||||
}) : null,
|
}) : null,
|
||||||
self.info.in_wanted ? new Element('span.in_wanted', {
|
self.info.in_wanted ? new Element('span.in_wanted', {
|
||||||
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
||||||
|
|||||||
@@ -135,8 +135,7 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
success = True
|
success = True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
message = 'Failed deleting Profile: %s' % e
|
message = log.error('Failed deleting Profile: %s', e)
|
||||||
log.error(message)
|
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
|
|
||||||
@@ -163,7 +162,7 @@ class ProfilePlugin(Plugin):
|
|||||||
# Create default quality profile
|
# Create default quality profile
|
||||||
order = -2
|
order = -2
|
||||||
for profile in profiles:
|
for profile in profiles:
|
||||||
log.info('Creating default profile: %s' % profile.get('label'))
|
log.info('Creating default profile: %s', profile.get('label'))
|
||||||
p = Profile(
|
p = Profile(
|
||||||
label = toUnicode(profile.get('label')),
|
label = toUnicode(profile.get('label')),
|
||||||
order = order
|
order = order
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
qualities = [
|
qualities = [
|
||||||
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
|
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
|
||||||
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts']},
|
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
|
||||||
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts', 'ts']},
|
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
|
||||||
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
||||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
||||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
@@ -26,7 +26,7 @@ class QualityPlugin(Plugin):
|
|||||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']}
|
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']}
|
||||||
]
|
]
|
||||||
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class QualityPlugin(Plugin):
|
|||||||
quality = db.query(Quality).filter_by(identifier = q.get('identifier')).first()
|
quality = db.query(Quality).filter_by(identifier = q.get('identifier')).first()
|
||||||
|
|
||||||
if not quality:
|
if not quality:
|
||||||
log.info('Creating quality: %s' % q.get('label'))
|
log.info('Creating quality: %s', q.get('label'))
|
||||||
quality = Quality()
|
quality = Quality()
|
||||||
db.add(quality)
|
db.add(quality)
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ class QualityPlugin(Plugin):
|
|||||||
).all()
|
).all()
|
||||||
|
|
||||||
if not profile:
|
if not profile:
|
||||||
log.info('Creating profile: %s' % q.get('label'))
|
log.info('Creating profile: %s', q.get('label'))
|
||||||
profile = Profile(
|
profile = Profile(
|
||||||
core = True,
|
core = True,
|
||||||
label = toUnicode(quality.label),
|
label = toUnicode(quality.label),
|
||||||
@@ -170,20 +170,20 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
# Check tags
|
# Check tags
|
||||||
if quality['identifier'] in words:
|
if quality['identifier'] in words:
|
||||||
log.debug('Found via identifier "%s" in %s' % (quality['identifier'], cur_file))
|
log.debug('Found via identifier "%s" in %s', (quality['identifier'], cur_file))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
if list(set(quality.get('alternative', [])) & set(words)):
|
if list(set(quality.get('alternative', [])) & set(words)):
|
||||||
log.debug('Found %s via alt %s in %s' % (quality['identifier'], quality.get('alternative'), cur_file))
|
log.debug('Found %s via alt %s in %s', (quality['identifier'], quality.get('alternative'), cur_file))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
for tag in quality.get('tags', []):
|
for tag in quality.get('tags', []):
|
||||||
if isinstance(tag, tuple) and '.'.join(tag) in '.'.join(words):
|
if isinstance(tag, tuple) and '.'.join(tag) in '.'.join(words):
|
||||||
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), cur_file))
|
log.debug('Found %s via tag %s in %s', (quality['identifier'], quality.get('tags'), cur_file))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
if list(set(quality.get('tags', [])) & set(words)):
|
if list(set(quality.get('tags', [])) & set(words)):
|
||||||
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), cur_file))
|
log.debug('Found %s via tag %s in %s', (quality['identifier'], quality.get('tags'), cur_file))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
# Try again with loose testing
|
# Try again with loose testing
|
||||||
@@ -191,7 +191,7 @@ class QualityPlugin(Plugin):
|
|||||||
if quality:
|
if quality:
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
log.debug('Could not identify quality for: %s' % files)
|
log.debug('Could not identify quality for: %s', files)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def guessLoose(self, hash, extra):
|
def guessLoose(self, hash, extra):
|
||||||
@@ -200,7 +200,7 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
# Last check on resolution only
|
# Last check on resolution only
|
||||||
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
||||||
log.debug('Found %s via resolution_width: %s == %s' % (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
log.debug('Found %s via resolution_width: %s == %s', (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
if 480 <= extra.get('resolution_width', 0) <= 720:
|
if 480 <= extra.get('resolution_width', 0) <= 720:
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class Release(Plugin):
|
|||||||
rel.files.append(added_file)
|
rel.files.append(added_file)
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.debug('Failed to attach "%s" to release: %s' % (cur_file, e))
|
log.debug('Failed to attach "%s" to release: %s', (cur_file, e))
|
||||||
|
|
||||||
fireEvent('movie.restatus', movie.id)
|
fireEvent('movie.restatus', movie.id)
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ class Release(Plugin):
|
|||||||
item[info.identifier] = info.value
|
item[info.identifier] = info.value
|
||||||
|
|
||||||
# Get matching provider
|
# Get matching provider
|
||||||
provider = fireEvent('provider.belongs_to', item['url'], single = True)
|
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
||||||
item['download'] = provider.download
|
item['download'] = provider.download
|
||||||
|
|
||||||
fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
|
fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
|
||||||
@@ -158,7 +158,7 @@ class Release(Plugin):
|
|||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
log.error('Couldn\'t find release with id: %s' % id)
|
log.error('Couldn\'t find release with id: %s', id)
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ rename_options = {
|
|||||||
'source': 'Source media (Bluray)',
|
'source': 'Source media (Bluray)',
|
||||||
'original': 'Original filename',
|
'original': 'Original filename',
|
||||||
'original_folder': 'Original foldername',
|
'original_folder': 'Original foldername',
|
||||||
|
'imdb_id': 'IMDB id (tt0123456)',
|
||||||
|
'cd': 'CD number (cd1)',
|
||||||
|
'cd_nr': 'Just the cd nr. (1)',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,19 +86,13 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# Add _UNKNOWN_ if no library item is connected
|
# Add _UNKNOWN_ if no library item is connected
|
||||||
if not group['library'] or not movie_title:
|
if not group['library'] or not movie_title:
|
||||||
if group['dirname']:
|
self.tagDir(group, 'unknown')
|
||||||
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_UNKNOWN_%s' % group['dirname'])
|
continue
|
||||||
else: # Add it to filename
|
|
||||||
for file_type in group['files']:
|
|
||||||
for rename_me in group['files'][file_type]:
|
|
||||||
filename = os.path.basename(rename_me)
|
|
||||||
rename_files[rename_me] = rename_me.replace(filename, '_UNKNOWN_%s' % filename)
|
|
||||||
|
|
||||||
# Rename the files using the library data
|
# Rename the files using the library data
|
||||||
else:
|
else:
|
||||||
group['library'] = fireEvent('library.update', identifier = group['library']['identifier'], single = True)
|
group['library'] = fireEvent('library.update', identifier = group['library']['identifier'], single = True)
|
||||||
if not group['library']:
|
if not group['library']:
|
||||||
log.error('Could not rename, no library item to work with: %s' % group_identifier)
|
log.error('Could not rename, no library item to work with: %s', group_identifier)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
library = group['library']
|
library = group['library']
|
||||||
@@ -129,13 +123,14 @@ class Renamer(Plugin):
|
|||||||
'source': group['meta_data']['source'],
|
'source': group['meta_data']['source'],
|
||||||
'resolution_width': group['meta_data'].get('resolution_width'),
|
'resolution_width': group['meta_data'].get('resolution_width'),
|
||||||
'resolution_height': group['meta_data'].get('resolution_height'),
|
'resolution_height': group['meta_data'].get('resolution_height'),
|
||||||
|
'imdb_id': library['identifier'],
|
||||||
}
|
}
|
||||||
|
|
||||||
for file_type in group['files']:
|
for file_type in group['files']:
|
||||||
|
|
||||||
# Move nfo depending on settings
|
# Move nfo depending on settings
|
||||||
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
||||||
log.debug('Skipping, renaming of %s disabled' % file_type)
|
log.debug('Skipping, renaming of %s disabled', file_type)
|
||||||
if self.conf('cleanup'):
|
if self.conf('cleanup'):
|
||||||
for current_file in group['files'][file_type]:
|
for current_file in group['files'][file_type]:
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
@@ -194,7 +189,7 @@ class Renamer(Plugin):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
log.error('Could not determine dvd structure for: %s' % current_file)
|
log.error('Could not determine dvd structure for: %s', current_file)
|
||||||
|
|
||||||
# Do rename others
|
# Do rename others
|
||||||
else:
|
else:
|
||||||
@@ -208,7 +203,7 @@ class Renamer(Plugin):
|
|||||||
if file_type is 'subtitle':
|
if file_type is 'subtitle':
|
||||||
|
|
||||||
# rename subtitles with or without language
|
# rename subtitles with or without language
|
||||||
#rename_files[current_file] = os.path.join(destination, final_folder_name, final_file_name)
|
rename_files[current_file] = os.path.join(destination, final_folder_name, final_file_name)
|
||||||
sub_langs = group['subtitle_language'].get(current_file, [])
|
sub_langs = group['subtitle_language'].get(current_file, [])
|
||||||
|
|
||||||
rename_extras = self.getRenameExtras(
|
rename_extras = self.getRenameExtras(
|
||||||
@@ -263,13 +258,13 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# Mark movie "done" onces it found the quality with the finish check
|
# Mark movie "done" onces it found the quality with the finish check
|
||||||
try:
|
try:
|
||||||
if movie.status_id == active_status.get('id'):
|
if movie.status_id == active_status.get('id') and movie.profile:
|
||||||
for profile_type in movie.profile.types:
|
for profile_type in movie.profile.types:
|
||||||
if profile_type.quality_id == group['meta_data']['quality']['id'] and profile_type.finish:
|
if profile_type.quality_id == group['meta_data']['quality']['id'] and profile_type.finish:
|
||||||
movie.status_id = done_status.get('id')
|
movie.status_id = done_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
|
||||||
|
|
||||||
# Go over current movie releases
|
# Go over current movie releases
|
||||||
for release in movie.releases:
|
for release in movie.releases:
|
||||||
@@ -279,30 +274,23 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# This is where CP removes older, lesser quality releases
|
# This is where CP removes older, lesser quality releases
|
||||||
if release.quality.order > group['meta_data']['quality']['order']:
|
if release.quality.order > group['meta_data']['quality']['order']:
|
||||||
log.info('Removing lesser quality %s for %s.' % (movie.library.titles[0].title, release.quality.label))
|
log.info('Removing lesser quality %s for %s.', (movie.library.titles[0].title, release.quality.label))
|
||||||
for current_file in release.files:
|
for current_file in release.files:
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
remove_releases.append(release)
|
remove_releases.append(release)
|
||||||
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
|
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
|
||||||
elif release.quality.order is group['meta_data']['quality']['order']:
|
elif release.quality.order is group['meta_data']['quality']['order']:
|
||||||
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label))
|
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.', (movie.library.titles[0].title, release.quality.label))
|
||||||
for current_file in release.files:
|
for current_file in release.files:
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
remove_releases.append(release)
|
remove_releases.append(release)
|
||||||
|
|
||||||
# Downloaded a lower quality, rename the newly downloaded files/folder to exclude them from scan
|
# Downloaded a lower quality, rename the newly downloaded files/folder to exclude them from scan
|
||||||
else:
|
else:
|
||||||
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
|
log.info('Better quality release already exists for %s, with quality %s', (movie.library.titles[0].title, release.quality.label))
|
||||||
|
|
||||||
# Add _EXISTS_ to the parent dir
|
# Add _EXISTS_ to the parent dir
|
||||||
if group['dirname']:
|
self.tagDir(group, 'exists')
|
||||||
for rename_me in rename_files: # Don't rename anything in this group
|
|
||||||
rename_files[rename_me] = None
|
|
||||||
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_EXISTS_%s' % group['dirname'])
|
|
||||||
else: # Add it to filename
|
|
||||||
for rename_me in rename_files:
|
|
||||||
filename = os.path.basename(rename_me)
|
|
||||||
rename_files[rename_me] = rename_me.replace(filename, '_EXISTS_%s' % filename)
|
|
||||||
|
|
||||||
# Notify on rename fail
|
# Notify on rename fail
|
||||||
download_message = 'Renaming of %s (%s) canceled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label)
|
download_message = 'Renaming of %s (%s) canceled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label)
|
||||||
@@ -311,7 +299,6 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
break
|
break
|
||||||
elif release.status_id is snatched_status.get('id'):
|
elif release.status_id is snatched_status.get('id'):
|
||||||
print release.quality.label, group['meta_data']['quality']['label']
|
|
||||||
if release.quality.id is group['meta_data']['quality']['id']:
|
if release.quality.id is group['meta_data']['quality']['id']:
|
||||||
log.debug('Marking release as downloaded')
|
log.debug('Marking release as downloaded')
|
||||||
release.status_id = downloaded_status.get('id')
|
release.status_id = downloaded_status.get('id')
|
||||||
@@ -323,14 +310,32 @@ class Renamer(Plugin):
|
|||||||
for current_file in group['files']['leftover']:
|
for current_file in group['files']['leftover']:
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
elif not remove_leftovers: # Don't remove anything
|
elif not remove_leftovers: # Don't remove anything
|
||||||
remove_files = []
|
break
|
||||||
|
|
||||||
|
# Remove files
|
||||||
|
for src in remove_files:
|
||||||
|
|
||||||
|
if isinstance(src, File):
|
||||||
|
src = src.path
|
||||||
|
|
||||||
|
if rename_files.get(src):
|
||||||
|
log.debug('Not removing file that will be renamed: %s', src)
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.info('Removing "%s"', src)
|
||||||
|
try:
|
||||||
|
if os.path.isfile(src):
|
||||||
|
os.remove(src)
|
||||||
|
except:
|
||||||
|
log.error('Failed removing %s: %s', (src, traceback.format_exc()))
|
||||||
|
self.tagDir(group, 'failed_remove')
|
||||||
|
|
||||||
# Rename all files marked
|
# Rename all files marked
|
||||||
group['renamed_files'] = []
|
group['renamed_files'] = []
|
||||||
for src in rename_files:
|
for src in rename_files:
|
||||||
if rename_files[src]:
|
if rename_files[src]:
|
||||||
dst = rename_files[src]
|
dst = rename_files[src]
|
||||||
log.info('Renaming "%s" to "%s"' % (src, dst))
|
log.info('Renaming "%s" to "%s"', (src, dst))
|
||||||
|
|
||||||
# Create dir
|
# Create dir
|
||||||
self.makeDir(os.path.dirname(dst))
|
self.makeDir(os.path.dirname(dst))
|
||||||
@@ -339,34 +344,23 @@ class Renamer(Plugin):
|
|||||||
self.moveFile(src, dst)
|
self.moveFile(src, dst)
|
||||||
group['renamed_files'].append(dst)
|
group['renamed_files'].append(dst)
|
||||||
except:
|
except:
|
||||||
log.error('Failed moving the file "%s" : %s' % (os.path.basename(src), traceback.format_exc()))
|
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
||||||
|
self.tagDir(group, 'failed_rename')
|
||||||
# Remove files
|
|
||||||
for src in remove_files:
|
|
||||||
|
|
||||||
if isinstance(src, File):
|
|
||||||
src = src.path
|
|
||||||
|
|
||||||
log.info('Removing "%s"' % src)
|
|
||||||
try:
|
|
||||||
os.remove(src)
|
|
||||||
except:
|
|
||||||
log.error('Failed removing %s: %s' % (src, traceback.format_exc()))
|
|
||||||
|
|
||||||
# Remove matching releases
|
# Remove matching releases
|
||||||
for release in remove_releases:
|
for release in remove_releases:
|
||||||
log.debug('Removing release %s' % release.identifier)
|
log.debug('Removing release %s', release.identifier)
|
||||||
try:
|
try:
|
||||||
db.delete(release)
|
db.delete(release)
|
||||||
except:
|
except:
|
||||||
log.error('Failed removing %s: %s' % (release.identifier, traceback.format_exc()))
|
log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc()))
|
||||||
|
|
||||||
if group['dirname'] and group['parentdir']:
|
if group['dirname'] and group['parentdir']:
|
||||||
try:
|
try:
|
||||||
log.info('Deleting folder: %s' % group['parentdir'])
|
log.info('Deleting folder: %s', group['parentdir'])
|
||||||
self.deleteEmptyFolder(group['parentdir'])
|
self.deleteEmptyFolder(group['parentdir'])
|
||||||
except:
|
except:
|
||||||
log.error('Failed removing %s: %s' % (group['parentdir'], traceback.format_exc()))
|
log.error('Failed removing %s: %s', (group['parentdir'], traceback.format_exc()))
|
||||||
|
|
||||||
# Search for trailers etc
|
# Search for trailers etc
|
||||||
fireEventAsync('renamer.after', group)
|
fireEventAsync('renamer.after', group)
|
||||||
@@ -398,17 +392,43 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
return rename_files
|
return rename_files
|
||||||
|
|
||||||
|
def tagDir(self, group, tag):
|
||||||
|
|
||||||
|
rename_files = {}
|
||||||
|
|
||||||
|
if group['dirname']:
|
||||||
|
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_%s_%s' % (tag.upper(), group['dirname']))
|
||||||
|
else: # Add it to filename
|
||||||
|
for file_type in group['files']:
|
||||||
|
for rename_me in group['files'][file_type]:
|
||||||
|
filename = os.path.basename(rename_me)
|
||||||
|
rename_files[rename_me] = rename_me.replace(filename, '_%s_%s' % (tag.upper(), filename))
|
||||||
|
|
||||||
|
for src in rename_files:
|
||||||
|
if rename_files[src]:
|
||||||
|
dst = rename_files[src]
|
||||||
|
log.info('Renaming "%s" to "%s"', (src, dst))
|
||||||
|
|
||||||
|
# Create dir
|
||||||
|
self.makeDir(os.path.dirname(dst))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.moveFile(src, dst)
|
||||||
|
except:
|
||||||
|
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
||||||
|
raise
|
||||||
|
|
||||||
def moveFile(self, old, dest):
|
def moveFile(self, old, dest):
|
||||||
try:
|
try:
|
||||||
shutil.move(old, dest)
|
shutil.move(old, dest)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.chmod(dest, Env.getPermission('folder'))
|
os.chmod(dest, Env.getPermission('file'))
|
||||||
except:
|
except:
|
||||||
log.error('Failed setting permissions for file: %s' % dest)
|
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error("Couldn't move file '%s' to '%s': %s" % (old, dest, traceback.format_exc()))
|
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -421,7 +441,7 @@ class Renamer(Plugin):
|
|||||||
replaced = toUnicode(string)
|
replaced = toUnicode(string)
|
||||||
for x, r in replacements.iteritems():
|
for x, r in replacements.iteritems():
|
||||||
if r is not None:
|
if r is not None:
|
||||||
replaced = replaced.replace('<%s>' % toUnicode(x), toUnicode(r))
|
replaced = replaced.replace(u'<%s>' % toUnicode(x), toUnicode(r))
|
||||||
else:
|
else:
|
||||||
#If information is not available, we don't want the tag in the filename
|
#If information is not available, we don't want the tag in the filename
|
||||||
replaced = replaced.replace('<' + x + '>', '')
|
replaced = replaced.replace('<' + x + '>', '')
|
||||||
@@ -444,9 +464,9 @@ class Renamer(Plugin):
|
|||||||
try:
|
try:
|
||||||
os.rmdir(full_path)
|
os.rmdir(full_path)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t remove empty directory %s: %s' % (full_path, traceback.format_exc()))
|
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.rmdir(folder)
|
os.rmdir(folder)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t remove empty directory %s: %s' % (folder, traceback.format_exc()))
|
log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
from couchpotato.core.helpers.encoding import toUnicode, simplifyString, ss
|
||||||
from couchpotato.core.helpers.variable import getExt, getImdb, tryInt
|
from couchpotato.core.helpers.variable import getExt, getImdb, tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import File
|
from couchpotato.core.settings.model import File, Movie
|
||||||
from couchpotato.environment import Env
|
|
||||||
from enzyme.exceptions import NoParserError, ParseError
|
from enzyme.exceptions import NoParserError, ParseError
|
||||||
from guessit import guess_movie_info
|
from guessit import guess_movie_info
|
||||||
from subliminal.videos import Video
|
from subliminal.videos import Video
|
||||||
@@ -24,10 +23,10 @@ class Scanner(Plugin):
|
|||||||
'media': 314572800, # 300MB
|
'media': 314572800, # 300MB
|
||||||
'trailer': 1048576, # 1MB
|
'trailer': 1048576, # 1MB
|
||||||
}
|
}
|
||||||
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
||||||
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
||||||
extensions = {
|
extensions = {
|
||||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts'],
|
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
|
||||||
'movie_extra': ['mds'],
|
'movie_extra': ['mds'],
|
||||||
'dvd': ['vts_*', 'vob'],
|
'dvd': ['vts_*', 'vob'],
|
||||||
'nfo': ['nfo', 'txt', 'tag'],
|
'nfo': ['nfo', 'txt', 'tag'],
|
||||||
@@ -89,24 +88,28 @@ class Scanner(Plugin):
|
|||||||
addEvent('scanner.partnumber', self.getPartNumber)
|
addEvent('scanner.partnumber', self.getPartNumber)
|
||||||
|
|
||||||
def after_rename(group):
|
def after_rename(group):
|
||||||
return self.scanFilesToLibrary(self, folder = group['destination_dir'], files = group['renamed_files'])
|
return self.scanFilesToLibrary(folder = group['destination_dir'], files = group['renamed_files'])
|
||||||
|
|
||||||
addEvent('rename.after', after_rename)
|
addEvent('renamer.after', after_rename)
|
||||||
|
|
||||||
def scanFilesToLibrary(self, folder = None, files = None):
|
def scanFilesToLibrary(self, folder = None, files = None):
|
||||||
|
|
||||||
|
folder = os.path.normpath(folder)
|
||||||
|
|
||||||
groups = self.scan(folder = folder, files = files)
|
groups = self.scan(folder = folder, files = files)
|
||||||
|
|
||||||
for group in groups.itervalues():
|
for group in groups.itervalues():
|
||||||
if group['library']:
|
if group['library']:
|
||||||
fireEvent('release.add', group = group)
|
fireEvent('release.add', group = group)
|
||||||
|
|
||||||
def scanFolderToLibrary(self, folder = None, newer_than = None, simple = True):
|
def scanFolderToLibrary(self, folder = None, newer_than = 0, simple = True):
|
||||||
|
|
||||||
|
folder = os.path.normpath(folder)
|
||||||
|
|
||||||
if not os.path.isdir(folder):
|
if not os.path.isdir(folder):
|
||||||
return
|
return
|
||||||
|
|
||||||
groups = self.scan(folder = folder, simple = simple)
|
groups = self.scan(folder = folder, simple = simple, newer_than = newer_than)
|
||||||
|
|
||||||
added_identifier = []
|
added_identifier = []
|
||||||
while True and not self.shuttingDown():
|
while True and not self.shuttingDown():
|
||||||
@@ -127,10 +130,12 @@ class Scanner(Plugin):
|
|||||||
return added_identifier
|
return added_identifier
|
||||||
|
|
||||||
|
|
||||||
def scan(self, folder = None, files = [], simple = False):
|
def scan(self, folder = None, files = [], simple = False, newer_than = 0):
|
||||||
|
|
||||||
|
folder = ss(os.path.normpath(folder))
|
||||||
|
|
||||||
if not folder or not os.path.isdir(folder):
|
if not folder or not os.path.isdir(folder):
|
||||||
log.error('Folder doesn\'t exists: %s' % folder)
|
log.error('Folder doesn\'t exists: %s', folder)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Get movie "master" files
|
# Get movie "master" files
|
||||||
@@ -141,19 +146,15 @@ class Scanner(Plugin):
|
|||||||
if len(files) == 0:
|
if len(files) == 0:
|
||||||
try:
|
try:
|
||||||
files = []
|
files = []
|
||||||
for root, dirs, walk_files in os.walk(toUnicode(folder)):
|
for root, dirs, walk_files in os.walk(folder):
|
||||||
for filename in walk_files:
|
for filename in walk_files:
|
||||||
files.append(os.path.join(root, filename))
|
files.append(os.path.join(root, filename))
|
||||||
except:
|
except:
|
||||||
try:
|
log.error('Failed getting files from %s: %s', (folder, traceback.format_exc()))
|
||||||
files = []
|
else:
|
||||||
folder = toUnicode(folder).encode(Env.get('encoding'))
|
files = [ss(x) for x in files]
|
||||||
log.info('Trying to convert unicode to str path: %s, %s' % (folder, type(folder)))
|
|
||||||
for root, dirs, walk_files in os.walk(folder):
|
db = get_session()
|
||||||
for filename in walk_files:
|
|
||||||
files.append(os.path.join(root, filename))
|
|
||||||
except:
|
|
||||||
log.error('Failed getting files from %s: %s' % (folder, traceback.format_exc()))
|
|
||||||
|
|
||||||
for file_path in files:
|
for file_path in files:
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@ class Scanner(Plugin):
|
|||||||
for identifier, group in movie_files.iteritems():
|
for identifier, group in movie_files.iteritems():
|
||||||
if identifier not in group['identifiers'] and len(identifier) > 0: group['identifiers'].append(identifier)
|
if identifier not in group['identifiers'] and len(identifier) > 0: group['identifiers'].append(identifier)
|
||||||
|
|
||||||
log.debug('Grouping files: %s' % identifier)
|
log.debug('Grouping files: %s', identifier)
|
||||||
|
|
||||||
for file_path in group['unsorted_files']:
|
for file_path in group['unsorted_files']:
|
||||||
wo_ext = file_path[:-(len(getExt(file_path)) + 1)]
|
wo_ext = file_path[:-(len(getExt(file_path)) + 1)]
|
||||||
@@ -231,19 +232,53 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
|
|
||||||
# Group the files based on the identifier
|
# Group the files based on the identifier
|
||||||
for identifier, group in movie_files.iteritems():
|
delete_identifiers = []
|
||||||
log.debug('Grouping files on identifier: %s' % identifier)
|
for identifier, found_files in self.path_identifiers.iteritems():
|
||||||
|
log.debug('Grouping files on identifier: %s', identifier)
|
||||||
|
|
||||||
found_files = set(self.path_identifiers.get(identifier, []))
|
group = movie_files.get(identifier)
|
||||||
group['unsorted_files'].extend(found_files)
|
if group:
|
||||||
|
group['unsorted_files'].extend(found_files)
|
||||||
|
delete_identifiers.append(identifier)
|
||||||
|
|
||||||
# Remove the found files from the leftover stack
|
# Remove the found files from the leftover stack
|
||||||
leftovers = leftovers - found_files
|
leftovers = leftovers - set(found_files)
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Cleaning up used
|
||||||
|
for identifier in delete_identifiers:
|
||||||
|
if self.path_identifiers.get(identifier):
|
||||||
|
del self.path_identifiers[identifier]
|
||||||
|
del delete_identifiers
|
||||||
|
|
||||||
|
# Group based on folder
|
||||||
|
delete_identifiers = []
|
||||||
|
for identifier, found_files in self.path_identifiers.iteritems():
|
||||||
|
log.debug('Grouping files on foldername: %s', identifier)
|
||||||
|
|
||||||
|
for ff in found_files:
|
||||||
|
new_identifier = self.createStringIdentifier(os.path.dirname(ff), folder)
|
||||||
|
|
||||||
|
group = movie_files.get(new_identifier)
|
||||||
|
if group:
|
||||||
|
group['unsorted_files'].extend([ff])
|
||||||
|
delete_identifiers.append(identifier)
|
||||||
|
|
||||||
|
# Remove the found files from the leftover stack
|
||||||
|
leftovers = leftovers - set([ff])
|
||||||
|
|
||||||
|
# Break if CP wants to shut down
|
||||||
|
if self.shuttingDown():
|
||||||
|
break
|
||||||
|
|
||||||
|
# Cleaning up used
|
||||||
|
for identifier in delete_identifiers:
|
||||||
|
if self.path_identifiers.get(identifier):
|
||||||
|
del self.path_identifiers[identifier]
|
||||||
|
del delete_identifiers
|
||||||
|
|
||||||
# Determine file types
|
# Determine file types
|
||||||
processed_movies = {}
|
processed_movies = {}
|
||||||
@@ -256,13 +291,38 @@ class Scanner(Plugin):
|
|||||||
# Check if movie is fresh and maybe still unpacking, ignore files new then 1 minute
|
# Check if movie is fresh and maybe still unpacking, ignore files new then 1 minute
|
||||||
file_too_new = False
|
file_too_new = False
|
||||||
for cur_file in group['unsorted_files']:
|
for cur_file in group['unsorted_files']:
|
||||||
file_time = os.path.getmtime(cur_file)
|
if not os.path.isfile(cur_file):
|
||||||
if file_time > time.time() - 60:
|
file_too_new = time.time()
|
||||||
file_too_new = tryInt(time.time() - file_time)
|
break
|
||||||
|
file_time = [os.path.getmtime(cur_file), os.path.getctime(cur_file)]
|
||||||
|
for t in file_time:
|
||||||
|
if t > time.time() - 60:
|
||||||
|
file_too_new = tryInt(time.time() - t)
|
||||||
|
break
|
||||||
|
|
||||||
|
if file_too_new:
|
||||||
break
|
break
|
||||||
|
|
||||||
if file_too_new:
|
if file_too_new:
|
||||||
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s' % (time.ctime(file_time), identifier))
|
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time.ctime(file_time[0]), identifier))
|
||||||
|
|
||||||
|
# Delete the unsorted list
|
||||||
|
del group['unsorted_files']
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Only process movies newer than x
|
||||||
|
if newer_than and newer_than > 0:
|
||||||
|
for cur_file in group['unsorted_files']:
|
||||||
|
file_time = [os.path.getmtime(cur_file), os.path.getctime(cur_file)]
|
||||||
|
if file_time[0] > time.time() or file_time[1] > time.time():
|
||||||
|
break
|
||||||
|
|
||||||
|
log.debug('None of the files have changed since %s for %s, skipping.', (time.ctime(newer_than), identifier))
|
||||||
|
|
||||||
|
# Delete the unsorted list
|
||||||
|
del group['unsorted_files']
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Group extra (and easy) files first
|
# Group extra (and easy) files first
|
||||||
@@ -284,10 +344,10 @@ class Scanner(Plugin):
|
|||||||
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
||||||
|
|
||||||
if len(group['files']['movie']) == 0:
|
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
|
continue
|
||||||
|
|
||||||
log.debug('Getting metadata for %s' % identifier)
|
log.debug('Getting metadata for %s', identifier)
|
||||||
group['meta_data'] = self.getMetaData(group)
|
group['meta_data'] = self.getMetaData(group)
|
||||||
|
|
||||||
# Subtitle meta
|
# Subtitle meta
|
||||||
@@ -320,7 +380,11 @@ class Scanner(Plugin):
|
|||||||
# Determine movie
|
# Determine movie
|
||||||
group['library'] = self.determineMovie(group)
|
group['library'] = self.determineMovie(group)
|
||||||
if not group['library']:
|
if not group['library']:
|
||||||
log.error('Unable to determine movie: %s' % group['identifiers'])
|
log.error('Unable to determine movie: %s', group['identifiers'])
|
||||||
|
else:
|
||||||
|
movie = db.query(Movie).filter_by(library_id = group['library']['id']).first()
|
||||||
|
group['movie_id'] = None if not movie else movie.id
|
||||||
|
|
||||||
|
|
||||||
processed_movies[identifier] = group
|
processed_movies[identifier] = group
|
||||||
|
|
||||||
@@ -329,9 +393,9 @@ class Scanner(Plugin):
|
|||||||
self.path_identifiers = {}
|
self.path_identifiers = {}
|
||||||
|
|
||||||
if len(processed_movies) > 0:
|
if len(processed_movies) > 0:
|
||||||
log.info('Found %s movies in the folder %s' % (len(processed_movies), folder))
|
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
||||||
else:
|
else:
|
||||||
log.debug('Found no movies in the folder %s' % (folder))
|
log.debug('Found no movies in the folder %s', (folder))
|
||||||
return processed_movies
|
return processed_movies
|
||||||
|
|
||||||
def getMetaData(self, group):
|
def getMetaData(self, group):
|
||||||
@@ -351,7 +415,7 @@ class Scanner(Plugin):
|
|||||||
data['resolution_height'] = meta.get('resolution_height', 480)
|
data['resolution_height'] = meta.get('resolution_height', 480)
|
||||||
data['aspect'] = meta.get('resolution_width', 720) / meta.get('resolution_height', 480)
|
data['aspect'] = meta.get('resolution_width', 720) / meta.get('resolution_height', 480)
|
||||||
except:
|
except:
|
||||||
log.debug('Error parsing metadata: %s %s' % (cur_file, traceback.format_exc()))
|
log.debug('Error parsing metadata: %s %s', (cur_file, traceback.format_exc()))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if data.get('audio'): break
|
if data.get('audio'): break
|
||||||
@@ -379,11 +443,11 @@ class Scanner(Plugin):
|
|||||||
'resolution_height': tryInt(p.video[0].height),
|
'resolution_height': tryInt(p.video[0].height),
|
||||||
}
|
}
|
||||||
except ParseError:
|
except ParseError:
|
||||||
log.debug('Failed to parse meta for %s' % filename)
|
log.debug('Failed to parse meta for %s', filename)
|
||||||
except NoParserError:
|
except NoParserError:
|
||||||
log.debug('No parser found for %s' % filename)
|
log.debug('No parser found for %s', filename)
|
||||||
except:
|
except:
|
||||||
log.debug('Failed parsing %s' % filename)
|
log.debug('Failed parsing %s', filename)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -396,7 +460,7 @@ class Scanner(Plugin):
|
|||||||
scan_result = []
|
scan_result = []
|
||||||
for p in paths:
|
for p in paths:
|
||||||
if not group['is_dvd']:
|
if not group['is_dvd']:
|
||||||
video = Video.from_path(p)
|
video = Video.from_path(toUnicode(p))
|
||||||
video_result = [(video, video.scan())]
|
video_result = [(video, video.scan())]
|
||||||
scan_result.extend(video_result)
|
scan_result.extend(video_result)
|
||||||
|
|
||||||
@@ -405,7 +469,7 @@ class Scanner(Plugin):
|
|||||||
if s.language and s.path not in paths:
|
if s.language and s.path not in paths:
|
||||||
detected_languages[s.path] = [s.language]
|
detected_languages[s.path] = [s.language]
|
||||||
except:
|
except:
|
||||||
log.debug('Failed parsing subtitle languages for %s: %s' % (paths, traceback.format_exc()))
|
log.debug('Failed parsing subtitle languages for %s: %s', (paths, traceback.format_exc()))
|
||||||
|
|
||||||
# IDX
|
# IDX
|
||||||
for extra in group['files']['subtitle_extra']:
|
for extra in group['files']['subtitle_extra']:
|
||||||
@@ -421,7 +485,7 @@ class Scanner(Plugin):
|
|||||||
if len(idx_langs) > 0 and os.path.isfile(sub_file):
|
if len(idx_langs) > 0 and os.path.isfile(sub_file):
|
||||||
detected_languages[sub_file] = idx_langs
|
detected_languages[sub_file] = idx_langs
|
||||||
except:
|
except:
|
||||||
log.error('Failed parsing subtitle idx for %s: %s' % (extra, traceback.format_exc()))
|
log.error('Failed parsing subtitle idx for %s: %s', (extra, traceback.format_exc()))
|
||||||
|
|
||||||
return detected_languages
|
return detected_languages
|
||||||
|
|
||||||
@@ -434,7 +498,7 @@ class Scanner(Plugin):
|
|||||||
for cur_file in files['movie']:
|
for cur_file in files['movie']:
|
||||||
imdb_id = self.getCPImdb(cur_file)
|
imdb_id = self.getCPImdb(cur_file)
|
||||||
if imdb_id:
|
if imdb_id:
|
||||||
log.debug('Found movie via CP tag: %s' % cur_file)
|
log.debug('Found movie via CP tag: %s', cur_file)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check and see if nfo contains the imdb-id
|
# Check and see if nfo contains the imdb-id
|
||||||
@@ -443,11 +507,23 @@ class Scanner(Plugin):
|
|||||||
for nfo_file in files['nfo']:
|
for nfo_file in files['nfo']:
|
||||||
imdb_id = getImdb(nfo_file)
|
imdb_id = getImdb(nfo_file)
|
||||||
if imdb_id:
|
if imdb_id:
|
||||||
log.debug('Found movie via nfo file: %s' % nfo_file)
|
log.debug('Found movie via nfo file: %s', nfo_file)
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Check and see if filenames contains the imdb-id
|
||||||
|
if not imdb_id:
|
||||||
|
try:
|
||||||
|
for filetype in files:
|
||||||
|
for filetype_file in files[filetype]:
|
||||||
|
imdb_id = getImdb(filetype_file, check_inside = False)
|
||||||
|
if imdb_id:
|
||||||
|
log.debug('Found movie via imdb in filename: %s', nfo_file)
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Check if path is already in db
|
# Check if path is already in db
|
||||||
if not imdb_id:
|
if not imdb_id:
|
||||||
db = get_session()
|
db = get_session()
|
||||||
@@ -455,7 +531,7 @@ class Scanner(Plugin):
|
|||||||
f = db.query(File).filter_by(path = toUnicode(cur_file)).first()
|
f = db.query(File).filter_by(path = toUnicode(cur_file)).first()
|
||||||
try:
|
try:
|
||||||
imdb_id = f.library[0].identifier
|
imdb_id = f.library[0].identifier
|
||||||
log.debug('Found movie via database: %s' % cur_file)
|
log.debug('Found movie via database: %s', cur_file)
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -469,7 +545,7 @@ class Scanner(Plugin):
|
|||||||
if len(movie) > 0:
|
if len(movie) > 0:
|
||||||
imdb_id = movie[0]['imdb']
|
imdb_id = movie[0]['imdb']
|
||||||
if imdb_id:
|
if imdb_id:
|
||||||
log.debug('Found movie via OpenSubtitleHash: %s' % cur_file)
|
log.debug('Found movie via OpenSubtitleHash: %s', cur_file)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Search based on identifiers
|
# Search based on identifiers
|
||||||
@@ -486,17 +562,17 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
if len(movie) > 0:
|
if len(movie) > 0:
|
||||||
imdb_id = movie[0]['imdb']
|
imdb_id = movie[0]['imdb']
|
||||||
log.debug('Found movie via search: %s' % cur_file)
|
log.debug('Found movie via search: %s', cur_file)
|
||||||
if imdb_id: break
|
if imdb_id: break
|
||||||
else:
|
else:
|
||||||
log.debug('Identifier to short to use for search: %s' % identifier)
|
log.debug('Identifier to short to use for search: %s', identifier)
|
||||||
|
|
||||||
if imdb_id:
|
if imdb_id:
|
||||||
return fireEvent('library.add', attrs = {
|
return fireEvent('library.add', attrs = {
|
||||||
'identifier': imdb_id
|
'identifier': imdb_id
|
||||||
}, update_after = False, single = True)
|
}, update_after = False, single = True)
|
||||||
|
|
||||||
log.error('No imdb_id found for %s. Add a NFO file with IMDB id or add the year to the filename.' % group['identifiers'])
|
log.error('No imdb_id found for %s. Add a NFO file with IMDB id or add the year to the filename.', group['identifiers'])
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def getCPImdb(self, string):
|
def getCPImdb(self, string):
|
||||||
@@ -585,17 +661,17 @@ class Scanner(Plugin):
|
|||||||
# ignoredpaths
|
# ignoredpaths
|
||||||
for i in self.ignored_in_path:
|
for i in self.ignored_in_path:
|
||||||
if i in filename.lower():
|
if i in filename.lower():
|
||||||
log.debug('Ignored "%s" contains "%s".' % (filename, i))
|
log.debug('Ignored "%s" contains "%s".', (filename, i))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Sample file
|
# Sample file
|
||||||
if self.isSampleFile(filename):
|
if self.isSampleFile(filename):
|
||||||
log.debug('Is sample file "%s".' % filename)
|
log.debug('Is sample file "%s".', filename)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Minimal size
|
# Minimal size
|
||||||
if self.filesizeBetween(filename, self.minimal_filesize['media']):
|
if self.filesizeBetween(filename, self.minimal_filesize['media']):
|
||||||
log.debug('File to small: %s' % filename)
|
log.debug('File to small: %s', filename)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# All is OK
|
# All is OK
|
||||||
@@ -603,14 +679,14 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
def isSampleFile(self, filename):
|
def isSampleFile(self, filename):
|
||||||
is_sample = re.search('(^|[\W_])sample\d*[\W_]', filename.lower())
|
is_sample = re.search('(^|[\W_])sample\d*[\W_]', filename.lower())
|
||||||
if is_sample: log.debug('Is sample file: %s' % filename)
|
if is_sample: log.debug('Is sample file: %s', filename)
|
||||||
return is_sample
|
return is_sample
|
||||||
|
|
||||||
def filesizeBetween(self, file, min = 0, max = 100000):
|
def filesizeBetween(self, file, min = 0, max = 100000):
|
||||||
try:
|
try:
|
||||||
return (min * 1048576) < os.path.getsize(file) < (max * 1048576)
|
return (min * 1048576) < os.path.getsize(file) < (max * 1048576)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t get filesize of %s.' % file)
|
log.error('Couldn\'t get filesize of %s.', file)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -629,7 +705,7 @@ class Scanner(Plugin):
|
|||||||
identifier = self.removeCPTag(identifier)
|
identifier = self.removeCPTag(identifier)
|
||||||
|
|
||||||
# groups, release tags, scenename cleaner, regex isn't correct
|
# groups, release tags, scenename cleaner, regex isn't correct
|
||||||
identifier = re.sub(self.clean, '::', simplifyString(identifier))
|
identifier = re.sub(self.clean, '::', simplifyString(identifier)).strip(':')
|
||||||
|
|
||||||
# Year
|
# Year
|
||||||
year = self.findYear(identifier)
|
year = self.findYear(identifier)
|
||||||
@@ -703,26 +779,28 @@ class Scanner(Plugin):
|
|||||||
def getReleaseNameYear(self, release_name, file_name = None):
|
def getReleaseNameYear(self, release_name, file_name = None):
|
||||||
|
|
||||||
# Use guessit first
|
# Use guessit first
|
||||||
|
guess = {}
|
||||||
if file_name:
|
if file_name:
|
||||||
try:
|
try:
|
||||||
guess = guess_movie_info(file_name)
|
guess = guess_movie_info(toUnicode(file_name))
|
||||||
if guess.get('title') and guess.get('year'):
|
if guess.get('title') and guess.get('year'):
|
||||||
return {
|
guess = {
|
||||||
'name': guess.get('title'),
|
'name': guess.get('title'),
|
||||||
'year': guess.get('year'),
|
'year': guess.get('year'),
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
log.debug('Could not detect via guessit "%s": %s' % (file_name, traceback.format_exc()))
|
log.debug('Could not detect via guessit "%s": %s', (file_name, traceback.format_exc()))
|
||||||
|
|
||||||
# Backup to simple
|
# Backup to simple
|
||||||
cleaned = ' '.join(re.split('\W+', simplifyString(release_name)))
|
cleaned = ' '.join(re.split('\W+', simplifyString(release_name)))
|
||||||
cleaned = re.sub(self.clean, ' ', cleaned)
|
cleaned = re.sub(self.clean, ' ', cleaned)
|
||||||
year = self.findYear(cleaned)
|
year = self.findYear(cleaned)
|
||||||
|
cp_guess = {}
|
||||||
|
|
||||||
if year: # Split name on year
|
if year: # Split name on year
|
||||||
try:
|
try:
|
||||||
movie_name = cleaned.split(year).pop(0).strip()
|
movie_name = cleaned.split(year).pop(0).strip()
|
||||||
return {
|
cp_guess = {
|
||||||
'name': movie_name,
|
'name': movie_name,
|
||||||
'year': int(year),
|
'year': int(year),
|
||||||
}
|
}
|
||||||
@@ -731,11 +809,16 @@ class Scanner(Plugin):
|
|||||||
else: # Split name on multiple spaces
|
else: # Split name on multiple spaces
|
||||||
try:
|
try:
|
||||||
movie_name = cleaned.split(' ').pop(0).strip()
|
movie_name = cleaned.split(' ').pop(0).strip()
|
||||||
return {
|
cp_guess = {
|
||||||
'name': movie_name,
|
'name': movie_name,
|
||||||
'year': int(year),
|
'year': int(year),
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return {}
|
if cp_guess.get('year') == guess.get('year') and len(cp_guess.get('name', '')) > len(guess.get('name', '')):
|
||||||
|
return cp_guess
|
||||||
|
elif guess == {}:
|
||||||
|
return cp_guess
|
||||||
|
|
||||||
|
return guess
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from couchpotato.core.helpers.variable import getTitle
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore, \
|
from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore, \
|
||||||
sizeScore, providerScore, duplicateScore
|
sizeScore, providerScore, duplicateScore, partialIgnoredScore, namePositionScore, \
|
||||||
|
halfMultipartScore
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ class Score(Plugin):
|
|||||||
|
|
||||||
for movie_title in movie['library']['titles']:
|
for movie_title in movie['library']['titles']:
|
||||||
score += nameRatioScore(toUnicode(nzb['name']), toUnicode(movie_title['title']))
|
score += nameRatioScore(toUnicode(nzb['name']), toUnicode(movie_title['title']))
|
||||||
|
score += namePositionScore(toUnicode(nzb['name']), toUnicode(movie_title['title']))
|
||||||
|
|
||||||
score += sizeScore(nzb['size'])
|
score += sizeScore(nzb['size'])
|
||||||
|
|
||||||
@@ -38,4 +40,15 @@ class Score(Plugin):
|
|||||||
# Duplicates in name
|
# Duplicates in name
|
||||||
score += duplicateScore(nzb['name'], getTitle(movie['library']))
|
score += duplicateScore(nzb['name'], getTitle(movie['library']))
|
||||||
|
|
||||||
|
# Partial ignored words
|
||||||
|
score += partialIgnoredScore(nzb['name'], getTitle(movie['library']))
|
||||||
|
|
||||||
|
# Ignore single downloads from multipart
|
||||||
|
score += halfMultipartScore(nzb['name'])
|
||||||
|
|
||||||
|
# Extra provider specific check
|
||||||
|
extra_score = nzb.get('extra_score')
|
||||||
|
if extra_score:
|
||||||
|
score += extra_score(nzb)
|
||||||
|
|
||||||
return score
|
return score
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import simplifyString
|
from couchpotato.core.helpers.encoding import simplifyString
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -16,11 +17,12 @@ name_scores = [
|
|||||||
'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10', 'dutch:-10',
|
'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10', 'dutch:-10',
|
||||||
# Release groups
|
# Release groups
|
||||||
'imbt:1', 'cocain:1', 'vomit:1', 'fico:1', 'arrow:1', 'pukka:1', 'prism:1', 'devise:1', 'esir:1', 'ctrlhd:1',
|
'imbt:1', 'cocain:1', 'vomit:1', 'fico:1', 'arrow:1', 'pukka:1', 'prism:1', 'devise:1', 'esir:1', 'ctrlhd:1',
|
||||||
'metis:1', 'diamond:1', 'wiki:1', 'cbgb:1', 'crossbow:1', 'sinners:1', 'amiable:1', 'refined:1', 'twizted:1', 'felony:1', 'hubris:1', 'machd:1',
|
'metis:10', 'diamond:10', 'wiki:10', 'cbgb:10', 'crossbow:1', 'sinners:10', 'amiable:10', 'refined:1', 'twizted:1', 'felony:1', 'hubris:1', 'machd:1',
|
||||||
# Extras
|
# Extras
|
||||||
'extras:-40', 'trilogy:-40',
|
'extras:-40', 'trilogy:-40',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def nameScore(name, year):
|
def nameScore(name, year):
|
||||||
''' Calculate score for words in the NZB name '''
|
''' Calculate score for words in the NZB name '''
|
||||||
|
|
||||||
@@ -47,8 +49,8 @@ def nameScore(name, year):
|
|||||||
|
|
||||||
return score
|
return score
|
||||||
|
|
||||||
def nameRatioScore(nzb_name, movie_name):
|
|
||||||
|
|
||||||
|
def nameRatioScore(nzb_name, movie_name):
|
||||||
nzb_words = re.split('\W+', fireEvent('scanner.create_file_identifier', nzb_name, single = True))
|
nzb_words = re.split('\W+', fireEvent('scanner.create_file_identifier', nzb_name, single = True))
|
||||||
movie_words = re.split('\W+', simplifyString(movie_name))
|
movie_words = re.split('\W+', simplifyString(movie_name))
|
||||||
|
|
||||||
@@ -56,15 +58,68 @@ def nameRatioScore(nzb_name, movie_name):
|
|||||||
return 10 - len(left_over)
|
return 10 - len(left_over)
|
||||||
|
|
||||||
|
|
||||||
|
def namePositionScore(nzb_name, movie_name):
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
nzb_words = re.split('\W+', simplifyString(nzb_name))
|
||||||
|
qualities = fireEvent('quality.all', single = True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
nzb_name = re.search(r'([\'"])[^\1]*\1', nzb_name).group(0)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
name_year = fireEvent('scanner.name_year', nzb_name, single = True)
|
||||||
|
|
||||||
|
# Give points for movies beginning with the correct name
|
||||||
|
name_split = simplifyString(nzb_name).split(simplifyString(movie_name))
|
||||||
|
if name_split[0].strip() == '':
|
||||||
|
score += 10
|
||||||
|
|
||||||
|
# If year is second in line, give more points
|
||||||
|
if len(name_split) > 1 and name_year:
|
||||||
|
after_name = name_split[1].strip()
|
||||||
|
if tryInt(after_name[:4]) == name_year.get('year', None):
|
||||||
|
score += 10
|
||||||
|
after_name = after_name[4:]
|
||||||
|
|
||||||
|
# Give -point to crap between year and quality
|
||||||
|
found_quality = None
|
||||||
|
for quality in qualities:
|
||||||
|
# Main in words
|
||||||
|
if quality['identifier'] in nzb_words:
|
||||||
|
found_quality = quality['identifier']
|
||||||
|
|
||||||
|
# Alt in words
|
||||||
|
for alt in quality['alternative']:
|
||||||
|
if alt in nzb_words:
|
||||||
|
found_quality = alt
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found_quality:
|
||||||
|
return score - 20
|
||||||
|
|
||||||
|
allowed = []
|
||||||
|
for value in name_scores:
|
||||||
|
name, sc = value.split(':')
|
||||||
|
allowed.append(name)
|
||||||
|
|
||||||
|
inbetween = re.split('\W+', after_name.split(found_quality)[0].strip())
|
||||||
|
|
||||||
|
score -= (10 * len(set(inbetween) - set(allowed)))
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
|
|
||||||
def sizeScore(size):
|
def sizeScore(size):
|
||||||
return 0 if size else -20
|
return 0 if size else -20
|
||||||
|
|
||||||
|
|
||||||
def providerScore(provider):
|
def providerScore(provider):
|
||||||
if provider in ['NZBMatrix', 'Nzbs', 'Newzbin']:
|
if provider in ['NZBMatrix', 'Nzbs', 'Newzbin']:
|
||||||
return 30
|
return 20
|
||||||
|
|
||||||
if provider in ['Newznab', 'Moovee', 'X264']:
|
if provider in ['Newznab']:
|
||||||
return 10
|
return 10
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -79,3 +134,31 @@ def duplicateScore(nzb_name, movie_name):
|
|||||||
duplicates = [x for i, x in enumerate(nzb_words) if nzb_words[i:].count(x) > 1]
|
duplicates = [x for i, x in enumerate(nzb_words) if nzb_words[i:].count(x) > 1]
|
||||||
|
|
||||||
return len(list(set(duplicates) - set(movie_words))) * -4
|
return len(list(set(duplicates) - set(movie_words))) * -4
|
||||||
|
|
||||||
|
|
||||||
|
def partialIgnoredScore(nzb_name, movie_name):
|
||||||
|
|
||||||
|
nzb_name = nzb_name.lower()
|
||||||
|
movie_name = movie_name.lower()
|
||||||
|
|
||||||
|
ignored_words = [x.strip().lower() for x in Env.setting('ignored_words', section = 'searcher').split(',')]
|
||||||
|
|
||||||
|
score = 0
|
||||||
|
for ignored_word in ignored_words:
|
||||||
|
if ignored_word in nzb_name and ignored_word not in movie_name:
|
||||||
|
score -= 5
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
|
def halfMultipartScore(nzb_name):
|
||||||
|
|
||||||
|
wrong_found = 0
|
||||||
|
for nr in [1, 2, 3, 4, 5, 'i', 'ii', 'iii', 'iv', 'v', 'a', 'b', 'c', 'd', 'e']:
|
||||||
|
for wrong in ['cd', 'part', 'dis', 'disc', 'dvd']:
|
||||||
|
if '%s%s' % (wrong, nr) in nzb_name.lower():
|
||||||
|
wrong_found += 1
|
||||||
|
|
||||||
|
if wrong_found == 1:
|
||||||
|
return -30
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|||||||
@@ -29,7 +29,15 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'ignored_words',
|
'name': 'ignored_words',
|
||||||
'label': 'Ignored words',
|
'label': 'Ignored words',
|
||||||
'default': 'german, dutch, french, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub',
|
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'preferred_method',
|
||||||
|
'label': 'First search',
|
||||||
|
'description': 'Which of the methods do you prefer',
|
||||||
|
'default': 'nzb',
|
||||||
|
'type': 'dropdown',
|
||||||
|
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from couchpotato.core.logger import CPLog
|
|||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
from inspect import ismethod, isfunction
|
||||||
from sqlalchemy.exc import InterfaceError
|
from sqlalchemy.exc import InterfaceError
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
@@ -28,6 +29,9 @@ class Searcher(Plugin):
|
|||||||
# Schedule cronjob
|
# Schedule cronjob
|
||||||
fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
||||||
|
|
||||||
|
|
||||||
|
addEvent('app.load', self.all_movies)
|
||||||
|
|
||||||
def all_movies(self):
|
def all_movies(self):
|
||||||
|
|
||||||
if self.in_progress:
|
if self.in_progress:
|
||||||
@@ -55,7 +59,7 @@ class Searcher(Plugin):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
fireEvent('library.update', movie_dict['library']['identifier'], force = True)
|
fireEvent('library.update', movie_dict['library']['identifier'], force = True)
|
||||||
except:
|
except:
|
||||||
log.error('Search failed for %s: %s' % (movie_dict['library']['identifier'], traceback.format_exc()))
|
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
@@ -77,36 +81,46 @@ class Searcher(Plugin):
|
|||||||
pre_releases = fireEvent('quality.pre_releases', single = True)
|
pre_releases = fireEvent('quality.pre_releases', single = True)
|
||||||
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
||||||
available_status = fireEvent('status.get', 'available', single = True)
|
available_status = fireEvent('status.get', 'available', single = True)
|
||||||
|
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
||||||
|
|
||||||
default_title = getTitle(movie['library'])
|
default_title = getTitle(movie['library'])
|
||||||
if not default_title:
|
if not default_title:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
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']:
|
for quality_type in movie['profile']['types']:
|
||||||
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
|
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
|
||||||
log.info('To early to search for %s, %s' % (quality_type['quality']['identifier'], default_title))
|
log.info('Too early to search for %s, %s', (quality_type['quality']['identifier'], default_title))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
has_better_quality = 0
|
has_better_quality = 0
|
||||||
|
|
||||||
# See if beter quality is available
|
# See if better quality is available
|
||||||
for release in movie['releases']:
|
for release in movie['releases']:
|
||||||
if release['quality']['order'] <= quality_type['quality']['order'] and release['status_id'] is not available_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
|
has_better_quality += 1
|
||||||
|
|
||||||
# Don't search for quality lower then already available.
|
# Don't search for quality lower then already available.
|
||||||
if has_better_quality is 0:
|
if has_better_quality is 0:
|
||||||
|
|
||||||
log.info('Search for %s in %s' % (default_title, quality_type['quality']['label']))
|
log.info('Search for %s in %s', (default_title, quality_type['quality']['label']))
|
||||||
quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True)
|
quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True)
|
||||||
|
|
||||||
results = fireEvent('yarr.search', movie, quality, merge = True)
|
results = fireEvent('yarr.search', movie, quality, merge = True)
|
||||||
|
|
||||||
sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
|
sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
|
||||||
if len(sorted_results) == 0:
|
if len(sorted_results) == 0:
|
||||||
log.debug('Nothing found for %s in %s' % (default_title, quality_type['quality']['label']))
|
log.debug('Nothing found for %s in %s', (default_title, quality_type['quality']['label']))
|
||||||
|
|
||||||
|
download_preference = self.conf('preferred_method')
|
||||||
|
if download_preference != 'both':
|
||||||
|
sorted_results = sorted(sorted_results, key = lambda k: k['type'], reverse = (download_preference == 'torrent'))
|
||||||
|
|
||||||
# Check if movie isn't deleted while searching
|
# Check if movie isn't deleted while searching
|
||||||
if not db.query(Movie).filter_by(id = movie.get('id')).first():
|
if not db.query(Movie).filter_by(id = movie.get('id')).first():
|
||||||
return
|
break
|
||||||
|
|
||||||
# Add them to this movie releases list
|
# Add them to this movie releases list
|
||||||
for nzb in sorted_results:
|
for nzb in sorted_results:
|
||||||
@@ -137,31 +151,52 @@ class Searcher(Plugin):
|
|||||||
rls.info.append(rls_info)
|
rls.info.append(rls_info)
|
||||||
db.commit()
|
db.commit()
|
||||||
except InterfaceError:
|
except InterfaceError:
|
||||||
log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc()))
|
log.debug('Couldn\'t add %s to ReleaseInfo: %s', (info, traceback.format_exc()))
|
||||||
|
|
||||||
|
nzb['status_id'] = rls.status_id
|
||||||
|
|
||||||
|
|
||||||
for nzb in sorted_results:
|
for nzb in sorted_results:
|
||||||
|
if nzb['status_id'] == ignored_status.get('id'):
|
||||||
|
log.info('Ignored: %s', nzb['name'])
|
||||||
|
continue
|
||||||
|
|
||||||
|
if nzb['score'] <= 0:
|
||||||
|
log.info('Ignored, score to low: %s', nzb['name'])
|
||||||
|
continue
|
||||||
|
|
||||||
downloaded = self.download(data = nzb, movie = movie)
|
downloaded = self.download(data = nzb, movie = movie)
|
||||||
if downloaded:
|
if downloaded is True:
|
||||||
return True
|
ret = True
|
||||||
else:
|
break
|
||||||
|
elif downloaded != 'try_next':
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
log.info('Better quality (%s) already available or snatched for %s' % (quality_type['quality']['label'], default_title))
|
log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title))
|
||||||
fireEvent('movie.restatus', movie['id'])
|
fireEvent('movie.restatus', movie['id'])
|
||||||
break
|
break
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown():
|
if self.shuttingDown() or ret:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True)
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return False
|
return ret
|
||||||
|
|
||||||
def download(self, data, movie, manual = False):
|
def download(self, data, movie, manual = False):
|
||||||
|
|
||||||
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||||
successful = fireEvent('download', data = data, movie = movie, manual = manual, 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 is 'try_next':
|
||||||
|
return filedata
|
||||||
|
|
||||||
|
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||||
|
|
||||||
if successful:
|
if successful:
|
||||||
|
|
||||||
@@ -185,7 +220,7 @@ class Searcher(Plugin):
|
|||||||
if movie['status_id'] == active_status.get('id'):
|
if movie['status_id'] == active_status.get('id'):
|
||||||
for profile_type in movie['profile']['types']:
|
for profile_type in movie['profile']['types']:
|
||||||
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
||||||
log.info('Renamer disabled, marking movie as finished: %s' % log_movie)
|
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
||||||
|
|
||||||
# Mark release done
|
# Mark release done
|
||||||
rls.status_id = done_status.get('id')
|
rls.status_id = done_status.get('id')
|
||||||
@@ -196,7 +231,7 @@ class Searcher(Plugin):
|
|||||||
mvie.status_id = done_status.get('id')
|
mvie.status_id = done_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return True
|
return True
|
||||||
@@ -211,27 +246,29 @@ class Searcher(Plugin):
|
|||||||
retention = Env.setting('retention', section = 'nzb')
|
retention = Env.setting('retention', section = 'nzb')
|
||||||
|
|
||||||
if nzb.get('seeds') is None and retention < nzb.get('age', 0):
|
if nzb.get('seeds') is None and retention < nzb.get('age', 0):
|
||||||
log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s' % (nzb['age'], retention, nzb['name']))
|
log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s', (nzb['age'], retention, nzb['name']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
movie_name = simplifyString(nzb['name'])
|
movie_name = getTitle(movie['library'])
|
||||||
nzb_words = re.split('\W+', movie_name)
|
movie_words = re.split('\W+', simplifyString(movie_name))
|
||||||
required_words = [x.strip() for x in self.conf('required_words').split(',')]
|
nzb_name = simplifyString(nzb['name'])
|
||||||
|
nzb_words = re.split('\W+', nzb_name)
|
||||||
|
required_words = [x.strip().lower() for x in self.conf('required_words').lower().split(',')]
|
||||||
|
|
||||||
if self.conf('required_words') and not list(set(nzb_words) & set(required_words)):
|
if self.conf('required_words') and not list(set(nzb_words) & set(required_words)):
|
||||||
log.info("NZB doesn't contain any of the required words.")
|
log.info("Wrong: Required word missing: %s" % nzb['name'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ignored_words = [x.strip() for x in self.conf('ignored_words').split(',')]
|
ignored_words = [x.strip().lower() for x in self.conf('ignored_words').split(',')]
|
||||||
blacklisted = list(set(nzb_words) & set(ignored_words))
|
blacklisted = list(set(nzb_words) & set(ignored_words))
|
||||||
if self.conf('ignored_words') and blacklisted:
|
if self.conf('ignored_words') and blacklisted:
|
||||||
log.info("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
|
log.info("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs']
|
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
|
||||||
for p_tag in pron_tags:
|
for p_tag in pron_tags:
|
||||||
if p_tag in movie_name:
|
if p_tag in nzb_words and p_tag not in movie_words:
|
||||||
log.info('Wrong: %s, probably pr0n' % (nzb['name']))
|
log.info('Wrong: %s, probably pr0n', (nzb['name']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#qualities = fireEvent('quality.all', single = True)
|
#qualities = fireEvent('quality.all', single = True)
|
||||||
@@ -239,18 +276,28 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality, single_category = single_category):
|
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality, single_category = single_category):
|
||||||
log.info('Wrong: %s, looking for %s' % (nzb['name'], quality['label']))
|
log.info('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# File to small
|
# File to small
|
||||||
if nzb['size'] and preferred_quality['size_min'] > nzb['size']:
|
if nzb['size'] and preferred_quality['size_min'] > nzb['size']:
|
||||||
log.info('"%s" is too small to be %s. %sMB instead of the minimal of %sMB.' % (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_min']))
|
log.info('"%s" is too small to be %s. %sMB instead of the minimal of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_min']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# File to large
|
# File to large
|
||||||
if nzb['size'] and preferred_quality.get('size_max') < nzb['size']:
|
if nzb['size'] and preferred_quality.get('size_max') < nzb['size']:
|
||||||
log.info('"%s" is too large to be %s. %sMB instead of the maximum of %sMB.' % (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_max']))
|
log.info('"%s" is too large to be %s. %sMB instead of the maximum of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_max']))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Provider specific functions
|
||||||
|
get_more = nzb.get('get_more_info')
|
||||||
|
if get_more:
|
||||||
|
get_more(nzb)
|
||||||
|
|
||||||
|
extra_check = nzb.get('extra_check')
|
||||||
|
if extra_check and not extra_check(nzb):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -277,7 +324,7 @@ class Searcher(Plugin):
|
|||||||
if self.checkNFO(nzb['name'], movie['library']['identifier']):
|
if self.checkNFO(nzb['name'], movie['library']['identifier']):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], getTitle(movie['library']), movie['library']['year']))
|
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False):
|
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False):
|
||||||
@@ -379,17 +426,19 @@ class Searcher(Plugin):
|
|||||||
if dates.get('theater') - 604800 < now:
|
if dates.get('theater') - 604800 < now:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# 6 weeks after theater release
|
# 12 weeks after theater release
|
||||||
if dates.get('theater') + 3628800 < now:
|
if dates.get('theater') > 0 and dates.get('theater') + 7257600 < now:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 6 weeks before dvd release
|
if dates.get('dvd') > 0:
|
||||||
if dates.get('dvd') - 3628800 < now:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Dvd should be released
|
# 3 weeks before dvd release
|
||||||
if dates.get('dvd') > 0 and dates.get('dvd') < now:
|
if dates.get('dvd') - 1814400 < now:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Dvd should be released
|
||||||
|
if dates.get('dvd') < now:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class StatusPlugin(Plugin):
|
|||||||
for identifier, label in self.statuses.iteritems():
|
for identifier, label in self.statuses.iteritems():
|
||||||
s = db.query(Status).filter_by(identifier = identifier).first()
|
s = db.query(Status).filter_by(identifier = identifier).first()
|
||||||
if not s:
|
if not s:
|
||||||
log.info('Creating status: %s' % label)
|
log.info('Creating status: %s', label)
|
||||||
s = Status(
|
s = Status(
|
||||||
identifier = identifier,
|
identifier = identifier,
|
||||||
label = toUnicode(label)
|
label = toUnicode(label)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Library, FileType
|
from couchpotato.core.settings.model import Library, FileType
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import subliminal
|
import subliminal
|
||||||
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -44,16 +46,26 @@ class Subtitle(Plugin):
|
|||||||
|
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
available_languages = sum(group['subtitle_language'].itervalues(), [])
|
try:
|
||||||
downloaded = []
|
available_languages = sum(group['subtitle_language'].itervalues(), [])
|
||||||
for lang in self.getLanguages():
|
downloaded = []
|
||||||
if lang not in available_languages:
|
files = [toUnicode(x) for x in group['files']['movie']]
|
||||||
download = subliminal.download_subtitles(group['files']['movie'], multi = True, force = False, languages = [lang], services = self.services, cache_dir = Env.get('cache_dir'))
|
|
||||||
downloaded.extend(download)
|
|
||||||
|
|
||||||
for d_sub in downloaded:
|
for lang in self.getLanguages():
|
||||||
group['files']['subtitle'].add(d_sub.path)
|
if lang not in available_languages:
|
||||||
group['subtitle_language'][d_sub.path] = [d_sub.language]
|
download = subliminal.download_subtitles(files, multi = True, force = False, languages = [lang], services = self.services, cache_dir = Env.get('cache_dir'))
|
||||||
|
downloaded.extend(download)
|
||||||
|
|
||||||
|
for d_sub in downloaded:
|
||||||
|
group['files']['subtitle'].add(d_sub.path)
|
||||||
|
group['subtitle_language'][d_sub.path] = [d_sub.language]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed searching for subtitle: %s', (traceback.format_exc()))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def getLanguages(self):
|
def getLanguages(self):
|
||||||
return [x.strip() for x in self.conf('languages').split(',')]
|
return [x.strip() for x in self.conf('languages').split(',')]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.variable import getExt
|
from couchpotato.core.helpers.variable import getExt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
import os
|
import os
|
||||||
@@ -17,13 +17,16 @@ class Trailer(Plugin):
|
|||||||
if self.isDisabled() or len(group['files']['trailer']) > 0: return
|
if self.isDisabled() or len(group['files']['trailer']) > 0: return
|
||||||
|
|
||||||
trailers = fireEvent('trailer.search', group = group, merge = True)
|
trailers = fireEvent('trailer.search', group = group, merge = True)
|
||||||
|
if not trailers or trailers == []:
|
||||||
|
log.info('No trailers found for: %s', getTitle(group['library']))
|
||||||
|
return
|
||||||
|
|
||||||
for trailer in trailers.get(self.conf('quality'), []):
|
for trailer in trailers.get(self.conf('quality'), []):
|
||||||
destination = '%s-trailer.%s' % (self.getRootName(group), getExt(trailer))
|
destination = '%s-trailer.%s' % (self.getRootName(group), getExt(trailer))
|
||||||
if not os.path.isfile(destination):
|
if not os.path.isfile(destination):
|
||||||
fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
||||||
else:
|
else:
|
||||||
log.debug('Trailer already exists: %s' % destination)
|
log.debug('Trailer already exists: %s', destination)
|
||||||
|
|
||||||
# Download first and break
|
# Download first and break
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class Userscript(Plugin):
|
|||||||
'movie': fireEvent('userscript.get_movie_via_url', url = url, single = True)
|
'movie': fireEvent('userscript.get_movie_via_url', url = url, single = True)
|
||||||
}
|
}
|
||||||
if not isDict(params['movie']):
|
if not isDict(params['movie']):
|
||||||
log.error('Failed adding movie via url: %s' % url)
|
log.error('Failed adding movie via url: %s', url)
|
||||||
params['error'] = params['movie'] if params['movie'] else 'Failed getting movie info'
|
params['error'] = params['movie'] if params['movie'] else 'Failed getting movie info'
|
||||||
|
|
||||||
return jsonified(params)
|
return jsonified(params)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Automation(Plugin):
|
|||||||
def _getMovies(self):
|
def _getMovies(self):
|
||||||
|
|
||||||
if not self.canCheck():
|
if not self.canCheck():
|
||||||
log.debug('Just checked, skipping %s' % self.getName())
|
log.debug('Just checked, skipping %s', self.getName())
|
||||||
return []
|
return []
|
||||||
|
|
||||||
self.last_checked = time.time()
|
self.last_checked = time.time()
|
||||||
@@ -35,19 +35,28 @@ class Automation(Plugin):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def isMinimal(self, identifier):
|
def isMinimalMovie(self, movie):
|
||||||
|
if movie['rating'] and movie['rating'].get('imdb'):
|
||||||
movie = fireEvent('movie.info', identifier = identifier, merge = True)
|
movie['votes'] = movie['rating']['imdb'][1]
|
||||||
|
movie['rating'] = movie['rating']['imdb'][0]
|
||||||
|
identifier = movie['imdb']
|
||||||
for minimal_type in ['year', 'rating', 'votes']:
|
for minimal_type in ['year', 'rating', 'votes']:
|
||||||
type_value = movie.get(minimal_type, 0)
|
type_value = movie.get(minimal_type, 0)
|
||||||
type_min = self.getMinimal(minimal_type)
|
type_min = self.getMinimal(minimal_type)
|
||||||
if type_value < type_min:
|
if type_value < type_min:
|
||||||
log.info('%s to low for %s, need %s has %s' % (minimal_type, identifier, type_min, type_value))
|
log.info('%s too low for %s, need %s has %s', (minimal_type, identifier, type_min, type_value))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getIMDBFromTitle(self, name, year = None):
|
||||||
|
result = fireEvent('movie.search', q = '%s %s' % (name, year), limit = 1, merge = True)
|
||||||
|
|
||||||
|
if len(result) > 0:
|
||||||
|
return result[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def getMinimal(self, min_type):
|
def getMinimal(self, min_type):
|
||||||
return Env.setting(min_type, 'automation')
|
return Env.setting(min_type, 'automation')
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from .main import Bluray
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Bluray()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'bluray',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'name': 'bluray_automation',
|
||||||
|
'label': 'Blu-ray.com',
|
||||||
|
'description': 'Imports movies from blu-ray.com. (uses minimal requirements)',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Bluray(Automation, RSS):
|
||||||
|
|
||||||
|
interval = 1800
|
||||||
|
rss_url = 'http://www.blu-ray.com/rss/newreleasesfeed.xml'
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
RSSMovie = {'name': 'placeholder', 'year' : 'placeholder'}
|
||||||
|
RSSMovies = []
|
||||||
|
|
||||||
|
cache_key = 'bluray.%s' % md5(self.rss_url)
|
||||||
|
rss_data = self.getCache(cache_key, self.rss_url)
|
||||||
|
data = XMLTree.fromstring(rss_data)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
rss_movies = self.getElements(data, 'channel/item')
|
||||||
|
|
||||||
|
for movie in rss_movies:
|
||||||
|
RSSMovie['name'] = self.getTextElement(movie, "title").lower().split("blu-ray")[0].strip("(").rstrip()
|
||||||
|
RSSMovie['year'] = self.getTextElement(movie, "description").split("|")[1].strip("(").strip()
|
||||||
|
|
||||||
|
if not RSSMovie['name'].find("/") == -1: # make sure it is not a double movie release
|
||||||
|
continue
|
||||||
|
|
||||||
|
if int(RSSMovie['year']) < Env.setting('year', 'automation'): #do year filtering
|
||||||
|
continue
|
||||||
|
|
||||||
|
for test in RSSMovies:
|
||||||
|
if test.values() == RSSMovie.values(): # make sure we did not already include it...
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
log.info('Release found: %s.' % RSSMovie)
|
||||||
|
RSSMovies.append(RSSMovie.copy())
|
||||||
|
|
||||||
|
if not RSSMovies:
|
||||||
|
log.info('No movies found.')
|
||||||
|
return
|
||||||
|
|
||||||
|
log.debug("Applying IMDB filter to found movies...")
|
||||||
|
|
||||||
|
for RSSMovie in RSSMovies:
|
||||||
|
imdb = self.getIMDBFromTitle(RSSMovie['name'] + ' ' + RSSMovie['year'])
|
||||||
|
|
||||||
|
if imdb:
|
||||||
|
if self.isMinimalMovie(imdb):
|
||||||
|
movies.append(imdb['imdb'])
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'automation',
|
'tab': 'automation',
|
||||||
'name': 'imdb_automation',
|
'name': 'imdb_automation',
|
||||||
'label': 'IMDB',
|
'label': 'IMDB',
|
||||||
'description': 'From any <strong>public</strong> IMDB watchlists',
|
'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the RSS link.',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'automation_enabled',
|
'name': 'automation_enabled',
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from couchpotato.core.helpers.variable import md5
|
from couchpotato.core.helpers.rss import RSS
|
||||||
|
from couchpotato.core.helpers.variable import md5, getImdb
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
import StringIO
|
|
||||||
import csv
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IMDB(Automation):
|
class IMDB(Automation, RSS):
|
||||||
|
|
||||||
interval = 1800
|
interval = 1800
|
||||||
|
|
||||||
@@ -21,34 +21,45 @@ class IMDB(Automation):
|
|||||||
return
|
return
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
headers = {}
|
|
||||||
|
|
||||||
for csv_url in self.conf('automation_urls').split(','):
|
enablers = self.conf('automation_urls_use').split(',')
|
||||||
prop_name = 'automation.imdb.last_update.%s' % md5(csv_url)
|
|
||||||
|
index = -1
|
||||||
|
for rss_url in self.conf('automation_urls').split(','):
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
if not enablers[index]:
|
||||||
|
continue
|
||||||
|
elif 'rss.imdb' not in rss_url:
|
||||||
|
log.error('This isn\'t the correct url.: %s', rss_url)
|
||||||
|
continue
|
||||||
|
|
||||||
|
prop_name = 'automation.imdb.last_update.%s' % md5(rss_url)
|
||||||
last_update = float(Env.prop(prop_name, default = 0))
|
last_update = float(Env.prop(prop_name, default = 0))
|
||||||
|
|
||||||
|
last_movie_added = 0
|
||||||
try:
|
try:
|
||||||
cache_key = 'imdb_csv.%s' % md5(csv_url)
|
cache_key = 'imdb.rss.%s' % md5(rss_url)
|
||||||
csv_data = self.getCache(cache_key, csv_url)
|
|
||||||
csv_reader = csv.reader(StringIO.StringIO(csv_data))
|
|
||||||
if not headers:
|
|
||||||
nr = 0
|
|
||||||
for column in csv_reader.next():
|
|
||||||
headers[column] = nr
|
|
||||||
nr += 1
|
|
||||||
|
|
||||||
for row in csv_reader:
|
rss_data = self.getCache(cache_key, rss_url)
|
||||||
created = int(time.mktime(parse(row[headers['created']]).timetuple()))
|
data = XMLTree.fromstring(rss_data)
|
||||||
if created < last_update:
|
rss_movies = self.getElements(data, 'channel/item')
|
||||||
|
|
||||||
|
for movie in rss_movies:
|
||||||
|
created = int(time.mktime(parse(self.getTextElement(movie, "pubDate")).timetuple()))
|
||||||
|
imdb = getImdb(self.getTextElement(movie, "link"))
|
||||||
|
|
||||||
|
if created > last_movie_added:
|
||||||
|
last_movie_added = created
|
||||||
|
|
||||||
|
if not imdb or created <= last_update:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
imdb = row[headers['const']]
|
movies.append(imdb)
|
||||||
if imdb:
|
|
||||||
movies.append(imdb)
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed loading IMDB watchlist: %s %s' % (csv_url, traceback.format_exc()))
|
log.error('Failed loading IMDB watchlist: %s %s', (rss_url, traceback.format_exc()))
|
||||||
|
|
||||||
Env.prop(prop_name, time.time())
|
|
||||||
|
|
||||||
|
Env.prop(prop_name, last_movie_added)
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from .main import Kinepolis
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Kinepolis()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'kinepolis',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'name': 'kinepolis_automation',
|
||||||
|
'label': 'Kinepolis',
|
||||||
|
'description': 'Imports movies from the current top 10 of kinepolis. (uses minimal requirements)',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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__)
|
||||||
|
|
||||||
|
|
||||||
|
class Kinepolis(Automation, RSS):
|
||||||
|
|
||||||
|
interval = 1800
|
||||||
|
rss_url = 'http://kinepolis.be/nl/top10-box-office/feed'
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
RSSMovie = {'name': 'placeholder', 'year' : 'placeholder'}
|
||||||
|
|
||||||
|
cache_key = 'kinepolis.%s' % md5(self.rss_url)
|
||||||
|
rss_data = self.getCache(cache_key, self.rss_url)
|
||||||
|
data = XMLTree.fromstring(rss_data)
|
||||||
|
|
||||||
|
if data is not None:
|
||||||
|
rss_movies = self.getElements(data, 'channel/item')
|
||||||
|
|
||||||
|
for movie in rss_movies:
|
||||||
|
RSSMovie['name'] = self.getTextElement(movie, "title")
|
||||||
|
currentYear = datetime.datetime.now().strftime("%Y")
|
||||||
|
RSSMovie['year'] = currentYear
|
||||||
|
|
||||||
|
log.debug('Release found: %s.', RSSMovie)
|
||||||
|
imdb = self.getIMDBFromTitle(RSSMovie['name'], RSSMovie['year'])
|
||||||
|
|
||||||
|
if imdb:
|
||||||
|
movies.append(imdb['imdb'])
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -41,13 +41,19 @@ class Trakt(Automation):
|
|||||||
|
|
||||||
def call(self, method_url):
|
def call(self, method_url):
|
||||||
|
|
||||||
if self.conf('automation_password'):
|
try:
|
||||||
headers = {
|
if self.conf('automation_password'):
|
||||||
'Authorization': "Basic %s" % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
headers = {
|
||||||
}
|
'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
||||||
else:
|
}
|
||||||
headers = {}
|
else:
|
||||||
|
headers = {}
|
||||||
|
|
||||||
cache_key = 'trakt.%s' % md5(method_url)
|
cache_key = 'trakt.%s' % md5(method_url)
|
||||||
json_string = self.getCache(cache_key, self.urls['base'] + method_url, headers = headers)
|
json_string = self.getCache(cache_key, self.urls['base'] + method_url, headers = headers)
|
||||||
return json.loads(json_string)
|
if json_string:
|
||||||
|
return json.loads(json_string)
|
||||||
|
except:
|
||||||
|
log.error('Failed to get data from trakt, check your login.')
|
||||||
|
|
||||||
|
return []
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ from couchpotato.core.logger import CPLog
|
|||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
from urllib import quote_plus
|
||||||
|
from couchpotato.core.helpers.encoding import simplifyString
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -31,7 +34,7 @@ class Provider(Plugin):
|
|||||||
self.urlopen(test_url, 30)
|
self.urlopen(test_url, 30)
|
||||||
self.is_available[host] = True
|
self.is_available[host] = True
|
||||||
except:
|
except:
|
||||||
log.error('"%s" unavailable, trying again in an 15 minutes.' % host)
|
log.error('"%s" unavailable, trying again in an 15 minutes.', host)
|
||||||
self.is_available[host] = False
|
self.is_available[host] = False
|
||||||
|
|
||||||
return self.is_available.get(host, False)
|
return self.is_available.get(host, False)
|
||||||
@@ -62,8 +65,11 @@ class YarrProvider(Provider):
|
|||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def belongsTo(self, url, host = None):
|
def belongsTo(self, url, provider = None, host = None):
|
||||||
try:
|
try:
|
||||||
|
if provider and provider == self.getName():
|
||||||
|
return self
|
||||||
|
|
||||||
hostname = urlparse(url).hostname
|
hostname = urlparse(url).hostname
|
||||||
if host and hostname in host:
|
if host and hostname in host:
|
||||||
return self
|
return self
|
||||||
@@ -73,7 +79,7 @@ class YarrProvider(Provider):
|
|||||||
if hostname in download_url:
|
if hostname in download_url:
|
||||||
return self
|
return self
|
||||||
except:
|
except:
|
||||||
log.debug('Url % s doesn\'t belong to %s' % (url, self.getName()))
|
log.debug('Url % s doesn\'t belong to %s', (url, self.getName()))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -106,4 +112,4 @@ class YarrProvider(Provider):
|
|||||||
return [self.cat_backup_id]
|
return [self.cat_backup_id]
|
||||||
|
|
||||||
def found(self, new):
|
def found(self, new):
|
||||||
log.info('Found: score(%(score)s) on %(provider)s: %(name)s' % new)
|
log.info('Found: score(%(score)s) on %(provider)s: %(name)s', new)
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ class MetaDataBase(Plugin):
|
|||||||
def create(self, release):
|
def create(self, release):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
log.info('Creating %s metadata.' % self.getName())
|
log.info('Creating %s metadata.', self.getName())
|
||||||
|
|
||||||
# Update library to get latest info
|
# Update library to get latest info
|
||||||
try:
|
try:
|
||||||
updated_library = fireEvent('library.update', release['library']['identifier'], force = True, single = True)
|
updated_library = fireEvent('library.update', release['library']['identifier'], force = True, single = True)
|
||||||
release['library'] = mergeDicts(release['library'], updated_library)
|
release['library'] = mergeDicts(release['library'], updated_library)
|
||||||
except:
|
except:
|
||||||
log.error('Failed to update movie, before creating metadata: %s' % traceback.format_exc())
|
log.error('Failed to update movie, before creating metadata: %s', traceback.format_exc())
|
||||||
|
|
||||||
root_name = self.getRootName(release)
|
root_name = self.getRootName(release)
|
||||||
meta_name = os.path.basename(root_name)
|
meta_name = os.path.basename(root_name)
|
||||||
@@ -44,14 +44,14 @@ class MetaDataBase(Plugin):
|
|||||||
# Get file content
|
# Get file content
|
||||||
content = getattr(self, 'get' + file_type.capitalize())(movie_info = movie_info, data = release)
|
content = getattr(self, 'get' + file_type.capitalize())(movie_info = movie_info, data = release)
|
||||||
if content:
|
if content:
|
||||||
log.debug('Creating %s file: %s' % (file_type, name))
|
log.debug('Creating %s file: %s', (file_type, name))
|
||||||
if os.path.isfile(content):
|
if os.path.isfile(content):
|
||||||
shutil.copy2(content, name)
|
shutil.copy2(content, name)
|
||||||
else:
|
else:
|
||||||
self.createFile(name, content)
|
self.createFile(name, content)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Unable to create %s file: %s' % (file_type, traceback.format_exc()))
|
log.error('Unable to create %s file: %s', (file_type, traceback.format_exc()))
|
||||||
|
|
||||||
def getRootName(self, data):
|
def getRootName(self, data):
|
||||||
return
|
return
|
||||||
@@ -75,7 +75,7 @@ class MetaDataBase(Plugin):
|
|||||||
break
|
break
|
||||||
|
|
||||||
for cur_file in data['library'].get('files', []):
|
for cur_file in data['library'].get('files', []):
|
||||||
if cur_file.get('type_id') is file_type.get('id'):
|
if cur_file.get('type_id') is file_type.get('id') and os.path.isfile(cur_file.get('path')):
|
||||||
return cur_file.get('path')
|
return cur_file.get('path')
|
||||||
|
|
||||||
def getFanart(self, movie_info = {}, data = {}):
|
def getFanart(self, movie_info = {}, data = {}):
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class XBMC(MetaDataBase):
|
|||||||
votes.text = str(v)
|
votes.text = str(v)
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
log.debug('Failed adding rating info from %s: %s' % (rating_type, traceback.format_exc()))
|
log.debug('Failed adding rating info from %s: %s', (rating_type, traceback.format_exc()))
|
||||||
|
|
||||||
# Genre
|
# Genre
|
||||||
for genre in movie_info.get('genres', []):
|
for genre in movie_info.get('genres', []):
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.variable import mergeDicts
|
from couchpotato.core.helpers.variable import mergeDicts, randomString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Library
|
from couchpotato.core.settings.model import Library
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -23,11 +22,19 @@ class MovieResultModifier(Plugin):
|
|||||||
|
|
||||||
# Combine on imdb id
|
# Combine on imdb id
|
||||||
for item in results:
|
for item in results:
|
||||||
imdb = item.get('imdb', 'random-%s' % time.time())
|
random_string = randomString()
|
||||||
|
imdb = item.get('imdb', random_string)
|
||||||
|
imdb = imdb if imdb else random_string
|
||||||
|
|
||||||
if not temp.get(imdb):
|
if not temp.get(imdb):
|
||||||
temp[imdb] = self.getLibraryTags(imdb)
|
temp[imdb] = self.getLibraryTags(imdb)
|
||||||
order.append(imdb)
|
order.append(imdb)
|
||||||
|
|
||||||
|
if item.get('via_imdb'):
|
||||||
|
if order.index(imdb):
|
||||||
|
order.remove(imdb)
|
||||||
|
order.insert(0, imdb)
|
||||||
|
|
||||||
# Merge dicts
|
# Merge dicts
|
||||||
temp[imdb] = mergeDicts(temp[imdb], item)
|
temp[imdb] = mergeDicts(temp[imdb], item)
|
||||||
|
|
||||||
@@ -61,7 +68,7 @@ class MovieResultModifier(Plugin):
|
|||||||
if release.status_id == done_status['id']:
|
if release.status_id == done_status['id']:
|
||||||
temp['in_library'] = fireEvent('movie.get', movie.id, single = True)
|
temp['in_library'] = fireEvent('movie.get', movie.id, single = True)
|
||||||
except:
|
except:
|
||||||
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
|
log.error('Tried getting more info on searched movies: %s', traceback.format_exc())
|
||||||
|
|
||||||
#db.close()
|
#db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
headers = {'X-CP-Version': fireEvent('app.version', single = True)}
|
headers = {'X-CP-Version': fireEvent('app.version', single = True)}
|
||||||
data = self.urlopen((self.api_url % ('eta')) + (identifier + '/'), headers = headers)
|
data = self.urlopen((self.api_url % ('eta')) + (identifier + '/'), headers = headers)
|
||||||
dates = json.loads(data)
|
dates = json.loads(data)
|
||||||
log.debug('Found ETA for %s: %s' % (identifier, dates))
|
log.debug('Found ETA for %s: %s', (identifier, dates))
|
||||||
return dates
|
return dates
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Error getting ETA for %s: %s' % (identifier, e))
|
log.error('Error getting ETA for %s: %s', (identifier, e))
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -43,9 +43,9 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
try:
|
try:
|
||||||
data = self.urlopen((self.api_url % ('suggest')) + ','.join(movies) + '/' + ','.join(ignore) + '/')
|
data = self.urlopen((self.api_url % ('suggest')) + ','.join(movies) + '/' + ','.join(ignore) + '/')
|
||||||
suggestions = json.loads(data)
|
suggestions = json.loads(data)
|
||||||
log.info('Found Suggestions for %s' % (suggestions))
|
log.info('Found Suggestions for %s', (suggestions))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Error getting suggestions for %s: %s' % (movies, e))
|
log.error('Error getting suggestions for %s: %s', (movies, e))
|
||||||
|
|
||||||
return suggestions
|
return suggestions
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('movie.search', self.search)
|
addEvent('movie.search', self.search)
|
||||||
|
addEvent('movie.searchimdb', self.search)
|
||||||
addEvent('movie.info', self.getInfo)
|
addEvent('movie.info', self.getInfo)
|
||||||
|
|
||||||
def search(self, q, limit = 12):
|
def search(self, q, limit = 12):
|
||||||
@@ -36,7 +37,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
if cached:
|
if cached:
|
||||||
result = self.parseMovie(cached)
|
result = self.parseMovie(cached)
|
||||||
if result.get('titles') and len(result.get('titles')) > 0:
|
if result.get('titles') and len(result.get('titles')) > 0:
|
||||||
log.info('Found: %s' % result['titles'][0] + ' (' + str(result['year']) + ')')
|
log.info('Found: %s', result['titles'][0] + ' (' + str(result['year']) + ')')
|
||||||
return [result]
|
return [result]
|
||||||
|
|
||||||
return []
|
return []
|
||||||
@@ -54,7 +55,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
if cached:
|
if cached:
|
||||||
result = self.parseMovie(cached)
|
result = self.parseMovie(cached)
|
||||||
if result.get('titles') and len(result.get('titles')) > 0:
|
if result.get('titles') and len(result.get('titles')) > 0:
|
||||||
log.info('Found: %s' % result['titles'][0] + ' (' + str(result['year']) + ')')
|
log.info('Found: %s', result['titles'][0] + ' (' + str(result['year']) + ')')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
@@ -82,13 +83,14 @@ class IMDBAPI(MovieProvider):
|
|||||||
year = tryInt(movie.get('Year', ''))
|
year = tryInt(movie.get('Year', ''))
|
||||||
|
|
||||||
movie_data = {
|
movie_data = {
|
||||||
|
'via_imdb': True,
|
||||||
'titles': [movie.get('Title')] if movie.get('Title') else [],
|
'titles': [movie.get('Title')] if movie.get('Title') else [],
|
||||||
'original_title': movie.get('Title', ''),
|
'original_title': movie.get('Title', ''),
|
||||||
'images': {
|
'images': {
|
||||||
'poster': [movie.get('Poster', '')] if movie.get('Poster') and len(movie.get('Poster', '')) > 4 else [],
|
'poster': [movie.get('Poster', '')] if movie.get('Poster') and len(movie.get('Poster', '')) > 4 else [],
|
||||||
},
|
},
|
||||||
'rating': {
|
'rating': {
|
||||||
'imdb': (tryFloat(movie.get('imdbRating', 0)), tryInt(movie.get('imdbVotes', ''))),
|
'imdb': (tryFloat(movie.get('imdbRating', 0)), tryInt(movie.get('imdbVotes', '').replace(',', ''))),
|
||||||
#'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', 0))),
|
#'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', 0))),
|
||||||
},
|
},
|
||||||
'imdb': str(movie.get('imdbID', '')),
|
'imdb': str(movie.get('imdbID', '')),
|
||||||
@@ -102,17 +104,17 @@ class IMDBAPI(MovieProvider):
|
|||||||
'actors': movie.get('Actors', '').split(','),
|
'actors': movie.get('Actors', '').split(','),
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
log.error('Failed parsing IMDB API json: %s' % traceback.format_exc())
|
log.error('Failed parsing IMDB API json: %s', traceback.format_exc())
|
||||||
|
|
||||||
return movie_data
|
return movie_data
|
||||||
|
|
||||||
def runtimeToMinutes(self, runtime_str):
|
def runtimeToMinutes(self, runtime_str):
|
||||||
runtime = 0
|
runtime = 0
|
||||||
|
|
||||||
regex = '(\d*.?\d+).(hr|hrs|mins|min)+'
|
regex = '(\d*.?\d+).(h|hr|hrs|mins|min)+'
|
||||||
matches = re.findall(regex, runtime_str)
|
matches = re.findall(regex, runtime_str)
|
||||||
for match in matches:
|
for match in matches:
|
||||||
nr, size = match
|
nr, size = match
|
||||||
runtime += tryInt(nr) * (60 if 'hr' in str(size) else 1)
|
runtime += tryInt(nr) * (60 if 'h' is str(size)[0] else 1)
|
||||||
|
|
||||||
return runtime
|
return runtime
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.movie.base import MovieProvider
|
from couchpotato.core.providers.movie.base import MovieProvider
|
||||||
from libs.themoviedb import tmdb
|
from libs.themoviedb import tmdb
|
||||||
import re
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ class TheMovieDb(MovieProvider):
|
|||||||
results = self.getCache(cache_key)
|
results = self.getCache(cache_key)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
log.debug('Searching for movie by hash: %s' % file)
|
log.debug('Searching for movie by hash: %s', file)
|
||||||
try:
|
try:
|
||||||
raw = tmdb.searchByHashingFile(file)
|
raw = tmdb.searchByHashingFile(file)
|
||||||
|
|
||||||
@@ -37,15 +36,15 @@ class TheMovieDb(MovieProvider):
|
|||||||
if raw:
|
if raw:
|
||||||
try:
|
try:
|
||||||
results = self.parseMovie(raw)
|
results = self.parseMovie(raw)
|
||||||
log.info('Found: %s' % results['titles'][0] + ' (' + str(results['year']) + ')')
|
log.info('Found: %s', results['titles'][0] + ' (' + str(results['year']) + ')')
|
||||||
|
|
||||||
self.setCache(cache_key, results)
|
self.setCache(cache_key, results)
|
||||||
return results
|
return results
|
||||||
except SyntaxError, e:
|
except SyntaxError, e:
|
||||||
log.error('Failed to parse XML response: %s' % e)
|
log.error('Failed to parse XML response: %s', e)
|
||||||
return False
|
return False
|
||||||
except:
|
except:
|
||||||
log.debug('No movies known by hash for: %s' % file)
|
log.debug('No movies known by hash for: %s', file)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return results
|
return results
|
||||||
@@ -61,7 +60,7 @@ class TheMovieDb(MovieProvider):
|
|||||||
results = self.getCache(cache_key)
|
results = self.getCache(cache_key)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
log.debug('Searching for movie: %s' % q)
|
log.debug('Searching for movie: %s', q)
|
||||||
raw = tmdb.search(search_string)
|
raw = tmdb.search(search_string)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
@@ -76,18 +75,21 @@ class TheMovieDb(MovieProvider):
|
|||||||
if nr == limit:
|
if nr == limit:
|
||||||
break
|
break
|
||||||
|
|
||||||
log.info('Found: %s' % [result['titles'][0] + ' (' + str(result['year']) + ')' for result in results])
|
log.info('Found: %s', [result['titles'][0] + ' (' + str(result['year']) + ')' for result in results])
|
||||||
|
|
||||||
self.setCache(cache_key, results)
|
self.setCache(cache_key, results)
|
||||||
return results
|
return results
|
||||||
except SyntaxError, e:
|
except SyntaxError, e:
|
||||||
log.error('Failed to parse XML response: %s' % e)
|
log.error('Failed to parse XML response: %s', e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def getInfo(self, identifier = None):
|
def getInfo(self, identifier = None):
|
||||||
|
|
||||||
|
if not identifier:
|
||||||
|
return {}
|
||||||
|
|
||||||
cache_key = 'tmdb.cache.%s' % identifier
|
cache_key = 'tmdb.cache.%s' % identifier
|
||||||
result = self.getCache(cache_key)
|
result = self.getCache(cache_key)
|
||||||
|
|
||||||
@@ -96,7 +98,7 @@ class TheMovieDb(MovieProvider):
|
|||||||
movie = None
|
movie = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.debug('Getting info: %s' % cache_key)
|
log.debug('Getting info: %s', cache_key)
|
||||||
movie = tmdb.imdbLookup(id = identifier)
|
movie = tmdb.imdbLookup(id = identifier)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -117,7 +119,7 @@ class TheMovieDb(MovieProvider):
|
|||||||
movie = None
|
movie = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.debug('Getting info: %s' % cache_key)
|
log.debug('Getting info: %s', cache_key)
|
||||||
movie = tmdb.getMovieInfo(id = id)
|
movie = tmdb.getMovieInfo(id = id)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -148,7 +150,8 @@ class TheMovieDb(MovieProvider):
|
|||||||
year = None
|
year = None
|
||||||
|
|
||||||
movie_data = {
|
movie_data = {
|
||||||
'id': int(movie.get('id', 0)),
|
'via_tmdb': True,
|
||||||
|
'tmdb_id': int(movie.get('id', 0)),
|
||||||
'titles': [toUnicode(movie.get('name'))],
|
'titles': [toUnicode(movie.get('name'))],
|
||||||
'original_title': movie.get('original_name'),
|
'original_title': movie.get('original_name'),
|
||||||
'images': {
|
'images': {
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
|
||||||
from couchpotato.core.helpers.variable import getTitle
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
|
||||||
from dateutil.parser import parse
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Moovee(NZBProvider):
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
'download': 'http://85.214.105.230/get_nzb.php?id=%s§ion=moovee',
|
|
||||||
'search': 'http://abmoovee.allfilled.com/search.php?q=%s&Search=Search',
|
|
||||||
}
|
|
||||||
|
|
||||||
regex = '<td class="cell_reqid">(?P<reqid>.*?)</td>.+?<td class="cell_request">(?P<title>.*?)</td>.+?<td class="cell_statuschange">(?P<age>.*?)</td>'
|
|
||||||
|
|
||||||
http_time_between_calls = 2 # Seconds
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
|
||||||
|
|
||||||
results = []
|
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']) or quality.get('hd', False):
|
|
||||||
return results
|
|
||||||
|
|
||||||
q = '%s %s' % (getTitle(movie['library']), quality.get('identifier'))
|
|
||||||
url = self.urls['search'] % tryUrlencode(q)
|
|
||||||
|
|
||||||
cache_key = 'moovee.%s' % q
|
|
||||||
data = self.getCache(cache_key, url)
|
|
||||||
if data:
|
|
||||||
match = re.compile(self.regex, re.DOTALL).finditer(data)
|
|
||||||
|
|
||||||
for nzb in match:
|
|
||||||
new = {
|
|
||||||
'id': nzb.group('reqid'),
|
|
||||||
'name': nzb.group('title'),
|
|
||||||
'type': 'nzb',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'age': self.calculateAge(time.mktime(parse(nzb.group('age')).timetuple())),
|
|
||||||
'size': None,
|
|
||||||
'url': self.urls['download'] % (nzb.group('reqid')),
|
|
||||||
'detail_url': '',
|
|
||||||
'description': '',
|
|
||||||
'check_nzb': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = False, single_category = False, single = True)
|
|
||||||
if is_correct_movie:
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def belongsTo(self, url, host = None):
|
|
||||||
match = re.match('http://85\.214\.105\.230/get_nzb\.php\?id=[0-9]*§ion=moovee', url)
|
|
||||||
if match:
|
|
||||||
return self
|
|
||||||
return
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from BeautifulSoup import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \
|
||||||
|
simplifyString
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
@@ -22,11 +23,13 @@ class Mysterbin(NZBProvider):
|
|||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
if self.isDisabled():
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '"%s" %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
q = '"%s" %s %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
||||||
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
||||||
|
if len(q) + len(ignored.strip()) > 126:
|
||||||
|
break
|
||||||
q = '%s -%s' % (q, ignored.strip())
|
q = '%s -%s' % (q, ignored.strip())
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
@@ -39,28 +42,28 @@ class Mysterbin(NZBProvider):
|
|||||||
'nopasswd': 'on',
|
'nopasswd': 'on',
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_key = 'mysterbin.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
cache_key = 'mysterbin.%s.%s.%s' % (movie['library']['identifier'], quality.get('identifier'), q)
|
||||||
data = self.getCache(cache_key, self.urls['search'] % tryUrlencode(params))
|
data = self.getCache(cache_key, self.urls['search'] % tryUrlencode(params))
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
html = BeautifulSoup(data)
|
html = BeautifulSoup(data)
|
||||||
resultable = html.find('table', attrs = {'class':'t'})
|
resultable = html.find('table', attrs = {'class':'t'})
|
||||||
for result in resultable.findAll('tr'):
|
for result in resultable.find_all('tr'):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
myster_id = result.find('input', attrs = {'class': 'check4nzb'})['value']
|
myster_id = result.find('input', attrs = {'class': 'check4nzb'})['value']
|
||||||
|
|
||||||
# Age
|
# Age
|
||||||
age = ''
|
age = ''
|
||||||
for temp in result.find('td', attrs = {'class': 'cdetail'}).findAll(text = True):
|
for temp in result.find('td', attrs = {'class': 'cdetail'}).find_all(text = True):
|
||||||
if 'days' in temp:
|
if 'days' in temp:
|
||||||
age = tryInt(temp.split(' ')[0])
|
age = tryInt(temp.split(' ')[0])
|
||||||
break
|
break
|
||||||
|
|
||||||
# size
|
# size
|
||||||
size = None
|
size = None
|
||||||
for temp in result.find('div', attrs = {'class': 'cdetail'}).findAll(text = True):
|
for temp in result.find('div', attrs = {'class': 'cdetail'}).find_all(text = True):
|
||||||
if 'gb' in temp.lower() or 'mb' in temp.lower() or 'kb' in temp.lower():
|
if 'gb' in temp.lower() or 'mb' in temp.lower() or 'kb' in temp.lower():
|
||||||
size = self.parseSize(temp)
|
size = self.parseSize(temp)
|
||||||
break
|
break
|
||||||
@@ -71,13 +74,14 @@ class Mysterbin(NZBProvider):
|
|||||||
|
|
||||||
new = {
|
new = {
|
||||||
'id': myster_id,
|
'id': myster_id,
|
||||||
'name': ''.join(result.find('span', attrs = {'class': 'cname'}).findAll(text = True)),
|
'name': ''.join(result.find('span', attrs = {'class': 'cname'}).find_all(text = True)),
|
||||||
'type': 'nzb',
|
'type': 'nzb',
|
||||||
'provider': self.getName(),
|
'provider': self.getName(),
|
||||||
'age': age,
|
'age': age,
|
||||||
'size': size,
|
'size': size,
|
||||||
'url': self.urls['download'] % myster_id,
|
'url': self.urls['download'] % myster_id,
|
||||||
'description': description,
|
'description': description,
|
||||||
|
'download': self.download,
|
||||||
'check_nzb': False,
|
'check_nzb': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class Newzbin(NZBProvider, RSS):
|
|||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
if self.isDisabled():
|
||||||
return results
|
return results
|
||||||
|
|
||||||
format_id = self.getFormatId(type)
|
format_id = self.getFormatId(type)
|
||||||
@@ -57,11 +57,12 @@ class Newzbin(NZBProvider, RSS):
|
|||||||
'category': '6',
|
'category': '6',
|
||||||
'ps_rb_video_format': str(cat_id),
|
'ps_rb_video_format': str(cat_id),
|
||||||
'ps_rb_source': str(format_id),
|
'ps_rb_source': str(format_id),
|
||||||
|
'u_post_larger_than': quality.get('size_min'),
|
||||||
|
'u_post_smaller_than': quality.get('size_max'),
|
||||||
})
|
})
|
||||||
|
|
||||||
url = "%s?%s" % (self.urls['search'], arguments)
|
url = "%s?%s" % (self.urls['search'], arguments)
|
||||||
cache_key = str('newzbin.%s.%s.%s' % (movie['library']['identifier'], str(format_id), str(cat_id)))
|
cache_key = str('newzbin.%s.%s.%s' % (movie['library']['identifier'], str(format_id), str(cat_id)))
|
||||||
single_cat = True
|
|
||||||
|
|
||||||
data = self.getCache(cache_key)
|
data = self.getCache(cache_key)
|
||||||
if not data:
|
if not data:
|
||||||
@@ -81,7 +82,7 @@ class Newzbin(NZBProvider, RSS):
|
|||||||
data = XMLTree.fromstring(data)
|
data = XMLTree.fromstring(data)
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
nzbs = self.getElements(data, 'channel/item')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.debug('%s, %s' % (self.getName(), e))
|
log.debug('%s, %s', (self.getName(), e))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
@@ -115,12 +116,12 @@ class Newzbin(NZBProvider, RSS):
|
|||||||
'description': self.getTextElement(nzb, "description"),
|
'description': self.getTextElement(nzb, "description"),
|
||||||
'check_nzb': False,
|
'check_nzb': False,
|
||||||
}
|
}
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = True, single_category = single_cat, single = True)
|
imdb_results = True, single = True)
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ class Newzbin(NZBProvider, RSS):
|
|||||||
|
|
||||||
def download(self, url = '', nzb_id = ''):
|
def download(self, url = '', nzb_id = ''):
|
||||||
try:
|
try:
|
||||||
log.info('Download nzb from newzbin, report id: %s ' % nzb_id)
|
log.info('Download nzb from newzbin, report id: %s ', nzb_id)
|
||||||
|
|
||||||
return self.urlopen(self.urls['download'], params = {
|
return self.urlopen(self.urls['download'], params = {
|
||||||
'username' : self.conf('username'),
|
'username' : self.conf('username'),
|
||||||
@@ -140,7 +141,7 @@ class Newzbin(NZBProvider, RSS):
|
|||||||
'reportid' : nzb_id
|
'reportid' : nzb_id
|
||||||
}, show_error = False)
|
}, show_error = False)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed downloading from newzbin, check credit: %s' % e)
|
log.error('Failed downloading from newzbin, check credit: %s', e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getFormatId(self, format):
|
def getFormatId(self, format):
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ from couchpotato.core.logger import CPLog
|
|||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
|
from urllib2 import HTTPError
|
||||||
|
from urlparse import urlparse
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
import xml.etree.ElementTree as XMLTree
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -20,6 +23,8 @@ class Newznab(NZBProvider, RSS):
|
|||||||
'search': 'movie',
|
'search': 'movie',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
limits_reached = {}
|
||||||
|
|
||||||
cat_ids = [
|
cat_ids = [
|
||||||
([2010], ['dvdr']),
|
([2010], ['dvdr']),
|
||||||
([2030], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
([2030], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||||
@@ -46,7 +51,7 @@ class Newznab(NZBProvider, RSS):
|
|||||||
def singleFeed(self, host):
|
def singleFeed(self, host):
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if self.isDisabled(host) or not self.isAvailable(self.getUrl(host['host'], self.urls['search'])):
|
if self.isDisabled(host):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
@@ -78,7 +83,7 @@ class Newznab(NZBProvider, RSS):
|
|||||||
def singleSearch(self, host, movie, quality):
|
def singleSearch(self, host, movie, quality):
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if self.isDisabled(host) or not self.isAvailable(self.getUrl(host['host'], self.urls['search'])):
|
if self.isDisabled(host):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
cat_id = self.getCatId(quality['identifier'])
|
cat_id = self.getCatId(quality['identifier'])
|
||||||
@@ -107,7 +112,7 @@ class Newznab(NZBProvider, RSS):
|
|||||||
data = XMLTree.fromstring(data)
|
data = XMLTree.fromstring(data)
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
nzbs = self.getElements(data, 'channel/item')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.debug('%s, %s' % (self.getName(), e))
|
log.debug('%s, %s', (self.getName(), e))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
@@ -121,8 +126,8 @@ class Newznab(NZBProvider, RSS):
|
|||||||
elif item.attrib.get('name') == 'usenetdate':
|
elif item.attrib.get('name') == 'usenetdate':
|
||||||
date = item.attrib.get('value')
|
date = item.attrib.get('value')
|
||||||
|
|
||||||
if date is '': log.debug('Date not parsed properly or not available for %s: %s' % (host['host'], self.getTextElement(nzb, "title")))
|
if date is '': log.debug('Date not parsed properly or not available for %s: %s', (host['host'], self.getTextElement(nzb, "title")))
|
||||||
if size is 0: log.debug('Size not parsed properly or not available for %s: %s' % (host['host'], self.getTextElement(nzb, "title")))
|
if size is 0: log.debug('Size not parsed properly or not available for %s: %s', (host['host'], self.getTextElement(nzb, "title")))
|
||||||
|
|
||||||
id = self.getTextElement(nzb, "guid").split('/')[-1:].pop()
|
id = self.getTextElement(nzb, "guid").split('/')[-1:].pop()
|
||||||
new = {
|
new = {
|
||||||
@@ -139,13 +144,12 @@ class Newznab(NZBProvider, RSS):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if not for_feed:
|
if not for_feed:
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = True, single_category = single_cat, single = True)
|
imdb_results = True, single_category = single_cat, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
else:
|
else:
|
||||||
@@ -153,7 +157,7 @@ class Newznab(NZBProvider, RSS):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
log.error('Failed to parse XML response from Newznab: %s' % host)
|
log.error('Failed to parse XML response from Newznab: %s', host)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def getHosts(self):
|
def getHosts(self):
|
||||||
@@ -172,17 +176,15 @@ class Newznab(NZBProvider, RSS):
|
|||||||
|
|
||||||
return list
|
return list
|
||||||
|
|
||||||
def belongsTo(self, url):
|
def belongsTo(self, url, provider = None):
|
||||||
|
|
||||||
hosts = self.getHosts()
|
hosts = self.getHosts()
|
||||||
|
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
result = super(Newznab, self).belongsTo(url, host = host['host'])
|
result = super(Newznab, self).belongsTo(url, host = host['host'], provider = provider)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def getUrl(self, host, type):
|
def getUrl(self, host, type):
|
||||||
return cleanHost(host) + 'api?t=' + type
|
return cleanHost(host) + 'api?t=' + type
|
||||||
|
|
||||||
@@ -194,3 +196,27 @@ class Newznab(NZBProvider, RSS):
|
|||||||
|
|
||||||
def getApiExt(self, host):
|
def getApiExt(self, host):
|
||||||
return '&apikey=%s' % host['api_key']
|
return '&apikey=%s' % host['api_key']
|
||||||
|
|
||||||
|
def download(self, url = '', nzb_id = ''):
|
||||||
|
host = urlparse(url).hostname
|
||||||
|
|
||||||
|
if self.limits_reached.get(host):
|
||||||
|
# Try again in 3 hours
|
||||||
|
if self.limits_reached[host] > time.time() - 10800:
|
||||||
|
return 'try_next'
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.urlopen(url, show_error = False)
|
||||||
|
self.limits_reached[host] = False
|
||||||
|
return data
|
||||||
|
except HTTPError, e:
|
||||||
|
if e.code == 503:
|
||||||
|
response = e.read().lower()
|
||||||
|
if 'maximum api' in response or 'download limit' in response:
|
||||||
|
if not self.limits_reached.get(host):
|
||||||
|
log.error('Limit reached for newznab provider: %s', host)
|
||||||
|
self.limits_reached[host] = time.time()
|
||||||
|
return 'try_next'
|
||||||
|
|
||||||
|
log.error('Failed download from %s', (host, traceback.format_exc()))
|
||||||
|
raise
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from BeautifulSoup import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \
|
||||||
|
simplifyString
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -19,15 +20,15 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
'search': 'https://www.nzbclub.com/nzbfeed.aspx?%s',
|
'search': 'https://www.nzbclub.com/nzbfeed.aspx?%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 3 #seconds
|
http_time_between_calls = 4 #seconds
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
if self.isDisabled():
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '"%s" %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
q = '"%s %s" %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
||||||
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
||||||
q = '%s -%s' % (q, ignored.strip())
|
q = '%s -%s' % (q, ignored.strip())
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
'ns': 1,
|
'ns': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_key = 'nzbclub.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
cache_key = 'nzbclub.%s.%s.%s' % (movie['library']['identifier'], quality.get('identifier'), q)
|
||||||
data = self.getCache(cache_key, self.urls['search'] % tryUrlencode(params))
|
data = self.getCache(cache_key, self.urls['search'] % tryUrlencode(params))
|
||||||
if data:
|
if data:
|
||||||
try:
|
try:
|
||||||
@@ -48,7 +49,7 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
data = XMLTree.fromstring(data)
|
data = XMLTree.fromstring(data)
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
nzbs = self.getElements(data, 'channel/item')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.debug('%s, %s' % (self.getName(), e))
|
log.debug('%s, %s', (self.getName(), e))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
@@ -58,10 +59,15 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
size = enclosure['length']
|
size = enclosure['length']
|
||||||
date = self.getTextElement(nzb, "pubDate")
|
date = self.getTextElement(nzb, "pubDate")
|
||||||
|
|
||||||
full_description = self.getCache('nzbclub.%s' % nzbclub_id, self.getTextElement(nzb, "link"), cache_timeout = 25920000)
|
def extra_check(item):
|
||||||
html = BeautifulSoup(full_description)
|
full_description = self.getCache('nzbclub.%s' % nzbclub_id, item['detail_url'], cache_timeout = 25920000)
|
||||||
nfo_pre = html.find('pre', attrs = {'class':'nfo'})
|
|
||||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
for ignored in ['ARCHIVE inside ARCHIVE', 'Incomplete', 'repair impossible']:
|
||||||
|
if ignored in full_description:
|
||||||
|
log.info('Wrong: Seems to be passworded or corrupted files: %s', new['name'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
new = {
|
new = {
|
||||||
'id': nzbclub_id,
|
'id': nzbclub_id,
|
||||||
@@ -73,19 +79,17 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
'url': enclosure['url'].replace(' ', '_'),
|
'url': enclosure['url'].replace(' ', '_'),
|
||||||
'download': self.download,
|
'download': self.download,
|
||||||
'detail_url': self.getTextElement(nzb, "link"),
|
'detail_url': self.getTextElement(nzb, "link"),
|
||||||
'description': description,
|
'description': '',
|
||||||
|
'get_more_info': self.getMoreInfo,
|
||||||
|
'extra_check': extra_check
|
||||||
}
|
}
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
|
|
||||||
if 'ARCHIVE inside ARCHIVE' in full_description:
|
|
||||||
log.info('Wrong: Seems to be passworded files: %s' % new['name'])
|
|
||||||
continue
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single_category = False, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
|
|
||||||
@@ -94,3 +98,21 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
log.error('Failed to parse XML response from NZBClub')
|
log.error('Failed to parse XML response from NZBClub')
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def getMoreInfo(self, item):
|
||||||
|
full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||||
|
html = BeautifulSoup(full_description)
|
||||||
|
nfo_pre = html.find('pre', attrs = {'class':'nfo'})
|
||||||
|
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
||||||
|
|
||||||
|
item['description'] = description
|
||||||
|
return item
|
||||||
|
|
||||||
|
def extraCheck(self, item):
|
||||||
|
full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||||
|
|
||||||
|
if 'ARCHIVE inside ARCHIVE' in full_description:
|
||||||
|
log.info('Wrong: Seems to be passworded files: %s', item['name'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from BeautifulSoup import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \
|
||||||
|
simplifyString
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -9,6 +10,7 @@ from couchpotato.environment import Env
|
|||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
import xml.etree.ElementTree as XMLTree
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -26,10 +28,10 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['api']):
|
if self.isDisabled():
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '%s %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
q = '"%s %s" %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'q': q,
|
'q': q,
|
||||||
'age': Env.setting('retention', 'nzb'),
|
'age': Env.setting('retention', 'nzb'),
|
||||||
@@ -52,7 +54,7 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
data = XMLTree.fromstring(data)
|
data = XMLTree.fromstring(data)
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
nzbs = self.getElements(data, 'channel/item')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.debug('%s, %s' % (self.getName(), e))
|
log.debug('%s, %s', (self.getName(), e))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
@@ -63,42 +65,56 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
description = self.getTextElement(nzb, "description")
|
description = self.getTextElement(nzb, "description")
|
||||||
if '/nfo/' in description.lower():
|
|
||||||
nfo_url = re.search('href=\"(?P<nfo>.+)\" ', description).group('nfo')
|
|
||||||
full_description = self.getCache('nzbindex.%s' % nzbindex_id, url = nfo_url, cache_timeout = 25920000)
|
|
||||||
html = BeautifulSoup(full_description)
|
|
||||||
description = toUnicode(html.find('pre', attrs = {'id':'nfo0'}).text)
|
|
||||||
except:
|
except:
|
||||||
pass
|
description = ''
|
||||||
|
|
||||||
|
def extra_check(new):
|
||||||
|
if '#c20000' in new['description'].lower():
|
||||||
|
log.info('Wrong: Seems to be passworded: %s', new['name'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
new = {
|
new = {
|
||||||
'id': nzbindex_id,
|
'id': nzbindex_id,
|
||||||
'type': 'nzb',
|
'type': 'nzb',
|
||||||
'provider': self.getName(),
|
'provider': self.getName(),
|
||||||
|
'download': self.download,
|
||||||
'name': self.getTextElement(nzb, "title"),
|
'name': self.getTextElement(nzb, "title"),
|
||||||
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
||||||
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
||||||
'url': enclosure['url'],
|
'url': enclosure['url'],
|
||||||
'detail_url': enclosure['url'].replace('/download/', '/release/'),
|
'detail_url': enclosure['url'].replace('/download/', '/release/'),
|
||||||
'description': description,
|
'description': description,
|
||||||
|
'get_more_info': self.getMoreInfo,
|
||||||
|
'extra_check': extra_check,
|
||||||
'check_nzb': True,
|
'check_nzb': True,
|
||||||
}
|
}
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single_category = False, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
except SyntaxError:
|
except:
|
||||||
log.error('Failed to parse XML response from NZBMatrix.com')
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def getMoreInfo(self, item):
|
||||||
|
try:
|
||||||
|
if '/nfo/' in item['description'].lower():
|
||||||
|
nfo_url = re.search('href=\"(?P<nfo>.+)\" ', item['description']).group('nfo')
|
||||||
|
full_description = self.getCache('nzbindex.%s' % item['id'], url = nfo_url, cache_timeout = 25920000)
|
||||||
|
html = BeautifulSoup(full_description)
|
||||||
|
item['description'] = toUnicode(html.find('pre', attrs = {'id':'nfo0'}).text)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def isEnabled(self):
|
def isEnabled(self):
|
||||||
return NZBProvider.isEnabled(self) and self.conf('enabled')
|
return NZBProvider.isEnabled(self) and self.conf('enabled')
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
if self.isDisabled():
|
||||||
return results
|
return results
|
||||||
|
|
||||||
cat_ids = ','.join(['%s' % x for x in self.getCatId(quality.get('identifier'))])
|
cat_ids = ','.join(['%s' % x for x in self.getCatId(quality.get('identifier'))])
|
||||||
@@ -58,7 +58,7 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
data = XMLTree.fromstring(data)
|
data = XMLTree.fromstring(data)
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
nzbs = self.getElements(data, 'channel/item')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.debug('%s, %s' % (self.getName(), e))
|
log.debug('%s, %s', (self.getName(), e))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
@@ -83,13 +83,13 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
'description': self.getTextElement(nzb, "description"),
|
'description': self.getTextElement(nzb, "description"),
|
||||||
'check_nzb': True,
|
'check_nzb': True,
|
||||||
}
|
}
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = True, single_category = single_cat, single = True)
|
imdb_results = True, single_category = single_cat, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
|
|
||||||
@@ -99,6 +99,9 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def download(self, url = '', nzb_id = ''):
|
||||||
|
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
def getApiExt(self):
|
def getApiExt(self):
|
||||||
return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('api_key'))
|
return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('api_key'))
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
from .main import X264
|
|
||||||
|
|
||||||
def start():
|
|
||||||
return X264()
|
|
||||||
|
|
||||||
config = [{
|
|
||||||
'name': 'x264',
|
|
||||||
'groups': [
|
|
||||||
{
|
|
||||||
'tab': 'searcher',
|
|
||||||
'subtab': 'providers',
|
|
||||||
'name': '#alt.binaries.hdtv.x264',
|
|
||||||
'description': 'HD movies only',
|
|
||||||
'options': [
|
|
||||||
{
|
|
||||||
'name': 'enabled',
|
|
||||||
'type': 'enabler',
|
|
||||||
'default': False,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}]
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
|
||||||
import re
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class X264(NZBProvider):
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
'download': 'http://85.214.105.230/get_nzb.php?id=%s§ion=hd',
|
|
||||||
'search': 'http://85.214.105.230/x264/requests.php?release=%s&status=FILLED&age=1300&sort=ID',
|
|
||||||
}
|
|
||||||
|
|
||||||
regex = '<tr class="req_filled"><td class="reqid">(?P<id>.*?)</td><td class="release">(?P<title>.*?)</td>.+?<td class="age">(?P<age>.*?)</td>'
|
|
||||||
|
|
||||||
http_time_between_calls = 2 # Seconds
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
|
||||||
|
|
||||||
results = []
|
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['search'].split('requests')[0]) or not quality.get('hd', False):
|
|
||||||
return results
|
|
||||||
|
|
||||||
q = '%s %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
|
||||||
url = self.urls['search'] % tryUrlencode(q)
|
|
||||||
|
|
||||||
cache_key = 'x264.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
|
||||||
data = self.getCache(cache_key, url)
|
|
||||||
if data:
|
|
||||||
match = re.compile(self.regex, re.DOTALL).finditer(data)
|
|
||||||
|
|
||||||
for nzb in match:
|
|
||||||
try:
|
|
||||||
age_match = re.match('((?P<day>\d+)d)', nzb.group('age'))
|
|
||||||
age = age_match.group('day')
|
|
||||||
except:
|
|
||||||
age = 1
|
|
||||||
|
|
||||||
new = {
|
|
||||||
'id': nzb.group('id'),
|
|
||||||
'name': nzb.group('title'),
|
|
||||||
'type': 'nzb',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'age': tryInt(age),
|
|
||||||
'size': None,
|
|
||||||
'url': self.urls['download'] % (nzb.group('id')),
|
|
||||||
'detail_url': '',
|
|
||||||
'description': '',
|
|
||||||
'check_nzb': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = False, single_category = False, single = True)
|
|
||||||
if is_correct_movie:
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def belongsTo(self, url, host = None):
|
|
||||||
match = re.match('http://85\.214\.105\.230/get_nzb\.php\?id=[0-9]*§ion=hd', url)
|
|
||||||
if match:
|
|
||||||
return self
|
|
||||||
return
|
|
||||||
@@ -1,5 +1,57 @@
|
|||||||
|
from couchpotato.core.helpers.variable import getImdb, md5
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import YarrProvider
|
from couchpotato.core.providers.base import YarrProvider
|
||||||
|
import cookielib
|
||||||
|
import traceback
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TorrentProvider(YarrProvider):
|
class TorrentProvider(YarrProvider):
|
||||||
|
|
||||||
type = 'torrent'
|
type = 'torrent'
|
||||||
|
login_opener = None
|
||||||
|
|
||||||
|
def imdbMatch(self, url, imdbId):
|
||||||
|
if getImdb(url) == imdbId:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if url[:4] == 'http':
|
||||||
|
try:
|
||||||
|
cache_key = md5(url)
|
||||||
|
data = self.getCache(cache_key, url)
|
||||||
|
except IOError:
|
||||||
|
log.error('Failed to open %s.', url)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return getImdb(data) == imdbId
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
cookiejar = cookielib.CookieJar()
|
||||||
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
|
||||||
|
urllib2.install_opener(opener)
|
||||||
|
f = opener.open(self.urls['login'], self.getLoginParams())
|
||||||
|
f.read()
|
||||||
|
f.close()
|
||||||
|
self.login_opener = opener
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
log.error('Failed to login %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def loginDownload(self, url = '', nzb_id = ''):
|
||||||
|
try:
|
||||||
|
if not self.login_opener and not self.login():
|
||||||
|
log.error('Failed downloading from %s', self.getName())
|
||||||
|
return self.urlopen(url, opener = self.login_opener)
|
||||||
|
except:
|
||||||
|
log.error('Failed downloading from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
def getLoginParams(self):
|
||||||
|
return ''
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from BeautifulSoup import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -34,7 +34,7 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if self.isDisabled() or not self.isAvailable(self.urls['test']):
|
if self.isDisabled():
|
||||||
return results
|
return results
|
||||||
|
|
||||||
cache_key = 'kickasstorrents.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
cache_key = 'kickasstorrents.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
||||||
@@ -47,14 +47,14 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
try:
|
try:
|
||||||
html = BeautifulSoup(data)
|
html = BeautifulSoup(data)
|
||||||
resultdiv = html.find('div', attrs = {'class':'tabs'})
|
resultdiv = html.find('div', attrs = {'class':'tabs'})
|
||||||
for result in resultdiv.findAll('div', recursive = False):
|
for result in resultdiv.find_all('div', recursive = False):
|
||||||
if result.get('id').lower() not in cat_ids:
|
if result.get('id').lower() not in cat_ids:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for temp in result.findAll('tr'):
|
for temp in result.find_all('tr'):
|
||||||
if temp['class'] is 'firstr' or not temp.get('id'):
|
if temp['class'] is 'firstr' or not temp.get('id'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -68,15 +68,17 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
}
|
}
|
||||||
|
|
||||||
nr = 0
|
nr = 0
|
||||||
for td in temp.findAll('td'):
|
for td in temp.find_all('td'):
|
||||||
column_name = table_order[nr]
|
column_name = table_order[nr]
|
||||||
if column_name:
|
if column_name:
|
||||||
|
|
||||||
if column_name is 'name':
|
if column_name is 'name':
|
||||||
link = td.find('div', {'class': 'torrentname'}).findAll('a')[1]
|
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
||||||
new['id'] = temp.get('id')[-8:]
|
new['id'] = temp.get('id')[-8:]
|
||||||
new['name'] = link.text
|
new['name'] = link.text
|
||||||
new['url'] = td.findAll('a', 'idownload')[1]['href']
|
new['url'] = td.find_all('a', 'idownload')[1]['href']
|
||||||
|
if new['url'][:2] == '//':
|
||||||
|
new['url'] = 'http:%s' % new['url']
|
||||||
new['score'] = 20 if td.find('a', 'iverif') else 0
|
new['score'] = 20 if td.find('a', 'iverif') else 0
|
||||||
elif column_name is 'size':
|
elif column_name is 'size':
|
||||||
new['size'] = self.parseSize(td.text)
|
new['size'] = self.parseSize(td.text)
|
||||||
@@ -97,7 +99,7 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
except:
|
except:
|
||||||
log.error('Failed parsing KickAssTorrents: %s' % traceback.format_exc())
|
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -1,16 +1,16 @@
|
|||||||
from .main import Moovee
|
from .main import PublicHD
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
return Moovee()
|
return PublicHD()
|
||||||
|
|
||||||
config = [{
|
config = [{
|
||||||
'name': 'moovee',
|
'name': 'publichd',
|
||||||
'groups': [
|
'groups': [
|
||||||
{
|
{
|
||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': '#alt.binaries.moovee',
|
'name': 'PublicHD',
|
||||||
'description': 'SD movies only',
|
'description': 'Public Torrent site with only HD content.',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -20,4 +20,4 @@ config = [{
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}]
|
}]
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||||
|
from couchpotato.core.helpers.variable import getTitle, tryInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
|
from urlparse import parse_qs
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PublicHD(TorrentProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'test': 'http://publichd.eu',
|
||||||
|
'download': 'http://publichd.eu/%s',
|
||||||
|
'detail': 'http://publichd.eu/index.php?page=torrent-details&id=%s',
|
||||||
|
'search': 'http://publichd.eu/index.php',
|
||||||
|
}
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([9], ['bd50']),
|
||||||
|
([5], ['1080p']),
|
||||||
|
([2], ['720p']),
|
||||||
|
([15, 16], ['brrip']),
|
||||||
|
]
|
||||||
|
|
||||||
|
cat_backup_id = 0
|
||||||
|
http_time_between_calls = 0
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
if self.isDisabled() or quality['hd'] != True:
|
||||||
|
return results
|
||||||
|
|
||||||
|
params = tryUrlencode({
|
||||||
|
'page':'torrents',
|
||||||
|
'search': getTitle(movie['library']) + ' ' + quality['identifier'],
|
||||||
|
'active': 1,
|
||||||
|
'category': self.getCatId(quality['identifier'])[0]
|
||||||
|
})
|
||||||
|
url = '%s?%s' % (self.urls['search'], params)
|
||||||
|
|
||||||
|
cache_key = 'publichd.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
||||||
|
data = self.getCache(cache_key, url)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
|
||||||
|
try:
|
||||||
|
soup = BeautifulSoup(data)
|
||||||
|
|
||||||
|
results_table = soup.find('table', attrs = {'id': 'bgtorrlist2'})
|
||||||
|
entries = results_table.find_all('tr')
|
||||||
|
|
||||||
|
for result in entries[2:len(entries) - 1]:
|
||||||
|
info_url = result.find(href = re.compile('torrent-details'))
|
||||||
|
download = result.find(href = re.compile('\.torrent'))
|
||||||
|
|
||||||
|
if info_url and download:
|
||||||
|
|
||||||
|
url = parse_qs(info_url['href'])
|
||||||
|
|
||||||
|
new = {
|
||||||
|
'id': url['id'][0],
|
||||||
|
'name': info_url.string,
|
||||||
|
'type': 'torrent',
|
||||||
|
'check_nzb': False,
|
||||||
|
'description': '',
|
||||||
|
'provider': self.getName(),
|
||||||
|
'download': self.download,
|
||||||
|
'url': self.urls['download'] % download['href'],
|
||||||
|
'detail_url': self.urls['detail'] % url['id'][0],
|
||||||
|
'size': self.parseSize(result.find_all('td')[7].string),
|
||||||
|
'seeders': tryInt(result.find_all('td')[4].string),
|
||||||
|
'leechers': tryInt(result.find_all('td')[5].string),
|
||||||
|
'get_more_info': self.getMoreInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
|
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
||||||
|
imdb_results = False, single_category = False, single = True)
|
||||||
|
|
||||||
|
if is_correct_movie:
|
||||||
|
results.append(new)
|
||||||
|
self.found(new)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def getMoreInfo(self, item):
|
||||||
|
full_description = self.getCache('publichd.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||||
|
html = BeautifulSoup(full_description)
|
||||||
|
nfo_pre = html.find('div', attrs = {'id':'torrmain'})
|
||||||
|
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
||||||
|
|
||||||
|
item['description'] = description
|
||||||
|
return item
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user