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/
|
||||
+5
-2
@@ -35,7 +35,11 @@ class Loader(object):
|
||||
settings.setFile(self.options.config_file)
|
||||
|
||||
# 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 == '':
|
||||
self.data_dir = getDataDir()
|
||||
|
||||
@@ -89,7 +93,6 @@ class Loader(object):
|
||||
if self.runAsDaemon():
|
||||
try: self.daemon.stop()
|
||||
except: pass
|
||||
self.daemon.delpid()
|
||||
except:
|
||||
self.log.critical(traceback.format_exc())
|
||||
|
||||
|
||||
@@ -69,10 +69,14 @@ def getApiKey():
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
index_url = url_for('web.index')
|
||||
url = getattr(request, 'path')[len(index_url):]
|
||||
url = request.path[len(index_url):]
|
||||
|
||||
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:
|
||||
time.sleep(0.1)
|
||||
return 'Wrong API key used', 404
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
from flask.blueprints import Blueprint
|
||||
from flask.helpers import url_for
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import RequestHandler, asynchronous
|
||||
from werkzeug.utils import redirect
|
||||
|
||||
api = Blueprint('api', __name__)
|
||||
api_docs = {}
|
||||
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):
|
||||
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:
|
||||
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 """
|
||||
def 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.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from flask import request
|
||||
from tornado.ioloop import IOLoop
|
||||
from uuid import uuid4
|
||||
import os
|
||||
import platform
|
||||
@@ -18,7 +18,10 @@ log = CPLog(__name__)
|
||||
|
||||
class Core(Plugin):
|
||||
|
||||
ignore_restart = ['Core.crappyRestart', 'Core.crappyShutdown']
|
||||
ignore_restart = [
|
||||
'Core.restart', 'Core.shutdown',
|
||||
'Updater.check', 'Updater.autoUpdate',
|
||||
]
|
||||
shutdown_started = False
|
||||
|
||||
def __init__(self):
|
||||
@@ -37,12 +40,13 @@ class Core(Plugin):
|
||||
'desc': 'Get version.'
|
||||
})
|
||||
|
||||
addEvent('app.crappy_shutdown', self.crappyShutdown)
|
||||
addEvent('app.crappy_restart', self.crappyRestart)
|
||||
addEvent('app.shutdown', self.shutdown)
|
||||
addEvent('app.restart', self.restart)
|
||||
addEvent('app.load', self.launchBrowser, priority = 1)
|
||||
addEvent('app.base_url', self.createBaseUrl)
|
||||
addEvent('app.api_url', self.createApiUrl)
|
||||
addEvent('app.version', self.version)
|
||||
addEvent('app.load', self.checkDataDir)
|
||||
|
||||
addEvent('setting.save.core.password', self.md5Password)
|
||||
addEvent('setting.save.core.api_key', self.checkApikey)
|
||||
@@ -54,39 +58,35 @@ class Core(Plugin):
|
||||
def checkApikey(self, value):
|
||||
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):
|
||||
return jsonified({
|
||||
'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):
|
||||
self.initShutdown()
|
||||
if self.shutdown_started:
|
||||
return False
|
||||
|
||||
def shutdown():
|
||||
self.initShutdown()
|
||||
IOLoop.instance().add_callback(shutdown)
|
||||
|
||||
return 'shutdown'
|
||||
|
||||
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'
|
||||
|
||||
def initShutdown(self, restart = False):
|
||||
@@ -102,17 +102,20 @@ class Core(Plugin):
|
||||
log.debug('Every plugin got shutdown event')
|
||||
|
||||
loop = True
|
||||
starttime = time.time()
|
||||
while loop:
|
||||
log.debug('Asking who is running')
|
||||
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:
|
||||
break
|
||||
elif starttime < time.time() - 30: # Always force break after 30s wait
|
||||
break
|
||||
|
||||
running = list(set(still_running) - set(self.ignore_restart))
|
||||
if len(running) > 0:
|
||||
log.info('Waiting on plugins to finish: %s' % running)
|
||||
log.info('Waiting on plugins to finish: %s', running)
|
||||
else:
|
||||
loop = False
|
||||
|
||||
@@ -121,11 +124,11 @@ class Core(Plugin):
|
||||
log.debug('Save to shutdown/restart')
|
||||
|
||||
try:
|
||||
request.environ.get('werkzeug.server.shutdown')()
|
||||
IOLoop.instance().stop()
|
||||
except RuntimeError:
|
||||
pass
|
||||
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)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ if Env.get('desktop'):
|
||||
addEvent('app.after_shutdown', desktop.afterShutdown)
|
||||
|
||||
def onClose(self, event):
|
||||
return fireEvent('app.crappy_shutdown', single = True)
|
||||
return fireEvent('app.shutdown', single = True)
|
||||
|
||||
else:
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class Scheduler(Plugin):
|
||||
for type in ['interval', 'cron']:
|
||||
try:
|
||||
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:
|
||||
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'])
|
||||
cron['job'] = job
|
||||
except ValueError, e:
|
||||
log.error("Failed adding cronjob: %s" % e)
|
||||
log.error('Failed adding cronjob: %s', e)
|
||||
|
||||
# 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'])
|
||||
interval['job'] = job
|
||||
except ValueError, e:
|
||||
log.error("Failed adding interval cronjob: %s" % e)
|
||||
log.error('Failed adding interval cronjob: %s', e)
|
||||
|
||||
# Start it
|
||||
log.debug('Starting scheduler')
|
||||
@@ -75,7 +75,7 @@ class Scheduler(Plugin):
|
||||
self.started = False
|
||||
|
||||
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.crons[identifier] = {
|
||||
@@ -86,7 +86,7 @@ class Scheduler(Plugin):
|
||||
}
|
||||
|
||||
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.intervals[identifier] = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from couchpotato.api import addApiView
|
||||
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.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
@@ -19,6 +20,8 @@ log = CPLog(__name__)
|
||||
|
||||
class Updater(Plugin):
|
||||
|
||||
available_notified = False
|
||||
|
||||
def __init__(self):
|
||||
|
||||
if Env.get('desktop'):
|
||||
@@ -28,7 +31,7 @@ class Updater(Plugin):
|
||||
else:
|
||||
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('updater.info', self.info)
|
||||
|
||||
@@ -48,17 +51,36 @@ class Updater(Plugin):
|
||||
'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):
|
||||
if self.isDisabled():
|
||||
return
|
||||
|
||||
if self.updater.check():
|
||||
if self.conf('automatic') and not self.updater.update_failed:
|
||||
if self.updater.doUpdate():
|
||||
fireEventAsync('app.crappy_restart')
|
||||
else:
|
||||
if self.conf('notification'):
|
||||
fireEvent('updater.available', message = 'A new update is available', data = self.updater.info())
|
||||
if not self.available_notified and self.conf('notification') and not self.conf('automatic'):
|
||||
fireEvent('updater.available', message = 'A new update is available', data = self.updater.info())
|
||||
self.available_notified = True
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def info(self):
|
||||
return self.updater.info()
|
||||
@@ -67,12 +89,22 @@ class Updater(Plugin):
|
||||
return jsonified(self.updater.info())
|
||||
|
||||
def checkView(self):
|
||||
self.check()
|
||||
return self.updater.getInfo()
|
||||
return jsonified({
|
||||
'update_available': self.check(),
|
||||
'info': self.updater.info()
|
||||
})
|
||||
|
||||
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({
|
||||
'success': self.updater.doUpdate()
|
||||
'success': success
|
||||
})
|
||||
|
||||
|
||||
@@ -107,7 +139,7 @@ class BaseUpdater(Plugin):
|
||||
|
||||
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)
|
||||
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:
|
||||
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:
|
||||
os.remove(full_path)
|
||||
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:
|
||||
full_path = os.path.join(root, dir_name)
|
||||
@@ -127,7 +159,7 @@ class BaseUpdater(Plugin):
|
||||
try:
|
||||
os.rmdir(full_path)
|
||||
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)
|
||||
|
||||
def doUpdate(self):
|
||||
|
||||
try:
|
||||
log.debug('Stashing local changes')
|
||||
self.repo.saveStash()
|
||||
|
||||
log.info('Updating to latest version')
|
||||
info = self.info()
|
||||
self.repo.pull()
|
||||
|
||||
# Delete leftover .pyc files
|
||||
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
|
||||
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
|
||||
|
||||
@@ -165,14 +193,14 @@ class GitUpdater(BaseUpdater):
|
||||
if not self.version:
|
||||
try:
|
||||
output = self.repo.getHead() # Yes, please
|
||||
log.debug('Git version output: %s' % output.hash)
|
||||
log.debug('Git version output: %s', output.hash)
|
||||
self.version = {
|
||||
'hash': output.hash[:8],
|
||||
'date': output.getDate(),
|
||||
'type': 'git',
|
||||
}
|
||||
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 self.version
|
||||
@@ -182,7 +210,7 @@ class GitUpdater(BaseUpdater):
|
||||
if self.update_version:
|
||||
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'):
|
||||
self.repo.fetch()
|
||||
|
||||
@@ -194,7 +222,7 @@ class GitUpdater(BaseUpdater):
|
||||
local = self.repo.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():
|
||||
self.update_version = {
|
||||
@@ -237,24 +265,24 @@ class SourceUpdater(BaseUpdater):
|
||||
tar.close()
|
||||
os.remove(destination)
|
||||
|
||||
self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0]))
|
||||
self.removeDir(extracted_path)
|
||||
if self.replaceWith(os.path.join(extracted_path, os.listdir(extracted_path)[0])):
|
||||
self.removeDir(extracted_path)
|
||||
|
||||
# Write update version to file
|
||||
self.createFile(self.version_file, json.dumps(self.update_version))
|
||||
# Write update version to file
|
||||
self.createFile(self.version_file, json.dumps(self.update_version))
|
||||
|
||||
return True
|
||||
return True
|
||||
except:
|
||||
log.error('Failed updating: %s' % traceback.format_exc())
|
||||
log.error('Failed updating: %s', traceback.format_exc())
|
||||
|
||||
self.update_failed = True
|
||||
return False
|
||||
|
||||
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
|
||||
self.deletePyc(only_excess = False)
|
||||
self.deletePyc()
|
||||
existing_files = []
|
||||
for root, subfiles, filenames in os.walk(app_dir):
|
||||
for filename in filenames:
|
||||
@@ -267,18 +295,30 @@ class SourceUpdater(BaseUpdater):
|
||||
|
||||
if not Env.get('dev'):
|
||||
try:
|
||||
os.remove(tofile)
|
||||
except:
|
||||
pass
|
||||
if os.path.isfile(tofile):
|
||||
os.remove(tofile)
|
||||
|
||||
try:
|
||||
os.renames(fromfile, tofile)
|
||||
dirname = os.path.dirname(tofile)
|
||||
if not os.path.isdir(dirname):
|
||||
self.makeDir(dirname)
|
||||
|
||||
os.rename(fromfile, tofile)
|
||||
try:
|
||||
existing_files.remove(tofile)
|
||||
except ValueError:
|
||||
pass
|
||||
except Exception, e:
|
||||
log.error('Failed overwriting file: %s' % e)
|
||||
except:
|
||||
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):
|
||||
@@ -297,11 +337,11 @@ class SourceUpdater(BaseUpdater):
|
||||
output = json.loads(f.read())
|
||||
f.close()
|
||||
|
||||
log.debug('Source version output: %s' % output)
|
||||
log.debug('Source version output: %s', output)
|
||||
self.version = output
|
||||
self.version['type'] = 'source'
|
||||
except Exception, e:
|
||||
log.error('Failed using source updater. %s' % e)
|
||||
log.error('Failed using source updater. %s', e)
|
||||
return {}
|
||||
|
||||
return self.version
|
||||
@@ -318,7 +358,7 @@ class SourceUpdater(BaseUpdater):
|
||||
|
||||
self.last_check = time.time()
|
||||
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
|
||||
|
||||
@@ -333,12 +373,12 @@ class SourceUpdater(BaseUpdater):
|
||||
'date': int(time.mktime(parse(commit['commit']['committer']['date']).timetuple())),
|
||||
}
|
||||
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 {}
|
||||
|
||||
|
||||
class DesktopUpdater(Plugin):
|
||||
class DesktopUpdater(BaseUpdater):
|
||||
|
||||
version = None
|
||||
update_failed = False
|
||||
@@ -350,9 +390,15 @@ class DesktopUpdater(Plugin):
|
||||
|
||||
def doUpdate(self):
|
||||
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:
|
||||
log.error('Failed updating desktop: %s' % traceback.format_exc())
|
||||
self.update_failed = True
|
||||
|
||||
return False
|
||||
@@ -379,7 +425,7 @@ class DesktopUpdater(Plugin):
|
||||
|
||||
self.last_check = time.time()
|
||||
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
|
||||
|
||||
|
||||
@@ -16,7 +16,17 @@ var UpdaterBase = new Class({
|
||||
var self = this;
|
||||
|
||||
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){
|
||||
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)
|
||||
changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash
|
||||
|
||||
@@ -81,13 +91,19 @@ var UpdaterBase = new Class({
|
||||
Api.request('updater.update', {
|
||||
'onComplete': function(json){
|
||||
if(json.success){
|
||||
App.restart('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
|
||||
App.checkAvailable.delay(500, App);
|
||||
if(self.message)
|
||||
self.message.destroy();
|
||||
self.updating();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
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):
|
||||
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, data.get('type'))
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ config = [{
|
||||
'label': 'Use for',
|
||||
'default': 'both',
|
||||
'type': 'dropdown',
|
||||
'values': [('nzbs & torrents', 'both'), ('nzb', 'nzb'), ('torrent', 'torrent')],
|
||||
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrent', 'torrent')],
|
||||
},
|
||||
{
|
||||
'name': 'manual',
|
||||
|
||||
@@ -10,38 +10,36 @@ class Blackhole(Downloader):
|
||||
|
||||
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')])):
|
||||
return
|
||||
|
||||
directory = self.conf('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:
|
||||
try:
|
||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
|
||||
if len(filedata) < 50:
|
||||
log.error('No nzb available!')
|
||||
if not filedata or len(filedata) < 50:
|
||||
log.error('No nzb/torrent 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))
|
||||
log.info('Downloading %s to %s.', (data.get('type'), fullPath))
|
||||
with open(fullPath, 'wb') as f:
|
||||
f.write(filedata)
|
||||
return True
|
||||
else:
|
||||
log.info('File %s already exists.' % fullPath)
|
||||
log.info('File %s already exists.', fullPath)
|
||||
return True
|
||||
|
||||
except:
|
||||
log.error('Failed to download to blackhole %s' % traceback.format_exc())
|
||||
log.error('Failed to download to blackhole %s', traceback.format_exc())
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
@@ -14,12 +14,16 @@ class NZBGet(Downloader):
|
||||
|
||||
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')):
|
||||
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')}
|
||||
nzb_name = '%s.nzb' % self.createNzbName(data, movie)
|
||||
@@ -37,25 +41,12 @@ class NZBGet(Downloader):
|
||||
if e.errcode == 401:
|
||||
log.error('Password is incorrect.')
|
||||
else:
|
||||
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())
|
||||
log.error('Protocol Error: %s', e)
|
||||
return False
|
||||
|
||||
if rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip())):
|
||||
log.info('NZB sent successfully to NZBGet')
|
||||
return True
|
||||
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
|
||||
|
||||
@@ -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',
|
||||
'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',
|
||||
'default': 0,
|
||||
|
||||
@@ -2,11 +2,6 @@ from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.variable import cleanHost
|
||||
from couchpotato.core.logger import CPLog
|
||||
from inspect import ismethod, isfunction
|
||||
from tempfile import mkstemp
|
||||
import base64
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -15,25 +10,12 @@ class Sabnzbd(Downloader):
|
||||
|
||||
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')):
|
||||
return
|
||||
|
||||
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
|
||||
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
||||
|
||||
params = {
|
||||
'apikey': self.conf('api_key'),
|
||||
@@ -42,29 +24,24 @@ class Sabnzbd(Downloader):
|
||||
'nzbname': self.createNzbName(data, movie),
|
||||
}
|
||||
|
||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
||||
nzb_file = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
|
||||
if not nzb_file or len(nzb_file) < 50:
|
||||
log.error('No nzb available!')
|
||||
if filedata:
|
||||
if len(filedata) < 50:
|
||||
log.error('No proper nzb available!')
|
||||
return False
|
||||
|
||||
# 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'
|
||||
else:
|
||||
params['name'] = data.get('url')
|
||||
|
||||
if pp:
|
||||
params['script'] = pp_script_fn
|
||||
|
||||
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
||||
|
||||
try:
|
||||
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:
|
||||
data = self.urlopen(url, show_error = False)
|
||||
data = self.urlopen(url, timeout = 60, show_error = False)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
return False
|
||||
@@ -84,48 +61,3 @@ class Sabnzbd(Downloader):
|
||||
else:
|
||||
log.error("Unknown error: " + result[:40])
|
||||
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',
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
'description': 'Add the torrent paused.',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,48 +2,139 @@ from base64 import b64encode
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.encoding import isInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from libs import transmissionrpc
|
||||
import httplib
|
||||
import json
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
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')):
|
||||
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.
|
||||
host = self.conf('host').split(':')
|
||||
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
|
||||
|
||||
# Set parameters for Transmission
|
||||
params = {
|
||||
'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:
|
||||
tc = transmissionrpc.Client(host[0], port = host[1], user = self.conf('username'), password = self.conf('password'))
|
||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
torrent = tc.add_torrent(b64encode(filedata), **params)
|
||||
trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
|
||||
if data.get('type') == 'torrent_magnet':
|
||||
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
|
||||
try:
|
||||
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)
|
||||
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
|
||||
|
||||
return True
|
||||
|
||||
except transmissionrpc.TransmissionError, e:
|
||||
log.error('Failed to send link to transmission: %s' % e)
|
||||
except Exception, err:
|
||||
log.error('Failed to change settings for transfer: %s', err)
|
||||
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)
|
||||
except:
|
||||
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):
|
||||
|
||||
@@ -43,7 +43,7 @@ def removeEvent(name, handler):
|
||||
|
||||
def fireEvent(name, *args, **kwargs):
|
||||
if not events.get(name): return
|
||||
#log.debug('Firing event %s' % name)
|
||||
#log.debug('Firing event %s', name)
|
||||
try:
|
||||
|
||||
# Fire after event
|
||||
@@ -53,6 +53,13 @@ def fireEvent(name, *args, **kwargs):
|
||||
is_after_event = True
|
||||
except: pass
|
||||
|
||||
# onComplete event
|
||||
on_complete = False
|
||||
try:
|
||||
on_complete = kwargs['on_complete']
|
||||
del kwargs['on_complete']
|
||||
except: pass
|
||||
|
||||
# Return single handler
|
||||
single = False
|
||||
try:
|
||||
@@ -93,7 +100,7 @@ def fireEvent(name, *args, **kwargs):
|
||||
elif r[1]:
|
||||
errorHandler(r[1])
|
||||
else:
|
||||
log.debug('Assume disabled eventhandler for: %s' % name)
|
||||
log.debug('Assume disabled eventhandler for: %s', name)
|
||||
|
||||
else:
|
||||
results = []
|
||||
@@ -123,30 +130,29 @@ def fireEvent(name, *args, **kwargs):
|
||||
|
||||
modified_results = fireEvent('result.modify.%s' % name, results, single = True)
|
||||
if modified_results:
|
||||
log.debug('Return modified results for %s' % name)
|
||||
log.debug('Return modified results for %s', name)
|
||||
results = modified_results
|
||||
|
||||
if not is_after_event:
|
||||
fireEvent('%s.after' % name, is_after_event = True)
|
||||
|
||||
if on_complete:
|
||||
on_complete()
|
||||
|
||||
return results
|
||||
except KeyError, e:
|
||||
pass
|
||||
except Exception:
|
||||
log.error('%s: %s' % (name, traceback.format_exc()))
|
||||
log.error('%s: %s', (name, traceback.format_exc()))
|
||||
|
||||
def fireEventAsync(name, *args, **kwargs):
|
||||
#log.debug('Async "%s": %s, %s' % (name, args, kwargs))
|
||||
def fireEventAsync(*args, **kwargs):
|
||||
try:
|
||||
e = events[name]
|
||||
e.lock.acquire()
|
||||
e.asynchronous = True
|
||||
e.error_handler = errorHandler
|
||||
e(*args, **kwargs)
|
||||
e.lock.release()
|
||||
my_thread = threading.Thread(target = fireEvent, args = args, kwargs = kwargs)
|
||||
my_thread.setDaemon(True)
|
||||
my_thread.start()
|
||||
return True
|
||||
except Exception, e:
|
||||
log.error('%s: %s' % (name, e))
|
||||
log.error('%s: %s', (args[0], e))
|
||||
|
||||
def errorHandler(error):
|
||||
etype, value, tb = error
|
||||
|
||||
@@ -31,10 +31,14 @@ def toUnicode(original, *args):
|
||||
except:
|
||||
raise
|
||||
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')
|
||||
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):
|
||||
if isinstance(original, (str, unicode)):
|
||||
try:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import natcmp
|
||||
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 urllib import unquote
|
||||
import flask
|
||||
@@ -26,7 +26,7 @@ def getParams():
|
||||
|
||||
for item in nested:
|
||||
if item is nested[-1]:
|
||||
current[item] = toUnicode(unquote(value)).encode('utf-8')
|
||||
current[item] = toUnicode(unquote(value))
|
||||
else:
|
||||
try:
|
||||
current[item]
|
||||
@@ -35,7 +35,7 @@ def getParams():
|
||||
|
||||
current = current[item]
|
||||
else:
|
||||
temp[param] = toUnicode(unquote(value)).encode('utf-8')
|
||||
temp[param] = toUnicode(unquote(value))
|
||||
|
||||
return dictToList(temp)
|
||||
|
||||
@@ -57,7 +57,7 @@ def dictToList(params):
|
||||
|
||||
def getParam(attr, default = None):
|
||||
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:
|
||||
return default
|
||||
|
||||
@@ -70,9 +70,13 @@ def jsonify(mimetype, *args, **kwargs):
|
||||
return getattr(current_app, 'response_class')(content, mimetype = mimetype)
|
||||
|
||||
def jsonified(*args, **kwargs):
|
||||
from couchpotato.environment import Env
|
||||
callback = getParam('callback_func', None)
|
||||
if callback:
|
||||
return padded_jsonify(callback, *args, **kwargs)
|
||||
content = padded_jsonify(callback, *args, **kwargs)
|
||||
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:
|
||||
return XMLTree.parse(data).findall(path)
|
||||
except Exception, e:
|
||||
log.error('Error parsing RSS. %s' % e)
|
||||
log.error('Error parsing RSS. %s', e)
|
||||
return []
|
||||
|
||||
@@ -2,7 +2,10 @@ from couchpotato.core.logger import CPLog
|
||||
import hashlib
|
||||
import os.path
|
||||
import platform
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -20,6 +23,10 @@ def getDataDir():
|
||||
if 'darwin' in platform.platform().lower():
|
||||
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
|
||||
return os.path.join(user_dir, '.couchpotato')
|
||||
|
||||
@@ -77,9 +84,9 @@ def cleanHost(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')
|
||||
txt = output.read()
|
||||
output.close()
|
||||
@@ -94,7 +101,7 @@ def getImdb(txt):
|
||||
|
||||
def tryInt(s):
|
||||
try: return int(s)
|
||||
except: return s
|
||||
except: return 0
|
||||
|
||||
def tryFloat(s):
|
||||
try: return float(s) if '.' in s else tryInt(s)
|
||||
@@ -111,9 +118,12 @@ def getTitle(library_dict):
|
||||
try:
|
||||
return library_dict['titles'][0]['title']
|
||||
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
|
||||
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
|
||||
|
||||
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:
|
||||
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
|
||||
did_save += self.loadSettings(m, module_name, save = False)
|
||||
@@ -57,10 +57,10 @@ class Loader(object):
|
||||
log.error(e.message)
|
||||
pass
|
||||
# todo:: this needs to be more descriptive.
|
||||
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.error('Import error, remove the empty folder: %s', plugin.get('module'))
|
||||
log.debug('Can\'t import %s: %s', (module_name, traceback.format_exc()))
|
||||
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:
|
||||
fireEvent('settings.save')
|
||||
@@ -84,7 +84,7 @@ class Loader(object):
|
||||
fireEvent('settings.register', section_name = section['name'], options = options, save = save)
|
||||
return True
|
||||
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
|
||||
|
||||
def loadPlugins(self, module, name):
|
||||
@@ -97,7 +97,7 @@ class Loader(object):
|
||||
|
||||
return True
|
||||
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
|
||||
|
||||
def addModule(self, priority, plugin_type, module, name):
|
||||
|
||||
+29
-17
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import re
|
||||
import traceback
|
||||
|
||||
class CPLog(object):
|
||||
|
||||
@@ -13,31 +14,42 @@ class CPLog(object):
|
||||
self.context = context
|
||||
self.logger = logging.getLogger()
|
||||
|
||||
def info(self, msg):
|
||||
self.logger.info(self.addContext(msg))
|
||||
def info(self, msg, replace_tuple = ()):
|
||||
self.logger.info(self.addContext(msg, replace_tuple))
|
||||
|
||||
def debug(self, msg):
|
||||
self.logger.debug(self.addContext(msg))
|
||||
def debug(self, msg, replace_tuple = ()):
|
||||
self.logger.debug(self.addContext(msg, replace_tuple))
|
||||
|
||||
def error(self, msg):
|
||||
self.logger.error(self.addContext(msg))
|
||||
def error(self, msg, replace_tuple = ()):
|
||||
self.logger.error(self.addContext(msg, replace_tuple))
|
||||
|
||||
def warning(self, msg):
|
||||
self.logger.warning(self.addContext(msg))
|
||||
def warning(self, msg, replace_tuple = ()):
|
||||
self.logger.warning(self.addContext(msg, replace_tuple))
|
||||
|
||||
def critical(self, msg):
|
||||
self.logger.critical(self.addContext(msg), exc_info = 1)
|
||||
def critical(self, msg, replace_tuple = ()):
|
||||
self.logger.critical(self.addContext(msg, replace_tuple), exc_info = 1)
|
||||
|
||||
def addContext(self, msg):
|
||||
return '[%+25.25s] %s' % (self.context[-25:], self.removePrivateData(msg))
|
||||
def addContext(self, msg, replace_tuple = ()):
|
||||
return '[%+25.25s] %s' % (self.context[-25:], self.safeMessage(msg, replace_tuple))
|
||||
|
||||
def removePrivateData(self, msg):
|
||||
try:
|
||||
msg = unicode(msg)
|
||||
except:
|
||||
pass
|
||||
def safeMessage(self, msg, replace_tuple = ()):
|
||||
|
||||
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'):
|
||||
|
||||
for replace in self.replace_private:
|
||||
|
||||
@@ -13,7 +13,10 @@ class Notification(Plugin):
|
||||
default_title = Env.get('appname')
|
||||
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 = []
|
||||
|
||||
def __init__(self):
|
||||
@@ -41,7 +44,7 @@ class Notification(Plugin):
|
||||
|
||||
test_type = self.testNotifyName()
|
||||
|
||||
log.info('Sending test to %s' % test_type)
|
||||
log.info('Sending test to %s', test_type)
|
||||
|
||||
success = self.notify(
|
||||
message = self.test_message,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.api import addApiView, addNonBlockApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.request import jsonified, getParam
|
||||
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.settings.model import Notification as Notif
|
||||
from sqlalchemy.sql.expression import or_
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class CoreNotifier(Notification):
|
||||
|
||||
m_lock = threading.Lock()
|
||||
messages = []
|
||||
listeners = []
|
||||
|
||||
listen_to = [
|
||||
'movie.downloaded', 'movie.snatched',
|
||||
'updater.available', 'updater.updated',
|
||||
@@ -30,7 +35,7 @@ class CoreNotifier(Notification):
|
||||
addApiView('notification.markread', self.markAsRead, docs = {
|
||||
'desc': 'Mark notifications as read',
|
||||
'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)
|
||||
|
||||
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):
|
||||
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()
|
||||
|
||||
q = db.query(Notif) \
|
||||
.filter(or_(*[Notif.id == tryInt(s) for s in ids]))
|
||||
if 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})
|
||||
|
||||
db.commit()
|
||||
@@ -83,6 +97,8 @@ class CoreNotifier(Notification):
|
||||
limit = splt[0]
|
||||
offset = 0 if len(splt) is 1 else splt[1]
|
||||
q = q.limit(limit).offset(offset)
|
||||
else:
|
||||
q = q.limit(200)
|
||||
|
||||
results = q.all()
|
||||
notifications = []
|
||||
@@ -114,25 +130,86 @@ class CoreNotifier(Notification):
|
||||
ndict = n.to_dict()
|
||||
ndict['type'] = 'notification'
|
||||
ndict['time'] = time.time()
|
||||
self.messages.append(ndict)
|
||||
|
||||
self.frontend(type = listener, data = data)
|
||||
|
||||
#db.close()
|
||||
return True
|
||||
|
||||
def frontend(self, type = 'notification', data = {}):
|
||||
self.messages.append({
|
||||
def frontend(self, type = 'notification', data = {}, message = None):
|
||||
|
||||
self.m_lock.acquire()
|
||||
notification = {
|
||||
'message_id': str(uuid.uuid4()),
|
||||
'time': time.time(),
|
||||
'type': type,
|
||||
'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):
|
||||
|
||||
messages = []
|
||||
for message in self.messages:
|
||||
#delete message older then 15s
|
||||
if message['time'] > (time.time() - 15):
|
||||
messages.append(message)
|
||||
|
||||
# Get unread
|
||||
if getParam('init'):
|
||||
@@ -146,9 +223,6 @@ class CoreNotifier(Notification):
|
||||
ndict['type'] = 'notification'
|
||||
messages.append(ndict)
|
||||
|
||||
#db.close()
|
||||
|
||||
self.messages = []
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'result': messages,
|
||||
|
||||
@@ -8,9 +8,10 @@ var NotificationBase = new Class({
|
||||
self.setOptions(options);
|
||||
|
||||
// Listener
|
||||
App.addEvent('load', self.startInterval.bind(self));
|
||||
App.addEvent('unload', self.stopTimer.bind(self));
|
||||
App.addEvent('unload', self.stopPoll.bind(self));
|
||||
App.addEvent('reload', self.startInterval.bind(self, [true]));
|
||||
App.addEvent('notification', self.notify.bind(self));
|
||||
App.addEvent('message', self.showMessage.bind(self));
|
||||
|
||||
// Add test buttons to settings page
|
||||
App.addEvent('load', self.addTestButtons.bind(self));
|
||||
@@ -30,7 +31,11 @@ var NotificationBase = new Class({
|
||||
'href': App.createUrl('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)
|
||||
Api.request('notification.markread', {
|
||||
'data': {
|
||||
'ids': ids.join(',')
|
||||
},
|
||||
'onSuccess': function(){
|
||||
self.setBadge('')
|
||||
}
|
||||
@@ -83,39 +85,88 @@ var NotificationBase = new Class({
|
||||
|
||||
},
|
||||
|
||||
startInterval: function(){
|
||||
startInterval: function(force){
|
||||
var self = this;
|
||||
|
||||
self.request = Api.request('notification.listener', {
|
||||
'initialDelay': 100,
|
||||
'delay': 3000,
|
||||
if(self.stopped && !force){
|
||||
self.stopped = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Api.request('notification.listener', {
|
||||
'data': {'init':true},
|
||||
'onSuccess': self.processData.bind(self)
|
||||
})
|
||||
|
||||
self.request.startTimer()
|
||||
}).send()
|
||||
|
||||
},
|
||||
|
||||
startTimer: function(){
|
||||
if(this.request)
|
||||
this.request.startTimer()
|
||||
startPoll: function(){
|
||||
var self = this;
|
||||
|
||||
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)
|
||||
this.request.stopTimer()
|
||||
this.request.cancel()
|
||||
this.stopped = true;
|
||||
},
|
||||
|
||||
processData: function(json){
|
||||
var self = this;
|
||||
|
||||
self.request.options.data = {}
|
||||
Array.each(json.result, function(result){
|
||||
App.fireEvent(result.type, result)
|
||||
})
|
||||
// Process data
|
||||
if(json){
|
||||
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(){
|
||||
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.notifications.base import Notification
|
||||
from couchpotato.environment import Env
|
||||
from gntp import notifier
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -17,7 +16,7 @@ class Growl(Notification):
|
||||
super(Growl, self).__init__()
|
||||
|
||||
if self.isEnabled():
|
||||
self.register()
|
||||
addEvent('app.load', self.register)
|
||||
|
||||
def register(self):
|
||||
if self.registered: return
|
||||
@@ -39,7 +38,7 @@ class Growl(Notification):
|
||||
self.growl.register()
|
||||
self.registered = True
|
||||
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):
|
||||
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:
|
||||
terminal = telnetlib.Telnet(host)
|
||||
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()
|
||||
|
||||
log.debug('Connected to %s via telnet' % (host))
|
||||
log.debug('Connected to %s via telnet', (host))
|
||||
terminal.read_until('sh-3.00# ')
|
||||
terminal.write('cat /tmp/source\n')
|
||||
terminal.write('cat /tmp/netshare\n')
|
||||
@@ -48,9 +48,9 @@ class NMJ(Notification):
|
||||
if match:
|
||||
database = match.group(1)
|
||||
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:
|
||||
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()
|
||||
|
||||
if device.startswith('NETWORK_SHARE/'):
|
||||
@@ -58,7 +58,7 @@ class NMJ(Notification):
|
||||
|
||||
if match:
|
||||
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:
|
||||
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
|
||||
return self.failed()
|
||||
@@ -77,7 +77,7 @@ class NMJ(Notification):
|
||||
database = self.conf('database')
|
||||
|
||||
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:
|
||||
data = self.urlopen(mount)
|
||||
except:
|
||||
@@ -102,11 +102,11 @@ class NMJ(Notification):
|
||||
et = etree.fromstring(response)
|
||||
result = et.findtext('returnValue')
|
||||
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
|
||||
|
||||
if int(result) > 0:
|
||||
log.error('Popcorn Hour returned an errorcode: %s' % (result))
|
||||
log.error('Popcorn Hour returned an errorcode: %s', (result))
|
||||
return False
|
||||
else:
|
||||
log.info('NMJ started background scan')
|
||||
|
||||
@@ -32,7 +32,7 @@ class Notifo(Notification):
|
||||
raise Exception
|
||||
|
||||
except:
|
||||
log.error('Notification failed: %s' % traceback.format_exc())
|
||||
log.error('Notification failed: %s', traceback.format_exc())
|
||||
return False
|
||||
|
||||
log.info('Notifo notification successful.')
|
||||
|
||||
@@ -23,6 +23,6 @@ class NotifyMyAndroid(Notification):
|
||||
|
||||
for key in keys:
|
||||
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
|
||||
|
||||
@@ -17,7 +17,7 @@ class NotifyMyWP(Notification):
|
||||
|
||||
for key in keys:
|
||||
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 response
|
||||
|
||||
@@ -17,8 +17,9 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost:32400',
|
||||
'description': 'Default should be on localhost:32400',
|
||||
'default': 'localhost',
|
||||
'description': 'Default should be on localhost',
|
||||
'advanced': True,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.variable import cleanHost
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
@@ -11,13 +12,14 @@ log = CPLog(__name__)
|
||||
class Plex(Notification):
|
||||
|
||||
def __init__(self):
|
||||
super(Plex, self).__init__()
|
||||
addEvent('renamer.after', self.addToLibrary)
|
||||
|
||||
def addToLibrary(self, group = {}):
|
||||
if self.isDisabled(): return
|
||||
|
||||
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:
|
||||
|
||||
@@ -36,7 +38,33 @@ class Plex(Notification):
|
||||
x = self.urlopen(url)
|
||||
|
||||
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 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.')
|
||||
return True
|
||||
elif request_status == 401:
|
||||
log.error('Prowl auth failed: %s' % response.reason)
|
||||
log.error('Prowl auth failed: %s', response.reason)
|
||||
return False
|
||||
else:
|
||||
log.error('Prowl notification failed.')
|
||||
|
||||
@@ -35,7 +35,7 @@ class Pushover(Notification):
|
||||
log.info('Pushover notifications sent.')
|
||||
return True
|
||||
elif request_status == 401:
|
||||
log.error('Pushover auth failed: %s' % response.reason)
|
||||
log.error('Pushover auth failed: %s', response.reason)
|
||||
return False
|
||||
else:
|
||||
log.error('Pushover notification failed.')
|
||||
|
||||
@@ -15,14 +15,14 @@ class Synoindex(Notification):
|
||||
if self.isDisabled(): return
|
||||
|
||||
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:
|
||||
p = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
|
||||
out = p.communicate()
|
||||
log.info('Result from synoindex: %s' % str(out))
|
||||
log.info('Result from synoindex: %s', str(out))
|
||||
return True
|
||||
except OSError, e:
|
||||
log.error('Unable to run synoindex: %s' % e)
|
||||
log.error('Unable to run synoindex: %s', e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -55,7 +55,7 @@ class Twitter(Notification):
|
||||
else:
|
||||
api.PostUpdate('[%s] %s' % (self.default_title, message))
|
||||
except Exception, e:
|
||||
log.error('Error sending tweet: %s' % e)
|
||||
log.error('Error sending tweet: %s', e)
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -71,7 +71,7 @@ class Twitter(Notification):
|
||||
resp, content = oauth_client.request(self.urls['request'], 'POST', body = tryUrlencode({'oauth_callback': callback_url}))
|
||||
|
||||
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({
|
||||
'success': False,
|
||||
})
|
||||
@@ -80,7 +80,7 @@ class Twitter(Notification):
|
||||
|
||||
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({
|
||||
'success': True,
|
||||
'url': auth_url,
|
||||
@@ -100,10 +100,10 @@ class Twitter(Notification):
|
||||
access_token = dict(parse_qsl(content))
|
||||
|
||||
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'
|
||||
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_secret', value = access_token['oauth_token_secret'])
|
||||
|
||||
@@ -35,5 +35,5 @@ class XBMC(Notification):
|
||||
log.error("Couldn't sent command to XBMC")
|
||||
return False
|
||||
|
||||
log.info('XBMC notification to %s successful.' % host)
|
||||
log.info('XBMC notification to %s successful.', host)
|
||||
return True
|
||||
|
||||
@@ -25,7 +25,7 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'rating',
|
||||
'default': 6.0,
|
||||
'default': 7.0,
|
||||
'type': 'float',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -19,4 +19,8 @@ class Automation(Plugin):
|
||||
|
||||
movies = fireEvent('automation.get_movies', merge = True)
|
||||
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.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.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
@@ -71,6 +71,7 @@ class Plugin(object):
|
||||
return send_from_directory(d, filename)
|
||||
|
||||
def createFile(self, path, content, binary = False):
|
||||
path = ss(path)
|
||||
|
||||
self.makeDir(os.path.dirname(path))
|
||||
|
||||
@@ -80,20 +81,21 @@ class Plugin(object):
|
||||
f.close()
|
||||
os.chmod(path, Env.getPermission('file'))
|
||||
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):
|
||||
path = ss(path)
|
||||
try:
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path, Env.getPermission('folder'))
|
||||
return True
|
||||
except Exception, e:
|
||||
log.error('Unable to create folder "%s": %s' % (path, e))
|
||||
log.error('Unable to create folder "%s": %s', (path, e))
|
||||
|
||||
return False
|
||||
|
||||
# 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
|
||||
if not headers.get('Referer'):
|
||||
@@ -106,7 +108,7 @@ class Plugin(object):
|
||||
# Don't try for failed requests
|
||||
if self.http_failed_disabled.get(host, 0) > 0:
|
||||
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
|
||||
else:
|
||||
del self.http_failed_request[host]
|
||||
@@ -116,7 +118,7 @@ class Plugin(object):
|
||||
try:
|
||||
|
||||
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)
|
||||
|
||||
cookies = cookielib.CookieJar()
|
||||
@@ -124,16 +126,19 @@ class Plugin(object):
|
||||
|
||||
data = opener.open(request, timeout = timeout).read()
|
||||
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
|
||||
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
|
||||
except IOError:
|
||||
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
|
||||
try:
|
||||
@@ -147,7 +152,7 @@ class Plugin(object):
|
||||
self.http_failed_disabled[host] = time.time()
|
||||
|
||||
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
|
||||
|
||||
@@ -163,7 +168,7 @@ class Plugin(object):
|
||||
wait = math.ceil(last_use - now + self.http_time_between_calls)
|
||||
|
||||
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)
|
||||
|
||||
def beforeCall(self, handler):
|
||||
@@ -199,9 +204,10 @@ class Plugin(object):
|
||||
|
||||
|
||||
def getCache(self, cache_key, url = None, **kwargs):
|
||||
cache_key = simplifyString(cache_key)
|
||||
cache = Env.get('cache').get(cache_key)
|
||||
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
|
||||
|
||||
if url:
|
||||
@@ -213,13 +219,15 @@ class Plugin(object):
|
||||
del kwargs['cache_timeout']
|
||||
|
||||
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
|
||||
except:
|
||||
pass
|
||||
|
||||
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)
|
||||
return value
|
||||
|
||||
|
||||
@@ -62,13 +62,15 @@ class FileBrowser(Plugin):
|
||||
|
||||
def view(self):
|
||||
|
||||
path = getParam('path', '/')
|
||||
|
||||
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:
|
||||
dirs = []
|
||||
|
||||
return jsonified({
|
||||
'is_root': getParam('path', '/') == '/',
|
||||
'is_root': path == '/' or not path,
|
||||
'empty': len(dirs) == 0,
|
||||
'dirs': dirs,
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import FileType, File
|
||||
from couchpotato.environment import Env
|
||||
import os.path
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -46,6 +47,7 @@ class FileManager(Plugin):
|
||||
try:
|
||||
filedata = self.urlopen(url, **urlopen_kwargs)
|
||||
except:
|
||||
log.error('Failed downloading file %s: %s', (url, traceback.format_exc()))
|
||||
return False
|
||||
|
||||
self.createFile(dest, filedata, binary = True)
|
||||
|
||||
@@ -78,7 +78,7 @@ class LibraryPlugin(Plugin):
|
||||
except: pass
|
||||
|
||||
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
|
||||
|
||||
# Main info
|
||||
@@ -95,7 +95,7 @@ class LibraryPlugin(Plugin):
|
||||
db.commit()
|
||||
|
||||
titles = info.get('titles', [])
|
||||
log.debug('Adding titles: %s' % titles)
|
||||
log.debug('Adding titles: %s', titles)
|
||||
for title in titles:
|
||||
if not title:
|
||||
continue
|
||||
@@ -117,26 +117,29 @@ class LibraryPlugin(Plugin):
|
||||
continue
|
||||
|
||||
file_path = fireEvent('file.download', url = image, single = True)
|
||||
file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', type), single = True)
|
||||
try:
|
||||
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
|
||||
library.files.append(file_obj)
|
||||
db.commit()
|
||||
except:
|
||||
log.debug('Failed to attach to library: %s' % traceback.format_exc())
|
||||
if file_path:
|
||||
file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', type), single = True)
|
||||
try:
|
||||
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
|
||||
library.files.append(file_obj)
|
||||
db.commit()
|
||||
except:
|
||||
log.debug('Failed to attach to library: %s', traceback.format_exc())
|
||||
|
||||
library_dict = library.to_dict(self.default_dict)
|
||||
|
||||
fireEvent('library.update_finish', data = library_dict)
|
||||
|
||||
#db.close()
|
||||
return library_dict
|
||||
|
||||
def updateReleaseDate(self, identifier):
|
||||
|
||||
db = get_session()
|
||||
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():
|
||||
dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -71,14 +73,14 @@ class Logging(Plugin):
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'log': log,
|
||||
'log': toUnicode(log),
|
||||
'total': total,
|
||||
})
|
||||
|
||||
def partial(self):
|
||||
|
||||
log_type = getParam('type', 'all')
|
||||
total_lines = getParam('lines', 30)
|
||||
total_lines = tryInt(getParam('lines', 30))
|
||||
|
||||
log_lines = []
|
||||
|
||||
@@ -92,13 +94,12 @@ class Logging(Plugin):
|
||||
|
||||
reversed_lines = []
|
||||
f = open(path, 'r')
|
||||
reversed_lines = f.read().split('[0m\n')
|
||||
reversed_lines = toUnicode(f.read()).split('[0m\n')
|
||||
reversed_lines.reverse()
|
||||
|
||||
brk = False
|
||||
for line in reversed_lines:
|
||||
|
||||
#print '%s ' % log_type in line.lower()
|
||||
if log_type == 'all' or '%s ' % log_type.upper() in line:
|
||||
log_lines.append(line)
|
||||
|
||||
@@ -149,7 +150,7 @@ class Logging(Plugin):
|
||||
except:
|
||||
log.error(log_message)
|
||||
except:
|
||||
log.error('Couldn\'t log via API: %s' % params)
|
||||
log.error('Couldn\'t log via API: %s', params)
|
||||
|
||||
|
||||
return jsonified({
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
width: 14%;
|
||||
color: lightgrey;
|
||||
padding: 3px 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.page.log .container .time:last-child { display: none; }
|
||||
|
||||
@@ -45,7 +45,7 @@ Page.Log = new Class({
|
||||
new Fx.Scroll(window, {'duration': 0}).toBottom();
|
||||
|
||||
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', {
|
||||
'text': i+1,
|
||||
'class': nr == i ? 'active': '',
|
||||
@@ -78,11 +78,16 @@ Page.Log = new Class({
|
||||
addColors: function(text){
|
||||
var self = this;
|
||||
|
||||
text = text.replace(/\u001b\[31m/gi, '</span><span class="error">')
|
||||
text = text.replace(/\u001b\[36m/gi, '</span><span class="debug">')
|
||||
text = text.replace(/\u001b\[33m/gi, '</span><span class="debug">')
|
||||
text = text.replace(/\u001b\[0m\n/gi, '</span><span class="time">')
|
||||
text = text.replace(/\u001b\[0m/gi, '</span><span>')
|
||||
text = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.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>';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from couchpotato.api import addApiView
|
||||
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.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -25,13 +25,14 @@ class Manage(Plugin):
|
||||
})
|
||||
|
||||
if not Env.get('dev'):
|
||||
addEvent('app.load', self.updateLibrary)
|
||||
def updateLibrary():
|
||||
self.updateLibrary(full = False)
|
||||
addEvent('app.load', updateLibrary)
|
||||
|
||||
def updateLibraryView(self):
|
||||
|
||||
params = getParams()
|
||||
|
||||
fireEventAsync('manage.update', full = params.get('full', True))
|
||||
full = getParam('full', default = 1)
|
||||
fireEventAsync('manage.update', full = True if full == '1' else False)
|
||||
|
||||
return jsonified({
|
||||
'success': True
|
||||
@@ -51,11 +52,11 @@ class Manage(Plugin):
|
||||
|
||||
if not os.path.isdir(directory):
|
||||
if len(directory) > 0:
|
||||
log.error('Directory doesn\'t exist: %s' % directory)
|
||||
log.error('Directory doesn\'t exist: %s', directory)
|
||||
continue
|
||||
|
||||
log.info('Updating manage library: %s' % directory)
|
||||
identifiers = fireEvent('scanner.folder', folder = directory, newer_than = last_update, single = True)
|
||||
log.info('Updating manage library: %s', directory)
|
||||
identifiers = fireEvent('scanner.folder', folder = directory, newer_than = last_update if not full else 0, single = True)
|
||||
if identifiers:
|
||||
added_identifiers.extend(identifiers)
|
||||
|
||||
@@ -67,11 +68,11 @@ class Manage(Plugin):
|
||||
if self.conf('cleanup') and full and not self.shuttingDown():
|
||||
|
||||
# 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:
|
||||
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())
|
||||
|
||||
|
||||
@@ -130,6 +130,8 @@ class MoviePlugin(Plugin):
|
||||
.filter(or_(*[Movie.status.has(identifier = s) for s in status])) \
|
||||
.group_by(Movie.id)
|
||||
|
||||
total_count = q.count()
|
||||
|
||||
filter_or = []
|
||||
if starts_with:
|
||||
starts_with = toUnicode(starts_with.lower())
|
||||
@@ -156,8 +158,7 @@ class MoviePlugin(Plugin):
|
||||
.options(joinedload_all('library.titles')) \
|
||||
.options(joinedload_all('library.files')) \
|
||||
.options(joinedload_all('status')) \
|
||||
.options(joinedload_all('files')) \
|
||||
|
||||
.options(joinedload_all('files'))
|
||||
|
||||
if limit_offset:
|
||||
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]
|
||||
q2 = q2.limit(limit).offset(offset)
|
||||
|
||||
|
||||
results = q2.all()
|
||||
movies = []
|
||||
for movie in results:
|
||||
@@ -178,7 +178,7 @@ class MoviePlugin(Plugin):
|
||||
movies.append(temp)
|
||||
|
||||
#db.close()
|
||||
return movies
|
||||
return (total_count, movies)
|
||||
|
||||
def availableChars(self, status = ['active']):
|
||||
|
||||
@@ -214,11 +214,12 @@ class MoviePlugin(Plugin):
|
||||
starts_with = params.get('starts_with', 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({
|
||||
'success': True,
|
||||
'empty': len(movies) == 0,
|
||||
'total': total_movies,
|
||||
'movies': movies,
|
||||
})
|
||||
|
||||
@@ -241,14 +242,16 @@ class MoviePlugin(Plugin):
|
||||
for id in getParam('id').split(','):
|
||||
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:
|
||||
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()
|
||||
return jsonified({
|
||||
@@ -277,6 +280,10 @@ class MoviePlugin(Plugin):
|
||||
|
||||
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)
|
||||
|
||||
# Status
|
||||
@@ -287,6 +294,7 @@ class MoviePlugin(Plugin):
|
||||
|
||||
db = get_session()
|
||||
m = db.query(Movie).filter_by(library_id = library.get('id')).first()
|
||||
added = True
|
||||
do_search = False
|
||||
if not m:
|
||||
m = Movie(
|
||||
@@ -295,8 +303,14 @@ class MoviePlugin(Plugin):
|
||||
status_id = status_active.get('id'),
|
||||
)
|
||||
db.add(m)
|
||||
fireEvent('library.update', params.get('identifier'), default_title = params.get('title', ''))
|
||||
do_search = True
|
||||
db.commit()
|
||||
|
||||
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:
|
||||
# Clean snatched history
|
||||
for release in m.releases:
|
||||
@@ -305,10 +319,12 @@ class MoviePlugin(Plugin):
|
||||
|
||||
m.profile_id = params.get('profile_id', default_profile.get('id'))
|
||||
else:
|
||||
log.debug('Movie already exists, not updating: %s' % params)
|
||||
log.debug('Movie already exists, not updating: %s', params)
|
||||
added = False
|
||||
|
||||
if force_readd:
|
||||
m.status_id = status_active.get('id')
|
||||
do_search = True
|
||||
|
||||
db.commit()
|
||||
|
||||
@@ -321,8 +337,12 @@ class MoviePlugin(Plugin):
|
||||
|
||||
movie_dict = m.to_dict(self.default_dict)
|
||||
|
||||
if (force_readd or do_search) and search_after:
|
||||
fireEventAsync('searcher.single', movie_dict)
|
||||
if do_search and search_after:
|
||||
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()
|
||||
return movie_dict
|
||||
@@ -336,7 +356,7 @@ class MoviePlugin(Plugin):
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'added': True,
|
||||
'added': True if movie_dict else False,
|
||||
'movie': movie_dict,
|
||||
})
|
||||
|
||||
@@ -351,6 +371,9 @@ class MoviePlugin(Plugin):
|
||||
for movie_id in ids:
|
||||
|
||||
m = db.query(Movie).filter_by(id = movie_id).first()
|
||||
if not m:
|
||||
continue
|
||||
|
||||
m.profile_id = params.get('profile_id')
|
||||
|
||||
# Remove releases
|
||||
@@ -369,7 +392,7 @@ class MoviePlugin(Plugin):
|
||||
fireEvent('movie.restatus', m.id)
|
||||
|
||||
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()
|
||||
return jsonified({
|
||||
@@ -421,6 +444,7 @@ class MoviePlugin(Plugin):
|
||||
db.commit()
|
||||
elif new_movie_status:
|
||||
new_status = fireEvent('status.get', new_movie_status, single = True)
|
||||
movie.profile_id = None
|
||||
movie.status_id = new_status.get('id')
|
||||
db.commit()
|
||||
else:
|
||||
@@ -441,7 +465,7 @@ class MoviePlugin(Plugin):
|
||||
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
||||
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:
|
||||
m.status_id = done_status.get('id')
|
||||
else:
|
||||
@@ -458,3 +482,22 @@ class MoviePlugin(Plugin):
|
||||
#db.close()
|
||||
|
||||
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: {
|
||||
navigation: true,
|
||||
limit: 50,
|
||||
menu: []
|
||||
menu: [],
|
||||
add_new: false
|
||||
},
|
||||
|
||||
movies: [],
|
||||
movies_added: {},
|
||||
letters: {},
|
||||
filter: {
|
||||
'startswith': null,
|
||||
@@ -30,6 +32,17 @@ var MovieList = new Class({
|
||||
})
|
||||
);
|
||||
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(){
|
||||
@@ -59,7 +72,7 @@ var MovieList = new Class({
|
||||
self.created = true;
|
||||
},
|
||||
|
||||
addMovies: function(movies){
|
||||
addMovies: function(movies, total){
|
||||
var self = this;
|
||||
|
||||
if(!self.created) self.create();
|
||||
@@ -71,25 +84,41 @@ var MovieList = new Class({
|
||||
}
|
||||
|
||||
Object.each(movies, function(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.createMovie(movie);
|
||||
});
|
||||
|
||||
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(){
|
||||
var self = this;
|
||||
@@ -100,6 +129,7 @@ var MovieList = new Class({
|
||||
|
||||
self.navigation = new Element('div.alph_nav').adopt(
|
||||
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', {
|
||||
'events': {
|
||||
'click:relay(li)': function(e, el){
|
||||
@@ -260,6 +290,7 @@ var MovieList = new Class({
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).preventDefault();
|
||||
this.set('text', 'Deleting..')
|
||||
Api.request('movie.delete', {
|
||||
'data': {
|
||||
'id': ids.join(','),
|
||||
@@ -268,14 +299,19 @@ var MovieList = new Class({
|
||||
'onSuccess': function(){
|
||||
qObj.close();
|
||||
|
||||
var erase_movies = [];
|
||||
self.movies.each(function(movie){
|
||||
if (movie.isSelected()){
|
||||
$(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),
|
||||
'onComplete': function(json){
|
||||
self.store(json.movies);
|
||||
self.addMovies(json.movies);
|
||||
self.addMovies(json.movies, json.total);
|
||||
self.load_more.set('text', 'load more movies');
|
||||
if(self.scrollspy) self.scrollspy.start();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
.movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover {
|
||||
background: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.movies .movie_container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.movies .data {
|
||||
padding: 20px;
|
||||
@@ -32,7 +36,6 @@
|
||||
position: relative;
|
||||
float: right;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
.movies .list_view .data, .movies .mass_edit_view .data {
|
||||
@@ -89,7 +92,7 @@
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
text-overflow: ellipsis;
|
||||
width: 64%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.movies .info .year {
|
||||
@@ -152,7 +155,6 @@
|
||||
.movies .list_view .data .quality, .movies .mass_edit_view .data .quality {
|
||||
text-align: right;
|
||||
float: right;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.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 {
|
||||
margin: -34px 2px 0 0;
|
||||
background: #4e5969;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.movies .delete_container {
|
||||
@@ -310,6 +313,33 @@
|
||||
padding-bottom: 4px;
|
||||
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 {
|
||||
display: block;
|
||||
@@ -330,15 +360,17 @@
|
||||
width: 1080px;
|
||||
margin: 0 -60px;
|
||||
box-shadow: 0 20px 20px -22px rgba(0,0,0,0.1);
|
||||
background: #4e5969;
|
||||
}
|
||||
|
||||
.movies .alph_nav.float {
|
||||
box-shadow: 0 30px 30px -32px rgba(0,0,0,0.5);
|
||||
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;
|
||||
padding: 0 0 1px;
|
||||
margin: 0;
|
||||
@@ -346,10 +378,15 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.movies .alph_nav .counter {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.movies .alph_nav .numbers li, .movies .alph_nav .actions li {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 22px;
|
||||
width: 20px;
|
||||
height: 24px;
|
||||
line-height: 26px;
|
||||
text-align: center;
|
||||
@@ -361,7 +398,6 @@
|
||||
}
|
||||
.movies .alph_nav .numbers li:first-child {
|
||||
width: 43px;
|
||||
margin-left: 7px;
|
||||
}
|
||||
.movies .alph_nav li.available {
|
||||
color: rgba(255,255,255,0.8);
|
||||
@@ -370,8 +406,8 @@
|
||||
}
|
||||
.movies .alph_nav li.active.available, .movies .alph_nav li.available:hover {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.movies .alph_nav input {
|
||||
|
||||
@@ -11,53 +11,124 @@ var Movie = new Class({
|
||||
self.view = options.view || 'thumbs';
|
||||
self.list = list;
|
||||
|
||||
self.el = new Element('div.movie.inlay');
|
||||
|
||||
self.profile = Quality.getProfile(data.profile_id) || {};
|
||||
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(){
|
||||
var self = this;
|
||||
|
||||
self.el = new Element('div.movie.inlay').adopt(
|
||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': function(){
|
||||
self.fireEvent('select')
|
||||
}
|
||||
}
|
||||
}),
|
||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||
self.data_container = new Element('div.data.inlay.light', {
|
||||
'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.el.adopt(
|
||||
self.container = new Element('div.movie_container').adopt(
|
||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': function(){
|
||||
self.fireEvent('select')
|
||||
}
|
||||
})
|
||||
),
|
||||
self.actions = new Element('div.actions')
|
||||
}
|
||||
}),
|
||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||
self.data_container = new Element('div.data.inlay.light', {
|
||||
'tween': {
|
||||
duration: 400,
|
||||
transition: 'quint:in:out',
|
||||
onComplete: self.fireEvent.bind(self, 'slideEnd')
|
||||
}
|
||||
}).adopt(
|
||||
self.info_container = new Element('div.info').adopt(
|
||||
self.title = new Element('div.title', {
|
||||
'text': self.getTitle() || 'n/a'
|
||||
}),
|
||||
self.year = new Element('div.year', {
|
||||
'text': self.data.library.year || 'n/a'
|
||||
}),
|
||||
self.rating = new Element('div.rating.icon', {
|
||||
'text': self.data.library.rating
|
||||
}),
|
||||
self.description = new Element('div.description', {
|
||||
'text': self.data.library.plot
|
||||
}),
|
||||
self.quality = new Element('div.quality', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
var releases = self.el.getElement('.actions .releases');
|
||||
if(releases)
|
||||
releases.fireEvent('click', [e])
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
self.actions = new Element('div.actions')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -150,7 +221,7 @@ var Movie = new Class({
|
||||
self.el.removeEvents('outerClick')
|
||||
|
||||
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);
|
||||
@@ -178,6 +249,10 @@ var Movie = new Class({
|
||||
|
||||
isSelected: function(){
|
||||
return this.select_checkbox.get('checked');
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
return this.el;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -290,7 +365,7 @@ var ReleaseAction = new Class({
|
||||
} catch(e){}
|
||||
|
||||
new Element('div', {
|
||||
'class': 'item'
|
||||
'class': 'item '+status.identifier
|
||||
}).adopt(
|
||||
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
||||
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
||||
@@ -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(
|
||||
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,
|
||||
new Element('div.info').adopt(
|
||||
self.title = new Element('h2', {
|
||||
@@ -299,11 +301,11 @@ Block.Search.Item = new Class({
|
||||
'title': self.title_select.get('value'),
|
||||
'profile_id': self.profile_select.get('value')
|
||||
},
|
||||
'onComplete': function(){
|
||||
'onComplete': function(json){
|
||||
self.options.empty();
|
||||
self.options.adopt(
|
||||
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(
|
||||
new Element('div').adopt(
|
||||
self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||
'src': self.info.images.poster[0]
|
||||
self.option_thumbnail = self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
||||
'src': self.info.images.poster[0],
|
||||
'height': null,
|
||||
'width': null
|
||||
}) : null,
|
||||
self.info.in_wanted ? new Element('span.in_wanted', {
|
||||
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
||||
|
||||
@@ -135,8 +135,7 @@ class ProfilePlugin(Plugin):
|
||||
|
||||
success = True
|
||||
except Exception, e:
|
||||
message = 'Failed deleting Profile: %s' % e
|
||||
log.error(message)
|
||||
message = log.error('Failed deleting Profile: %s', e)
|
||||
|
||||
#db.close()
|
||||
|
||||
@@ -163,7 +162,7 @@ class ProfilePlugin(Plugin):
|
||||
# Create default quality profile
|
||||
order = -2
|
||||
for profile in profiles:
|
||||
log.info('Creating default profile: %s' % profile.get('label'))
|
||||
log.info('Creating default profile: %s', profile.get('label'))
|
||||
p = Profile(
|
||||
label = toUnicode(profile.get('label')),
|
||||
order = order
|
||||
|
||||
@@ -17,8 +17,8 @@ class QualityPlugin(Plugin):
|
||||
|
||||
qualities = [
|
||||
{'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': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts', 'ts']},
|
||||
{'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', 'ts']},
|
||||
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
|
||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
@@ -26,7 +26,7 @@ class QualityPlugin(Plugin):
|
||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
{'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']
|
||||
|
||||
@@ -116,7 +116,7 @@ class QualityPlugin(Plugin):
|
||||
quality = db.query(Quality).filter_by(identifier = q.get('identifier')).first()
|
||||
|
||||
if not quality:
|
||||
log.info('Creating quality: %s' % q.get('label'))
|
||||
log.info('Creating quality: %s', q.get('label'))
|
||||
quality = Quality()
|
||||
db.add(quality)
|
||||
|
||||
@@ -133,7 +133,7 @@ class QualityPlugin(Plugin):
|
||||
).all()
|
||||
|
||||
if not profile:
|
||||
log.info('Creating profile: %s' % q.get('label'))
|
||||
log.info('Creating profile: %s', q.get('label'))
|
||||
profile = Profile(
|
||||
core = True,
|
||||
label = toUnicode(quality.label),
|
||||
@@ -170,20 +170,20 @@ class QualityPlugin(Plugin):
|
||||
|
||||
# Check tags
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
for tag in quality.get('tags', []):
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
# Try again with loose testing
|
||||
@@ -191,7 +191,7 @@ class QualityPlugin(Plugin):
|
||||
if 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
|
||||
|
||||
def guessLoose(self, hash, extra):
|
||||
@@ -200,7 +200,7 @@ class QualityPlugin(Plugin):
|
||||
|
||||
# Last check on resolution only
|
||||
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
||||
log.debug('Found %s via resolution_width: %s == %s' % (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
||||
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)
|
||||
|
||||
if 480 <= extra.get('resolution_width', 0) <= 720:
|
||||
|
||||
@@ -79,7 +79,7 @@ class Release(Plugin):
|
||||
rel.files.append(added_file)
|
||||
db.commit()
|
||||
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)
|
||||
|
||||
@@ -143,7 +143,7 @@ class Release(Plugin):
|
||||
item[info.identifier] = info.value
|
||||
|
||||
# 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
|
||||
|
||||
fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
|
||||
@@ -158,7 +158,7 @@ class Release(Plugin):
|
||||
'success': True
|
||||
})
|
||||
else:
|
||||
log.error('Couldn\'t find release with id: %s' % id)
|
||||
log.error('Couldn\'t find release with id: %s', id)
|
||||
|
||||
#db.close()
|
||||
return jsonified({
|
||||
|
||||
@@ -19,6 +19,9 @@ rename_options = {
|
||||
'source': 'Source media (Bluray)',
|
||||
'original': 'Original filename',
|
||||
'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
|
||||
if not group['library'] or not movie_title:
|
||||
if group['dirname']:
|
||||
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_UNKNOWN_%s' % 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, '_UNKNOWN_%s' % filename)
|
||||
|
||||
self.tagDir(group, 'unknown')
|
||||
continue
|
||||
# Rename the files using the library data
|
||||
else:
|
||||
group['library'] = fireEvent('library.update', identifier = group['library']['identifier'], single = True)
|
||||
if not group['library']:
|
||||
log.error('Could not rename, no library item to work with: %s' % group_identifier)
|
||||
log.error('Could not rename, no library item to work with: %s', group_identifier)
|
||||
continue
|
||||
|
||||
library = group['library']
|
||||
@@ -129,13 +123,14 @@ class Renamer(Plugin):
|
||||
'source': group['meta_data']['source'],
|
||||
'resolution_width': group['meta_data'].get('resolution_width'),
|
||||
'resolution_height': group['meta_data'].get('resolution_height'),
|
||||
'imdb_id': library['identifier'],
|
||||
}
|
||||
|
||||
for file_type in group['files']:
|
||||
|
||||
# Move nfo depending on settings
|
||||
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
||||
log.debug('Skipping, renaming of %s disabled' % file_type)
|
||||
log.debug('Skipping, renaming of %s disabled', file_type)
|
||||
if self.conf('cleanup'):
|
||||
for current_file in group['files'][file_type]:
|
||||
remove_files.append(current_file)
|
||||
@@ -194,7 +189,7 @@ class Renamer(Plugin):
|
||||
break
|
||||
|
||||
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
|
||||
else:
|
||||
@@ -208,7 +203,7 @@ class Renamer(Plugin):
|
||||
if file_type is 'subtitle':
|
||||
|
||||
# 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, [])
|
||||
|
||||
rename_extras = self.getRenameExtras(
|
||||
@@ -263,13 +258,13 @@ class Renamer(Plugin):
|
||||
|
||||
# Mark movie "done" onces it found the quality with the finish check
|
||||
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:
|
||||
if profile_type.quality_id == group['meta_data']['quality']['id'] and profile_type.finish:
|
||||
movie.status_id = done_status.get('id')
|
||||
db.commit()
|
||||
except Exception, e:
|
||||
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
||||
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
|
||||
|
||||
# Go over current movie releases
|
||||
for release in movie.releases:
|
||||
@@ -279,30 +274,23 @@ class Renamer(Plugin):
|
||||
|
||||
# This is where CP removes older, lesser quality releases
|
||||
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:
|
||||
remove_files.append(current_file)
|
||||
remove_releases.append(release)
|
||||
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
|
||||
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:
|
||||
remove_files.append(current_file)
|
||||
remove_releases.append(release)
|
||||
|
||||
# Downloaded a lower quality, rename the newly downloaded files/folder to exclude them from scan
|
||||
else:
|
||||
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
|
||||
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
|
||||
if group['dirname']:
|
||||
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)
|
||||
self.tagDir(group, 'exists')
|
||||
|
||||
# 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)
|
||||
@@ -311,7 +299,6 @@ class Renamer(Plugin):
|
||||
|
||||
break
|
||||
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']:
|
||||
log.debug('Marking release as downloaded')
|
||||
release.status_id = downloaded_status.get('id')
|
||||
@@ -323,14 +310,32 @@ class Renamer(Plugin):
|
||||
for current_file in group['files']['leftover']:
|
||||
remove_files.append(current_file)
|
||||
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
|
||||
group['renamed_files'] = []
|
||||
for src in rename_files:
|
||||
if 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
|
||||
self.makeDir(os.path.dirname(dst))
|
||||
@@ -339,34 +344,23 @@ class Renamer(Plugin):
|
||||
self.moveFile(src, dst)
|
||||
group['renamed_files'].append(dst)
|
||||
except:
|
||||
log.error('Failed moving the file "%s" : %s' % (os.path.basename(src), traceback.format_exc()))
|
||||
|
||||
# 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()))
|
||||
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
||||
self.tagDir(group, 'failed_rename')
|
||||
|
||||
# Remove matching releases
|
||||
for release in remove_releases:
|
||||
log.debug('Removing release %s' % release.identifier)
|
||||
log.debug('Removing release %s', release.identifier)
|
||||
try:
|
||||
db.delete(release)
|
||||
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']:
|
||||
try:
|
||||
log.info('Deleting folder: %s' % group['parentdir'])
|
||||
log.info('Deleting folder: %s', group['parentdir'])
|
||||
self.deleteEmptyFolder(group['parentdir'])
|
||||
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
|
||||
fireEventAsync('renamer.after', group)
|
||||
@@ -398,17 +392,43 @@ class Renamer(Plugin):
|
||||
|
||||
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):
|
||||
try:
|
||||
shutil.move(old, dest)
|
||||
|
||||
try:
|
||||
os.chmod(dest, Env.getPermission('folder'))
|
||||
os.chmod(dest, Env.getPermission('file'))
|
||||
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:
|
||||
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
|
||||
|
||||
return True
|
||||
@@ -421,7 +441,7 @@ class Renamer(Plugin):
|
||||
replaced = toUnicode(string)
|
||||
for x, r in replacements.iteritems():
|
||||
if r is not None:
|
||||
replaced = replaced.replace('<%s>' % toUnicode(x), toUnicode(r))
|
||||
replaced = replaced.replace(u'<%s>' % toUnicode(x), toUnicode(r))
|
||||
else:
|
||||
#If information is not available, we don't want the tag in the filename
|
||||
replaced = replaced.replace('<' + x + '>', '')
|
||||
@@ -444,9 +464,9 @@ class Renamer(Plugin):
|
||||
try:
|
||||
os.rmdir(full_path)
|
||||
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:
|
||||
os.rmdir(folder)
|
||||
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.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.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import File
|
||||
from couchpotato.environment import Env
|
||||
from couchpotato.core.settings.model import File, Movie
|
||||
from enzyme.exceptions import NoParserError, ParseError
|
||||
from guessit import guess_movie_info
|
||||
from subliminal.videos import Video
|
||||
@@ -24,10 +23,10 @@ class Scanner(Plugin):
|
||||
'media': 314572800, # 300MB
|
||||
'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']
|
||||
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'],
|
||||
'dvd': ['vts_*', 'vob'],
|
||||
'nfo': ['nfo', 'txt', 'tag'],
|
||||
@@ -89,24 +88,28 @@ class Scanner(Plugin):
|
||||
addEvent('scanner.partnumber', self.getPartNumber)
|
||||
|
||||
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):
|
||||
|
||||
folder = os.path.normpath(folder)
|
||||
|
||||
groups = self.scan(folder = folder, files = files)
|
||||
|
||||
for group in groups.itervalues():
|
||||
if group['library']:
|
||||
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):
|
||||
return
|
||||
|
||||
groups = self.scan(folder = folder, simple = simple)
|
||||
groups = self.scan(folder = folder, simple = simple, newer_than = newer_than)
|
||||
|
||||
added_identifier = []
|
||||
while True and not self.shuttingDown():
|
||||
@@ -127,10 +130,12 @@ class Scanner(Plugin):
|
||||
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):
|
||||
log.error('Folder doesn\'t exists: %s' % folder)
|
||||
log.error('Folder doesn\'t exists: %s', folder)
|
||||
return {}
|
||||
|
||||
# Get movie "master" files
|
||||
@@ -141,19 +146,15 @@ class Scanner(Plugin):
|
||||
if len(files) == 0:
|
||||
try:
|
||||
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:
|
||||
files.append(os.path.join(root, filename))
|
||||
except:
|
||||
try:
|
||||
files = []
|
||||
folder = toUnicode(folder).encode(Env.get('encoding'))
|
||||
log.info('Trying to convert unicode to str path: %s, %s' % (folder, type(folder)))
|
||||
for root, dirs, walk_files in os.walk(folder):
|
||||
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()))
|
||||
log.error('Failed getting files from %s: %s', (folder, traceback.format_exc()))
|
||||
else:
|
||||
files = [ss(x) for x in files]
|
||||
|
||||
db = get_session()
|
||||
|
||||
for file_path in files:
|
||||
|
||||
@@ -207,7 +208,7 @@ class Scanner(Plugin):
|
||||
for identifier, group in movie_files.iteritems():
|
||||
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']:
|
||||
wo_ext = file_path[:-(len(getExt(file_path)) + 1)]
|
||||
@@ -231,19 +232,53 @@ class Scanner(Plugin):
|
||||
|
||||
|
||||
# Group the files based on the identifier
|
||||
for identifier, group in movie_files.iteritems():
|
||||
log.debug('Grouping files on identifier: %s' % identifier)
|
||||
delete_identifiers = []
|
||||
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['unsorted_files'].extend(found_files)
|
||||
group = movie_files.get(identifier)
|
||||
if group:
|
||||
group['unsorted_files'].extend(found_files)
|
||||
delete_identifiers.append(identifier)
|
||||
|
||||
# Remove the found files from the leftover stack
|
||||
leftovers = leftovers - found_files
|
||||
# Remove the found files from the leftover stack
|
||||
leftovers = leftovers - set(found_files)
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
processed_movies = {}
|
||||
@@ -256,13 +291,38 @@ class Scanner(Plugin):
|
||||
# Check if movie is fresh and maybe still unpacking, ignore files new then 1 minute
|
||||
file_too_new = False
|
||||
for cur_file in group['unsorted_files']:
|
||||
file_time = os.path.getmtime(cur_file)
|
||||
if file_time > time.time() - 60:
|
||||
file_too_new = tryInt(time.time() - file_time)
|
||||
if not os.path.isfile(cur_file):
|
||||
file_too_new = time.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
|
||||
|
||||
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
|
||||
|
||||
# Group extra (and easy) files first
|
||||
@@ -284,10 +344,10 @@ class Scanner(Plugin):
|
||||
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
||||
|
||||
if len(group['files']['movie']) == 0:
|
||||
log.error('Couldn\t find any movie files for %s' % identifier)
|
||||
log.error('Couldn\t find any movie files for %s', identifier)
|
||||
continue
|
||||
|
||||
log.debug('Getting metadata for %s' % identifier)
|
||||
log.debug('Getting metadata for %s', identifier)
|
||||
group['meta_data'] = self.getMetaData(group)
|
||||
|
||||
# Subtitle meta
|
||||
@@ -320,7 +380,11 @@ class Scanner(Plugin):
|
||||
# Determine movie
|
||||
group['library'] = self.determineMovie(group)
|
||||
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
|
||||
|
||||
@@ -329,9 +393,9 @@ class Scanner(Plugin):
|
||||
self.path_identifiers = {}
|
||||
|
||||
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:
|
||||
log.debug('Found no movies in the folder %s' % (folder))
|
||||
log.debug('Found no movies in the folder %s', (folder))
|
||||
return processed_movies
|
||||
|
||||
def getMetaData(self, group):
|
||||
@@ -351,7 +415,7 @@ class Scanner(Plugin):
|
||||
data['resolution_height'] = meta.get('resolution_height', 480)
|
||||
data['aspect'] = meta.get('resolution_width', 720) / meta.get('resolution_height', 480)
|
||||
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
|
||||
|
||||
if data.get('audio'): break
|
||||
@@ -379,11 +443,11 @@ class Scanner(Plugin):
|
||||
'resolution_height': tryInt(p.video[0].height),
|
||||
}
|
||||
except ParseError:
|
||||
log.debug('Failed to parse meta for %s' % filename)
|
||||
log.debug('Failed to parse meta for %s', filename)
|
||||
except NoParserError:
|
||||
log.debug('No parser found for %s' % filename)
|
||||
log.debug('No parser found for %s', filename)
|
||||
except:
|
||||
log.debug('Failed parsing %s' % filename)
|
||||
log.debug('Failed parsing %s', filename)
|
||||
|
||||
return {}
|
||||
|
||||
@@ -396,7 +460,7 @@ class Scanner(Plugin):
|
||||
scan_result = []
|
||||
for p in paths:
|
||||
if not group['is_dvd']:
|
||||
video = Video.from_path(p)
|
||||
video = Video.from_path(toUnicode(p))
|
||||
video_result = [(video, video.scan())]
|
||||
scan_result.extend(video_result)
|
||||
|
||||
@@ -405,7 +469,7 @@ class Scanner(Plugin):
|
||||
if s.language and s.path not in paths:
|
||||
detected_languages[s.path] = [s.language]
|
||||
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
|
||||
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):
|
||||
detected_languages[sub_file] = idx_langs
|
||||
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
|
||||
|
||||
@@ -434,7 +498,7 @@ class Scanner(Plugin):
|
||||
for cur_file in files['movie']:
|
||||
imdb_id = self.getCPImdb(cur_file)
|
||||
if imdb_id:
|
||||
log.debug('Found movie via CP tag: %s' % cur_file)
|
||||
log.debug('Found movie via CP tag: %s', cur_file)
|
||||
break
|
||||
|
||||
# Check and see if nfo contains the imdb-id
|
||||
@@ -443,11 +507,23 @@ class Scanner(Plugin):
|
||||
for nfo_file in files['nfo']:
|
||||
imdb_id = getImdb(nfo_file)
|
||||
if imdb_id:
|
||||
log.debug('Found movie via nfo file: %s' % nfo_file)
|
||||
log.debug('Found movie via nfo file: %s', nfo_file)
|
||||
break
|
||||
except:
|
||||
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
|
||||
if not imdb_id:
|
||||
db = get_session()
|
||||
@@ -455,7 +531,7 @@ class Scanner(Plugin):
|
||||
f = db.query(File).filter_by(path = toUnicode(cur_file)).first()
|
||||
try:
|
||||
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
|
||||
except:
|
||||
pass
|
||||
@@ -469,7 +545,7 @@ class Scanner(Plugin):
|
||||
if len(movie) > 0:
|
||||
imdb_id = movie[0]['imdb']
|
||||
if imdb_id:
|
||||
log.debug('Found movie via OpenSubtitleHash: %s' % cur_file)
|
||||
log.debug('Found movie via OpenSubtitleHash: %s', cur_file)
|
||||
break
|
||||
|
||||
# Search based on identifiers
|
||||
@@ -486,17 +562,17 @@ class Scanner(Plugin):
|
||||
|
||||
if len(movie) > 0:
|
||||
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
|
||||
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:
|
||||
return fireEvent('library.add', attrs = {
|
||||
'identifier': imdb_id
|
||||
}, 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 {}
|
||||
|
||||
def getCPImdb(self, string):
|
||||
@@ -585,17 +661,17 @@ class Scanner(Plugin):
|
||||
# ignoredpaths
|
||||
for i in self.ignored_in_path:
|
||||
if i in filename.lower():
|
||||
log.debug('Ignored "%s" contains "%s".' % (filename, i))
|
||||
log.debug('Ignored "%s" contains "%s".', (filename, i))
|
||||
return False
|
||||
|
||||
# Sample file
|
||||
if self.isSampleFile(filename):
|
||||
log.debug('Is sample file "%s".' % filename)
|
||||
log.debug('Is sample file "%s".', filename)
|
||||
return False
|
||||
|
||||
# Minimal size
|
||||
if self.filesizeBetween(filename, self.minimal_filesize['media']):
|
||||
log.debug('File to small: %s' % filename)
|
||||
log.debug('File to small: %s', filename)
|
||||
return False
|
||||
|
||||
# All is OK
|
||||
@@ -603,14 +679,14 @@ class Scanner(Plugin):
|
||||
|
||||
def isSampleFile(self, filename):
|
||||
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
|
||||
|
||||
def filesizeBetween(self, file, min = 0, max = 100000):
|
||||
try:
|
||||
return (min * 1048576) < os.path.getsize(file) < (max * 1048576)
|
||||
except:
|
||||
log.error('Couldn\'t get filesize of %s.' % file)
|
||||
log.error('Couldn\'t get filesize of %s.', file)
|
||||
|
||||
return False
|
||||
|
||||
@@ -629,7 +705,7 @@ class Scanner(Plugin):
|
||||
identifier = self.removeCPTag(identifier)
|
||||
|
||||
# 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 = self.findYear(identifier)
|
||||
@@ -703,26 +779,28 @@ class Scanner(Plugin):
|
||||
def getReleaseNameYear(self, release_name, file_name = None):
|
||||
|
||||
# Use guessit first
|
||||
guess = {}
|
||||
if file_name:
|
||||
try:
|
||||
guess = guess_movie_info(file_name)
|
||||
guess = guess_movie_info(toUnicode(file_name))
|
||||
if guess.get('title') and guess.get('year'):
|
||||
return {
|
||||
guess = {
|
||||
'name': guess.get('title'),
|
||||
'year': guess.get('year'),
|
||||
}
|
||||
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
|
||||
cleaned = ' '.join(re.split('\W+', simplifyString(release_name)))
|
||||
cleaned = re.sub(self.clean, ' ', cleaned)
|
||||
year = self.findYear(cleaned)
|
||||
cp_guess = {}
|
||||
|
||||
if year: # Split name on year
|
||||
try:
|
||||
movie_name = cleaned.split(year).pop(0).strip()
|
||||
return {
|
||||
cp_guess = {
|
||||
'name': movie_name,
|
||||
'year': int(year),
|
||||
}
|
||||
@@ -731,11 +809,16 @@ class Scanner(Plugin):
|
||||
else: # Split name on multiple spaces
|
||||
try:
|
||||
movie_name = cleaned.split(' ').pop(0).strip()
|
||||
return {
|
||||
cp_guess = {
|
||||
'name': movie_name,
|
||||
'year': int(year),
|
||||
}
|
||||
except:
|
||||
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.plugins.base import Plugin
|
||||
from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore, \
|
||||
sizeScore, providerScore, duplicateScore
|
||||
sizeScore, providerScore, duplicateScore, partialIgnoredScore, namePositionScore, \
|
||||
halfMultipartScore
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -21,6 +22,7 @@ class Score(Plugin):
|
||||
|
||||
for movie_title in movie['library']['titles']:
|
||||
score += nameRatioScore(toUnicode(nzb['name']), toUnicode(movie_title['title']))
|
||||
score += namePositionScore(toUnicode(nzb['name']), toUnicode(movie_title['title']))
|
||||
|
||||
score += sizeScore(nzb['size'])
|
||||
|
||||
@@ -38,4 +40,15 @@ class Score(Plugin):
|
||||
# Duplicates in name
|
||||
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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.environment import Env
|
||||
import re
|
||||
|
||||
@@ -16,11 +17,12 @@ name_scores = [
|
||||
'german:-10', 'french:-10', 'spanish:-10', 'swesub:-20', 'danish:-10', 'dutch:-10',
|
||||
# Release groups
|
||||
'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:-40', 'trilogy:-40',
|
||||
]
|
||||
|
||||
|
||||
def nameScore(name, year):
|
||||
''' Calculate score for words in the NZB name '''
|
||||
|
||||
@@ -47,8 +49,8 @@ def nameScore(name, year):
|
||||
|
||||
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))
|
||||
movie_words = re.split('\W+', simplifyString(movie_name))
|
||||
|
||||
@@ -56,15 +58,68 @@ def nameRatioScore(nzb_name, movie_name):
|
||||
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):
|
||||
return 0 if size else -20
|
||||
|
||||
|
||||
def providerScore(provider):
|
||||
if provider in ['NZBMatrix', 'Nzbs', 'Newzbin']:
|
||||
return 30
|
||||
return 20
|
||||
|
||||
if provider in ['Newznab', 'Moovee', 'X264']:
|
||||
if provider in ['Newznab']:
|
||||
return 10
|
||||
|
||||
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]
|
||||
|
||||
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',
|
||||
'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.settings.model import Movie, Release, ReleaseInfo
|
||||
from couchpotato.environment import Env
|
||||
from inspect import ismethod, isfunction
|
||||
from sqlalchemy.exc import InterfaceError
|
||||
import datetime
|
||||
import re
|
||||
@@ -28,6 +29,9 @@ class Searcher(Plugin):
|
||||
# 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'))
|
||||
|
||||
|
||||
addEvent('app.load', self.all_movies)
|
||||
|
||||
def all_movies(self):
|
||||
|
||||
if self.in_progress:
|
||||
@@ -55,7 +59,7 @@ class Searcher(Plugin):
|
||||
except IndexError:
|
||||
fireEvent('library.update', movie_dict['library']['identifier'], force = True)
|
||||
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
|
||||
if self.shuttingDown():
|
||||
@@ -77,36 +81,46 @@ class Searcher(Plugin):
|
||||
pre_releases = fireEvent('quality.pre_releases', single = True)
|
||||
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
||||
available_status = fireEvent('status.get', 'available', single = True)
|
||||
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
||||
|
||||
default_title = getTitle(movie['library'])
|
||||
if not default_title:
|
||||
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']:
|
||||
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
|
||||
|
||||
has_better_quality = 0
|
||||
|
||||
# See if beter quality is available
|
||||
# See if better quality is available
|
||||
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
|
||||
|
||||
# Don't search for quality lower then already available.
|
||||
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)
|
||||
|
||||
results = fireEvent('yarr.search', movie, quality, merge = True)
|
||||
|
||||
sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
|
||||
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
|
||||
if not db.query(Movie).filter_by(id = movie.get('id')).first():
|
||||
return
|
||||
break
|
||||
|
||||
# Add them to this movie releases list
|
||||
for nzb in sorted_results:
|
||||
@@ -137,31 +151,52 @@ class Searcher(Plugin):
|
||||
rls.info.append(rls_info)
|
||||
db.commit()
|
||||
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:
|
||||
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)
|
||||
if downloaded:
|
||||
return True
|
||||
else:
|
||||
if downloaded is True:
|
||||
ret = True
|
||||
break
|
||||
elif downloaded != 'try_next':
|
||||
break
|
||||
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'])
|
||||
break
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
if self.shuttingDown() or ret:
|
||||
break
|
||||
|
||||
fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True)
|
||||
|
||||
#db.close()
|
||||
return False
|
||||
return ret
|
||||
|
||||
def download(self, data, movie, manual = False):
|
||||
|
||||
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:
|
||||
|
||||
@@ -185,7 +220,7 @@ class Searcher(Plugin):
|
||||
if movie['status_id'] == active_status.get('id'):
|
||||
for profile_type in movie['profile']['types']:
|
||||
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
|
||||
log.info('Renamer disabled, marking movie as finished: %s' % log_movie)
|
||||
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
|
||||
|
||||
# Mark release done
|
||||
rls.status_id = done_status.get('id')
|
||||
@@ -196,7 +231,7 @@ class Searcher(Plugin):
|
||||
mvie.status_id = done_status.get('id')
|
||||
db.commit()
|
||||
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()
|
||||
return True
|
||||
@@ -211,27 +246,29 @@ class Searcher(Plugin):
|
||||
retention = Env.setting('retention', section = 'nzb')
|
||||
|
||||
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
|
||||
|
||||
movie_name = simplifyString(nzb['name'])
|
||||
nzb_words = re.split('\W+', movie_name)
|
||||
required_words = [x.strip() for x in self.conf('required_words').split(',')]
|
||||
movie_name = getTitle(movie['library'])
|
||||
movie_words = re.split('\W+', simplifyString(movie_name))
|
||||
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)):
|
||||
log.info("NZB doesn't contain any of the required words.")
|
||||
log.info("Wrong: Required word missing: %s" % nzb['name'])
|
||||
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))
|
||||
if self.conf('ignored_words') and blacklisted:
|
||||
log.info("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
|
||||
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:
|
||||
if p_tag in movie_name:
|
||||
log.info('Wrong: %s, probably pr0n' % (nzb['name']))
|
||||
if p_tag in nzb_words and p_tag not in movie_words:
|
||||
log.info('Wrong: %s, probably pr0n', (nzb['name']))
|
||||
return False
|
||||
|
||||
#qualities = fireEvent('quality.all', single = True)
|
||||
@@ -239,18 +276,28 @@ class Searcher(Plugin):
|
||||
|
||||
# Contains lower quality string
|
||||
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
|
||||
|
||||
|
||||
# File to small
|
||||
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
|
||||
|
||||
# File to large
|
||||
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
|
||||
|
||||
|
||||
@@ -277,7 +324,7 @@ class Searcher(Plugin):
|
||||
if self.checkNFO(nzb['name'], movie['library']['identifier']):
|
||||
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
|
||||
|
||||
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:
|
||||
return True
|
||||
else:
|
||||
# 6 weeks after theater release
|
||||
if dates.get('theater') + 3628800 < now:
|
||||
# 12 weeks after theater release
|
||||
if dates.get('theater') > 0 and dates.get('theater') + 7257600 < now:
|
||||
return True
|
||||
|
||||
# 6 weeks before dvd release
|
||||
if dates.get('dvd') - 3628800 < now:
|
||||
return True
|
||||
if dates.get('dvd') > 0:
|
||||
|
||||
# Dvd should be released
|
||||
if dates.get('dvd') > 0 and dates.get('dvd') < now:
|
||||
return True
|
||||
# 3 weeks before dvd release
|
||||
if dates.get('dvd') - 1814400 < now:
|
||||
return True
|
||||
|
||||
# Dvd should be released
|
||||
if dates.get('dvd') < now:
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
@@ -92,7 +92,7 @@ class StatusPlugin(Plugin):
|
||||
for identifier, label in self.statuses.iteritems():
|
||||
s = db.query(Status).filter_by(identifier = identifier).first()
|
||||
if not s:
|
||||
log.info('Creating status: %s' % label)
|
||||
log.info('Creating status: %s', label)
|
||||
s = Status(
|
||||
identifier = identifier,
|
||||
label = toUnicode(label)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library, FileType
|
||||
from couchpotato.environment import Env
|
||||
import subliminal
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -44,16 +46,26 @@ class Subtitle(Plugin):
|
||||
|
||||
if self.isDisabled(): return
|
||||
|
||||
available_languages = sum(group['subtitle_language'].itervalues(), [])
|
||||
downloaded = []
|
||||
for lang in self.getLanguages():
|
||||
if lang not in available_languages:
|
||||
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)
|
||||
try:
|
||||
available_languages = sum(group['subtitle_language'].itervalues(), [])
|
||||
downloaded = []
|
||||
files = [toUnicode(x) for x in group['files']['movie']]
|
||||
|
||||
for d_sub in downloaded:
|
||||
group['files']['subtitle'].add(d_sub.path)
|
||||
group['subtitle_language'][d_sub.path] = [d_sub.language]
|
||||
for lang in self.getLanguages():
|
||||
if lang not in available_languages:
|
||||
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):
|
||||
return [x.strip() for x in self.conf('languages').split(',')]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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.plugins.base import Plugin
|
||||
import os
|
||||
@@ -17,13 +17,16 @@ class Trailer(Plugin):
|
||||
if self.isDisabled() or len(group['files']['trailer']) > 0: return
|
||||
|
||||
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'), []):
|
||||
destination = '%s-trailer.%s' % (self.getRootName(group), getExt(trailer))
|
||||
if not os.path.isfile(destination):
|
||||
fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
||||
else:
|
||||
log.debug('Trailer already exists: %s' % destination)
|
||||
log.debug('Trailer already exists: %s', destination)
|
||||
|
||||
# Download first and break
|
||||
break
|
||||
|
||||
@@ -73,7 +73,7 @@ class Userscript(Plugin):
|
||||
'movie': fireEvent('userscript.get_movie_via_url', url = url, single = True)
|
||||
}
|
||||
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'
|
||||
|
||||
return jsonified(params)
|
||||
|
||||
@@ -20,7 +20,7 @@ class Automation(Plugin):
|
||||
def _getMovies(self):
|
||||
|
||||
if not self.canCheck():
|
||||
log.debug('Just checked, skipping %s' % self.getName())
|
||||
log.debug('Just checked, skipping %s', self.getName())
|
||||
return []
|
||||
|
||||
self.last_checked = time.time()
|
||||
@@ -35,19 +35,28 @@ class Automation(Plugin):
|
||||
else:
|
||||
return None
|
||||
|
||||
def isMinimal(self, identifier):
|
||||
|
||||
movie = fireEvent('movie.info', identifier = identifier, merge = True)
|
||||
|
||||
def isMinimalMovie(self, movie):
|
||||
if movie['rating'] and movie['rating'].get('imdb'):
|
||||
movie['votes'] = movie['rating']['imdb'][1]
|
||||
movie['rating'] = movie['rating']['imdb'][0]
|
||||
identifier = movie['imdb']
|
||||
for minimal_type in ['year', 'rating', 'votes']:
|
||||
type_value = movie.get(minimal_type, 0)
|
||||
type_min = self.getMinimal(minimal_type)
|
||||
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 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):
|
||||
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',
|
||||
'name': 'imdb_automation',
|
||||
'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': [
|
||||
{
|
||||
'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.providers.automation.base import Automation
|
||||
from couchpotato.environment import Env
|
||||
from dateutil.parser import parse
|
||||
import StringIO
|
||||
import csv
|
||||
import time
|
||||
import traceback
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class IMDB(Automation):
|
||||
class IMDB(Automation, RSS):
|
||||
|
||||
interval = 1800
|
||||
|
||||
@@ -21,34 +21,45 @@ class IMDB(Automation):
|
||||
return
|
||||
|
||||
movies = []
|
||||
headers = {}
|
||||
|
||||
for csv_url in self.conf('automation_urls').split(','):
|
||||
prop_name = 'automation.imdb.last_update.%s' % md5(csv_url)
|
||||
enablers = self.conf('automation_urls_use').split(',')
|
||||
|
||||
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_movie_added = 0
|
||||
try:
|
||||
cache_key = 'imdb_csv.%s' % md5(csv_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
|
||||
cache_key = 'imdb.rss.%s' % md5(rss_url)
|
||||
|
||||
for row in csv_reader:
|
||||
created = int(time.mktime(parse(row[headers['created']]).timetuple()))
|
||||
if created < last_update:
|
||||
rss_data = self.getCache(cache_key, rss_url)
|
||||
data = XMLTree.fromstring(rss_data)
|
||||
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
|
||||
|
||||
imdb = row[headers['const']]
|
||||
if imdb:
|
||||
movies.append(imdb)
|
||||
movies.append(imdb)
|
||||
|
||||
except:
|
||||
log.error('Failed loading IMDB watchlist: %s %s' % (csv_url, traceback.format_exc()))
|
||||
|
||||
Env.prop(prop_name, time.time())
|
||||
log.error('Failed loading IMDB watchlist: %s %s', (rss_url, traceback.format_exc()))
|
||||
|
||||
Env.prop(prop_name, last_movie_added)
|
||||
|
||||
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):
|
||||
|
||||
if self.conf('automation_password'):
|
||||
headers = {
|
||||
'Authorization': "Basic %s" % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
||||
}
|
||||
else:
|
||||
headers = {}
|
||||
try:
|
||||
if self.conf('automation_password'):
|
||||
headers = {
|
||||
'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
||||
}
|
||||
else:
|
||||
headers = {}
|
||||
|
||||
cache_key = 'trakt.%s' % md5(method_url)
|
||||
json_string = self.getCache(cache_key, self.urls['base'] + method_url, headers = headers)
|
||||
return json.loads(json_string)
|
||||
cache_key = 'trakt.%s' % md5(method_url)
|
||||
json_string = self.getCache(cache_key, self.urls['base'] + method_url, headers = headers)
|
||||
if json_string:
|
||||
return json.loads(json_string)
|
||||
except:
|
||||
log.error('Failed to get data from trakt, check your login.')
|
||||
|
||||
return []
|
||||
|
||||
@@ -3,6 +3,9 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from urlparse import urlparse
|
||||
from urllib import quote_plus
|
||||
from couchpotato.core.helpers.encoding import simplifyString
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
@@ -31,7 +34,7 @@ class Provider(Plugin):
|
||||
self.urlopen(test_url, 30)
|
||||
self.is_available[host] = True
|
||||
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
|
||||
|
||||
return self.is_available.get(host, False)
|
||||
@@ -62,8 +65,11 @@ class YarrProvider(Provider):
|
||||
def search(self, movie, quality):
|
||||
return []
|
||||
|
||||
def belongsTo(self, url, host = None):
|
||||
def belongsTo(self, url, provider = None, host = None):
|
||||
try:
|
||||
if provider and provider == self.getName():
|
||||
return self
|
||||
|
||||
hostname = urlparse(url).hostname
|
||||
if host and hostname in host:
|
||||
return self
|
||||
@@ -73,7 +79,7 @@ class YarrProvider(Provider):
|
||||
if hostname in download_url:
|
||||
return self
|
||||
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
|
||||
|
||||
@@ -106,4 +112,4 @@ class YarrProvider(Provider):
|
||||
return [self.cat_backup_id]
|
||||
|
||||
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):
|
||||
if self.isDisabled(): return
|
||||
|
||||
log.info('Creating %s metadata.' % self.getName())
|
||||
log.info('Creating %s metadata.', self.getName())
|
||||
|
||||
# Update library to get latest info
|
||||
try:
|
||||
updated_library = fireEvent('library.update', release['library']['identifier'], force = True, single = True)
|
||||
release['library'] = mergeDicts(release['library'], updated_library)
|
||||
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)
|
||||
meta_name = os.path.basename(root_name)
|
||||
@@ -44,14 +44,14 @@ class MetaDataBase(Plugin):
|
||||
# Get file content
|
||||
content = getattr(self, 'get' + file_type.capitalize())(movie_info = movie_info, data = release)
|
||||
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):
|
||||
shutil.copy2(content, name)
|
||||
else:
|
||||
self.createFile(name, content)
|
||||
|
||||
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):
|
||||
return
|
||||
@@ -75,7 +75,7 @@ class MetaDataBase(Plugin):
|
||||
break
|
||||
|
||||
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')
|
||||
|
||||
def getFanart(self, movie_info = {}, data = {}):
|
||||
|
||||
@@ -77,7 +77,7 @@ class XBMC(MetaDataBase):
|
||||
votes.text = str(v)
|
||||
break
|
||||
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
|
||||
for genre in movie_info.get('genres', []):
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from couchpotato import get_session
|
||||
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.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library
|
||||
import time
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -23,11 +22,19 @@ class MovieResultModifier(Plugin):
|
||||
|
||||
# Combine on imdb id
|
||||
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):
|
||||
temp[imdb] = self.getLibraryTags(imdb)
|
||||
order.append(imdb)
|
||||
|
||||
if item.get('via_imdb'):
|
||||
if order.index(imdb):
|
||||
order.remove(imdb)
|
||||
order.insert(0, imdb)
|
||||
|
||||
# Merge dicts
|
||||
temp[imdb] = mergeDicts(temp[imdb], item)
|
||||
|
||||
@@ -61,7 +68,7 @@ class MovieResultModifier(Plugin):
|
||||
if release.status_id == done_status['id']:
|
||||
temp['in_library'] = fireEvent('movie.get', movie.id, single = True)
|
||||
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()
|
||||
return temp
|
||||
|
||||
@@ -32,10 +32,10 @@ class CouchPotatoApi(MovieProvider):
|
||||
headers = {'X-CP-Version': fireEvent('app.version', single = True)}
|
||||
data = self.urlopen((self.api_url % ('eta')) + (identifier + '/'), headers = headers)
|
||||
dates = json.loads(data)
|
||||
log.debug('Found ETA for %s: %s' % (identifier, dates))
|
||||
log.debug('Found ETA for %s: %s', (identifier, dates))
|
||||
return dates
|
||||
except Exception, e:
|
||||
log.error('Error getting ETA for %s: %s' % (identifier, e))
|
||||
log.error('Error getting ETA for %s: %s', (identifier, e))
|
||||
|
||||
return {}
|
||||
|
||||
@@ -43,9 +43,9 @@ class CouchPotatoApi(MovieProvider):
|
||||
try:
|
||||
data = self.urlopen((self.api_url % ('suggest')) + ','.join(movies) + '/' + ','.join(ignore) + '/')
|
||||
suggestions = json.loads(data)
|
||||
log.info('Found Suggestions for %s' % (suggestions))
|
||||
log.info('Found Suggestions for %s', (suggestions))
|
||||
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
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class IMDBAPI(MovieProvider):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('movie.search', self.search)
|
||||
addEvent('movie.searchimdb', self.search)
|
||||
addEvent('movie.info', self.getInfo)
|
||||
|
||||
def search(self, q, limit = 12):
|
||||
@@ -36,7 +37,7 @@ class IMDBAPI(MovieProvider):
|
||||
if cached:
|
||||
result = self.parseMovie(cached)
|
||||
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 []
|
||||
@@ -54,7 +55,7 @@ class IMDBAPI(MovieProvider):
|
||||
if cached:
|
||||
result = self.parseMovie(cached)
|
||||
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 {}
|
||||
@@ -82,13 +83,14 @@ class IMDBAPI(MovieProvider):
|
||||
year = tryInt(movie.get('Year', ''))
|
||||
|
||||
movie_data = {
|
||||
'via_imdb': True,
|
||||
'titles': [movie.get('Title')] if movie.get('Title') else [],
|
||||
'original_title': movie.get('Title', ''),
|
||||
'images': {
|
||||
'poster': [movie.get('Poster', '')] if movie.get('Poster') and len(movie.get('Poster', '')) > 4 else [],
|
||||
},
|
||||
'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))),
|
||||
},
|
||||
'imdb': str(movie.get('imdbID', '')),
|
||||
@@ -102,17 +104,17 @@ class IMDBAPI(MovieProvider):
|
||||
'actors': movie.get('Actors', '').split(','),
|
||||
}
|
||||
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
|
||||
|
||||
def runtimeToMinutes(self, runtime_str):
|
||||
runtime = 0
|
||||
|
||||
regex = '(\d*.?\d+).(hr|hrs|mins|min)+'
|
||||
regex = '(\d*.?\d+).(h|hr|hrs|mins|min)+'
|
||||
matches = re.findall(regex, runtime_str)
|
||||
for match in matches:
|
||||
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
|
||||
|
||||
@@ -3,7 +3,6 @@ from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.movie.base import MovieProvider
|
||||
from libs.themoviedb import tmdb
|
||||
import re
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -29,7 +28,7 @@ class TheMovieDb(MovieProvider):
|
||||
results = self.getCache(cache_key)
|
||||
|
||||
if not results:
|
||||
log.debug('Searching for movie by hash: %s' % file)
|
||||
log.debug('Searching for movie by hash: %s', file)
|
||||
try:
|
||||
raw = tmdb.searchByHashingFile(file)
|
||||
|
||||
@@ -37,15 +36,15 @@ class TheMovieDb(MovieProvider):
|
||||
if raw:
|
||||
try:
|
||||
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)
|
||||
return results
|
||||
except SyntaxError, e:
|
||||
log.error('Failed to parse XML response: %s' % e)
|
||||
log.error('Failed to parse XML response: %s', e)
|
||||
return False
|
||||
except:
|
||||
log.debug('No movies known by hash for: %s' % file)
|
||||
log.debug('No movies known by hash for: %s', file)
|
||||
pass
|
||||
|
||||
return results
|
||||
@@ -61,7 +60,7 @@ class TheMovieDb(MovieProvider):
|
||||
results = self.getCache(cache_key)
|
||||
|
||||
if not results:
|
||||
log.debug('Searching for movie: %s' % q)
|
||||
log.debug('Searching for movie: %s', q)
|
||||
raw = tmdb.search(search_string)
|
||||
|
||||
results = []
|
||||
@@ -76,18 +75,21 @@ class TheMovieDb(MovieProvider):
|
||||
if nr == limit:
|
||||
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)
|
||||
return results
|
||||
except SyntaxError, e:
|
||||
log.error('Failed to parse XML response: %s' % e)
|
||||
log.error('Failed to parse XML response: %s', e)
|
||||
return False
|
||||
|
||||
return results
|
||||
|
||||
def getInfo(self, identifier = None):
|
||||
|
||||
if not identifier:
|
||||
return {}
|
||||
|
||||
cache_key = 'tmdb.cache.%s' % identifier
|
||||
result = self.getCache(cache_key)
|
||||
|
||||
@@ -96,7 +98,7 @@ class TheMovieDb(MovieProvider):
|
||||
movie = None
|
||||
|
||||
try:
|
||||
log.debug('Getting info: %s' % cache_key)
|
||||
log.debug('Getting info: %s', cache_key)
|
||||
movie = tmdb.imdbLookup(id = identifier)
|
||||
except:
|
||||
pass
|
||||
@@ -117,7 +119,7 @@ class TheMovieDb(MovieProvider):
|
||||
movie = None
|
||||
|
||||
try:
|
||||
log.debug('Getting info: %s' % cache_key)
|
||||
log.debug('Getting info: %s', cache_key)
|
||||
movie = tmdb.getMovieInfo(id = id)
|
||||
except:
|
||||
pass
|
||||
@@ -148,7 +150,8 @@ class TheMovieDb(MovieProvider):
|
||||
year = None
|
||||
|
||||
movie_data = {
|
||||
'id': int(movie.get('id', 0)),
|
||||
'via_tmdb': True,
|
||||
'tmdb_id': int(movie.get('id', 0)),
|
||||
'titles': [toUnicode(movie.get('name'))],
|
||||
'original_title': movie.get('original_name'),
|
||||
'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.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.logger import CPLog
|
||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||
@@ -22,11 +23,13 @@ class Mysterbin(NZBProvider):
|
||||
def search(self, movie, quality):
|
||||
|
||||
results = []
|
||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
||||
if self.isDisabled():
|
||||
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(','):
|
||||
if len(q) + len(ignored.strip()) > 126:
|
||||
break
|
||||
q = '%s -%s' % (q, ignored.strip())
|
||||
|
||||
params = {
|
||||
@@ -39,28 +42,28 @@ class Mysterbin(NZBProvider):
|
||||
'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))
|
||||
if data:
|
||||
|
||||
try:
|
||||
html = BeautifulSoup(data)
|
||||
resultable = html.find('table', attrs = {'class':'t'})
|
||||
for result in resultable.findAll('tr'):
|
||||
for result in resultable.find_all('tr'):
|
||||
|
||||
try:
|
||||
myster_id = result.find('input', attrs = {'class': 'check4nzb'})['value']
|
||||
|
||||
# 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:
|
||||
age = tryInt(temp.split(' ')[0])
|
||||
break
|
||||
|
||||
# size
|
||||
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():
|
||||
size = self.parseSize(temp)
|
||||
break
|
||||
@@ -71,13 +74,14 @@ class Mysterbin(NZBProvider):
|
||||
|
||||
new = {
|
||||
'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',
|
||||
'provider': self.getName(),
|
||||
'age': age,
|
||||
'size': size,
|
||||
'url': self.urls['download'] % myster_id,
|
||||
'description': description,
|
||||
'download': self.download,
|
||||
'check_nzb': False,
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Newzbin(NZBProvider, RSS):
|
||||
def search(self, movie, quality):
|
||||
|
||||
results = []
|
||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
||||
if self.isDisabled():
|
||||
return results
|
||||
|
||||
format_id = self.getFormatId(type)
|
||||
@@ -57,11 +57,12 @@ class Newzbin(NZBProvider, RSS):
|
||||
'category': '6',
|
||||
'ps_rb_video_format': str(cat_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)
|
||||
cache_key = str('newzbin.%s.%s.%s' % (movie['library']['identifier'], str(format_id), str(cat_id)))
|
||||
single_cat = True
|
||||
|
||||
data = self.getCache(cache_key)
|
||||
if not data:
|
||||
@@ -81,7 +82,7 @@ class Newzbin(NZBProvider, RSS):
|
||||
data = XMLTree.fromstring(data)
|
||||
nzbs = self.getElements(data, 'channel/item')
|
||||
except Exception, e:
|
||||
log.debug('%s, %s' % (self.getName(), e))
|
||||
log.debug('%s, %s', (self.getName(), e))
|
||||
return results
|
||||
|
||||
for nzb in nzbs:
|
||||
@@ -115,12 +116,12 @@ class Newzbin(NZBProvider, RSS):
|
||||
'description': self.getTextElement(nzb, "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 = True, single_category = single_cat, single = True)
|
||||
imdb_results = True, single = True)
|
||||
if is_correct_movie:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
|
||||
@@ -132,7 +133,7 @@ class Newzbin(NZBProvider, RSS):
|
||||
|
||||
def download(self, url = '', nzb_id = ''):
|
||||
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 = {
|
||||
'username' : self.conf('username'),
|
||||
@@ -140,7 +141,7 @@ class Newzbin(NZBProvider, RSS):
|
||||
'reportid' : nzb_id
|
||||
}, show_error = False)
|
||||
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
|
||||
|
||||
def getFormatId(self, format):
|
||||
|
||||
@@ -6,7 +6,10 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||
from couchpotato.environment import Env
|
||||
from dateutil.parser import parse
|
||||
from urllib2 import HTTPError
|
||||
from urlparse import urlparse
|
||||
import time
|
||||
import traceback
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -20,6 +23,8 @@ class Newznab(NZBProvider, RSS):
|
||||
'search': 'movie',
|
||||
}
|
||||
|
||||
limits_reached = {}
|
||||
|
||||
cat_ids = [
|
||||
([2010], ['dvdr']),
|
||||
([2030], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||
@@ -46,7 +51,7 @@ class Newznab(NZBProvider, RSS):
|
||||
def singleFeed(self, host):
|
||||
|
||||
results = []
|
||||
if self.isDisabled(host) or not self.isAvailable(self.getUrl(host['host'], self.urls['search'])):
|
||||
if self.isDisabled(host):
|
||||
return results
|
||||
|
||||
arguments = tryUrlencode({
|
||||
@@ -78,7 +83,7 @@ class Newznab(NZBProvider, RSS):
|
||||
def singleSearch(self, host, movie, quality):
|
||||
|
||||
results = []
|
||||
if self.isDisabled(host) or not self.isAvailable(self.getUrl(host['host'], self.urls['search'])):
|
||||
if self.isDisabled(host):
|
||||
return results
|
||||
|
||||
cat_id = self.getCatId(quality['identifier'])
|
||||
@@ -107,7 +112,7 @@ class Newznab(NZBProvider, RSS):
|
||||
data = XMLTree.fromstring(data)
|
||||
nzbs = self.getElements(data, 'channel/item')
|
||||
except Exception, e:
|
||||
log.debug('%s, %s' % (self.getName(), e))
|
||||
log.debug('%s, %s', (self.getName(), e))
|
||||
return results
|
||||
|
||||
results = []
|
||||
@@ -121,8 +126,8 @@ class Newznab(NZBProvider, RSS):
|
||||
elif item.attrib.get('name') == 'usenetdate':
|
||||
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 size is 0: log.debug('Size 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")))
|
||||
|
||||
id = self.getTextElement(nzb, "guid").split('/')[-1:].pop()
|
||||
new = {
|
||||
@@ -139,13 +144,12 @@ class Newznab(NZBProvider, RSS):
|
||||
}
|
||||
|
||||
if not for_feed:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
|
||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||
nzb = new, movie = movie, quality = quality,
|
||||
imdb_results = True, single_category = single_cat, single = True)
|
||||
|
||||
if is_correct_movie:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
else:
|
||||
@@ -153,7 +157,7 @@ class Newznab(NZBProvider, RSS):
|
||||
|
||||
return results
|
||||
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
|
||||
|
||||
def getHosts(self):
|
||||
@@ -172,17 +176,15 @@ class Newznab(NZBProvider, RSS):
|
||||
|
||||
return list
|
||||
|
||||
def belongsTo(self, url):
|
||||
def belongsTo(self, url, provider = None):
|
||||
|
||||
hosts = self.getHosts()
|
||||
|
||||
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:
|
||||
return result
|
||||
|
||||
return
|
||||
|
||||
def getUrl(self, host, type):
|
||||
return cleanHost(host) + 'api?t=' + type
|
||||
|
||||
@@ -194,3 +196,27 @@ class Newznab(NZBProvider, RSS):
|
||||
|
||||
def getApiExt(self, host):
|
||||
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.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.variable import tryInt, getTitle
|
||||
from couchpotato.core.logger import CPLog
|
||||
@@ -19,15 +20,15 @@ class NZBClub(NZBProvider, RSS):
|
||||
'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):
|
||||
|
||||
results = []
|
||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
||||
if self.isDisabled():
|
||||
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(','):
|
||||
q = '%s -%s' % (q, ignored.strip())
|
||||
|
||||
@@ -40,7 +41,7 @@ class NZBClub(NZBProvider, RSS):
|
||||
'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))
|
||||
if data:
|
||||
try:
|
||||
@@ -48,7 +49,7 @@ class NZBClub(NZBProvider, RSS):
|
||||
data = XMLTree.fromstring(data)
|
||||
nzbs = self.getElements(data, 'channel/item')
|
||||
except Exception, e:
|
||||
log.debug('%s, %s' % (self.getName(), e))
|
||||
log.debug('%s, %s', (self.getName(), e))
|
||||
return results
|
||||
|
||||
for nzb in nzbs:
|
||||
@@ -58,10 +59,15 @@ class NZBClub(NZBProvider, RSS):
|
||||
size = enclosure['length']
|
||||
date = self.getTextElement(nzb, "pubDate")
|
||||
|
||||
full_description = self.getCache('nzbclub.%s' % nzbclub_id, self.getTextElement(nzb, "link"), cache_timeout = 25920000)
|
||||
html = BeautifulSoup(full_description)
|
||||
nfo_pre = html.find('pre', attrs = {'class':'nfo'})
|
||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
||||
def extra_check(item):
|
||||
full_description = self.getCache('nzbclub.%s' % nzbclub_id, item['detail_url'], cache_timeout = 25920000)
|
||||
|
||||
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 = {
|
||||
'id': nzbclub_id,
|
||||
@@ -73,19 +79,17 @@ class NZBClub(NZBProvider, RSS):
|
||||
'url': enclosure['url'].replace(' ', '_'),
|
||||
'download': self.download,
|
||||
'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',
|
||||
nzb = new, movie = movie, quality = quality,
|
||||
imdb_results = False, single_category = False, single = True)
|
||||
|
||||
if is_correct_movie:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
|
||||
@@ -94,3 +98,21 @@ class NZBClub(NZBProvider, RSS):
|
||||
log.error('Failed to parse XML response from NZBClub')
|
||||
|
||||
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.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.variable import tryInt, getTitle
|
||||
from couchpotato.core.logger import CPLog
|
||||
@@ -9,6 +10,7 @@ from couchpotato.environment import Env
|
||||
from dateutil.parser import parse
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -26,10 +28,10 @@ class NzbIndex(NZBProvider, RSS):
|
||||
def search(self, movie, quality):
|
||||
|
||||
results = []
|
||||
if self.isDisabled() or not self.isAvailable(self.urls['api']):
|
||||
if self.isDisabled():
|
||||
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({
|
||||
'q': q,
|
||||
'age': Env.setting('retention', 'nzb'),
|
||||
@@ -52,7 +54,7 @@ class NzbIndex(NZBProvider, RSS):
|
||||
data = XMLTree.fromstring(data)
|
||||
nzbs = self.getElements(data, 'channel/item')
|
||||
except Exception, e:
|
||||
log.debug('%s, %s' % (self.getName(), e))
|
||||
log.debug('%s, %s', (self.getName(), e))
|
||||
return results
|
||||
|
||||
for nzb in nzbs:
|
||||
@@ -63,42 +65,56 @@ class NzbIndex(NZBProvider, RSS):
|
||||
|
||||
try:
|
||||
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:
|
||||
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 = {
|
||||
'id': nzbindex_id,
|
||||
'type': 'nzb',
|
||||
'provider': self.getName(),
|
||||
'download': self.download,
|
||||
'name': self.getTextElement(nzb, "title"),
|
||||
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
||||
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
||||
'url': enclosure['url'],
|
||||
'detail_url': enclosure['url'].replace('/download/', '/release/'),
|
||||
'description': description,
|
||||
'get_more_info': self.getMoreInfo,
|
||||
'extra_check': extra_check,
|
||||
'check_nzb': True,
|
||||
}
|
||||
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:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
|
||||
return results
|
||||
except SyntaxError:
|
||||
log.error('Failed to parse XML response from NZBMatrix.com')
|
||||
except:
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
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):
|
||||
return NZBProvider.isEnabled(self) and self.conf('enabled')
|
||||
|
||||
@@ -32,7 +32,7 @@ class NZBMatrix(NZBProvider, RSS):
|
||||
|
||||
results = []
|
||||
|
||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
||||
if self.isDisabled():
|
||||
return results
|
||||
|
||||
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)
|
||||
nzbs = self.getElements(data, 'channel/item')
|
||||
except Exception, e:
|
||||
log.debug('%s, %s' % (self.getName(), e))
|
||||
log.debug('%s, %s', (self.getName(), e))
|
||||
return results
|
||||
|
||||
for nzb in nzbs:
|
||||
@@ -83,13 +83,13 @@ class NZBMatrix(NZBProvider, RSS):
|
||||
'description': self.getTextElement(nzb, "description"),
|
||||
'check_nzb': True,
|
||||
}
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
|
||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||
nzb = new, movie = movie, quality = quality,
|
||||
imdb_results = True, single_category = single_cat, single = True)
|
||||
|
||||
if is_correct_movie:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
|
||||
@@ -99,6 +99,9 @@ class NZBMatrix(NZBProvider, RSS):
|
||||
|
||||
return results
|
||||
|
||||
def download(self, url = '', nzb_id = ''):
|
||||
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()})
|
||||
|
||||
def getApiExt(self):
|
||||
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
|
||||
import cookielib
|
||||
import traceback
|
||||
import urllib2
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class TorrentProvider(YarrProvider):
|
||||
|
||||
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.helpers.variable import tryInt, getTitle
|
||||
from couchpotato.core.logger import CPLog
|
||||
@@ -34,7 +34,7 @@ class KickAssTorrents(TorrentProvider):
|
||||
def search(self, movie, quality):
|
||||
|
||||
results = []
|
||||
if self.isDisabled() or not self.isAvailable(self.urls['test']):
|
||||
if self.isDisabled():
|
||||
return results
|
||||
|
||||
cache_key = 'kickasstorrents.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
||||
@@ -47,14 +47,14 @@ class KickAssTorrents(TorrentProvider):
|
||||
try:
|
||||
html = BeautifulSoup(data)
|
||||
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:
|
||||
continue
|
||||
|
||||
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'):
|
||||
continue
|
||||
|
||||
@@ -68,15 +68,17 @@ class KickAssTorrents(TorrentProvider):
|
||||
}
|
||||
|
||||
nr = 0
|
||||
for td in temp.findAll('td'):
|
||||
for td in temp.find_all('td'):
|
||||
column_name = table_order[nr]
|
||||
if column_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['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
|
||||
elif column_name is 'size':
|
||||
new['size'] = self.parseSize(td.text)
|
||||
@@ -97,7 +99,7 @@ class KickAssTorrents(TorrentProvider):
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
except:
|
||||
log.error('Failed parsing KickAssTorrents: %s' % traceback.format_exc())
|
||||
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
+6
-6
@@ -1,16 +1,16 @@
|
||||
from .main import Moovee
|
||||
from .main import PublicHD
|
||||
|
||||
def start():
|
||||
return Moovee()
|
||||
return PublicHD()
|
||||
|
||||
config = [{
|
||||
'name': 'moovee',
|
||||
'name': 'publichd',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': '#alt.binaries.moovee',
|
||||
'description': 'SD movies only',
|
||||
'name': 'PublicHD',
|
||||
'description': 'Public Torrent site with only HD content.',
|
||||
'options': [
|
||||
{
|
||||
'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