diff --git a/CouchPotato.py b/CouchPotato.py index e777f9bf..e73ed9a5 100755 --- a/CouchPotato.py +++ b/CouchPotato.py @@ -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 diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index b8aa3ab9..6b8cfd36 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -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') - diff --git a/couchpotato/api.py b/couchpotato/api.py index e86b127f..ba7f7b69 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -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) diff --git a/couchpotato/core/_base/_core/__init__.py b/couchpotato/core/_base/_core/__init__.py index 4d1a6840..58965bbb 100644 --- a/couchpotato/core/_base/_core/__init__.py +++ b/couchpotato/core/_base/_core/__init__.py @@ -1,6 +1,7 @@ from .main import Core from uuid import uuid4 + def start(): return Core() diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index f2435eb8..02e21f2d 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -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)) diff --git a/couchpotato/core/_base/clientscript/__init__.py b/couchpotato/core/_base/clientscript/__init__.py index 8490eae7..8070044e 100644 --- a/couchpotato/core/_base/clientscript/__init__.py +++ b/couchpotato/core/_base/clientscript/__init__.py @@ -1,5 +1,6 @@ from .main import ClientScript + def start(): return ClientScript() diff --git a/couchpotato/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py index b80ddcc1..248d2bc5 100644 --- a/couchpotato/core/_base/clientscript/main.py +++ b/couchpotato/core/_base/clientscript/main.py @@ -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' diff --git a/couchpotato/core/_base/desktop/__init__.py b/couchpotato/core/_base/desktop/__init__.py index 064492f2..e59ca523 100644 --- a/couchpotato/core/_base/desktop/__init__.py +++ b/couchpotato/core/_base/desktop/__init__.py @@ -1,5 +1,6 @@ from .main import Desktop + def start(): return Desktop() diff --git a/couchpotato/core/_base/scheduler/__init__.py b/couchpotato/core/_base/scheduler/__init__.py index aa1c5c90..abfc2305 100644 --- a/couchpotato/core/_base/scheduler/__init__.py +++ b/couchpotato/core/_base/scheduler/__init__.py @@ -1,5 +1,6 @@ from .main import Scheduler + def start(): return Scheduler() diff --git a/couchpotato/core/_base/updater/__init__.py b/couchpotato/core/_base/updater/__init__.py index a304f9e7..7ad30d27 100644 --- a/couchpotato/core/_base/updater/__init__.py +++ b/couchpotato/core/_base/updater/__init__.py @@ -2,6 +2,7 @@ from .main import Updater from couchpotato.environment import Env import os + def start(): return Updater() diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 8dc78939..ef595ad7 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -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, } diff --git a/couchpotato/core/_base/updater/static/updater.js b/couchpotato/core/_base/updater/static/updater.js index 860ad514..be436ed2 100644 --- a/couchpotato/core/_base/updater/static/updater.js +++ b/couchpotato/core/_base/updater/static/updater.js @@ -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']); } } }) diff --git a/couchpotato/core/downloaders/blackhole/__init__.py b/couchpotato/core/downloaders/blackhole/__init__.py index 91164d66..92d18e7f 100644 --- a/couchpotato/core/downloaders/blackhole/__init__.py +++ b/couchpotato/core/downloaders/blackhole/__init__.py @@ -1,6 +1,7 @@ from .main import Blackhole from couchpotato.core.helpers.variable import getDownloadDir + def start(): return Blackhole() diff --git a/couchpotato/core/downloaders/deluge/__init__.py b/couchpotato/core/downloaders/deluge/__init__.py index c7aa26e6..09fae751 100644 --- a/couchpotato/core/downloaders/deluge/__init__.py +++ b/couchpotato/core/downloaders/deluge/__init__.py @@ -1,5 +1,6 @@ from .main import Deluge + def start(): return Deluge() diff --git a/couchpotato/core/downloaders/deluge/main.py b/couchpotato/core/downloaders/deluge/main.py index 2d9084bf..c5f80167 100644 --- a/couchpotato/core/downloaders/deluge/main.py +++ b/couchpotato/core/downloaders/deluge/main.py @@ -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: diff --git a/couchpotato/core/downloaders/nzbget/__init__.py b/couchpotato/core/downloaders/nzbget/__init__.py index 1f21c056..551eb42c 100644 --- a/couchpotato/core/downloaders/nzbget/__init__.py +++ b/couchpotato/core/downloaders/nzbget/__init__.py @@ -1,5 +1,6 @@ from .main import NZBGet + def start(): return NZBGet() diff --git a/couchpotato/core/downloaders/nzbget/main.py b/couchpotato/core/downloaders/nzbget/main.py index a05fb118..a690572c 100644 --- a/couchpotato/core/downloaders/nzbget/main.py +++ b/couchpotato/core/downloaders/nzbget/main.py @@ -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: diff --git a/couchpotato/core/downloaders/nzbvortex/__init__.py b/couchpotato/core/downloaders/nzbvortex/__init__.py index 3087d75b..1c2d699e 100644 --- a/couchpotato/core/downloaders/nzbvortex/__init__.py +++ b/couchpotato/core/downloaders/nzbvortex/__init__.py @@ -1,5 +1,6 @@ from .main import NZBVortex + def start(): return NZBVortex() diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py index d2615bfd..205ceb1b 100644 --- a/couchpotato/core/downloaders/nzbvortex/main.py +++ b/couchpotato/core/downloaders/nzbvortex/main.py @@ -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) diff --git a/couchpotato/core/downloaders/pneumatic/__init__.py b/couchpotato/core/downloaders/pneumatic/__init__.py index 96574a7a..698643fb 100644 --- a/couchpotato/core/downloaders/pneumatic/__init__.py +++ b/couchpotato/core/downloaders/pneumatic/__init__.py @@ -1,5 +1,6 @@ from .main import Pneumatic + def start(): return Pneumatic() diff --git a/couchpotato/core/downloaders/pneumatic/main.py b/couchpotato/core/downloaders/pneumatic/main.py index d3fdad83..6af22d2d 100644 --- a/couchpotato/core/downloaders/pneumatic/main.py +++ b/couchpotato/core/downloaders/pneumatic/main.py @@ -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: diff --git a/couchpotato/core/downloaders/rtorrent/__init__.py b/couchpotato/core/downloaders/rtorrent/__init__.py index dbef6e6f..4a593fd2 100755 --- a/couchpotato/core/downloaders/rtorrent/__init__.py +++ b/couchpotato/core/downloaders/rtorrent/__init__.py @@ -1,5 +1,6 @@ from .main import rTorrent + def start(): return rTorrent() diff --git a/couchpotato/core/downloaders/rtorrent/main.py b/couchpotato/core/downloaders/rtorrent/main.py index 50276314..cfd1dce0 100755 --- a/couchpotato/core/downloaders/rtorrent/main.py +++ b/couchpotato/core/downloaders/rtorrent/main.py @@ -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 [] diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index 1edbbebf..2990078a 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -1,5 +1,6 @@ from .main import Sabnzbd + def start(): return Sabnzbd() diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 1d9073ff..72c23708 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -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) diff --git a/couchpotato/core/downloaders/synology/__init__.py b/couchpotato/core/downloaders/synology/__init__.py index 8be16f61..d0c57c2f 100644 --- a/couchpotato/core/downloaders/synology/__init__.py +++ b/couchpotato/core/downloaders/synology/__init__.py @@ -1,5 +1,6 @@ from .main import Synology + def start(): return Synology() diff --git a/couchpotato/core/downloaders/synology/main.py b/couchpotato/core/downloaders/synology/main.py index 7299fa81..f964f37f 100644 --- a/couchpotato/core/downloaders/synology/main.py +++ b/couchpotato/core/downloaders/synology/main.py @@ -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 diff --git a/couchpotato/core/downloaders/transmission/__init__.py b/couchpotato/core/downloaders/transmission/__init__.py index f96e628e..4c9b4aad 100644 --- a/couchpotato/core/downloaders/transmission/__init__.py +++ b/couchpotato/core/downloaders/transmission/__init__.py @@ -1,5 +1,6 @@ from .main import Transmission + def start(): return Transmission() diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index d41337f0..2daeab46 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -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): diff --git a/couchpotato/core/downloaders/utorrent/__init__.py b/couchpotato/core/downloaders/utorrent/__init__.py index d45e2e6c..da160956 100644 --- a/couchpotato/core/downloaders/utorrent/__init__.py +++ b/couchpotato/core/downloaders/utorrent/__init__.py @@ -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 localhost:8000', + 'description': 'Port can be found in settings when enabling WebUI.', }, { 'name': 'username', diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py index 89f75ccf..e0d6a921 100644 --- a/couchpotato/core/downloaders/utorrent/main.py +++ b/couchpotato/core/downloaders/utorrent/main.py @@ -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 diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index 7b01fbd8..a36c430d 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -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] diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py index fc7c919c..0b85f64b 100644 --- a/couchpotato/core/helpers/encoding.py +++ b/couchpotato/core/helpers/encoding.py @@ -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: diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 888e63fd..8a10f078 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -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 diff --git a/couchpotato/core/helpers/rss.py b/couchpotato/core/helpers/rss.py index b840d862..1a4d37c2 100644 --- a/couchpotato/core/helpers/rss.py +++ b/couchpotato/core/helpers/rss.py @@ -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 [] diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 658ea8c7..a586ceff 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -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) diff --git a/couchpotato/core/loader.py b/couchpotato/core/loader.py index c14b55bd..6c3a719d 100644 --- a/couchpotato/core/loader.py +++ b/couchpotato/core/loader.py @@ -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) diff --git a/couchpotato/core/logger.py b/couchpotato/core/logger.py index 69a031f1..8223f146 100644 --- a/couchpotato/core/logger.py +++ b/couchpotato/core/logger.py @@ -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) diff --git a/couchpotato/core/media/__init__.py b/couchpotato/core/media/__init__.py index 88094890..cf9302b1 100644 --- a/couchpotato/core/media/__init__.py +++ b/couchpotato/core/media/__init__.py @@ -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 diff --git a/couchpotato/core/media/_base/media/__init__.py b/couchpotato/core/media/_base/media/__init__.py index a9693a3d..e5f5a0ec 100644 --- a/couchpotato/core/media/_base/media/__init__.py +++ b/couchpotato/core/media/_base/media/__init__.py @@ -1,5 +1,6 @@ from .main import MediaPlugin + def start(): return MediaPlugin() diff --git a/couchpotato/core/media/_base/media/main.py b/couchpotato/core/media/_base/media/main.py index 206187fb..cbcd4245 100644 --- a/couchpotato/core/media/_base/media/main.py +++ b/couchpotato/core/media/_base/media/main.py @@ -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() diff --git a/couchpotato/core/media/_base/search/__init__.py b/couchpotato/core/media/_base/search/__init__.py index 4b2eae27..09bc84ef 100644 --- a/couchpotato/core/media/_base/search/__init__.py +++ b/couchpotato/core/media/_base/search/__init__.py @@ -1,5 +1,6 @@ from .main import Search + def start(): return Search() diff --git a/couchpotato/core/media/_base/searcher/__init__.py b/couchpotato/core/media/_base/searcher/__init__.py index 5e029a25..72c7d6ef 100644 --- a/couchpotato/core/media/_base/searcher/__init__.py +++ b/couchpotato/core/media/_base/searcher/__init__.py @@ -1,5 +1,6 @@ from .main import Searcher + def start(): return Searcher() diff --git a/couchpotato/core/media/_base/searcher/base.py b/couchpotato/core/media/_base/searcher/base.py index 368c6e2d..5322d850 100644 --- a/couchpotato/core/media/_base/searcher/base.py +++ b/couchpotato/core/media/_base/searcher/base.py @@ -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) diff --git a/couchpotato/core/media/_base/searcher/main.py b/couchpotato/core/media/_base/searcher/main.py index 3c73eb27..e7209b60 100644 --- a/couchpotato/core/media/_base/searcher/main.py +++ b/couchpotato/core/media/_base/searcher/main.py @@ -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 diff --git a/couchpotato/core/media/movie/_base/__init__.py b/couchpotato/core/media/movie/_base/__init__.py index 4be3b127..22211332 100644 --- a/couchpotato/core/media/movie/_base/__init__.py +++ b/couchpotato/core/media/movie/_base/__init__.py @@ -1,5 +1,6 @@ from .main import MovieBase + def start(): return MovieBase() diff --git a/couchpotato/core/media/movie/_base/main.py b/couchpotato/core/media/movie/_base/main.py index 03c22e3d..a7ecf2d2 100644 --- a/couchpotato/core/media/movie/_base/main.py +++ b/couchpotato/core/media/movie/_base/main.py @@ -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, } diff --git a/couchpotato/core/media/movie/library/movie/__init__.py b/couchpotato/core/media/movie/library/movie/__init__.py index 03494a11..98ed54c0 100644 --- a/couchpotato/core/media/movie/library/movie/__init__.py +++ b/couchpotato/core/media/movie/library/movie/__init__.py @@ -1,5 +1,6 @@ from .main import MovieLibraryPlugin + def start(): return MovieLibraryPlugin() diff --git a/couchpotato/core/media/movie/library/movie/main.py b/couchpotato/core/media/movie/library/movie/main.py index b0d05202..034a8fb0 100644 --- a/couchpotato/core/media/movie/library/movie/main.py +++ b/couchpotato/core/media/movie/library/movie/main.py @@ -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): diff --git a/couchpotato/core/media/movie/searcher/__init__.py b/couchpotato/core/media/movie/searcher/__init__.py index bae18902..4ae1ed32 100644 --- a/couchpotato/core/media/movie/searcher/__init__.py +++ b/couchpotato/core/media/movie/searcher/__init__.py @@ -1,6 +1,7 @@ from .main import MovieSearcher import random + def start(): return MovieSearcher() diff --git a/couchpotato/core/media/movie/searcher/main.py b/couchpotato/core/media/movie/searcher/main.py index 1c4810ec..7ae76a4e 100644 --- a/couchpotato/core/media/movie/searcher/main.py +++ b/couchpotato/core/media/movie/searcher/main.py @@ -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': diff --git a/couchpotato/core/media/movie/suggestion/__init__.py b/couchpotato/core/media/movie/suggestion/__init__.py index b63b5b13..50083fe7 100644 --- a/couchpotato/core/media/movie/suggestion/__init__.py +++ b/couchpotato/core/media/movie/suggestion/__init__.py @@ -1,5 +1,6 @@ from .main import Suggestion + def start(): return Suggestion() diff --git a/couchpotato/core/media/movie/suggestion/main.py b/couchpotato/core/media/movie/suggestion/main.py index f29281ea..22e23fe2 100644 --- a/couchpotato/core/media/movie/suggestion/main.py +++ b/couchpotato/core/media/movie/suggestion/main.py @@ -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) diff --git a/couchpotato/core/migration/versions/002_Movie_category.py b/couchpotato/core/migration/versions/002_Movie_category.py index 234e1136..023e47c6 100644 --- a/couchpotato/core/migration/versions/002_Movie_category.py +++ b/couchpotato/core/migration/versions/002_Movie_category.py @@ -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 diff --git a/couchpotato/core/notifications/boxcar/__init__.py b/couchpotato/core/notifications/boxcar/__init__.py index ab244c32..faab7a5c 100644 --- a/couchpotato/core/notifications/boxcar/__init__.py +++ b/couchpotato/core/notifications/boxcar/__init__.py @@ -1,5 +1,6 @@ from .main import Boxcar + def start(): return Boxcar() diff --git a/couchpotato/core/notifications/core/__init__.py b/couchpotato/core/notifications/core/__init__.py index 6e923dac..b68a915a 100644 --- a/couchpotato/core/notifications/core/__init__.py +++ b/couchpotato/core/notifications/core/__init__.py @@ -1,5 +1,6 @@ from .main import CoreNotifier + def start(): return CoreNotifier() diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index cd63c2cb..93f94d6a 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -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 = {} diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index a0c3b15c..18d09e76 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -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); }) diff --git a/couchpotato/core/notifications/email/__init__.py b/couchpotato/core/notifications/email/__init__.py index 33c2f634..aaf087b9 100644 --- a/couchpotato/core/notifications/email/__init__.py +++ b/couchpotato/core/notifications/email/__init__.py @@ -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', }, { diff --git a/couchpotato/core/notifications/email/main.py b/couchpotato/core/notifications/email/main.py index 41a4323b..b8544016 100644 --- a/couchpotato/core/notifications/email/main.py +++ b/couchpotato/core/notifications/email/main.py @@ -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() diff --git a/couchpotato/core/notifications/growl/__init__.py b/couchpotato/core/notifications/growl/__init__.py index 8e462236..dd01cb91 100644 --- a/couchpotato/core/notifications/growl/__init__.py +++ b/couchpotato/core/notifications/growl/__init__.py @@ -1,5 +1,6 @@ from .main import Growl + def start(): return Growl() diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py index dabeea01..a3927ed2 100644 --- a/couchpotato/core/notifications/growl/main.py +++ b/couchpotato/core/notifications/growl/main.py @@ -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: diff --git a/couchpotato/core/notifications/nmj/__init__.py b/couchpotato/core/notifications/nmj/__init__.py index 08a21a3e..461a450e 100644 --- a/couchpotato/core/notifications/nmj/__init__.py +++ b/couchpotato/core/notifications/nmj/__init__.py @@ -1,5 +1,6 @@ from .main import NMJ + def start(): return NMJ() diff --git a/couchpotato/core/notifications/nmj/main.py b/couchpotato/core/notifications/nmj/main.py index 1479fb1b..967b70e7 100644 --- a/couchpotato/core/notifications/nmj/main.py +++ b/couchpotato/core/notifications/nmj/main.py @@ -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 diff --git a/couchpotato/core/notifications/notifymyandroid/__init__.py b/couchpotato/core/notifications/notifymyandroid/__init__.py index 9ee5d90a..7d4f4aeb 100644 --- a/couchpotato/core/notifications/notifymyandroid/__init__.py +++ b/couchpotato/core/notifications/notifymyandroid/__init__.py @@ -1,5 +1,6 @@ from .main import NotifyMyAndroid + def start(): return NotifyMyAndroid() diff --git a/couchpotato/core/notifications/notifymyandroid/main.py b/couchpotato/core/notifications/notifymyandroid/main.py index 92e59562..16465101 100644 --- a/couchpotato/core/notifications/notifymyandroid/main.py +++ b/couchpotato/core/notifications/notifymyandroid/main.py @@ -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 diff --git a/couchpotato/core/notifications/notifymywp/__init__.py b/couchpotato/core/notifications/notifymywp/__init__.py index 6e0bd06d..4fcf1a9a 100644 --- a/couchpotato/core/notifications/notifymywp/__init__.py +++ b/couchpotato/core/notifications/notifymywp/__init__.py @@ -1,5 +1,6 @@ from .main import NotifyMyWP + def start(): return NotifyMyWP() diff --git a/couchpotato/core/notifications/notifymywp/main.py b/couchpotato/core/notifications/notifymywp/main.py index 167b6eeb..74010441 100644 --- a/couchpotato/core/notifications/notifymywp/main.py +++ b/couchpotato/core/notifications/notifymywp/main.py @@ -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 diff --git a/couchpotato/core/notifications/plex/__init__.py b/couchpotato/core/notifications/plex/__init__.py index d68ddb19..0de92ca3 100755 --- a/couchpotato/core/notifications/plex/__init__.py +++ b/couchpotato/core/notifications/plex/__init__.py @@ -1,5 +1,6 @@ from .main import Plex + def start(): return Plex() diff --git a/couchpotato/core/notifications/plex/client.py b/couchpotato/core/notifications/plex/client.py index b873518e..8864230d 100644 --- a/couchpotato/core/notifications/plex/client.py +++ b/couchpotato/core/notifications/plex/client.py @@ -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 diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py index ce25c8f0..a6853b2f 100755 --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -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): diff --git a/couchpotato/core/notifications/prowl/__init__.py b/couchpotato/core/notifications/prowl/__init__.py index e0564289..3721a0ad 100644 --- a/couchpotato/core/notifications/prowl/__init__.py +++ b/couchpotato/core/notifications/prowl/__init__.py @@ -1,5 +1,6 @@ from .main import Prowl + def start(): return Prowl() diff --git a/couchpotato/core/notifications/prowl/main.py b/couchpotato/core/notifications/prowl/main.py index 26e156a2..b3385863 100644 --- a/couchpotato/core/notifications/prowl/main.py +++ b/couchpotato/core/notifications/prowl/main.py @@ -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: diff --git a/couchpotato/core/notifications/pushalot/__init__.py b/couchpotato/core/notifications/pushalot/__init__.py index a2a297a3..ad0c853f 100644 --- a/couchpotato/core/notifications/pushalot/__init__.py +++ b/couchpotato/core/notifications/pushalot/__init__.py @@ -1,5 +1,6 @@ from .main import Pushalot + def start(): return Pushalot() diff --git a/couchpotato/core/notifications/pushalot/main.py b/couchpotato/core/notifications/pushalot/main.py index 0afb84b9..306ee1d1 100644 --- a/couchpotato/core/notifications/pushalot/main.py +++ b/couchpotato/core/notifications/pushalot/main.py @@ -5,6 +5,7 @@ import traceback log = CPLog(__name__) + class Pushalot(Notification): urls = { diff --git a/couchpotato/core/notifications/pushbullet/__init__.py b/couchpotato/core/notifications/pushbullet/__init__.py index e61a44e3..c52e7781 100644 --- a/couchpotato/core/notifications/pushbullet/__init__.py +++ b/couchpotato/core/notifications/pushbullet/__init__.py @@ -1,5 +1,6 @@ from .main import Pushbullet + def start(): return Pushbullet() diff --git a/couchpotato/core/notifications/pushbullet/main.py b/couchpotato/core/notifications/pushbullet/main.py index bc9fd64c..15120f0b 100644 --- a/couchpotato/core/notifications/pushbullet/main.py +++ b/couchpotato/core/notifications/pushbullet/main.py @@ -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) diff --git a/couchpotato/core/notifications/pushover/__init__.py b/couchpotato/core/notifications/pushover/__init__.py index 1ea1d5c0..da764860 100644 --- a/couchpotato/core/notifications/pushover/__init__.py +++ b/couchpotato/core/notifications/pushover/__init__.py @@ -1,5 +1,6 @@ from .main import Pushover + def start(): return Pushover() diff --git a/couchpotato/core/notifications/pushover/main.py b/couchpotato/core/notifications/pushover/main.py index 76f730b6..ba954a54 100644 --- a/couchpotato/core/notifications/pushover/main.py +++ b/couchpotato/core/notifications/pushover/main.py @@ -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() diff --git a/couchpotato/core/notifications/synoindex/__init__.py b/couchpotato/core/notifications/synoindex/__init__.py index eb3a793f..89d07b06 100644 --- a/couchpotato/core/notifications/synoindex/__init__.py +++ b/couchpotato/core/notifications/synoindex/__init__.py @@ -1,5 +1,6 @@ from .main import Synoindex + def start(): return Synoindex() diff --git a/couchpotato/core/notifications/synoindex/main.py b/couchpotato/core/notifications/synoindex/main.py index 0f7775d6..ec7a64ef 100644 --- a/couchpotato/core/notifications/synoindex/main.py +++ b/couchpotato/core/notifications/synoindex/main.py @@ -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 diff --git a/couchpotato/core/notifications/toasty/__init__.py b/couchpotato/core/notifications/toasty/__init__.py index 8e2dae76..31e055a0 100644 --- a/couchpotato/core/notifications/toasty/__init__.py +++ b/couchpotato/core/notifications/toasty/__init__.py @@ -1,5 +1,6 @@ from .main import Toasty + def start(): return Toasty() diff --git a/couchpotato/core/notifications/toasty/main.py b/couchpotato/core/notifications/toasty/main.py index c65b6b42..ea1f2192 100644 --- a/couchpotato/core/notifications/toasty/main.py +++ b/couchpotato/core/notifications/toasty/main.py @@ -5,6 +5,7 @@ import traceback log = CPLog(__name__) + class Toasty(Notification): urls = { diff --git a/couchpotato/core/notifications/trakt/__init__.py b/couchpotato/core/notifications/trakt/__init__.py index b119736c..20e2e3f9 100644 --- a/couchpotato/core/notifications/trakt/__init__.py +++ b/couchpotato/core/notifications/trakt/__init__.py @@ -1,5 +1,6 @@ from .main import Trakt + def start(): return Trakt() diff --git a/couchpotato/core/notifications/trakt/main.py b/couchpotato/core/notifications/trakt/main.py index e67f5fa8..c759c6db 100644 --- a/couchpotato/core/notifications/trakt/main.py +++ b/couchpotato/core/notifications/trakt/main.py @@ -3,6 +3,7 @@ from couchpotato.core.notifications.base import Notification log = CPLog(__name__) + class Trakt(Notification): urls = { diff --git a/couchpotato/core/notifications/twitter/__init__.py b/couchpotato/core/notifications/twitter/__init__.py index 9db8dcb8..1b9c7699 100644 --- a/couchpotato/core/notifications/twitter/__init__.py +++ b/couchpotato/core/notifications/twitter/__init__.py @@ -1,5 +1,6 @@ from .main import Twitter + def start(): return Twitter() diff --git a/couchpotato/core/notifications/twitter/main.py b/couchpotato/core/notifications/twitter/main.py index ad4fc315..559c830f 100644 --- a/couchpotato/core/notifications/twitter/main.py +++ b/couchpotato/core/notifications/twitter/main.py @@ -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 diff --git a/couchpotato/core/notifications/xbmc/__init__.py b/couchpotato/core/notifications/xbmc/__init__.py index 04662e27..34fed632 100644 --- a/couchpotato/core/notifications/xbmc/__init__.py +++ b/couchpotato/core/notifications/xbmc/__init__.py @@ -1,5 +1,6 @@ from .main import XBMC + def start(): return XBMC() diff --git a/couchpotato/core/notifications/xbmc/main.py b/couchpotato/core/notifications/xbmc/main.py index b53485aa..bfda85e1 100755 --- a/couchpotato/core/notifications/xbmc/main.py +++ b/couchpotato/core/notifications/xbmc/main.py @@ -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', {})]) diff --git a/couchpotato/core/notifications/xmpp/__init__.py b/couchpotato/core/notifications/xmpp/__init__.py index a52242ff..0e3e14d9 100644 --- a/couchpotato/core/notifications/xmpp/__init__.py +++ b/couchpotato/core/notifications/xmpp/__init__.py @@ -1,5 +1,6 @@ from .main import Xmpp + def start(): return Xmpp() diff --git a/couchpotato/core/plugins/automation/__init__.py b/couchpotato/core/plugins/automation/__init__.py index a81719c4..482a0090 100644 --- a/couchpotato/core/plugins/automation/__init__.py +++ b/couchpotato/core/plugins/automation/__init__.py @@ -1,5 +1,6 @@ from .main import Automation + def start(): return Automation() diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index b4688e49..0625535e 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -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: diff --git a/couchpotato/core/plugins/browser/__init__.py b/couchpotato/core/plugins/browser/__init__.py index 976fcd10..fae50657 100644 --- a/couchpotato/core/plugins/browser/__init__.py +++ b/couchpotato/core/plugins/browser/__init__.py @@ -1,5 +1,6 @@ from .main import FileBrowser + def start(): return FileBrowser() diff --git a/couchpotato/core/plugins/browser/main.py b/couchpotato/core/plugins/browser/main.py index 380e6826..956a7680 100644 --- a/couchpotato/core/plugins/browser/main.py +++ b/couchpotato/core/plugins/browser/main.py @@ -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): diff --git a/couchpotato/core/plugins/category/__init__.py b/couchpotato/core/plugins/category/__init__.py index 6dc41df7..dcdae90b 100644 --- a/couchpotato/core/plugins/category/__init__.py +++ b/couchpotato/core/plugins/category/__init__.py @@ -1,5 +1,6 @@ from .main import CategoryPlugin + def start(): return CategoryPlugin() diff --git a/couchpotato/core/plugins/category/main.py b/couchpotato/core/plugins/category/main.py index 87cd0ea4..c7abaee4 100644 --- a/couchpotato/core/plugins/category/main.py +++ b/couchpotato/core/plugins/category/main.py @@ -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() diff --git a/couchpotato/core/plugins/custom/__init__.py b/couchpotato/core/plugins/custom/__init__.py index 573cd99f..20a39351 100644 --- a/couchpotato/core/plugins/custom/__init__.py +++ b/couchpotato/core/plugins/custom/__init__.py @@ -1,5 +1,6 @@ from .main import Custom + def start(): return Custom() diff --git a/couchpotato/core/plugins/dashboard/__init__.py b/couchpotato/core/plugins/dashboard/__init__.py index 81279291..c43a44eb 100644 --- a/couchpotato/core/plugins/dashboard/__init__.py +++ b/couchpotato/core/plugins/dashboard/__init__.py @@ -1,5 +1,6 @@ from .main import Dashboard + def start(): return Dashboard() diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 4f4d85ab..949d8f50 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -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': {}, })) diff --git a/couchpotato/core/plugins/file/__init__.py b/couchpotato/core/plugins/file/__init__.py index 54d9cbe5..3dced3d0 100644 --- a/couchpotato/core/plugins/file/__init__.py +++ b/couchpotato/core/plugins/file/__init__.py @@ -1,5 +1,6 @@ from .main import FileManager + def start(): return FileManager() diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py index fc63aca8..c52a9801 100644 --- a/couchpotato/core/plugins/file/main.py +++ b/couchpotato/core/plugins/file/main.py @@ -66,7 +66,6 @@ class FileManager(Plugin): time.sleep(3) log.debug('Cleaning up unused files') - python_cache = Env.get('cache')._path try: db = get_session() for root, dirs, walk_files in os.walk(Env.get('cache_dir')): @@ -78,11 +77,13 @@ class FileManager(Plugin): os.remove(file_path) except: log.error('Failed removing unused file: %s', traceback.format_exc()) + db.rollback() + finally: + db.close() def showCacheFile(self, route, **kwargs): Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), StaticFileHandler, {'path': Env.get('cache_dir')})]) - def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None): if not urlopen_kwargs: urlopen_kwargs = {} @@ -104,42 +105,56 @@ class FileManager(Plugin): def add(self, path = '', part = 1, type_tuple = (), available = 1, properties = None): if not properties: properties = {} - type_id = self.getType(type_tuple).get('id') - db = get_session() + try: + db = get_session() + type_id = self.getType(type_tuple).get('id') - f = db.query(File).filter(File.path == toUnicode(path)).first() - if not f: - f = File() - db.add(f) + f = db.query(File).filter(File.path == toUnicode(path)).first() + if not f: + f = File() + db.add(f) - f.path = toUnicode(path) - f.part = part - f.available = available - f.type_id = type_id + f.path = toUnicode(path) + f.part = part + f.available = available + f.type_id = type_id - db.commit() + db.commit() - file_dict = f.to_dict() + file_dict = f.to_dict() - return file_dict + return file_dict + except: + log.error('Failed adding file: %s, %s', (path, traceback.format_exc())) + db.rollback() + finally: + db.close() def getType(self, type_tuple): - db = get_session() - type_type, type_identifier = type_tuple + try: + db = get_session() + type_type, type_identifier = type_tuple - ft = db.query(FileType).filter_by(identifier = type_identifier).first() - if not ft: - ft = FileType( - type = toUnicode(type_type), - identifier = type_identifier, - name = toUnicode(type_identifier[0].capitalize() + type_identifier[1:]) - ) - db.add(ft) - db.commit() + ft = db.query(FileType).filter_by(identifier = type_identifier).first() + if not ft: + ft = FileType( + type = toUnicode(type_type), + identifier = type_identifier, + name = toUnicode(type_identifier[0].capitalize() + type_identifier[1:]) + ) + db.add(ft) + db.commit() + + type_dict = ft.to_dict() + + return type_dict + except: + log.error('Failed getting type: %s, %s', (type_tuple, traceback.format_exc())) + db.rollback() + finally: + db.close() - type_dict = ft.to_dict() - return type_dict def getTypes(self): diff --git a/couchpotato/core/plugins/log/__init__.py b/couchpotato/core/plugins/log/__init__.py index 33dcf338..f5d9d105 100644 --- a/couchpotato/core/plugins/log/__init__.py +++ b/couchpotato/core/plugins/log/__init__.py @@ -1,5 +1,6 @@ from .main import Logging + def start(): return Logging() diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py index dc8f740f..2f471586 100644 --- a/couchpotato/core/plugins/log/main.py +++ b/couchpotato/core/plugins/log/main.py @@ -42,7 +42,7 @@ class Logging(Plugin): 'desc': 'Log errors', 'params': { 'type': {'desc': 'Type of logging, default "error"'}, - '**kwargs': {'type':'object', 'desc': 'All other params will be printed in the log string.'}, + '**kwargs': {'type': 'object', 'desc': 'All other params will be printed in the log string.'}, } }) diff --git a/couchpotato/core/plugins/log/static/log.js b/couchpotato/core/plugins/log/static/log.js index e9e4af05..159bfeaa 100644 --- a/couchpotato/core/plugins/log/static/log.js +++ b/couchpotato/core/plugins/log/static/log.js @@ -73,10 +73,10 @@ Page.Log = new Class({ .replace(/\u001b\[31m/gi, '') .replace(/\u001b\[36m/gi, '') .replace(/\u001b\[33m/gi, '') - .replace(/\u001b\[0m\n/gi, '') + .replace(/\u001b\[0m\n/gi, '