Merge branch 'refs/heads/develop'

This commit is contained in:
Ruud
2014-02-14 19:39:47 +01:00
255 changed files with 2191 additions and 1306 deletions

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
from __future__ import print_function
from logging import handlers
from os.path import dirname
import logging
@@ -132,14 +133,14 @@ if __name__ == '__main__':
pass
except SystemExit:
raise
except socket.error as (nr, msg):
except socket.error as e:
# log when socket receives SIGINT, but continue.
# previous code would have skipped over other types of IO errors too.
if nr != 4:
try:
l.log.critical(traceback.format_exc())
except:
print traceback.format_exc()
print(traceback.format_exc())
raise
except:
try:
@@ -148,7 +149,7 @@ if __name__ == '__main__':
if l:
l.log.critical(traceback.format_exc())
else:
print traceback.format_exc()
print(traceback.format_exc())
except:
print traceback.format_exc()
print(traceback.format_exc())
raise

View File

@@ -9,13 +9,12 @@ import os
import time
import traceback
log = CPLog(__name__)
log = CPLog(__name__)
views = {}
template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates'))
class BaseHandler(RequestHandler):
def get_current_user(self):
@@ -24,9 +23,10 @@ class BaseHandler(RequestHandler):
if username and password:
return self.get_secure_cookie('user')
else: # Login when no username or password are set
else: # Login when no username or password are set
return True
# Main web handler
class WebHandler(BaseHandler):
@@ -43,11 +43,13 @@ class WebHandler(BaseHandler):
log.error("Failed doing web request '%s': %s", (route, traceback.format_exc()))
self.write({'success': False, 'error': 'Failed returning results'})
def addView(route, func, static = False):
views[route] = func
def get_session(engine = None):
return Env.getSession(engine)
def get_session():
return Env.getSession()
# Web view
@@ -55,12 +57,10 @@ def index():
return template_loader.load('index.html').generate(sep = os.sep, fireEvent = fireEvent, Env = Env)
addView('', index)
# API docs
def apiDocs():
routes = []
for route in api.iterkeys():
routes.append(route)
routes = list(api.keys())
if api_docs.get(''):
del api_docs['']
@@ -70,21 +70,22 @@ def apiDocs():
addView('docs', apiDocs)
# Make non basic auth option to get api key
class KeyHandler(RequestHandler):
def get(self, *args, **kwargs):
api = None
api_key = None
try:
username = Env.setting('username')
password = Env.setting('password')
if (self.get_argument('u') == md5(username) or not username) and (self.get_argument('p') == password or not password):
api = Env.setting('api_key')
api_key = Env.setting('api_key')
self.write({
'success': api is not None,
'api_key': api
'success': api_key is not None,
'api_key': api_key
})
except:
log.error('Failed doing key request: %s', (traceback.format_exc()))
@@ -102,20 +103,21 @@ class LoginHandler(BaseHandler):
def post(self, *args, **kwargs):
api = None
api_key = None
username = Env.setting('username')
password = Env.setting('password')
if (self.get_argument('username') == username or not username) and (md5(self.get_argument('password')) == password or not password):
api = Env.setting('api_key')
api_key = Env.setting('api_key')
if api:
if api_key:
remember_me = tryInt(self.get_argument('remember_me', default = 0))
self.set_secure_cookie('user', api, expires_days = 30 if remember_me > 0 else None)
self.set_secure_cookie('user', api_key, expires_days = 30 if remember_me > 0 else None)
self.redirect(Env.get('web_base'))
class LogoutHandler(BaseHandler):
def get(self, *args, **kwargs):
@@ -136,4 +138,3 @@ def page_not_found(rh):
rh.set_status(404)
rh.write('Wrong API key used')

View File

@@ -20,6 +20,7 @@ api_nonblock = {}
api_docs = {}
api_docs_missing = []
def run_async(func):
@wraps(func)
def async_func(*args, **kwargs):
@@ -29,6 +30,7 @@ def run_async(func):
return async_func
# NonBlock API handler
class NonBlockHandler(RequestHandler):
@@ -61,6 +63,7 @@ class NonBlockHandler(RequestHandler):
self.stopper = None
def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
api_nonblock[route] = func_tuple
@@ -69,6 +72,7 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
else:
api_docs_missing.append(route)
# Blocking API handler
class ApiHandler(RequestHandler):
@@ -98,11 +102,12 @@ class ApiHandler(RequestHandler):
@run_async
def run_handler(callback):
try:
result = api[route](**kwargs)
callback(result)
res = api[route](**kwargs)
callback(res)
except:
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
callback({'success': False, 'error': 'Failed returning results'})
result = yield tornado.gen.Task(run_handler)
# Check JSONP callback
@@ -122,6 +127,7 @@ class ApiHandler(RequestHandler):
api_locks[route].release()
def addApiView(route, func, static = False, docs = None, **kwargs):
if static: func(route)

View File

@@ -1,6 +1,7 @@
from .main import Core
from uuid import uuid4
def start():
return Core()

View File

@@ -117,7 +117,7 @@ class Core(Plugin):
if len(still_running) == 0:
break
elif starttime < time.time() - 30: # Always force break after 30s wait
elif starttime < time.time() - 30: # Always force break after 30s wait
break
running = list(set(still_running) - set(self.ignore_restart))

View File

@@ -1,5 +1,6 @@
from .main import ClientScript
def start():
return ClientScript()

View File

@@ -53,9 +53,9 @@ class ClientScript(Plugin):
}
urls = {'style': {}, 'script': {}, }
minified = {'style': {}, 'script': {}, }
paths = {'style': {}, 'script': {}, }
urls = {'style': {}, 'script': {}}
minified = {'style': {}, 'script': {}}
paths = {'style': {}, 'script': {}}
comment = {
'style': '/*** %s:%d ***/\n',
'script': '// %s:%d\n'

View File

@@ -1,5 +1,6 @@
from .main import Desktop
def start():
return Desktop()

View File

@@ -1,5 +1,6 @@
from .main import Scheduler
def start():
return Scheduler()

View File

@@ -2,6 +2,7 @@ from .main import Updater
from couchpotato.environment import Env
import os
def start():
return Updater()

View File

@@ -15,6 +15,7 @@ import time
import traceback
import version
import zipfile
from six.moves import filter
log = CPLog(__name__)
@@ -63,7 +64,7 @@ class Updater(Plugin):
fireEvent('schedule.remove', 'updater.check', single = True)
if self.isEnabled():
fireEvent('schedule.interval', 'updater.check', self.autoUpdate, hours = 6)
self.autoUpdate() # Check after enabling
self.autoUpdate() # Check after enabling
def autoUpdate(self):
if self.isEnabled() and self.check() and self.conf('automatic') and not self.updater.update_failed:
@@ -151,6 +152,9 @@ class BaseUpdater(Plugin):
'branch': self.branch,
}
def getVersion(self):
pass
def check(self):
pass
@@ -179,7 +183,6 @@ class BaseUpdater(Plugin):
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
class GitUpdater(BaseUpdater):
def __init__(self, git_command):
@@ -206,7 +209,7 @@ class GitUpdater(BaseUpdater):
if not self.version:
try:
output = self.repo.getHead() # Yes, please
output = self.repo.getHead() # Yes, please
log.debug('Git version output: %s', output.hash)
self.version = {
'repr': 'git:(%s:%s % s) %s (%s)' % (self.repo_user, self.repo_name, self.branch, output.hash[:8], datetime.fromtimestamp(output.getDate())),
@@ -214,7 +217,7 @@ class GitUpdater(BaseUpdater):
'date': output.getDate(),
'type': 'git',
}
except Exception, e:
except Exception as e:
log.error('Failed using GIT updater, running from source, you need to have GIT installed. %s', e)
return 'No GIT'
@@ -250,7 +253,6 @@ class GitUpdater(BaseUpdater):
return False
class SourceUpdater(BaseUpdater):
def __init__(self):
@@ -276,9 +278,9 @@ class SourceUpdater(BaseUpdater):
# Extract
if download_data.get('type') == 'zip':
zip = zipfile.ZipFile(destination)
zip.extractall(extracted_path)
zip.close()
zip_file = zipfile.ZipFile(destination)
zip_file.extractall(extracted_path)
zip_file.close()
else:
tar = tarfile.open(destination)
tar.extractall(path = extracted_path)
@@ -345,13 +347,12 @@ class SourceUpdater(BaseUpdater):
return True
def removeDir(self, path):
try:
if os.path.isdir(path):
shutil.rmtree(path)
except OSError, inst:
os.chmod(inst.filename, 0777)
except OSError as inst:
os.chmod(inst.filename, 0o777)
self.removeDir(path)
def getVersion(self):
@@ -366,7 +367,7 @@ class SourceUpdater(BaseUpdater):
self.version = output
self.version['type'] = 'source'
self.version['repr'] = 'source:(%s:%s % s) %s (%s)' % (self.repo_user, self.repo_name, self.branch, output.get('hash', '')[:8], datetime.fromtimestamp(output.get('date', 0)))
except Exception, e:
except Exception as e:
log.error('Failed using source updater. %s', e)
return {}
@@ -396,7 +397,7 @@ class SourceUpdater(BaseUpdater):
return {
'hash': commit['sha'],
'date': int(time.mktime(parse(commit['commit']['committer']['date']).timetuple())),
'date': int(time.mktime(parse(commit['commit']['committer']['date']).timetuple())),
}
except:
log.error('Failed getting latest request from github: %s', traceback.format_exc())
@@ -441,7 +442,7 @@ class DesktopUpdater(BaseUpdater):
if latest and latest != current_version.get('hash'):
self.update_version = {
'hash': latest,
'date': None,
'date': None,
'changelog': self.desktop._changelogURL,
}

View File

@@ -24,7 +24,7 @@ var UpdaterBase = new Class({
self.doUpdate();
else {
App.unBlockPage();
App.on('message', 'No updates available');
App.trigger('message', ['No updates available']);
}
}
})

View File

@@ -1,6 +1,7 @@
from .main import Blackhole
from couchpotato.core.helpers.variable import getDownloadDir
def start():
return Blackhole()

View File

@@ -1,5 +1,6 @@
from .main import Deluge
def start():
return Deluge()

View File

@@ -109,7 +109,7 @@ class Deluge(Downloader):
continue
log.debug('name=%s / id=%s / save_path=%s / move_on_completed=%s / move_completed_path=%s / hash=%s / progress=%s / state=%s / eta=%s / ratio=%s / stop_ratio=%s / is_seed=%s / is_finished=%s / paused=%s', (torrent['name'], torrent['hash'], torrent['save_path'], torrent['move_on_completed'], torrent['move_completed_path'], torrent['hash'], torrent['progress'], torrent['state'], torrent['eta'], torrent['ratio'], torrent['stop_ratio'], torrent['is_seed'], torrent['is_finished'], torrent['paused']))
# Deluge has no easy way to work out if a torrent is stalled or failing.
#status = 'failed'
status = 'busy'
@@ -125,11 +125,11 @@ class Deluge(Downloader):
download_dir = sp(torrent['save_path'])
if torrent['move_on_completed']:
download_dir = torrent['move_completed_path']
torrent_files = []
for file_item in torrent['files']:
torrent_files.append(sp(os.path.join(download_dir, file_item['path'])))
release_downloads.append({
'id': torrent['hash'],
'name': torrent['name'],
@@ -157,6 +157,7 @@ class Deluge(Downloader):
log.debug('Requesting Deluge to remove the torrent %s%s.', (release_download['name'], ' and cleanup the downloaded files' if delete_files else ''))
return self.drpc.remove_torrent(release_download['id'], remove_local_data = delete_files)
class DelugeRPC(object):
host = 'localhost'
@@ -187,7 +188,7 @@ class DelugeRPC(object):
if torrent_id and options['label']:
self.client.label.set_torrent(torrent_id, options['label']).get()
except Exception, err:
except Exception as err:
log.error('Failed to add torrent magnet %s: %s %s', (torrent, err, traceback.format_exc()))
finally:
if self.client:
@@ -205,7 +206,7 @@ class DelugeRPC(object):
if torrent_id and options['label']:
self.client.label.set_torrent(torrent_id, options['label']).get()
except Exception, err:
except Exception as err:
log.error('Failed to add torrent file %s: %s %s', (filename, err, traceback.format_exc()))
finally:
if self.client:
@@ -218,7 +219,7 @@ class DelugeRPC(object):
try:
self.connect()
ret = self.client.core.get_torrents_status({'id': ids}, ('name', 'hash', 'save_path', 'move_completed_path', 'progress', 'state', 'eta', 'ratio', 'stop_ratio', 'is_seed', 'is_finished', 'paused', 'move_on_completed', 'files')).get()
except Exception, err:
except Exception as err:
log.error('Failed to get all torrents: %s %s', (err, traceback.format_exc()))
finally:
if self.client:
@@ -229,7 +230,7 @@ class DelugeRPC(object):
try:
self.connect()
self.client.core.pause_torrent(torrent_ids).get()
except Exception, err:
except Exception as err:
log.error('Failed to pause torrent: %s %s', (err, traceback.format_exc()))
finally:
if self.client:
@@ -239,7 +240,7 @@ class DelugeRPC(object):
try:
self.connect()
self.client.core.resume_torrent(torrent_ids).get()
except Exception, err:
except Exception as err:
log.error('Failed to resume torrent: %s %s', (err, traceback.format_exc()))
finally:
if self.client:
@@ -250,7 +251,7 @@ class DelugeRPC(object):
try:
self.connect()
ret = self.client.core.remove_torrent(torrent_id, remove_local_data).get()
except Exception, err:
except Exception as err:
log.error('Failed to remove torrent: %s %s', (err, traceback.format_exc()))
finally:
if self.client:

View File

@@ -1,5 +1,6 @@
from .main import NZBGet
def start():
return NZBGet()

View File

@@ -42,7 +42,7 @@ class NZBGet(Downloader):
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return False
except xmlrpclib.ProtocolError, e:
except xmlrpclib.ProtocolError as e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
@@ -56,7 +56,7 @@ class NZBGet(Downloader):
if xml_response:
log.info('NZB sent successfully to NZBGet')
nzb_id = md5(data['url']) # about as unique as they come ;)
nzb_id = md5(data['url']) # about as unique as they come ;)
couchpotato_id = "couchpotato=" + nzb_id
groups = rpc.listgroups()
file_id = [item['LastID'] for item in groups if item['NZBFilename'] == nzb_name]
@@ -83,7 +83,7 @@ class NZBGet(Downloader):
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return []
except xmlrpclib.ProtocolError, e:
except xmlrpclib.ProtocolError as e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:
@@ -116,7 +116,7 @@ class NZBGet(Downloader):
timeleft = str(timedelta(seconds = nzb['RemainingSizeMB'] / status['DownloadRate'] * 2 ^ 20))
except:
pass
release_downloads.append({
'id': nzb_id,
'name': nzb['NZBFilename'],
@@ -169,7 +169,7 @@ class NZBGet(Downloader):
except socket.error:
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
return False
except xmlrpclib.ProtocolError, e:
except xmlrpclib.ProtocolError as e:
if e.errcode == 401:
log.error('Password is incorrect.')
else:

View File

@@ -1,5 +1,6 @@
from .main import NZBVortex
def start():
return NZBVortex()

View File

@@ -56,13 +56,13 @@ class NZBVortex(Downloader):
status = 'completed'
elif nzb['state'] in [21, 22, 24]:
status = 'failed'
release_downloads.append({
'id': nzb['id'],
'name': nzb['uiTitle'],
'status': status,
'original_status': nzb['state'],
'timeleft':-1,
'timeleft': -1,
'folder': sp(nzb['destinationPath']),
})
@@ -102,7 +102,6 @@ class NZBVortex(Downloader):
log.error('Login failed, please check you api-key')
return False
def call(self, call, parameters = None, repeat = False, auth = True, *args, **kwargs):
# Login first
@@ -123,7 +122,7 @@ class NZBVortex(Downloader):
if data:
return json.loads(data)
except URLError, e:
except URLError as e:
if hasattr(e, 'code') and e.code == 403:
# Try login and do again
if not repeat:
@@ -145,7 +144,7 @@ class NZBVortex(Downloader):
try:
data = self.urlopen(url, show_error = False)
self.api_level = float(json.loads(data).get('apilevel'))
except URLError, e:
except URLError as e:
if hasattr(e, 'code') and e.code == 403:
log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher')
else:
@@ -175,6 +174,7 @@ class HTTPSConnection(httplib.HTTPSConnection):
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version = ssl.PROTOCOL_TLSv1)
class HTTPSHandler(urllib2.HTTPSHandler):
def https_open(self, req):
return self.do_open(HTTPSConnection, req)

View File

@@ -1,5 +1,6 @@
from .main import Pneumatic
def start():
return Pneumatic()

View File

@@ -26,26 +26,26 @@ class Pneumatic(Downloader):
log.error('No nzb available!')
return False
fullPath = os.path.join(directory, self.createFileName(data, filedata, media))
full_path = os.path.join(directory, self.createFileName(data, filedata, media))
try:
if not os.path.isfile(fullPath):
log.info('Downloading %s to %s.', (data.get('protocol'), fullPath))
with open(fullPath, 'wb') as f:
if not os.path.isfile(full_path):
log.info('Downloading %s to %s.', (data.get('protocol'), full_path))
with open(full_path, 'wb') as f:
f.write(filedata)
nzb_name = self.createNzbName(data, media)
strm_path = os.path.join(directory, nzb_name)
strm_file = open(strm_path + '.strm', 'wb')
strmContent = self.strm_syntax % (fullPath, nzb_name)
strmContent = self.strm_syntax % (full_path, nzb_name)
strm_file.write(strmContent)
strm_file.close()
return self.downloadReturnId('')
else:
log.info('File %s already exists.', fullPath)
log.info('File %s already exists.', full_path)
return self.downloadReturnId('')
except:

View File

@@ -1,5 +1,6 @@
from .main import rTorrent
def start():
return rTorrent()

View File

@@ -42,7 +42,7 @@ class rTorrent(Downloader):
if self.rt is not None:
return self.rt
url = cleanHost(self.conf('host'), protocol = True, ssl = self.conf('ssl')) + '/' + self.conf('rpc_url').strip('/ ') + '/'
url = cleanHost(self.conf('host'), protocol = True, ssl = self.conf('ssl')) + self.conf('rpc_url')
if self.conf('username') and self.conf('password'):
self.rt = RTorrent(
@@ -87,7 +87,7 @@ class rTorrent(Downloader):
# Reset group action and disable it
group.set_command()
group.disable()
except MethodError, err:
except MethodError as err:
log.error('Unable to set group options: %s', err.msg)
return False
@@ -111,7 +111,6 @@ class rTorrent(Downloader):
if self.conf('label'):
torrent_params['label'] = self.conf('label')
if not filedata and data.get('protocol') == 'torrent':
log.error('Failed sending torrent, no data')
return False
@@ -135,7 +134,7 @@ class rTorrent(Downloader):
# Send request to rTorrent
try:
# Send torrent to rTorrent
torrent = self.rt.load_torrent(filedata)
torrent = self.rt.load_torrent(filedata, verify_retries=10)
if not torrent:
log.error('Unable to find the torrent, did it fail to load?')
@@ -156,7 +155,7 @@ class rTorrent(Downloader):
torrent.start()
return self.downloadReturnId(torrent_hash)
except Exception, err:
except Exception as err:
log.error('Failed to send torrent to rTorrent: %s', err)
return False
@@ -173,9 +172,16 @@ class rTorrent(Downloader):
for torrent in torrents:
if torrent.info_hash in ids:
torrent_directory = os.path.normpath(torrent.directory)
torrent_files = []
for file_item in torrent.get_files():
torrent_files.append(sp(os.path.join(torrent.directory, file_item.path)))
for file in torrent.get_files():
if not os.path.normpath(file.path).startswith(torrent_directory):
file_path = os.path.join(torrent_directory, file.path.lstrip('/'))
else:
file_path = file.path
torrent_files.append(sp(file_path))
status = 'busy'
if torrent.complete:
@@ -197,7 +203,7 @@ class rTorrent(Downloader):
return release_downloads
except Exception, err:
except Exception as err:
log.error('Failed to get status from rTorrent: %s', err)
return []

View File

@@ -1,5 +1,6 @@
from .main import Sabnzbd
def start():
return Sabnzbd()

View File

@@ -95,7 +95,7 @@ class Sabnzbd(Downloader):
status = 'busy'
if 'ENCRYPTED / ' in nzb['filename']:
status = 'failed'
release_downloads.append({
'id': nzb['nzo_id'],
'name': nzb['filename'],
@@ -112,7 +112,7 @@ class Sabnzbd(Downloader):
status = 'failed'
elif nzb['status'] == 'Completed':
status = 'completed'
release_downloads.append({
'id': nzb['nzo_id'],
'name': nzb['name'],
@@ -166,8 +166,8 @@ class Sabnzbd(Downloader):
def call(self, request_params, use_json = True, **kwargs):
url = cleanHost(self.conf('host'), ssl = self.conf('ssl')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
'apikey': self.conf('api_key'),
'output': 'json'
'apikey': self.conf('api_key'),
'output': 'json'
}))
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}, **kwargs)

View File

@@ -1,5 +1,6 @@
from .main import Synology
def start():
return Synology()

View File

@@ -65,6 +65,7 @@ class Synology(Downloader):
return super(Synology, self).isEnabled(manual, data) and\
((self.conf('use_for') in for_protocol))
class SynologyRPC(object):
"""SynologyRPC lite library"""
@@ -107,11 +108,11 @@ class SynologyRPC(object):
if response['success']:
log.info('Synology action successfull')
return response
except requests.ConnectionError, err:
except requests.ConnectionError as err:
log.error('Synology connection error, check your config %s', err)
except requests.HTTPError, err:
except requests.HTTPError as err:
log.error('SynologyRPC HTTPError: %s', err)
except Exception, err:
except Exception as err:
log.error('Exception: %s', err)
finally:
return response

View File

@@ -1,5 +1,6 @@
from .main import Transmission
def start():
return Transmission()

View File

@@ -105,7 +105,7 @@ class Transmission(Downloader):
for torrent in queue['torrents']:
if torrent['hashString'] in ids:
log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / isStalled=%s / eta=%s / uploadRatio=%s / isFinished=%s / incomplete-dir-enabled=%s / incomplete-dir=%s',
(torrent['name'], torrent['id'], torrent['downloadDir'], torrent['hashString'], torrent['percentDone'], torrent['status'], torrent.get('isStalled', 'N/A'), torrent['eta'], torrent['uploadRatio'], torrent['isFinished'], session['incomplete-dir-enabled'], session['incomplete-dir']))
(torrent['name'], torrent['id'], torrent['downloadDir'], torrent['hashString'], torrent['percentDone'], torrent['status'], torrent.get('isStalled', 'N/A'), torrent['eta'], torrent['uploadRatio'], torrent['isFinished'], session['incomplete-dir-enabled'], session['incomplete-dir']))
status = 'busy'
if torrent.get('isStalled') and not torrent['percentDone'] == 1 and self.conf('stalled_as_failed'):
@@ -187,10 +187,10 @@ class TransmissionRPC(object):
else:
log.debug('Unknown failure sending command to Transmission. Return text is: %s', response['result'])
return False
except httplib.InvalidURL, err:
except httplib.InvalidURL as err:
log.error('Invalid Transmission host, check your config %s', err)
return False
except urllib2.HTTPError, err:
except urllib2.HTTPError as err:
if err.code == 401:
log.error('Invalid Transmission Username or Password, check your config')
return False
@@ -208,7 +208,7 @@ class TransmissionRPC(object):
log.error('Unable to get Transmission Session-Id %s', err)
else:
log.error('TransmissionRPC HTTPError: %s', err)
except urllib2.URLError, err:
except urllib2.URLError as err:
log.error('Unable to connect to Transmission %s', err)
def get_session(self):

View File

@@ -1,5 +1,6 @@
from .main import uTorrent
def start():
return uTorrent()
@@ -23,7 +24,7 @@ config = [{
{
'name': 'host',
'default': 'localhost:8000',
'description': 'Hostname with port. Usually <strong>localhost:8000</strong>',
'description': 'Port can be found in settings when enabling WebUI.',
},
{
'name': 'username',

View File

@@ -66,7 +66,7 @@ class uTorrent(Downloader):
new_settings['seed_prio_limitul_flag'] = True
log.info('Updated uTorrent settings to set a torrent to complete after it the seeding requirements are met.')
if settings.get('bt.read_only_on_complete'): #This doesn't work as this option seems to be not available through the api. Mitigated with removeReadOnly function
if settings.get('bt.read_only_on_complete'): #This doesn't work as this option seems to be not available through the api. Mitigated with removeReadOnly function
new_settings['bt.read_only_on_complete'] = False
log.info('Updated uTorrent settings to not set the files to read only after completing.')
@@ -149,7 +149,7 @@ class uTorrent(Downloader):
torrent_files = [sp(os.path.join(torrent[26], torrent_file[0])) for torrent_file in torrent_files['files'][1]]
except:
log.debug('Failed getting files from torrent: %s', torrent[2])
status = 'busy'
if (torrent[1] & self.status_flags['STARTED'] or torrent[1] & self.status_flags['QUEUED']) and torrent[4] == 1000:
status = 'seeding'
@@ -157,10 +157,10 @@ class uTorrent(Downloader):
status = 'failed'
elif torrent[4] == 1000:
status = 'completed'
if not status == 'busy':
self.removeReadOnly(torrent_files)
release_downloads.append({
'id': torrent[0],
'name': torrent[2],
@@ -231,14 +231,14 @@ class uTorrentAPI(object):
return response
else:
log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response)
except httplib.InvalidURL, err:
except httplib.InvalidURL as err:
log.error('Invalid uTorrent host, check your config %s', err)
except urllib2.HTTPError, err:
except urllib2.HTTPError as err:
if err.code == 401:
log.error('Invalid uTorrent Username or Password, check your config')
else:
log.error('uTorrent HTTPError: %s', err)
except urllib2.URLError, err:
except urllib2.URLError as err:
log.error('Unable to connect to uTorrent %s', err)
return False
@@ -261,7 +261,7 @@ class uTorrentAPI(object):
def set_torrent(self, hash, params):
action = 'action=setprops&hash=%s' % hash
for k, v in params.iteritems():
for k, v in params.items():
action += '&s=%s&v=%s' % (k, v)
return self._request(action)
@@ -304,7 +304,7 @@ class uTorrentAPI(object):
#log.debug('uTorrent settings: %s', settings_dict)
except Exception, err:
except Exception as err:
log.error('Failed to get settings from uTorrent: %s', err)
return settings_dict

View File

@@ -7,6 +7,7 @@ import traceback
log = CPLog(__name__)
events = {}
def runHandler(name, handler, *args, **kwargs):
try:
return handler(*args, **kwargs)
@@ -14,6 +15,7 @@ def runHandler(name, handler, *args, **kwargs):
from couchpotato.environment import Env
log.error('Error in event "%s", that wasn\'t caught: %s%s', (name, traceback.format_exc(), Env.all() if not Env.get('dev') else ''))
def addEvent(name, handler, priority = 100):
if not events.get(name):
@@ -27,7 +29,7 @@ def addEvent(name, handler, priority = 100):
has_parent = hasattr(handler, 'im_self')
parent = None
if has_parent:
parent = handler.im_self
parent = handler.__self__
bc = hasattr(parent, 'beforeCall')
if bc: parent.beforeCall(handler)
@@ -48,22 +50,24 @@ def addEvent(name, handler, priority = 100):
'priority': priority,
})
def removeEvent(name, handler):
e = events[name]
e -= handler
def fireEvent(name, *args, **kwargs):
if not events.has_key(name): return
if name not in events: return
#log.debug('Firing event %s', name)
try:
options = {
'is_after_event': False, # Fire after event
'on_complete': False, # onComplete event
'single': False, # Return single handler
'merge': False, # Merge items
'in_order': False, # Fire them in specific order, waits for the other to finish
'is_after_event': False, # Fire after event
'on_complete': False, # onComplete event
'single': False, # Return single handler
'merge': False, # Merge items
'in_order': False, # Fire them in specific order, waits for the other to finish
}
# Do options
@@ -101,11 +105,14 @@ def fireEvent(name, *args, **kwargs):
# Fire
result = e(*args, **kwargs)
result_keys = result.keys()
result_keys.sort(natcmp)
if options['single'] and not options['merge']:
results = None
# Loop over results, stop when first not None result is found.
for r_key in sorted(result.iterkeys(), cmp = natcmp):
for r_key in result_keys:
r = result[r_key]
if r[0] is True and r[1] is not None:
results = r[1]
@@ -117,7 +124,7 @@ def fireEvent(name, *args, **kwargs):
else:
results = []
for r_key in sorted(result.iterkeys(), cmp = natcmp):
for r_key in result_keys:
r = result[r_key]
if r[0] == True and r[1]:
results.append(r[1])
@@ -160,18 +167,21 @@ def fireEvent(name, *args, **kwargs):
except Exception:
log.error('%s: %s', (name, traceback.format_exc()))
def fireEventAsync(*args, **kwargs):
try:
t = threading.Thread(target = fireEvent, args = args, kwargs = kwargs)
t.setDaemon(True)
t.start()
return True
except Exception, e:
except Exception as e:
log.error('%s: %s', (args[0], e))
def errorHandler(error):
etype, value, tb = error
log.error(''.join(traceback.format_exception(etype, value, tb)))
def getEvent(name):
return events[name]

View File

@@ -5,29 +5,32 @@ import os
import re
import traceback
import unicodedata
import six
log = CPLog(__name__)
def toSafeString(original):
valid_chars = "-_.() %s%s" % (ascii_letters, digits)
cleanedFilename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
valid_string = ''.join(c for c in cleanedFilename if c in valid_chars)
cleaned_filename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
valid_string = ''.join(c for c in cleaned_filename if c in valid_chars)
return ' '.join(valid_string.split())
def simplifyString(original):
string = stripAccents(original.lower())
string = toSafeString(' '.join(re.split('\W+', string)))
split = re.split('\W+|_', string.lower())
return toUnicode(' '.join(split))
def toUnicode(original, *args):
try:
if isinstance(original, unicode):
return original
else:
try:
return unicode(original, *args)
return six.text_type(original, *args)
except:
try:
return ek(original, *args)
@@ -38,16 +41,18 @@ def toUnicode(original, *args):
ascii_text = str(original).encode('string_escape')
return toUnicode(ascii_text)
def ss(original, *args):
u_original = toUnicode(original, *args)
try:
from couchpotato.environment import Env
return u_original.encode(Env.get('encoding'))
except Exception, e:
except Exception as e:
log.debug('Failed ss encoding char, force UTF8: %s', e)
return u_original.encode('UTF-8')
def sp(path, *args):
# Standardise encoding, normalise case, path and strip trailing '/' or '\'
@@ -73,6 +78,7 @@ def sp(path, *args):
return path
def ek(original, *args):
if isinstance(original, (str, unicode)):
try:
@@ -83,6 +89,7 @@ def ek(original, *args):
return original
def isInt(value):
try:
int(value)
@@ -90,14 +97,16 @@ def isInt(value):
except ValueError:
return False
def stripAccents(s):
return ''.join((c for c in unicodedata.normalize('NFD', toUnicode(s)) if unicodedata.category(c) != 'Mn'))
def tryUrlencode(s):
new = u''
new = six.u('')
if isinstance(s, dict):
for key, value in s.iteritems():
new += u'&%s=%s' % (key, tryUrlencode(value))
for key, value in s.items():
new += six.u('&%s=%s') % (key, tryUrlencode(value))
return new[1:]
else:

View File

@@ -9,7 +9,7 @@ def getParams(params):
reg = re.compile('^[a-z0-9_\.]+$')
temp = {}
for param, value in sorted(params.iteritems()):
for param, value in sorted(params.items()):
nest = re.split("([\[\]]+)", param)
if len(nest) > 1:
@@ -37,13 +37,14 @@ def getParams(params):
return dictToList(temp)
def dictToList(params):
if type(params) is dict:
new = {}
for x, value in params.iteritems():
for x, value in params.items():
try:
new_value = [dictToList(value[k]) for k in sorted(value.iterkeys(), cmp = natcmp)]
new_value = [dictToList(value[k]) for k in sorted(value.keys(), cmp = natcmp)]
except:
new_value = value

View File

@@ -3,6 +3,7 @@ import xml.etree.ElementTree as XMLTree
log = CPLog(__name__)
class RSS(object):
def getTextElements(self, xml, path):
@@ -46,6 +47,6 @@ class RSS(object):
def getItems(self, data, path = 'channel/item'):
try:
return XMLTree.parse(data).findall(path)
except Exception, e:
except Exception as e:
log.error('Error parsing RSS. %s', e)
return []

View File

@@ -8,26 +8,32 @@ import random
import re
import string
import sys
import six
from six.moves import map, zip, filter
log = CPLog(__name__)
def fnEscape(pattern):
return pattern.replace('[','[[').replace(']','[]]').replace('[[','[[]')
return pattern.replace('[', '[[').replace(']', '[]]').replace('[[', '[[]')
def link(src, dst):
if os.name == 'nt':
import ctypes
if ctypes.windll.kernel32.CreateHardLinkW(unicode(dst), unicode(src), 0) == 0: raise ctypes.WinError()
if ctypes.windll.kernel32.CreateHardLinkW(six.text_type(dst), six.text_type(src), 0) == 0: raise ctypes.WinError()
else:
os.link(src, dst)
def symlink(src, dst):
if os.name == 'nt':
import ctypes
if ctypes.windll.kernel32.CreateSymbolicLinkW(unicode(dst), unicode(src), 1 if os.path.isdir(src) else 0) in [0, 1280]: raise ctypes.WinError()
if ctypes.windll.kernel32.CreateSymbolicLinkW(six.text_type(dst), six.text_type(src), 1 if os.path.isdir(src) else 0) in [0, 1280]: raise ctypes.WinError()
else:
os.symlink(src, dst)
def getUserDir():
try:
import pwd
@@ -37,6 +43,7 @@ def getUserDir():
return os.path.expanduser('~')
def getDownloadDir():
user_dir = getUserDir()
@@ -49,6 +56,7 @@ def getDownloadDir():
return user_dir
def getDataDir():
# Windows
@@ -68,8 +76,10 @@ def getDataDir():
# Linux
return os.path.join(user_dir, '.couchpotato')
def isDict(object):
return isinstance(object, dict)
def isDict(obj):
return isinstance(obj, dict)
def mergeDicts(a, b, prepend_list = False):
assert isDict(a), isDict(b)
@@ -91,6 +101,7 @@ def mergeDicts(a, b, prepend_list = False):
current_dst[key] = current_src[key]
return dst
def removeListDuplicates(seq):
checked = []
for e in seq:
@@ -98,38 +109,65 @@ def removeListDuplicates(seq):
checked.append(e)
return checked
def flattenList(l):
if isinstance(l, list):
return sum(map(flattenList, l))
else:
return l
def md5(text):
return hashlib.md5(ss(text)).hexdigest()
def sha1(text):
return hashlib.sha1(text).hexdigest()
def isLocalIP(ip):
ip = ip.lstrip('htps:/')
regex = '/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1)$/'
return re.search(regex, ip) is not None or 'localhost' in ip or ip[:4] == '127.'
def getExt(filename):
return os.path.splitext(filename)[1][1:]
def cleanHost(host, protocol = True, ssl = False, username = None, password = None):
"""Return a cleaned up host with given url options set
Changes protocol to https if ssl is set to True and http if ssl is set to false.
>>> cleanHost("localhost:80", ssl=True)
'https://localhost:80/'
>>> cleanHost("localhost:80", ssl=False)
'http://localhost:80/'
Username and password is managed with the username and password variables
>>> cleanHost("localhost:80", username="user", password="passwd")
'http://user:passwd@localhost:80/'
Output without scheme (protocol) can be forced with protocol=False
>>> cleanHost("localhost:80", protocol=False)
'localhost:80'
"""
if not '://' in host and protocol:
host = 'https://' if ssl else 'http://' + host
host = ('https://' if ssl else 'http://') + host
if not protocol:
host = host.split('://', 1)[-1]
if protocol and username and password:
login = '%s:%s@' % (username, password)
if not login in host:
host = host.replace('://', '://' + login, 1)
try:
auth = re.findall('^(?:.+?//)(.+?):(.+?)@(?:.+)$', host)
if auth:
log.error('Cleanhost error: auth already defined in url: %s, please remove BasicAuth from url.', host)
else:
host = host.replace('://', '://%s:%s@' % (username, password), 1)
except:
pass
host = host.rstrip('/ ')
if protocol:
@@ -137,6 +175,7 @@ def cleanHost(host, protocol = True, ssl = False, username = None, password = No
return host
def getImdb(txt, check_inside = False, multiple = False):
if not check_inside:
@@ -153,7 +192,7 @@ def getImdb(txt, check_inside = False, multiple = False):
ids = re.findall('(tt\d{4,7})', txt)
if multiple:
return list(set(['tt%07d' % tryInt(x[2:]) for x in ids])) if len(ids) > 0 else []
return removeDuplicate(['tt%07d' % tryInt(x[2:]) for x in ids]) if len(ids) > 0 else []
return 'tt%07d' % tryInt(ids[0][2:])
except IndexError:
@@ -161,10 +200,12 @@ def getImdb(txt, check_inside = False, multiple = False):
return False
def tryInt(s, default = 0):
try: return int(s)
except: return default
def tryFloat(s):
try:
if isinstance(s, str):
@@ -173,17 +214,24 @@ def tryFloat(s):
return float(s)
except: return 0
def natsortKey(s):
return map(tryInt, re.findall(r'(\d+|\D+)', s))
def natcmp(a, b):
return cmp(natsortKey(a), natsortKey(b))
a2 = natsortKey(a)
b2 = natsortKey(b)
return (a2 > b2) - (a2 < b2)
def toIterable(value):
if isinstance(value, collections.Iterable):
return value
return [value]
def getTitle(library_dict):
try:
try:
@@ -206,6 +254,7 @@ def getTitle(library_dict):
log.error('Could not get title for library item: %s', library_dict)
return None
def possibleTitles(raw_title):
titles = [
@@ -218,18 +267,31 @@ def possibleTitles(raw_title):
new_title = raw_title.replace('&', 'and')
titles.append(simplifyString(new_title))
return list(set(titles))
return removeDuplicate(titles)
def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
def splitString(str, split_on = ',', clean = True):
list = [x.strip() for x in str.split(split_on)] if str else []
return filter(None, list) if clean else list
l = [x.strip() for x in str.split(split_on)] if str else []
return removeEmpty(l) if clean else l
def removeEmpty(l):
return list(filter(None, l))
def removeDuplicate(l):
seen = set()
return [x for x in l if x not in seen and not seen.add(x)]
def dictIsSubset(a, b):
return all([k in b and b[k] == v for k, v in a.items()])
def isSubFolder(sub_folder, base_folder):
# Returns True if sub_folder is the same as or inside base_folder
return base_folder and sub_folder and os.path.normpath(base_folder).rstrip(os.path.sep) + os.path.sep in os.path.normpath(sub_folder).rstrip(os.path.sep) + os.path.sep
return base_folder and sub_folder and ss(os.path.normpath(base_folder).rstrip(os.path.sep) + os.path.sep) in ss(os.path.normpath(sub_folder).rstrip(os.path.sep) + os.path.sep)

View File

@@ -1,9 +1,10 @@
from couchpotato.core.event import fireEvent
from couchpotato.core.logger import CPLog
from importlib import import_module
from importhelper import import_module
import os
import sys
import traceback
import six
log = CPLog(__name__)
@@ -37,7 +38,7 @@ class Loader(object):
self.paths['custom_plugins'] = (30, '', custom_plugin_dir)
# Loop over all paths and add to module list
for plugin_type, plugin_tuple in self.paths.iteritems():
for plugin_type, plugin_tuple in self.paths.items():
priority, module, dir_name = plugin_tuple
self.addFromDir(plugin_type, priority, module, dir_name)
@@ -45,7 +46,7 @@ class Loader(object):
did_save = 0
for priority in sorted(self.modules):
for module_name, plugin in sorted(self.modules[priority].iteritems()):
for module_name, plugin in sorted(self.modules[priority].items()):
# Load module
try:
@@ -81,7 +82,7 @@ class Loader(object):
for filename in os.listdir(root_path):
path = os.path.join(root_path, filename)
if os.path.isdir(path) and filename[:2] != '__':
if u'__init__.py' in os.listdir(path):
if six.u('__init__.py') in os.listdir(path):
new_base_path = ''.join(s + '.' for s in base_path) + filename
self.paths[new_base_path.replace('.', '_')] = (priority, new_base_path, path)

View File

@@ -1,6 +1,7 @@
import logging
import re
class CPLog(object):
context = ''
@@ -37,7 +38,7 @@ class CPLog(object):
def safeMessage(self, msg, replace_tuple = ()):
from couchpotato.environment import Env
from couchpotato.core.helpers.encoding import ss
from couchpotato.core.helpers.encoding import ss, toUnicode
msg = ss(msg)
@@ -49,8 +50,8 @@ class CPLog(object):
msg = msg % tuple([ss(x) for x in list(replace_tuple)])
else:
msg = msg % ss(replace_tuple)
except Exception, e:
self.logger.error(u'Failed encoding stuff to log "%s": %s' % (msg, e))
except Exception as e:
self.logger.error('Failed encoding stuff to log "%s": %s' % (msg, e))
if not Env.get('dev'):
@@ -66,4 +67,4 @@ class CPLog(object):
except:
pass
return msg
return toUnicode(msg)

View File

@@ -1,8 +1,11 @@
from couchpotato import get_session
import traceback
from couchpotato import get_session, CPLog
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Media
log = CPLog(__name__)
class MediaBase(Plugin):
@@ -10,8 +13,8 @@ class MediaBase(Plugin):
default_dict = {
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}, 'files':{}, 'info': {}},
'library': {'titles': {}, 'files':{}},
'releases': {'status': {}, 'quality': {}, 'files': {}, 'info': {}},
'library': {'titles': {}, 'files': {}},
'files': {},
'status': {},
'category': {},
@@ -26,25 +29,33 @@ class MediaBase(Plugin):
def createOnComplete(self, id):
def onComplete():
db = get_session()
media = db.query(Media).filter_by(id = id).first()
media_dict = media.to_dict(self.default_dict)
event_name = '%s.searcher.single' % media.type
db.expire_all()
try:
db = get_session()
media = db.query(Media).filter_by(id = id).first()
media_dict = media.to_dict(self.default_dict)
event_name = '%s.searcher.single' % media.type
fireEvent(event_name, media_dict, on_complete = self.createNotifyFront(id))
fireEvent(event_name, media_dict, on_complete = self.createNotifyFront(id))
except:
log.error('Failed creating onComplete: %s', traceback.format_exc())
finally:
db.close()
return onComplete
def createNotifyFront(self, media_id):
def notifyFront():
db = get_session()
media = db.query(Media).filter_by(id = media_id).first()
media_dict = media.to_dict(self.default_dict)
event_name = '%s.update' % media.type
db.expire_all()
try:
db = get_session()
media = db.query(Media).filter_by(id = media_id).first()
media_dict = media.to_dict(self.default_dict)
event_name = '%s.update' % media.type
fireEvent('notify.frontend', type = event_name, data = media_dict)
fireEvent('notify.frontend', type = event_name, data = media_dict)
except:
log.error('Failed creating onComplete: %s', traceback.format_exc())
finally:
db.close()
return notifyFront

View File

@@ -1,5 +1,6 @@
from .main import MediaPlugin
def start():
return MediaPlugin()

View File

@@ -1,3 +1,4 @@
import traceback
from couchpotato import get_session, tryInt
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
@@ -70,8 +71,6 @@ class MediaPlugin(MediaBase):
addEvent('media.restatus', self.restatus)
def refresh(self, id = '', **kwargs):
db = get_session()
handlers = []
ids = splitString(id)
@@ -97,12 +96,12 @@ class MediaPlugin(MediaBase):
default_title = getTitle(media.library)
identifier = media.library.identifier
db.expire_all()
event = 'library.update.%s' % media.type
def handler():
fireEvent('library.update.%s' % media.type, identifier = identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(id))
fireEvent(event, identifier = identifier, default_title = default_title, on_complete = self.createOnComplete(id))
if handler:
return handler
def addSingleRefreshView(self):
@@ -125,7 +124,6 @@ class MediaPlugin(MediaBase):
if m:
results = m.to_dict(self.default_dict)
db.expire_all()
return results
def getView(self, id = None, **kwargs):
@@ -254,14 +252,13 @@ class MediaPlugin(MediaBase):
# Merge releases with movie dict
movies.append(mergeDicts(movie_dict[media_id].to_dict({
'library': {'titles': {}, 'files':{}},
'library': {'titles': {}, 'files': {}},
'files': {},
}), {
'releases': releases,
'releases_count': releases_count.get(media_id),
}))
db.expire_all()
return total_count, movies
def listView(self, **kwargs):
@@ -355,7 +352,6 @@ class MediaPlugin(MediaBase):
if len(chars) == 25:
break
db.expire_all()
return ''.join(sorted(chars))
def charView(self, **kwargs):
@@ -380,50 +376,55 @@ class MediaPlugin(MediaBase):
def delete(self, media_id, delete_from = None):
db = get_session()
try:
db = get_session()
media = db.query(Media).filter_by(id = media_id).first()
if media:
deleted = False
if delete_from == 'all':
db.delete(media)
db.commit()
deleted = True
else:
done_status = fireEvent('status.get', 'done', single = True)
total_releases = len(media.releases)
total_deleted = 0
new_movie_status = None
for release in media.releases:
if delete_from in ['wanted', 'snatched', 'late']:
if release.status_id != done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'done'
elif delete_from == 'manage':
if release.status_id == done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'active'
db.commit()
if total_releases == total_deleted:
media = db.query(Media).filter_by(id = media_id).first()
if media:
deleted = False
if delete_from == 'all':
db.delete(media)
db.commit()
deleted = True
elif new_movie_status:
new_status = fireEvent('status.get', new_movie_status, single = True)
media.profile_id = None
media.status_id = new_status.get('id')
db.commit()
else:
fireEvent('media.restatus', media.id, single = True)
done_status = fireEvent('status.get', 'done', single = True)
if deleted:
fireEvent('notify.frontend', type = 'movie.deleted', data = media.to_dict())
total_releases = len(media.releases)
total_deleted = 0
new_movie_status = None
for release in media.releases:
if delete_from in ['wanted', 'snatched', 'late']:
if release.status_id != done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'done'
elif delete_from == 'manage':
if release.status_id == done_status.get('id'):
db.delete(release)
total_deleted += 1
new_movie_status = 'active'
db.commit()
if total_releases == total_deleted:
db.delete(media)
db.commit()
deleted = True
elif new_movie_status:
new_status = fireEvent('status.get', new_movie_status, single = True)
media.profile_id = None
media.status_id = new_status.get('id')
db.commit()
else:
fireEvent('media.restatus', media.id, single = True)
if deleted:
fireEvent('notify.frontend', type = 'movie.deleted', data = media.to_dict())
except:
log.error('Failed deleting media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
db.expire_all()
return True
def deleteView(self, id = '', **kwargs):
@@ -447,27 +448,33 @@ class MediaPlugin(MediaBase):
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
db = get_session()
try:
db = get_session()
m = db.query(Media).filter_by(id = media_id).first()
if not m or len(m.library.titles) == 0:
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
return False
m = db.query(Media).filter_by(id = media_id).first()
if not m or len(m.library.titles) == 0:
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
return False
log.debug('Changing status for %s', m.library.titles[0].title)
if not m.profile:
m.status_id = done_status.get('id')
else:
move_to_wanted = True
log.debug('Changing status for %s', m.library.titles[0].title)
if not m.profile:
m.status_id = done_status.get('id')
else:
move_to_wanted = True
for t in m.profile.types:
for release in m.releases:
if t.quality.identifier is release.quality.identifier and (release.status_id is done_status.get('id') and t.finish):
move_to_wanted = False
for t in m.profile.types:
for release in m.releases:
if t.quality.identifier is release.quality.identifier and (release.status_id is done_status.get('id') and t.finish):
move_to_wanted = False
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
db.commit()
db.commit()
return True
return True
except:
log.error('Failed restatus: %s', traceback.format_exc())
db.rollback()
finally:
db.close()

View File

@@ -1,5 +1,6 @@
from .main import Search
def start():
return Search()

View File

@@ -1,5 +1,6 @@
from .main import Searcher
def start():
return Searcher()

View File

@@ -12,7 +12,6 @@ class SearcherBase(Plugin):
def __init__(self):
super(SearcherBase, self).__init__()
addEvent('searcher.progress', self.getProgress)
addEvent('%s.searcher.progress' % self.getType(), self.getProgress)
@@ -26,9 +25,8 @@ class SearcherBase(Plugin):
_type = self.getType()
def setCrons():
fireEvent('schedule.cron', '%s.searcher.all' % _type, self.searchAll,
day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
addEvent('app.load', setCrons)
addEvent('setting.save.%s_searcher.cron_day.after' % _type, setCrons)

View File

@@ -1,7 +1,7 @@
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import simplifyString
from couchpotato.core.helpers.variable import splitString
from couchpotato.core.helpers.variable import splitString, removeEmpty, removeDuplicate
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.searcher.base import SearcherBase
import datetime
@@ -107,10 +107,10 @@ class Searcher(SearcherBase):
# Hack for older movies that don't contain quality tag
year_name = fireEvent('scanner.name_year', name, single = True)
if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
if size > 3000: # Assume dvdr
if size > 3000: # Assume dvdr
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', size)
found['dvdr'] = True
else: # Assume dvdrip
else: # Assume dvdrip
log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', size)
found['dvdrip'] = True
@@ -150,12 +150,12 @@ class Searcher(SearcherBase):
try: check_names.append(max(re.findall(r'[^[]*\[([^]]*)\]', check_name), key = len).strip())
except: pass
for check_name in list(set(check_names)):
for check_name in removeDuplicate(check_names):
check_movie = fireEvent('scanner.name_year', check_name, single = True)
try:
check_words = filter(None, re.split('\W+', check_movie.get('name', '')))
movie_words = filter(None, re.split('\W+', simplifyString(movie_name)))
check_words = removeEmpty(re.split('\W+', check_movie.get('name', '')))
movie_words = removeEmpty(re.split('\W+', simplifyString(movie_name)))
if len(check_words) > 0 and len(movie_words) > 0 and len(list(set(check_words) - set(movie_words))) == 0:
return True
@@ -173,7 +173,7 @@ class Searcher(SearcherBase):
# Make sure it has required words
required_words = splitString(self.conf('required_words', section = 'searcher').lower())
try: required_words = list(set(required_words + splitString(media['category']['required'].lower())))
try: required_words = removeDuplicate(required_words + splitString(media['category']['required'].lower()))
except: pass
req_match = 0
@@ -187,7 +187,7 @@ class Searcher(SearcherBase):
# Ignore releases
ignored_words = splitString(self.conf('ignored_words', section = 'searcher').lower())
try: ignored_words = list(set(ignored_words + splitString(media['category']['ignored'].lower())))
try: ignored_words = removeDuplicate(ignored_words + splitString(media['category']['ignored'].lower()))
except: pass
ignored_match = 0

View File

@@ -1,5 +1,6 @@
from .main import MovieBase
def start():
return MovieBase()

View File

@@ -1,3 +1,4 @@
import traceback
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
@@ -61,7 +62,6 @@ class MovieBase(MovieTypeBase):
except:
pass
library = fireEvent('library.add.movie', single = True, attrs = params, update_after = update_library)
# Status
@@ -71,76 +71,81 @@ class MovieBase(MovieTypeBase):
default_profile = fireEvent('profile.default', single = True)
cat_id = params.get('category_id')
db = get_session()
m = db.query(Media).filter_by(library_id = library.get('id')).first()
added = True
do_search = False
search_after = search_after and self.conf('search_on_add', section = 'moviesearcher')
if not m:
m = Media(
library_id = library.get('id'),
profile_id = params.get('profile_id', default_profile.get('id')),
status_id = status_id if status_id else status_active.get('id'),
category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None,
)
db.add(m)
db.commit()
onComplete = None
if search_after:
onComplete = self.createOnComplete(m.id)
fireEventAsync('library.update.movie', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
search_after = False
elif force_readd:
# Clean snatched history
for release in m.releases:
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
if params.get('ignore_previous', False):
release.status_id = ignored_status.get('id')
else:
fireEvent('release.delete', release.id, single = True)
m.profile_id = params.get('profile_id', default_profile.get('id'))
m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else (m.category_id or None)
else:
log.debug('Movie already exists, not updating: %s', params)
added = False
if force_readd:
m.status_id = status_id if status_id else status_active.get('id')
m.last_edit = int(time.time())
do_search = True
db.commit()
# Remove releases
available_status = fireEvent('status.get', 'available', single = True)
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
try:
db = get_session()
m = db.query(Media).filter_by(library_id = library.get('id')).first()
added = True
do_search = False
search_after = search_after and self.conf('search_on_add', section = 'moviesearcher')
if not m:
m = Media(
library_id = library.get('id'),
profile_id = params.get('profile_id', default_profile.get('id')),
status_id = status_id if status_id else status_active.get('id'),
category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None,
)
db.add(m)
db.commit()
movie_dict = m.to_dict(self.default_dict)
onComplete = None
if search_after:
onComplete = self.createOnComplete(m.id)
if do_search and search_after:
onComplete = self.createOnComplete(m.id)
onComplete()
fireEventAsync('library.update.movie', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
search_after = False
elif force_readd:
if added:
if params.get('title'):
message = 'Successfully added "%s" to your wanted list.' % params.get('title', '')
# Clean snatched history
for release in m.releases:
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id'), done_status.get('id')]:
if params.get('ignore_previous', False):
release.status_id = ignored_status.get('id')
else:
fireEvent('release.delete', release.id, single = True)
m.profile_id = params.get('profile_id', default_profile.get('id'))
m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else (m.category_id or None)
else:
title = getTitle(m.library)
if title:
message = 'Successfully added "%s" to your wanted list.' % title
else:
message = 'Succesfully added to your wanted list.'
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = message)
log.debug('Movie already exists, not updating: %s', params)
added = False
db.expire_all()
return movie_dict
if force_readd:
m.status_id = status_id if status_id else status_active.get('id')
m.last_edit = int(time.time())
do_search = True
db.commit()
# Remove releases
available_status = fireEvent('status.get', 'available', single = True)
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
movie_dict = m.to_dict(self.default_dict)
if do_search and search_after:
onComplete = self.createOnComplete(m.id)
onComplete()
if added:
if params.get('title'):
message = 'Successfully added "%s" to your wanted list.' % params.get('title', '')
else:
title = getTitle(m.library)
if title:
message = 'Successfully added "%s" to your wanted list.' % title
else:
message = 'Succesfully added to your wanted list.'
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = message)
return movie_dict
except:
log.error('Failed deleting media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
def addView(self, **kwargs):
add_dict = self.add(params = kwargs)
@@ -152,42 +157,51 @@ class MovieBase(MovieTypeBase):
def edit(self, id = '', **kwargs):
db = get_session()
try:
db = get_session()
available_status = fireEvent('status.get', 'available', single = True)
available_status = fireEvent('status.get', 'available', single = True)
ids = splitString(id)
for media_id in ids:
ids = splitString(id)
for media_id in ids:
m = db.query(Media).filter_by(id = media_id).first()
if not m:
continue
m = db.query(Media).filter_by(id = media_id).first()
if not m:
continue
m.profile_id = kwargs.get('profile_id')
m.profile_id = kwargs.get('profile_id')
cat_id = kwargs.get('category_id')
if cat_id is not None:
m.category_id = tryInt(cat_id) if tryInt(cat_id) > 0 else None
cat_id = kwargs.get('category_id')
if cat_id is not None:
m.category_id = tryInt(cat_id) if tryInt(cat_id) > 0 else None
# Remove releases
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
# Remove releases
for rel in m.releases:
if rel.status_id is available_status.get('id'):
db.delete(rel)
db.commit()
# Default title
if kwargs.get('default_title'):
for title in m.library.titles:
title.default = toUnicode(kwargs.get('default_title', '')).lower() == toUnicode(title.title).lower()
# Default title
if kwargs.get('default_title'):
for title in m.library.titles:
title.default = toUnicode(kwargs.get('default_title', '')).lower() == toUnicode(title.title).lower()
db.commit()
db.commit()
fireEvent('media.restatus', m.id)
fireEvent('media.restatus', m.id)
movie_dict = m.to_dict(self.default_dict)
fireEventAsync('movie.searcher.single', movie_dict, on_complete = self.createNotifyFront(media_id))
movie_dict = m.to_dict(self.default_dict)
fireEventAsync('movie.searcher.single', movie_dict, on_complete = self.createNotifyFront(media_id))
return {
'success': True,
}
except:
log.error('Failed deleting media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
db.expire_all()
return {
'success': True,
'success': False,
}

View File

@@ -1,5 +1,6 @@
from .main import MovieLibraryPlugin
def start():
return MovieLibraryPlugin()

View File

@@ -7,13 +7,14 @@ from couchpotato.core.settings.model import Library, LibraryTitle, File
from string import ascii_letters
import time
import traceback
import six
log = CPLog(__name__)
class MovieLibraryPlugin(LibraryBase):
default_dict = {'titles': {}, 'files':{}}
default_dict = {'titles': {}, 'files': {}}
def __init__(self):
addEvent('library.add.movie', self.add)
@@ -25,69 +26,70 @@ class MovieLibraryPlugin(LibraryBase):
primary_provider = attrs.get('primary_provider', 'imdb')
db = get_session()
try:
db = get_session()
l = db.query(Library).filter_by(identifier = attrs.get('identifier')).first()
if not l:
status = fireEvent('status.get', 'needs_update', single = True)
l = Library(
year = attrs.get('year'),
identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
info = {}
)
l = db.query(Library).filter_by(identifier = attrs.get('identifier')).first()
if not l:
status = fireEvent('status.get', 'needs_update', single = True)
l = Library(
year = attrs.get('year'),
identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
info = {}
)
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
)
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
)
l.titles.append(title)
l.titles.append(title)
db.add(l)
db.commit()
db.add(l)
db.commit()
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
handle('library.update.movie', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
handle('library.update.movie', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
library_dict = l.to_dict(self.default_dict)
return library_dict
except:
log.error('Failed adding media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
db.expire_all()
return library_dict
return {}
def update(self, identifier, default_title = '', force = False):
def update(self, identifier, default_title = '', extended = False):
if self.shuttingDown():
return
db = get_session()
library = db.query(Library).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
try:
db = get_session()
library_dict = None
if library:
library_dict = library.to_dict(self.default_dict)
library = db.query(Library).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
do_update = True
info = fireEvent('movie.info', merge = True, extended = extended, identifier = identifier)
info = fireEvent('movie.info', merge = True, identifier = identifier)
# Don't need those here
try: del info['in_wanted']
except: pass
try: del info['in_library']
except: pass
# Don't need those here
try: del info['in_wanted']
except: pass
try: del info['in_library']
except: pass
if not info or len(info) == 0:
log.error('Could not update, no movie info to work with: %s', identifier)
return False
if not info or len(info) == 0:
log.error('Could not update, no movie info to work with: %s', identifier)
return False
# Main info
if do_update:
# Main info
library.plot = toUnicode(info.get('plot', ''))
library.tagline = toUnicode(info.get('tagline', ''))
library.year = info.get('year', 0)
@@ -102,6 +104,17 @@ class MovieLibraryPlugin(LibraryBase):
titles = info.get('titles', [])
log.debug('Adding titles: %s', titles)
counter = 0
def_title = None
for title in titles:
if (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == six.u('') and toUnicode(titles[0]) == title):
def_title = toUnicode(title)
break
counter += 1
if not def_title:
def_title = toUnicode(titles[0])
for title in titles:
if not title:
continue
@@ -109,10 +122,9 @@ class MovieLibraryPlugin(LibraryBase):
t = LibraryTitle(
title = title,
simple_title = self.simplifyTitle(title),
default = (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
default = title == def_title
)
library.titles.append(t)
counter += 1
db.commit()
@@ -134,30 +146,43 @@ class MovieLibraryPlugin(LibraryBase):
break
except:
log.debug('Failed to attach to library: %s', traceback.format_exc())
db.rollback()
library_dict = library.to_dict(self.default_dict)
return library_dict
except:
log.error('Failed update media: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
db.expire_all()
return library_dict
return {}
def updateReleaseDate(self, identifier):
db = get_session()
library = db.query(Library).filter_by(identifier = identifier).first()
try:
db = get_session()
library = db.query(Library).filter_by(identifier = identifier).first()
if not library.info:
library_dict = self.update(identifier, force = True)
dates = library_dict.get('info', {}).get('release_date')
else:
dates = library.info.get('release_date')
if not library.info:
library_dict = self.update(identifier)
dates = library_dict.get('info', {}).get('release_date')
else:
dates = library.info.get('release_date')
if dates and (dates.get('expires', 0) < time.time() or dates.get('expires', 0) > time.time() + (604800 * 4)) or not dates:
dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
library.info.update({'release_date': dates })
db.commit()
if dates and (dates.get('expires', 0) < time.time() or dates.get('expires', 0) > time.time() + (604800 * 4)) or not dates:
dates = fireEvent('movie.release_date', identifier = identifier, merge = True)
library.info.update({'release_date': dates})
db.commit()
db.expire_all()
return dates
return dates
except:
log.error('Failed updating release dates: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return {}
def simplifyTitle(self, title):

View File

@@ -1,6 +1,7 @@
from .main import MovieSearcher
import random
def start():
return MovieSearcher()

View File

@@ -73,10 +73,21 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
db = get_session()
movies = db.query(Media).filter(
movies_raw = db.query(Media).filter(
Media.status.has(identifier = 'active')
).all()
random.shuffle(movies)
random.shuffle(movies_raw)
movies = []
for m in movies_raw:
movies.append(m.to_dict({
'category': {},
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files': {}},
'files': {},
}))
self.in_progress = {
'total': len(movies),
@@ -87,21 +98,14 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
search_protocols = fireEvent('searcher.protocols', single = True)
for movie in movies:
movie_dict = movie.to_dict({
'category': {},
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {},
})
try:
self.single(movie_dict, search_protocols)
self.single(movie, search_protocols)
except IndexError:
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
fireEvent('library.update.movie', movie_dict['library']['identifier'], force = True)
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie['library']['identifier'], traceback.format_exc()))
fireEvent('library.update.movie', movie['library']['identifier'])
except:
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
log.error('Search failed for %s: %s', (movie['library']['identifier'], traceback.format_exc()))
self.in_progress['to_go'] -= 1
@@ -117,7 +121,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
def single(self, movie, search_protocols = None, manual = False):
# movies don't contain 'type' yet, so just set to default here
if not movie.has_key('type'):
if 'type' not in movie:
movie['type'] = 'movie'
# Find out search type
@@ -133,8 +137,6 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.')
return
db = get_session()
pre_releases = fireEvent('quality.pre_releases', single = True)
release_dates = fireEvent('library.update.movie.release_date', identifier = movie['library']['identifier'], merge = True)
available_status, ignored_status, failed_status = fireEvent('status.get', ['available', 'ignored', 'failed'], single = True)
@@ -150,6 +152,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'id': movie['id']}, message = 'Searching for "%s"' % default_title)
db = get_session()
ret = False
for quality_type in movie['profile']['types']:
@@ -345,7 +348,10 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
except:
log.error('Failed searching for next release: %s', traceback.format_exc())
db.rollback()
return False
finally:
db.close()
def getSearchTitle(self, media):
if media['type'] == 'movie':

View File

@@ -1,5 +1,6 @@
from .main import Suggestion
def start():
return Suggestion()

View File

@@ -1,7 +1,7 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import splitString
from couchpotato.core.helpers.variable import splitString, removeDuplicate
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Media, Library
from couchpotato.environment import Env
@@ -40,7 +40,7 @@ class Suggestion(Plugin):
movies.extend(splitString(Env.prop('suggest_seen', default = '')))
suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True)
self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks
self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks
return {
'success': True,
@@ -79,8 +79,10 @@ class Suggestion(Plugin):
seen = [] if not seen else seen
if ignore_imdb:
suggested_imdbs = []
for cs in cached_suggestion:
if cs.get('imdb') != ignore_imdb:
if cs.get('imdb') != ignore_imdb and cs.get('imdb') not in suggested_imdbs:
suggested_imdbs.append(cs.get('imdb'))
new_suggestions.append(cs)
# Get new results and add them
@@ -97,7 +99,7 @@ class Suggestion(Plugin):
movies.extend(seen)
ignored.extend([x.get('imdb') for x in cached_suggestion])
suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True)
suggestions = fireEvent('movie.suggest', movies = movies, ignore = removeDuplicate(ignored), single = True)
if suggestions:
new_suggestions.extend(suggestions)

View File

@@ -13,5 +13,6 @@ def upgrade(migrate_engine):
create_column(category_column, movie)
Index('ix_movie_category_id', movie.c.category_id).create()
def downgrade(migrate_engine):
pass

View File

@@ -1,5 +1,6 @@
from .main import Boxcar
def start():
return Boxcar()

View File

@@ -1,5 +1,6 @@
from .main import CoreNotifier
def start():
return CoreNotifier()

View File

@@ -67,28 +67,42 @@ class CoreNotifier(Notification):
def clean(self):
db = get_session()
db.query(Notif).filter(Notif.added <= (int(time.time()) - 2419200)).delete()
db.commit()
try:
db = get_session()
db.query(Notif).filter(Notif.added <= (int(time.time()) - 2419200)).delete()
db.commit()
except:
log.error('Failed cleaning notification: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
def markAsRead(self, ids = None, **kwargs):
ids = splitString(ids) if ids else None
db = get_session()
try:
db = get_session()
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)
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})
q.update({Notif.read: True})
db.commit()
db.commit()
return {
'success': True
}
except:
log.error('Failed mark as read: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return {
'success': True
'success': False
}
def listView(self, limit_offset = None, **kwargs):
@@ -140,24 +154,30 @@ class CoreNotifier(Notification):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
db = get_session()
try:
db = get_session()
data['notification_type'] = listener if listener else 'unknown'
data['notification_type'] = listener if listener else 'unknown'
n = Notif(
message = toUnicode(message),
data = data
)
db.add(n)
db.commit()
n = Notif(
message = toUnicode(message),
data = data
)
db.add(n)
db.commit()
ndict = n.to_dict()
ndict['type'] = 'notification'
ndict['time'] = time.time()
ndict = n.to_dict()
ndict['type'] = 'notification'
ndict['time'] = time.time()
self.frontend(type = listener, data = data)
self.frontend(type = listener, data = data)
return True
return True
except:
log.error('Failed notify: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
def frontend(self, type = 'notification', data = None, message = None):
if not data: data = {}

View File

@@ -147,7 +147,7 @@ var NotificationBase = new Class({
// Process data
if(json){
Array.each(json.result, function(result){
App.trigger(result.type, result);
App.trigger(result.type, [result]);
if(result.message && result.read === undefined)
self.showMessage(result.message);
})

View File

@@ -1,5 +1,6 @@
from .main import Email
def start():
return Email()
@@ -30,7 +31,7 @@ config = [{
},
{ 'name': 'smtp_port',
'label': 'SMTP server port',
'default': '25',
'default': '25',
'type': 'int',
},
{

View File

@@ -40,7 +40,7 @@ class Email(Notification):
log.debug("SMTP over SSL %s", ("enabled" if ssl == 1 else "disabled"))
mailserver = smtplib.SMTP_SSL(smtp_server) if ssl == 1 else smtplib.SMTP(smtp_server)
if (starttls):
if starttls:
log.debug("Using StartTLS to initiate the connection with the SMTP server")
mailserver.starttls()

View File

@@ -1,5 +1,6 @@
from .main import Growl
def start():
return Growl()

View File

@@ -37,7 +37,7 @@ class Growl(Notification):
)
self.growl.register()
self.registered = True
except Exception, e:
except Exception as e:
if 'timed out' in str(e):
self.registered = True
else:

View File

@@ -1,5 +1,6 @@
from .main import NMJ
def start():
return NMJ()

View File

@@ -86,18 +86,17 @@ class NMJ(Notification):
'arg3': '',
}
params = tryUrlencode(params)
UPDATE_URL = 'http://%(host)s:8008/metadata_database?%(params)s'
updateUrl = UPDATE_URL % {'host': host, 'params': params}
update_url = 'http://%(host)s:8008/metadata_database?%(params)s' % {'host': host, 'params': params}
try:
response = self.urlopen(updateUrl)
response = self.urlopen(update_url)
except:
return False
try:
et = etree.fromstring(response)
result = et.findtext('returnValue')
except SyntaxError, e:
except SyntaxError as e:
log.error('Unable to parse XML returned from the Popcorn Hour: %s', e)
return False

View File

@@ -1,5 +1,6 @@
from .main import NotifyMyAndroid
def start():
return NotifyMyAndroid()

View File

@@ -2,6 +2,7 @@ from couchpotato.core.helpers.variable import splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
import pynma
import six
log = CPLog(__name__)
@@ -26,7 +27,7 @@ class NotifyMyAndroid(Notification):
successful = 0
for key in keys:
if not response[str(key)]['code'] == u'200':
if not response[str(key)]['code'] == six.u('200'):
log.error('Could not send notification to NotifyMyAndroid (%s). %s', (key, response[key]['message']))
else:
successful += 1

View File

@@ -1,5 +1,6 @@
from .main import NotifyMyWP
def start():
return NotifyMyWP()

View File

@@ -2,13 +2,15 @@ from couchpotato.core.helpers.variable import splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from pynmwp import PyNMWP
import six
log = CPLog(__name__)
class NotifyMyWP(Notification):
def notify(self, message = '', data = {}, listener = None):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
keys = splitString(self.conf('api_key'))
p = PyNMWP(keys, self.conf('dev_key'))
@@ -16,7 +18,7 @@ class NotifyMyWP(Notification):
response = p.push(application = self.default_title, event = message, description = message, priority = self.conf('priority'), batch_mode = len(keys) > 1)
for key in keys:
if not response[key]['Code'] == u'200':
if not response[key]['Code'] == six.u('200'):
log.error('Could not send notification to NotifyMyWindowsPhone (%s). %s', (key, response[key]['message']))
return False

View File

@@ -1,5 +1,6 @@
from .main import Plex
def start():
return Plex()

View File

@@ -29,7 +29,7 @@ class PlexClientHTTP(PlexClientProtocol):
try:
self.plex.urlopen(url, headers = headers, timeout = 3, show_error = False)
except Exception, err:
except Exception as err:
log.error("Couldn't sent command to Plex: %s", err)
return False
@@ -68,7 +68,7 @@ class PlexClientJSON(PlexClientProtocol):
try:
requests.post(url, headers = headers, timeout = 3, data = json.dumps(request))
except Exception, err:
except Exception as err:
log.error("Couldn't sent command to Plex: %s", err)
return False

View File

@@ -23,9 +23,9 @@ class Plex(Notification):
addEvent('renamer.after', self.addToLibrary)
def addToLibrary(self, message = None, group = {}):
def addToLibrary(self, message = None, group = None):
if self.isDisabled(): return
if not group: group = {}
return self.server.refresh()
@@ -57,7 +57,8 @@ class Plex(Notification):
return success
def notify(self, message = '', data = {}, listener = None):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
return self.notifyClients(message, self.getClientNames())
def test(self, **kwargs):

View File

@@ -1,5 +1,6 @@
from .main import Prowl
def start():
return Prowl()

View File

@@ -22,7 +22,7 @@ class Prowl(Notification):
'priority': self.conf('priority'),
}
headers = {
'Content-type': 'application/x-www-form-urlencoded'
'Content-type': 'application/x-www-form-urlencoded'
}
try:

View File

@@ -1,5 +1,6 @@
from .main import Pushalot
def start():
return Pushalot()

View File

@@ -5,6 +5,7 @@ import traceback
log = CPLog(__name__)
class Pushalot(Notification):
urls = {

View File

@@ -1,5 +1,6 @@
from .main import Pushbullet
def start():
return Pushbullet()

View File

@@ -79,7 +79,7 @@ class Pushbullet(Notification):
data = self.urlopen(self.url % method, headers = headers, data = kwargs)
return json.loads(data)
except Exception, ex:
except Exception as ex:
log.error('Pushbullet request failed')
log.debug(ex)

View File

@@ -1,5 +1,6 @@
from .main import Pushover
def start():
return Pushover()

View File

@@ -30,9 +30,9 @@ class Pushover(Notification):
})
http_handler.request('POST',
"/1/messages.json",
headers = {'Content-type': 'application/x-www-form-urlencoded'},
body = tryUrlencode(api_data)
"/1/messages.json",
headers = {'Content-type': 'application/x-www-form-urlencoded'},
body = tryUrlencode(api_data)
)
response = http_handler.getresponse()

View File

@@ -1,5 +1,6 @@
from .main import Synoindex
def start():
return Synoindex()

View File

@@ -26,7 +26,7 @@ class Synoindex(Notification):
out = p.communicate()
log.info('Result from synoindex: %s', str(out))
return True
except OSError, e:
except OSError as e:
log.error('Unable to run synoindex: %s', e)
return False

View File

@@ -1,5 +1,6 @@
from .main import Toasty
def start():
return Toasty()

View File

@@ -5,6 +5,7 @@ import traceback
log = CPLog(__name__)
class Toasty(Notification):
urls = {

View File

@@ -1,5 +1,6 @@
from .main import Trakt
def start():
return Trakt()

View File

@@ -3,6 +3,7 @@ from couchpotato.core.notifications.base import Notification
log = CPLog(__name__)
class Trakt(Notification):
urls = {

View File

@@ -1,5 +1,6 @@
from .main import Twitter
def start():
return Twitter()

View File

@@ -64,7 +64,7 @@ class Twitter(Notification):
api.PostUpdate(update_message[135:] + ' 2/2')
else:
api.PostUpdate(update_message)
except Exception, e:
except Exception as e:
log.error('Error sending tweet: %s', e)
return False

View File

@@ -1,5 +1,6 @@
from .main import XBMC
def start():
return XBMC()

View File

@@ -1,7 +1,6 @@
from couchpotato.core.helpers.variable import splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from urllib2 import URLError
import base64
import json
import socket
@@ -46,7 +45,7 @@ class XBMC(Notification):
max_successful += len(calls)
response = self.request(host, calls)
else:
response = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
response = self.notifyXBMCnoJSON(host, {'title': self.default_title, 'message': message})
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
response += self.request(host, [('VideoLibrary.Scan', {})])

View File

@@ -1,5 +1,6 @@
from .main import Xmpp
def start():
return Xmpp()

View File

@@ -1,5 +1,6 @@
from .main import Automation
def start():
return Automation()

View File

@@ -85,7 +85,7 @@ class Plugin(object):
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
# View path
path = 'static/plugin/%s/' % (class_name)
path = 'static/plugin/%s/' % class_name
# Add handler to Tornado
Env.get('app').add_handlers(".*$", [(Env.get('web_base') + path + '(.*)', StaticFileHandler, {'path': static_folder})])
@@ -110,7 +110,7 @@ class Plugin(object):
f.write(content)
f.close()
os.chmod(path, Env.getPermission('file'))
except Exception, e:
except Exception as e:
log.error('Unable writing to file "%s": %s', (path, traceback.format_exc()))
if os.path.isfile(path):
os.remove(path)
@@ -121,7 +121,7 @@ class Plugin(object):
if not os.path.isdir(path):
os.makedirs(path, Env.getPermission('folder'))
return True
except Exception, e:
except Exception as e:
log.error('Unable to create folder "%s": %s', (path, e))
return False
@@ -169,7 +169,7 @@ class Plugin(object):
}
method = 'post' if len(data) > 0 or files else 'get'
log.info('Opening url: %s %s, data: %s', (method, url, [x for x in data.iterkeys()] if isinstance(data, dict) else 'with data'))
log.info('Opening url: %s %s, data: %s', (method, url, [x for x in data.keys()] if isinstance(data, dict) else 'with data'))
response = r.request(method, url, verify = False, **kwargs)
data = response.content
@@ -243,24 +243,27 @@ class Plugin(object):
except:
log.error("Something went wrong when finishing the plugin function. Could not find the 'is_running' key")
def getCache(self, cache_key, url = None, **kwargs):
cache_key_md5 = md5(cache_key)
cache = Env.get('cache').get(cache_key_md5)
if cache:
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
return cache
use_cache = not len(kwargs.get('data', {})) > 0 and not kwargs.get('files')
if use_cache:
cache_key_md5 = md5(cache_key)
cache = Env.get('cache').get(cache_key_md5)
if cache:
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
return cache
if url:
try:
cache_timeout = 300
if kwargs.has_key('cache_timeout'):
if 'cache_timeout' in kwargs:
cache_timeout = kwargs.get('cache_timeout')
del kwargs['cache_timeout']
data = self.urlopen(url, **kwargs)
if data and cache_timeout > 0:
if data and cache_timeout > 0 and use_cache:
self.setCache(cache_key, data, timeout = cache_timeout)
return data
except:

View File

@@ -1,5 +1,6 @@
from .main import FileBrowser
def start():
return FileBrowser()

View File

@@ -4,6 +4,7 @@ from couchpotato.core.plugins.base import Plugin
import ctypes
import os
import string
import six
if os.name == 'nt':
import imp
@@ -14,7 +15,7 @@ if os.name == 'nt':
raise ImportError("Missing the win32file module, which is a part of the prerequisite \
pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/")
else:
import win32file #@UnresolvedImport
import win32file #@UnresolvedImport
class FileBrowser(Plugin):
@@ -96,7 +97,7 @@ class FileBrowser(Plugin):
def has_hidden_attribute(self, filepath):
try:
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath)) #@UndefinedVariable
attrs = ctypes.windll.kernel32.GetFileAttributesW(six.text_type(filepath)) #@UndefinedVariable
assert attrs != -1
result = bool(attrs & 2)
except (AttributeError, AssertionError):

View File

@@ -1,5 +1,6 @@
from .main import CategoryPlugin
def start():
return CategoryPlugin()

View File

@@ -1,3 +1,4 @@
import traceback
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent
@@ -41,81 +42,116 @@ class CategoryPlugin(Plugin):
for category in categories:
temp.append(category.to_dict())
db.expire_all()
return temp
def save(self, **kwargs):
db = get_session()
try:
db = get_session()
c = db.query(Category).filter_by(id = kwargs.get('id')).first()
if not c:
c = Category()
db.add(c)
c = db.query(Category).filter_by(id = kwargs.get('id')).first()
if not c:
c = Category()
db.add(c)
c.order = kwargs.get('order', c.order if c.order else 0)
c.label = toUnicode(kwargs.get('label', ''))
c.ignored = toUnicode(kwargs.get('ignored', ''))
c.preferred = toUnicode(kwargs.get('preferred', ''))
c.required = toUnicode(kwargs.get('required', ''))
c.destination = toUnicode(kwargs.get('destination', ''))
c.order = kwargs.get('order', c.order if c.order else 0)
c.label = toUnicode(kwargs.get('label', ''))
c.ignored = toUnicode(kwargs.get('ignored', ''))
c.preferred = toUnicode(kwargs.get('preferred', ''))
c.required = toUnicode(kwargs.get('required', ''))
c.destination = toUnicode(kwargs.get('destination', ''))
db.commit()
db.commit()
category_dict = c.to_dict()
category_dict = c.to_dict()
return {
'success': True,
'category': category_dict
}
except:
log.error('Failed: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return {
'success': True,
'category': category_dict
'success': False,
'category': None
}
def saveOrder(self, **kwargs):
db = get_session()
try:
db = get_session()
order = 0
for category_id in kwargs.get('ids', []):
c = db.query(Category).filter_by(id = category_id).first()
c.order = order
order = 0
for category_id in kwargs.get('ids', []):
c = db.query(Category).filter_by(id = category_id).first()
c.order = order
order += 1
order += 1
db.commit()
db.commit()
return {
'success': True
}
except:
log.error('Failed: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
return {
'success': True
'success': False
}
def delete(self, id = None, **kwargs):
db = get_session()
success = False
message = ''
try:
c = db.query(Category).filter_by(id = id).first()
db.delete(c)
db.commit()
db = get_session()
# Force defaults on all empty category movies
self.removeFromMovie(id)
success = False
message = ''
try:
c = db.query(Category).filter_by(id = id).first()
db.delete(c)
db.commit()
success = True
except Exception, e:
message = log.error('Failed deleting category: %s', e)
# Force defaults on all empty category movies
self.removeFromMovie(id)
success = True
except Exception as e:
message = log.error('Failed deleting category: %s', e)
return {
'success': success,
'message': message
}
except:
log.error('Failed: %s', traceback.format_exc())
db.rollback()
finally:
db.close()
db.expire_all()
return {
'success': success,
'message': message
'success': False
}
def removeFromMovie(self, category_id):
db = get_session()
movies = db.query(Media).filter(Media.category_id == category_id).all()
try:
db = get_session()
movies = db.query(Media).filter(Media.category_id == category_id).all()
if len(movies) > 0:
for movie in movies:
movie.category_id = None
db.commit()
if len(movies) > 0:
for movie in movies:
movie.category_id = None
db.commit()
except:
log.error('Failed: %s', traceback.format_exc())
db.rollback()
finally:
db.close()

View File

@@ -1,5 +1,6 @@
from .main import Custom
def start():
return Custom()

View File

@@ -1,5 +1,6 @@
from .main import Dashboard
def start():
return Dashboard()

View File

@@ -115,7 +115,7 @@ class Dashboard(Plugin):
for movie_id in movie_ids:
movies.append(movie_dict[movie_id].to_dict({
'library': {'titles': {}, 'files':{}},
'library': {'titles': {}, 'files': {}},
'files': {},
}))

View File

@@ -1,5 +1,6 @@
from .main import FileManager
def start():
return FileManager()

Some files were not shown because too many files have changed in this diff Show More