diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index 6b8cfd36..dba45861 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -49,7 +49,11 @@ def addView(route, func, static = False): def get_session(): - return Env.getSession() + return None + + +def get_db(): + return Env.get('db') # Web view diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 02e21f2d..22b5a91b 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -42,11 +42,11 @@ class Core(Plugin): addEvent('app.shutdown', self.shutdown) addEvent('app.restart', self.restart) - addEvent('app.load', self.launchBrowser, priority = 1) + addEvent('app.load2', self.launchBrowser, priority = 1) addEvent('app.base_url', self.createBaseUrl) addEvent('app.api_url', self.createApiUrl) addEvent('app.version', self.version) - addEvent('app.load', self.checkDataDir) + addEvent('app.load2', self.checkDataDir) addEvent('setting.save.core.password', self.md5Password) addEvent('setting.save.core.api_key', self.checkApikey) diff --git a/couchpotato/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py index 248d2bc5..1c9d82a6 100644 --- a/couchpotato/core/_base/clientscript/main.py +++ b/couchpotato/core/_base/clientscript/main.py @@ -74,7 +74,7 @@ class ClientScript(Plugin): addEvent('clientscript.get_scripts', self.getScripts) if not Env.get('dev'): - addEvent('app.load', self.minify) + addEvent('app.load2', self.minify) self.addCore() diff --git a/couchpotato/core/_base/desktop/main.py b/couchpotato/core/_base/desktop/main.py index c3beff17..21499159 100644 --- a/couchpotato/core/_base/desktop/main.py +++ b/couchpotato/core/_base/desktop/main.py @@ -25,7 +25,7 @@ if Env.get('desktop'): # Events to desktop addEvent('app.after_shutdown', desktop.afterShutdown) - addEvent('app.load', desktop.onAppLoad, priority = 110) + addEvent('app.load2', desktop.onAppLoad, priority = 110) def onClose(self, event): return fireEvent('app.shutdown', single = True) diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index ef595ad7..aad4eee2 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -33,8 +33,8 @@ class Updater(Plugin): else: self.updater = SourceUpdater() - addEvent('app.load', self.logVersion, priority = 10000) - addEvent('app.load', self.setCrons) + addEvent('app.load2', self.logVersion, priority = 10000) + addEvent('app.load2', self.setCrons) addEvent('updater.info', self.info) addApiView('updater.info', self.info, docs = { diff --git a/couchpotato/core/downloaders/rtorrent/main.py b/couchpotato/core/downloaders/rtorrent/main.py index 27177756..23b7a25c 100755 --- a/couchpotato/core/downloaders/rtorrent/main.py +++ b/couchpotato/core/downloaders/rtorrent/main.py @@ -23,7 +23,7 @@ class rTorrent(Downloader): def __init__(self): super(rTorrent, self).__init__() - addEvent('app.load', self.migrate) + addEvent('app.load2', self.migrate) def migrate(self): diff --git a/couchpotato/core/media/__init__.py b/couchpotato/core/media/__init__.py index 512c52c2..db87aa75 100644 --- a/couchpotato/core/media/__init__.py +++ b/couchpotato/core/media/__init__.py @@ -1,9 +1,11 @@ import traceback -from couchpotato import get_session +from couchpotato import get_session, get_db, 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): @@ -24,20 +26,19 @@ class MediaBase(Plugin): def getType(self): return self._type - def createOnComplete(self, id): + def createOnComplete(self, media_id): def onComplete(): 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 + db = get_db() + media = db.get('id', media_id) + event_name = '%s.searcher.single' % media.get('type') - fireEvent(event_name, media_dict, on_complete = self.createNotifyFront(id)) + fireEvent(event_name, media, on_complete = self.createNotifyFront(media_id)) except: log.error('Failed creating onComplete: %s', traceback.format_exc()) finally: - db.close() + pass #db.close() return onComplete @@ -45,15 +46,14 @@ class MediaBase(Plugin): def notifyFront(): 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 + db = get_db() + media = db.get('id', media_id) + event_name = '%s.update' % media.get('type') - fireEvent('notify.frontend', type = event_name, data = media_dict) + fireEvent('notify.frontend', type = event_name, data = media) except: log.error('Failed creating onComplete: %s', traceback.format_exc()) finally: - db.close() + pass #db.close() return notifyFront diff --git a/couchpotato/core/media/_base/media/index.py b/couchpotato/core/media/_base/media/index.py new file mode 100644 index 00000000..bd90fcf4 --- /dev/null +++ b/couchpotato/core/media/_base/media/index.py @@ -0,0 +1,60 @@ +from CodernityDB.hash_index import HashIndex +from CodernityDB.tree_index import MultiTreeBasedIndex + + +class MediaIMDBIndex(HashIndex): + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = 'I' + super(MediaIMDBIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return int(key.strip('t')) + + def make_key_value(self, data): + if data.get('type') == 'media' and data.get('identifier'): + return int(data['identifier'].strip('t')), None + + +class MediaStatusIndex(HashIndex): + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = 's' + super(MediaStatusIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return int(key.strip('t')) + + def make_key_value(self, data): + if data.get('type') == 'media' and data.get('identifier'): + return int(data['identifier'].strip('t')), None + + +class TitleIndex(MultiTreeBasedIndex): + + custom_header = """from CodernityDB.tree_index import MultiTreeBasedIndex +from itertools import izip""" + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = '32s' + super(TitleIndex, self).__init__(*args, **kwargs) + self.__l = kwargs.get('w_len', 2) + + def make_key_value(self, data): + + if data.get('type') == 'title' and len(data.get('title', '')) > 0: + + out = set() + title = data.get('title').lower() + l = self.__l + max_l = len(title) + for x in xrange(l - 1, max_l): + m = (title, ) + for y in xrange(0, x): + m += (title[y + 1:],) + out.update(set(''.join(x).rjust(32, '_').lower() for x in izip(*m))) #ignore import error + + return out, {'media_id': data.get('media_id')} + + def make_key(self, key): + return key.rjust(32, '_').lower() diff --git a/couchpotato/core/media/_base/media/main.py b/couchpotato/core/media/_base/media/main.py index 5345b07c..9a50ffe8 100644 --- a/couchpotato/core/media/_base/media/main.py +++ b/couchpotato/core/media/_base/media/main.py @@ -1,11 +1,12 @@ import traceback -from couchpotato import get_session, tryInt +from couchpotato import get_session, tryInt, get_db from couchpotato.api import addApiView from couchpotato.core.event import fireEvent, fireEventAsync, addEvent from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.variable import mergeDicts, splitString, getImdb, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.media import MediaBase +from .index import MediaIMDBIndex, TitleIndex, MediaStatusIndex from couchpotato.core.settings.model import Library, LibraryTitle, Release, \ Media from sqlalchemy.orm import joinedload_all @@ -60,16 +61,40 @@ class MediaPlugin(MediaBase): addApiView('media.available_chars', self.charView) - addEvent('app.load', self.addSingleRefreshView) - addEvent('app.load', self.addSingleListView) - addEvent('app.load', self.addSingleCharView) - addEvent('app.load', self.addSingleDeleteView) + addEvent('database.setup', self.databaseSetup) + + addEvent('app.load2', self.addSingleRefreshView) + addEvent('app.load2', self.addSingleListView) + addEvent('app.load2', self.addSingleCharView) + addEvent('app.load2', self.addSingleDeleteView) addEvent('media.get', self.get) addEvent('media.list', self.list) addEvent('media.delete', self.delete) addEvent('media.restatus', self.restatus) + def databaseSetup(self): + + db = get_db() + + try: + db.add_index(MediaIMDBIndex(db.path, 'media')) + except: + log.debug('Index already exists') + db.update_index(MediaIMDBIndex(db.path, 'media')) + + try: + db.add_index(TitleIndex(db.path, 'media_title')) + except: + log.debug('Index already exists') + db.update_index(TitleIndex(db.path, 'media_title')) + + try: + db.add_index(MediaStatusIndex(db.path, 'media_status')) + except: + log.debug('Index already exists') + db.update_index(MediaStatusIndex(db.path, 'media_status')) + def refresh(self, id = '', **kwargs): handlers = [] ids = splitString(id) @@ -87,24 +112,23 @@ class MediaPlugin(MediaBase): 'success': True, } - def createRefreshHandler(self, id): - db = get_session() + def createRefreshHandler(self, media_id): - media = db.query(Media).filter_by(id = id).first() + try: + media = get_db().get('id', media_id) - if media: - - default_title = getTitle(media.library) - identifier = media.library.identifier - event = 'library.update.%s' % media.type + default_title = getTitle(media_id) + event = 'library.update.%s' % media.get('type') def handler(): - fireEvent(event, identifier = identifier, default_title = default_title, on_complete = self.createOnComplete(id)) + fireEvent(event, identifier = media.get('identifier'), default_title = default_title, on_complete = self.createOnComplete(media_id)) - db.close() + if handler: + return handler + + except: + log.error('Refresh handler for non existing media: %s', traceback.format_exc()) - if handler: - return handler def addSingleRefreshView(self): @@ -113,20 +137,19 @@ class MediaPlugin(MediaBase): def get(self, media_id): - db = get_session() + db = get_db() imdb_id = getImdb(str(media_id)) if imdb_id: - m = db.query(Media).filter(Media.library.has(identifier = imdb_id)).first() + m = db.get('media', imdb_id, with_doc = True)['doc'] else: - m = db.query(Media).filter_by(id = media_id).first() + m = db.get('id', media_id) results = None if m: - results = m.to_dict(self.default_dict) + results = db.run('media', 'to_dict', m, self.default_dict) - db.close() return results def getView(self, id = None, **kwargs): @@ -262,7 +285,7 @@ class MediaPlugin(MediaBase): 'releases_count': releases_count.get(media_id), })) - db.close() + pass #db.close() return total_count, movies def listView(self, **kwargs): @@ -356,7 +379,7 @@ class MediaPlugin(MediaBase): if len(chars) == 25: break - db.close() + pass #db.close() return ''.join(sorted(chars)) def charView(self, **kwargs): @@ -428,7 +451,7 @@ class MediaPlugin(MediaBase): log.error('Failed deleting media: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return True @@ -481,5 +504,5 @@ class MediaPlugin(MediaBase): log.error('Failed restatus: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() diff --git a/couchpotato/core/media/_base/search/main.py b/couchpotato/core/media/_base/search/main.py index 81897b5f..6f0f5172 100644 --- a/couchpotato/core/media/_base/search/main.py +++ b/couchpotato/core/media/_base/search/main.py @@ -25,7 +25,7 @@ class Search(Plugin): }"""} }) - addEvent('app.load', self.addSingleSearches) + addEvent('app.load2', self.addSingleSearches) def search(self, q = '', types = None, **kwargs): diff --git a/couchpotato/core/media/_base/searcher/base.py b/couchpotato/core/media/_base/searcher/base.py index 5322d850..60799bf0 100644 --- a/couchpotato/core/media/_base/searcher/base.py +++ b/couchpotato/core/media/_base/searcher/base.py @@ -28,7 +28,7 @@ class SearcherBase(Plugin): fireEvent('schedule.cron', '%s.searcher.all' % _type, self.searchAll, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) - addEvent('app.load', setCrons) + addEvent('app.load2', setCrons) addEvent('setting.save.%s_searcher.cron_day.after' % _type, setCrons) addEvent('setting.save.%s_searcher.cron_hour.after' % _type, setCrons) addEvent('setting.save.%s_searcher.cron_minute.after' % _type, setCrons) diff --git a/couchpotato/core/media/movie/_base/main.py b/couchpotato/core/media/movie/_base/main.py index a7ecf2d2..306766c4 100644 --- a/couchpotato/core/media/movie/_base/main.py +++ b/couchpotato/core/media/movie/_base/main.py @@ -1,5 +1,5 @@ import traceback -from couchpotato import get_session +from couchpotato import get_session, get_db from couchpotato.api import addApiView from couchpotato.core.event import fireEvent, fireEventAsync, addEvent from couchpotato.core.helpers.encoding import toUnicode @@ -62,78 +62,78 @@ class MovieBase(MovieTypeBase): except: pass - library = fireEvent('library.add.movie', single = True, attrs = params, update_after = update_library) - - # Status - status_active, snatched_status, ignored_status, done_status, downloaded_status = \ - fireEvent('status.get', ['active', 'snatched', 'ignored', 'done', 'downloaded'], single = True) + # library = fireEvent('library.add.movie', single = True, attrs = params, update_after = update_library) + info = fireEvent('movie.info', merge = True, extended = False, identifier = params.get('identifier')) default_profile = fireEvent('profile.default', single = True) cat_id = params.get('category_id') try: - db = get_session() - m = db.query(Media).filter_by(library_id = library.get('id')).first() + db = get_db() + + new = False + try: + m = db.get('movie', params.get('identifier'), with_doc = True)['doc'] + except: + new = True + m = db.insert({ + 'type': 'movie', + 'identifier': params.get('identifier'), + 'status': status_id if status_id else 'active', + 'profile_id': params.get('profile_id', default_profile.get('id')), + 'category_id': tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None, + }) + 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() - + if new: onComplete = None if search_after: - onComplete = self.createOnComplete(m.id) + onComplete = self.createOnComplete(m['_id']) - fireEventAsync('library.update.movie', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete) + # 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')]: + for release in db.run('release', 'for_media', m['_id']): + if release.get('status') in ['downloaded', 'snatched', 'done']: if params.get('ignore_previous', False): - release.status_id = ignored_status.get('id') + release['status'] = 'ignored' + db.update(release) else: - fireEvent('release.delete', release.id, single = True) + 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) + 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()) + m['status'] = status_id if status_id else 'active' + m['last_edit'] = int(time.time()) do_search = True - db.commit() + db.update(m) # Remove releases - available_status = fireEvent('status.get', 'available', single = True) - for rel in m.releases: - if rel.status_id is available_status.get('id'): + for rel in db.run('release', 'for_media', m['_id']): + if rel['status'] is 'available': db.delete(rel) - db.commit() - movie_dict = m.to_dict(self.default_dict) + movie_dict = db.run('movie', 'to_dict', m['_id']) if do_search and search_after: - onComplete = self.createOnComplete(m.id) + 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) + title = getTitle(m) if title: message = 'Successfully added "%s" to your wanted list.' % title else: @@ -142,10 +142,7 @@ class MovieBase(MovieTypeBase): return movie_dict except: - log.error('Failed deleting media: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() + log.error('Failed adding media: %s', traceback.format_exc()) def addView(self, **kwargs): add_dict = self.add(params = kwargs) @@ -158,49 +155,44 @@ class MovieBase(MovieTypeBase): def edit(self, id = '', **kwargs): try: - db = get_session() - - available_status = fireEvent('status.get', 'available', single = True) + db = get_db() ids = splitString(id) for media_id in ids: - m = db.query(Media).filter_by(id = media_id).first() - if not m: - continue + try: + m = db.get('media', media_id) + 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 db.run('release', 'for_media', m['_id']): + if rel['status'] is 'available': + db.delete(rel) - # 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['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.update(m) - db.commit() + fireEvent('media.restatus', m['_id']) - fireEvent('media.restatus', m.id) + movie_dict = db.run('media', 'to_dict', m['_id']) + 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)) + except: + log.error('Can\'t edit non-existing media') return { 'success': True, } except: - log.error('Failed deleting media: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() + log.error('Failed editing media: %s', traceback.format_exc()) return { 'success': False, diff --git a/couchpotato/core/media/movie/library/movie/main.py b/couchpotato/core/media/movie/library/movie/main.py index 034a8fb0..663c6d04 100644 --- a/couchpotato/core/media/movie/library/movie/main.py +++ b/couchpotato/core/media/movie/library/movie/main.py @@ -62,7 +62,7 @@ class MovieLibraryPlugin(LibraryBase): log.error('Failed adding media: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return {} @@ -130,31 +130,24 @@ class MovieLibraryPlugin(LibraryBase): # Files images = info.get('images', []) + library['files'] = [] for image_type in ['poster']: for image in images.get(image_type, []): if not isinstance(image, (str, unicode)): continue file_path = fireEvent('file.download', url = image, single = True) - if file_path: - file_obj = fireEvent('file.add', path = file_path, type_tuple = ('image', image_type), single = True) - try: - file_obj = db.query(File).filter_by(id = file_obj.get('id')).one() - library.files.append(file_obj) - db.commit() - break - except: - log.debug('Failed to attach to library: %s', traceback.format_exc()) - db.rollback() + # TODO: save in movie doc + library['files'].append({ + 'type': 'image_%s' % image_type, + 'path': file_path + }) 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() return {} @@ -180,7 +173,7 @@ class MovieLibraryPlugin(LibraryBase): log.error('Failed updating release dates: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return {} diff --git a/couchpotato/core/media/movie/searcher/main.py b/couchpotato/core/media/movie/searcher/main.py index bde5b44b..b4665deb 100644 --- a/couchpotato/core/media/movie/searcher/main.py +++ b/couchpotato/core/media/movie/searcher/main.py @@ -1,4 +1,4 @@ -from couchpotato import get_session +from couchpotato import get_session, get_db from couchpotato.api import addApiView from couchpotato.core.event import addEvent, fireEvent, fireEventAsync from couchpotato.core.helpers.encoding import simplifyString @@ -51,7 +51,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase): }) if self.conf('run_on_launch'): - addEvent('app.load', self.searchAll) + addEvent('app.load2', self.searchAll) def searchAllView(self, **kwargs): @@ -71,27 +71,16 @@ class MovieSearcher(SearcherBase, MovieTypeBase): self.in_progress = True fireEvent('notify.frontend', type = 'movie.searcher.started', data = True, message = 'Full search started') - db = get_session() + db = get_db() - movies_raw = db.query(Media).filter( - Media.status.has(identifier = 'active') - ).all() + movies = db.get_many('movie_status', 'active') - 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': {}, - })) + #TODO: random.shuffle(movies_raw) + total = db.count(db.get_many, 'movie_status', 'active') self.in_progress = { - 'total': len(movies), - 'to_go': len(movies), + 'total': total, + 'to_go': total, } try: @@ -99,13 +88,15 @@ class MovieSearcher(SearcherBase, MovieTypeBase): for movie in movies: + movie_dict = db.run('media', 'to_dict', movie['_id']) + try: - self.single(movie, search_protocols) + self.single(movie_dict, search_protocols) except IndexError: - 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']) + log.error('Forcing library update for %s, if you see this often, please report: %s', (movie['identifier'], traceback.format_exc())) + fireEvent('library.update.movie', movie['identifier']) except: - log.error('Search failed for %s: %s', (movie['library']['identifier'], traceback.format_exc())) + log.error('Search failed for %s: %s', (movie['identifier'], traceback.format_exc())) self.in_progress['to_go'] -= 1 @@ -117,7 +108,6 @@ class MovieSearcher(SearcherBase, MovieTypeBase): pass self.in_progress = False - db.close() def single(self, movie, search_protocols = None, manual = False): @@ -132,32 +122,30 @@ class MovieSearcher(SearcherBase, MovieTypeBase): except SearchSetupError: return - done_status = fireEvent('status.get', 'done', single = True) - - if not movie['profile'] or (movie['status_id'] == done_status.get('id') and not manual): + if not movie['profile_id'] or (movie['status'] == 'done' and not manual): log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.') return 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) found_releases = [] too_early_to_search = [] - default_title = getTitle(movie['library']) + default_title = getTitle(movie) if not default_title: log.error('No proper info found for movie, removing it from library to cause it from having more issues.') - fireEvent('media.delete', movie['id'], single = True) + fireEvent('media.delete', movie['_id'], single = True) return - fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'id': movie['id']}, message = 'Searching for "%s"' % default_title) + fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'id': movie['_id']}, message = 'Searching for "%s"' % default_title) - db = get_session() + db = get_db() ret = False + for quality_type in movie['profile']['types']: - if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): + if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['year']): too_early_to_search.append(quality_type['quality']['identifier']) continue @@ -165,7 +153,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase): # See if better quality is available for release in movie['releases']: - if release['quality']['order'] <= quality_type['quality']['order'] and release['status_id'] not in [available_status.get('id'), ignored_status.get('id'), failed_status.get('id')]: + if release['quality']['order'] <= quality_type['quality']['order'] and release['status'] not in ['available', 'ignored', 'failed']: has_better_quality += 1 # Don't search for quality lower then already available. @@ -179,7 +167,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase): log.debug('Nothing found for %s in %s', (default_title, quality_type['quality']['label'])) # Check if movie isn't deleted while searching - if not db.query(Media).filter_by(id = movie.get('id')).first(): + if not fireEvent('media.get', movie.get('_id'), single = True): break # Add them to this movie releases list @@ -191,12 +179,12 @@ class MovieSearcher(SearcherBase, MovieTypeBase): # Remove releases that aren't found anymore for release in movie.get('releases', []): - if release.get('status_id') == available_status.get('id') and release.get('identifier') not in found_releases: - fireEvent('release.delete', release.get('id'), single = True) + if release.get('status') == 'available' and release.get('identifier') not in found_releases: + fireEvent('release.delete', release.get('_id'), single = True) else: log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title)) - fireEvent('media.restatus', movie['id']) + fireEvent('media.restatus', movie['_id']) break # Break if CP wants to shut down @@ -206,9 +194,9 @@ class MovieSearcher(SearcherBase, MovieTypeBase): if len(too_early_to_search) > 0: log.info2('Too early to search for %s, %s', (too_early_to_search, default_title)) - fireEvent('notify.frontend', type = 'movie.searcher.ended', data = {'id': movie['id']}) + fireEvent('notify.frontend', type = 'movie.searcher.ended', data = {'id': movie['_id']}) - db.close() + pass #db.close() return ret def correctRelease(self, nzb = None, media = None, quality = None, **kwargs): @@ -329,31 +317,23 @@ class MovieSearcher(SearcherBase, MovieTypeBase): def tryNextRelease(self, media_id, manual = False): - snatched_status, done_status, ignored_status = fireEvent('status.get', ['snatched', 'done', 'ignored'], single = True) - try: - db = get_session() - rels = db.query(Release) \ - .filter_by(movie_id = media_id) \ - .filter(Release.status_id.in_([snatched_status.get('id'), done_status.get('id')])) \ - .all() + db = get_db() + rels = db.run('media', 'with_status', media_id, status = ['snatched', 'done']) for rel in rels: - rel.status_id = ignored_status.get('id') - db.commit() + rel['status'] = 'ignored' + db.update(rel) - movie_dict = fireEvent('media.get', media_id = media_id, single = True) - log.info('Trying next release for: %s', getTitle(movie_dict['library'])) - fireEvent('movie.searcher.single', movie_dict, manual = manual) + movie_dict = db.run('media', 'to_dict', media_id) + log.info('Trying next release for: %s', getTitle(movie_dict)) + self.single(movie_dict, manual = manual) return True 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/main.py b/couchpotato/core/media/movie/suggestion/main.py index d1d64089..eaccbe64 100644 --- a/couchpotato/core/media/movie/suggestion/main.py +++ b/couchpotato/core/media/movie/suggestion/main.py @@ -33,7 +33,7 @@ class Suggestion(Plugin): .options(joinedload_all('library')) \ .filter(or_(*[Media.status.has(identifier = s) for s in ['active', 'done']])).all() movies = [x.library.identifier for x in active_movies] - db.close() + pass #db.close() if not ignored or len(ignored) == 0: ignored = splitString(Env.prop('suggest_ignore', default = '')) @@ -98,7 +98,7 @@ class Suggestion(Plugin): .filter(Media.status_id.in_([active_status.get('id'), done_status.get('id')])).all() movies = [x[0] for x in active_movies] movies.extend(seen) - db.close() + pass #db.close() ignored.extend([x.get('imdb') for x in cached_suggestion]) suggestions = fireEvent('movie.suggest', movies = movies, ignore = removeDuplicate(ignored), single = True) diff --git a/couchpotato/core/notifications/core/index.py b/couchpotato/core/notifications/core/index.py new file mode 100644 index 00000000..e51124d6 --- /dev/null +++ b/couchpotato/core/notifications/core/index.py @@ -0,0 +1,42 @@ +import time +from CodernityDB.tree_index import TreeBasedIndex + + +class NotificationIndex(TreeBasedIndex): + + custom_header = """from CodernityDB.tree_index import TreeBasedIndex +import time""" + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = 'I' + super(NotificationIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return key + + def make_key_value(self, data): + if data.get('type') == 'notification': + added = data.get('added', time.time()) + data['added'] = added + + return added, None + + +class NotificationUnreadIndex(TreeBasedIndex): + + custom_header = """from CodernityDB.tree_index import TreeBasedIndex +import time""" + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = 'I' + super(NotificationUnreadIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return key + + def make_key_value(self, data): + if data.get('type') == 'notification' and not data.get('read'): + added = data.get('added', time.time()) + data['added'] = added + + return added, None diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index d77758b6..4a245cdd 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -1,14 +1,13 @@ -from couchpotato import get_session +from couchpotato import get_db from couchpotato.api import addApiView, addNonBlockApiView from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.variable import tryInt, splitString from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification -from couchpotato.core.settings.model import Notification as Notif +from .index import NotificationIndex, NotificationUnreadIndex from couchpotato.environment import Env from operator import itemgetter -from sqlalchemy.sql.expression import or_ import threading import time import traceback @@ -58,48 +57,55 @@ class CoreNotifier(Notification): fireEvent('schedule.interval', 'core.check_messages', self.checkMessages, hours = 12, single = True) fireEvent('schedule.interval', 'core.clean_messages', self.cleanMessages, seconds = 15, single = True) - addEvent('app.load', self.clean) - addEvent('app.load', self.checkMessages) + addEvent('app.load2', self.clean) + addEvent('app.load2', self.checkMessages) + + addEvent('database.setup', self.databaseSetup) self.messages = [] self.listeners = [] self.m_lock = threading.Lock() - def clean(self): + def databaseSetup(self): + + db = get_db() try: - db = get_session() - db.query(Notif).filter(Notif.added <= (int(time.time()) - 2419200)).delete() - db.commit() + db.add_index(NotificationIndex(db.path, 'notification')) + except: + log.debug('Index already exists') + db.edit_index(NotificationIndex(db.path, 'notification')) + + try: + db.add_index(NotificationUnreadIndex(db.path, 'notification_unread')) + except: + log.debug('Index already exists') + db.edit_index(NotificationUnreadIndex(db.path, 'notification_unread')) + + def clean(self): + try: + db = get_db() + for n in db.all('notification', with_doc = True): + if n['doc']['added'] <= (int(time.time()) - 2419200): + db.delete(n['doc']) 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 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) - - q.update({Notif.read: True}) - db.commit() - + db = get_db() + for x in db.all('notification_unread', with_doc = True): + if not ids or x['_id'] in ids: + x['doc']['read'] = True + db.update(x['doc']) return { 'success': True } except: log.error('Failed mark as read: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() return { 'success': False @@ -107,26 +113,20 @@ class CoreNotifier(Notification): def listView(self, limit_offset = None, **kwargs): - db = get_session() - - q = db.query(Notif) + db = get_db() if limit_offset: splt = splitString(limit_offset) limit = splt[0] offset = 0 if len(splt) is 1 else splt[1] - q = q.limit(limit).offset(offset) + results = db.get_many('notification', limit = limit, offset = offset, with_doc = True) else: - q = q.limit(200) + results = db.get_many('notification', limit = 200, with_doc = True) - results = q.all() notifications = [] for n in results: - ndict = n.to_dict() - ndict['type'] = 'notification' - notifications.append(ndict) + notifications.append(n['doc']) - db.close() return { 'success': True, 'empty': len(notifications) == 0, @@ -156,30 +156,23 @@ class CoreNotifier(Notification): if not data: data = {} try: - db = get_session() + db = get_db() data['notification_type'] = listener if listener else 'unknown' - n = Notif( - message = toUnicode(message), - data = data - ) - db.add(n) - db.commit() + n = { + 'type': 'notification', + 'time': time.time(), + 'message': toUnicode(message), + 'data': data + } + db.insert(n) - ndict = n.to_dict() - ndict['type'] = 'notification' - ndict['time'] = time.time() + self.frontend(type = listener, data = n) - self.frontend(type = listener, data = data) - - db.close() 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 = {} @@ -278,18 +271,13 @@ class CoreNotifier(Notification): # Get unread if init: - db = get_session() + db = get_db() - notifications = db.query(Notif) \ - .filter(or_(Notif.read == False, Notif.added > (time.time() - 259200))) \ - .all() + notifications = db.all('notification_unread', with_doc = True) for n in notifications: - ndict = n.to_dict() - ndict['type'] = 'notification' - messages.append(ndict) - - db.close() + if n['doc'].get('added') > (time.time() - 259200): + messages.append(n['doc']) return { 'success': True, diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py index a3927ed2..68d73cb1 100644 --- a/couchpotato/core/notifications/growl/main.py +++ b/couchpotato/core/notifications/growl/main.py @@ -16,7 +16,7 @@ class Growl(Notification): super(Growl, self).__init__() if self.isEnabled(): - addEvent('app.load', self.register) + addEvent('app.load2', self.register) def register(self): if self.registered: return diff --git a/couchpotato/core/plugins/automation/main.py b/couchpotato/core/plugins/automation/main.py index 2edcd3be..44d7673f 100644 --- a/couchpotato/core/plugins/automation/main.py +++ b/couchpotato/core/plugins/automation/main.py @@ -10,10 +10,10 @@ class Automation(Plugin): def __init__(self): - addEvent('app.load', self.setCrons) + addEvent('app.load2', self.setCrons) if not Env.get('dev'): - addEvent('app.load', self.addMovies) + addEvent('app.load2', self.addMovies) addEvent('setting.save.automation.hour.after', self.setCrons) diff --git a/couchpotato/core/plugins/category/main.py b/couchpotato/core/plugins/category/main.py index 41965b03..b26a8293 100644 --- a/couchpotato/core/plugins/category/main.py +++ b/couchpotato/core/plugins/category/main.py @@ -42,7 +42,7 @@ class CategoryPlugin(Plugin): for category in categories: temp.append(category.to_dict()) - db.close() + pass #db.close() return temp def save(self, **kwargs): @@ -66,7 +66,7 @@ class CategoryPlugin(Plugin): category_dict = c.to_dict() - db.close() + pass #db.close() return { 'success': True, 'category': category_dict @@ -75,7 +75,7 @@ class CategoryPlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return { 'success': False, @@ -96,7 +96,7 @@ class CategoryPlugin(Plugin): db.commit() - db.close() + pass #db.close() return { 'success': True } @@ -104,7 +104,7 @@ class CategoryPlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return { 'success': False @@ -129,7 +129,7 @@ class CategoryPlugin(Plugin): except Exception as e: message = log.error('Failed deleting category: %s', e) - db.close() + pass #db.close() return { 'success': success, 'message': message @@ -138,7 +138,7 @@ class CategoryPlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return { 'success': False @@ -158,4 +158,4 @@ class CategoryPlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() diff --git a/couchpotato/core/plugins/custom/main.py b/couchpotato/core/plugins/custom/main.py index a15c915c..42400994 100644 --- a/couchpotato/core/plugins/custom/main.py +++ b/couchpotato/core/plugins/custom/main.py @@ -10,7 +10,7 @@ log = CPLog(__name__) class Custom(Plugin): def __init__(self): - addEvent('app.load', self.createStructure) + addEvent('app.load2', self.createStructure) def createStructure(self): diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 73866411..bc14f4b5 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -119,7 +119,7 @@ class Dashboard(Plugin): 'files': {}, })) - db.close() + pass #db.close() return { 'success': True, 'empty': len(movies) == 0, diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py index 18071547..c24d0d58 100644 --- a/couchpotato/core/plugins/file/main.py +++ b/couchpotato/core/plugins/file/main.py @@ -19,9 +19,7 @@ log = CPLog(__name__) class FileManager(Plugin): def __init__(self): - addEvent('file.add', self.add) addEvent('file.download', self.download) - addEvent('file.types', self.getTypes) addApiView('file.cache/(.*)', self.showCacheFile, static = True, docs = { 'desc': 'Return a file from the cp_data/cache directory', @@ -31,36 +29,8 @@ class FileManager(Plugin): 'return': {'type': 'file'} }) - addApiView('file.types', self.getTypesView, docs = { - 'desc': 'Return a list of all the file types and their ids.', - 'return': {'type': 'object', 'example': """{ - 'types': [ - { - "identifier": "poster_original", - "type": "image", - "id": 1, - "name": "Poster_original" - }, - { - "identifier": "poster", - "type": "image", - "id": 2, - "name": "Poster" - }, - etc - ] -}"""} - }) - - addEvent('app.load', self.cleanup) - addEvent('app.load', self.init) - - def init(self): - - for type_tuple in Scanner.file_types.values(): - self.getType(type_tuple) - def cleanup(self): + # TODO: unused # Wait a bit after starting before cleanup time.sleep(3) @@ -77,9 +47,6 @@ 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')})]) @@ -101,76 +68,3 @@ class FileManager(Plugin): self.createFile(dest, filedata, binary = True) return dest - - def add(self, path = '', part = 1, type_tuple = (), available = 1, properties = None): - if not properties: properties = {} - - 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.path = toUnicode(path) - f.part = part - f.available = available - f.type_id = type_id - - db.commit() - - file_dict = f.to_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): - - 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() - - 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() - - - def getTypes(self): - - db = get_session() - - results = db.query(FileType).all() - - types = [] - for type_object in results: - types.append(type_object.to_dict()) - - db.close() - return types - - def getTypesView(self, **kwargs): - - return { - 'types': self.getTypes() - } diff --git a/couchpotato/core/plugins/manage/main.py b/couchpotato/core/plugins/manage/main.py index 2f297491..47081692 100644 --- a/couchpotato/core/plugins/manage/main.py +++ b/couchpotato/core/plugins/manage/main.py @@ -47,7 +47,7 @@ class Manage(Plugin): }) if not Env.get('dev') and self.conf('startup_scan'): - addEvent('app.load', self.updateLibraryQuick) + addEvent('app.load2', self.updateLibraryQuick) def getProgress(self, **kwargs): return { diff --git a/couchpotato/core/plugins/nosql/__init__.py b/couchpotato/core/plugins/nosql/__init__.py new file mode 100644 index 00000000..7f0e952e --- /dev/null +++ b/couchpotato/core/plugins/nosql/__init__.py @@ -0,0 +1,7 @@ +from .main import NoSQL + + +def start(): + return NoSQL() + +config = [] diff --git a/couchpotato/core/plugins/nosql/index.py b/couchpotato/core/plugins/nosql/index.py new file mode 100644 index 00000000..e1361799 --- /dev/null +++ b/couchpotato/core/plugins/nosql/index.py @@ -0,0 +1,20 @@ +from hashlib import md5 +from CodernityDB.tree_index import TreeBasedIndex + + +class ReleaseIndex(TreeBasedIndex): + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = '16s' + super(ReleaseIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return md5(key).digest() + + def make_key_value(self, data): + if data.get('type') == 'release': + return md5(data['media_id']).digest(), None + + def run_for_media(self, db, media_id): + for release in db.get_many('release', media_id, with_doc=True): + yield release['doc'] diff --git a/couchpotato/core/plugins/nosql/main.py b/couchpotato/core/plugins/nosql/main.py new file mode 100644 index 00000000..11f15cb9 --- /dev/null +++ b/couchpotato/core/plugins/nosql/main.py @@ -0,0 +1,72 @@ +import time +from couchpotato import CPLog +from couchpotato.core.plugins.base import Plugin +from .index import ReleaseIndex, MediaIMDBIndex, TitleIndex + +log = CPLog(__name__) + + +class NoSQL(Plugin): + + db = None + + def test(self): + + db = self.db + + try: db.add_index(ReleaseIndex(db.path, 'release')) + except: log.debug('Index already exists') + + for id in range(10): + media = db.insert({ + 'type': 'media', + 'tmdb': id, + 'imdb': 'tt%s' % id, + 'last_edit': 0, + 'status': 'active', + 'title': 'Lord of the Rings: The Return of the King', + 'year': 2011, + 'profile_id': 0, + 'category_id': 0, + }) + + db.insert({ + 'media_id': media['_id'], + 'type': 'title', + 'title': 'Lord of the Rings: The Return of the King', + }) + + for x in range(40): + db.insert({ + 'media_id': media['_id'], + 'type': 'release', + 'name': 'Release %s' % x + }) + + print db.count(db.all, 'media') + + m = db.get('media', 'tt0') + db.get('id', m['_id']) + + start = time.time() + print list(db.get_many('media_title', 'lord of')) + print time.time() - start + + return + + return + + for media in db.all('media', with_doc = True): + doc = media['doc'] + for r in db.run('release', 'for_media', media['_id']): + db.delete(r) + + db.delete(doc) + break + + start = time.time() + db.reindex() + print time.time() - start + + print db.count(db.all, 'media') + print db.count(db.all, 'release') diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py index cc048733..9d721852 100644 --- a/couchpotato/core/plugins/profile/main.py +++ b/couchpotato/core/plugins/profile/main.py @@ -1,5 +1,5 @@ import traceback -from couchpotato import get_session +from couchpotato import get_session, get_db from couchpotato.api import addApiView from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import toUnicode @@ -31,27 +31,24 @@ class ProfilePlugin(Plugin): }) addEvent('app.initialize', self.fill, priority = 90) - addEvent('app.load', self.forceDefaults) + addEvent('app.load2', self.forceDefaults) def forceDefaults(self): # Get all active movies without profile - active_status = fireEvent('status.get', 'active', single = True) - try: - db = get_session() - movies = db.query(Media).filter(Media.status_id == active_status.get('id'), Media.profile == None).all() + db = get_db() + medias = db.run('media', 'with_status', ['active']) - if len(movies) > 0: - default_profile = self.default() - for movie in movies: - movie.profile_id = default_profile.get('id') - db.commit() + profile_ids = [x.get('_id') for x in self.all()] + + for media in medias: + if media['profile_id'] not in profile_ids: + default_profile = self.default() + media['profile_id'] = default_profile.get('id') + db.update(media) except: log.error('Failed: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() def allView(self, **kwargs): @@ -62,17 +59,10 @@ class ProfilePlugin(Plugin): def all(self): - db = get_session() - profiles = db.query(Profile) \ - .options(joinedload_all('types')) \ - .all() + db = get_db() + profiles = db.all('profile', with_doc = True) - temp = [] - for profile in profiles: - temp.append(profile.to_dict(self.to_dict)) - - db.close() - return temp + return list(profiles) def save(self, **kwargs): @@ -115,7 +105,7 @@ class ProfilePlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return { 'success': False @@ -128,7 +118,7 @@ class ProfilePlugin(Plugin): .options(joinedload_all('types')) \ .first() default_dict = default.to_dict(self.to_dict) - db.close() + pass #db.close() return default_dict @@ -154,7 +144,7 @@ class ProfilePlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return { 'success': False @@ -188,7 +178,7 @@ class ProfilePlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return { 'success': False @@ -243,6 +233,6 @@ class ProfilePlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return False diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index cc6215d3..e5070093 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -79,7 +79,7 @@ class QualityPlugin(Plugin): self.cached_qualities = temp - db.close() + pass #db.close() return temp def single(self, identifier = ''): @@ -91,7 +91,7 @@ class QualityPlugin(Plugin): if quality: quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict()) - db.close() + pass #db.close() return quality_dict def getQuality(self, identifier): @@ -119,7 +119,7 @@ class QualityPlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return { 'success': False @@ -181,7 +181,7 @@ class QualityPlugin(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return False diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index 1e1352c5..9066fa18 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -1,4 +1,4 @@ -from couchpotato import get_session, md5 +from couchpotato import get_session, md5, get_db from couchpotato.api import addApiView from couchpotato.core.event import fireEvent, addEvent from couchpotato.core.helpers.encoding import ss, toUnicode @@ -72,34 +72,32 @@ class Release(Plugin): done_status, available_status, snatched_status, downloaded_status, ignored_status = \ fireEvent('status.get', ['done', 'available', 'snatched', 'downloaded', 'ignored'], single = True) - db = get_session() + db = get_db() # get movies last_edit more than a week ago - media = db.query(Media) \ - .filter(Media.status_id == done_status.get('id'), Media.last_edit < (now - week)) \ - .all() + medias = db.run('media', 'with_status', ['done']) - for item in media: - for rel in item.releases: + for media in medias: + if media['last_edit'] > (now - week): + continue + + for rel in db.run('release', 'for_media', media['_id']): # Remove all available releases - if rel.status_id in [available_status.get('id')]: - fireEvent('release.delete', id = rel.id, single = True) - # Set all snatched and downloaded releases to ignored to make sure they are ignored when re-adding the move - elif rel.status_id in [snatched_status.get('id'), downloaded_status.get('id')]: - self.updateStatus(id = rel.id, status = ignored_status) + if rel['status'] in ['available']: + fireEvent('release.delete', id = rel['_id'], single = True) - db.close() + # Set all snatched and downloaded releases to ignored to make sure they are ignored when re-adding the move + elif rel['status'] in ['snatched', 'downloaded']: + self.updateStatus(id = rel['id'], status = ignored_status) def add(self, group): try: - db = get_session() + db = get_db() identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier']) - done_status, snatched_status = fireEvent('status.get', ['done', 'snatched'], single = True) - # Add movie media = db.query(Media).filter_by(library_id = group['library'].get('id')).first() if not media: @@ -129,41 +127,28 @@ class Release(Plugin): db.commit() # Add each file type - added_files = [] + rel['files'] = [] for type in group['files']: for cur_file in group['files'][type]: - added_file = self.saveFile(cur_file, type = type, include_media_info = type is 'movie') - added_files.append(added_file.get('id')) + added_file = self.saveFile(cur_file, type = type) + rel['files'].append(added_file.get('id')) - # Add the release files in batch - try: - added_files = db.query(File).filter(or_(*[File.id == x for x in added_files])).all() - rel.files.extend(added_files) - db.commit() - except: - log.debug('Failed to attach "%s" to release: %s', (added_files, traceback.format_exc())) - - fireEvent('media.restatus', media.id) + fireEvent('media.restatus', media['_id']) return True except: log.error('Failed: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() return False def saveFile(self, filepath, type = 'unknown', include_media_info = False): - properties = {} - - # Get media info for files - if include_media_info: - properties = {} - # Check database and update/insert if necessary - return fireEvent('file.add', path = filepath, part = fireEvent('scanner.partnumber', file, single = True), type_tuple = Scanner.file_types.get(type), properties = properties, single = True) + return { + 'type': '%s_%s' % Scanner.file_types.get(type), + 'path': filepath, + 'part': fireEvent('scanner.partnumber', file, single = True), + } def deleteView(self, id = None, **kwargs): @@ -171,60 +156,58 @@ class Release(Plugin): 'success': self.delete(id) } - def delete(self, id): + def delete(self, release_id): try: - db = get_session() + db = get_db() - rel = db.query(Relea).filter_by(id = id).first() - if rel: - db.delete(rel) - db.commit() - return True + rel = db.get('release', release_id, with_doc = True) + db.delete(rel['doc']) + return True except: log.error('Failed: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() return False - def clean(self, id): + def clean(self, release_id): try: - db = get_session() + db = get_db() - rel = db.query(Relea).filter_by(id = id).first() - if rel: - for release_file in rel.files: - if not os.path.isfile(ss(release_file.path)): - db.delete(release_file) - db.commit() + rel = db.get('release', release_id, with_doc = True) + files = [] + for release_file in rel['files']: + if os.path.isfile(ss(release_file['path'])): + files.append(release_file) - if len(rel.files) == 0: - self.delete(id) + if len(rel['files']) == 0: + self.delete(rel['_id']) + else: + rel['files'] = files + db.update(rel) - return True + return True except: log.error('Failed: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() return False - def ignore(self, id = None, **kwargs): + def ignore(self, release_id = None, **kwargs): - db = get_session() + db = get_db() - rel = db.query(Relea).filter_by(id = id).first() - if rel: - ignored_status, failed_status, available_status = fireEvent('status.get', ['ignored', 'failed', 'available'], single = True) - self.updateStatus(id, available_status if rel.status_id in [ignored_status.get('id'), failed_status.get('id')] else ignored_status) + try: + rel = db.get('release', release_id, with_doc = True)['doc'] + self.updateStatus(release_id, 'available' if rel['status'] in ['ignored', 'failed'] else 'ignored') + + return { + 'success': True + } + except: + log.error('Failed: %s', traceback.format_exc()) - db.close() return { - 'success': True + 'success': False } def manualDownload(self, id = None, **kwargs): @@ -265,7 +248,7 @@ class Release(Plugin): if success: fireEvent('notify.frontend', type = 'release.manual_download', data = True, message = 'Successfully snatched "%s"' % item['name']) - db.close() + pass #db.close() return { 'success': success == True } @@ -361,7 +344,7 @@ class Release(Plugin): db.rollback() return False finally: - db.close() + pass #db.close() return True @@ -441,7 +424,7 @@ class Release(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return [] @@ -458,7 +441,7 @@ class Release(Plugin): releases = [r.to_dict({'info': {}, 'files': {}}) for r in releases_raw] releases = sorted(releases, key = lambda k: k['info'].get('score', 0), reverse = True) - db.close() + pass #db.close() return releases def forMovieView(self, id = None, **kwargs): @@ -506,6 +489,6 @@ class Release(Plugin): log.error('Failed: %s', traceback.format_exc()) db.rollback() finally: - db.close() + pass #db.close() return False diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index c4c67deb..868dbc59 100755 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -598,7 +598,7 @@ class Renamer(Plugin): break self.renaming_started = False - db.close() + pass #db.close() def getRenameExtras(self, extra_type = '', replacements = None, folder_name = '', file_name = '', destination = '', group = None, current_file = '', remove_multiple = False): if not group: group = {} @@ -1045,7 +1045,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) self.checking_snatched = False return True finally: - db.close() + pass #db.close() def extendReleaseDownload(self, release_download): @@ -1068,7 +1068,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) if not rls: log.error('Download ID %s from downloader %s not found in releases', (release_download.get('id'), release_download.get('downloader'))) - db.close() + pass #db.close() if rls: diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index 00a5b752..7e8fb9bb 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -441,7 +441,7 @@ class Scanner(Plugin): else: log.debug('Found no movies in the folder %s', folder) - db.close() + pass #db.close() return processed_movies def getMetaData(self, group, folder = '', release_download = None): @@ -609,7 +609,7 @@ class Scanner(Plugin): break except: pass - db.close() + pass #db.close() # Search based on identifiers if not imdb_id: diff --git a/couchpotato/core/plugins/status/__init__.py b/couchpotato/core/plugins/status/__init__.py deleted file mode 100644 index 204fbee7..00000000 --- a/couchpotato/core/plugins/status/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .main import StatusPlugin - - -def start(): - return StatusPlugin() - -config = [] diff --git a/couchpotato/core/plugins/status/main.py b/couchpotato/core/plugins/status/main.py deleted file mode 100644 index 55852610..00000000 --- a/couchpotato/core/plugins/status/main.py +++ /dev/null @@ -1,137 +0,0 @@ -import traceback -from couchpotato import get_session -from couchpotato.api import addApiView -from couchpotato.core.event import addEvent -from couchpotato.core.helpers.encoding import toUnicode -from couchpotato.core.logger import CPLog -from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Status - -log = CPLog(__name__) - - -class StatusPlugin(Plugin): - - statuses = { - 'needs_update': 'Needs update', - 'active': 'Active', - 'done': 'Done', - 'downloaded': 'Downloaded', - 'wanted': 'Wanted', - 'snatched': 'Snatched', - 'failed': 'Failed', - 'deleted': 'Deleted', - 'ignored': 'Ignored', - 'available': 'Available', - 'suggest': 'Suggest', - 'seeding': 'Seeding', - 'missing': 'Missing', - } - status_cached = {} - - def __init__(self): - addEvent('status.get', self.get) - addEvent('status.get_by_id', self.getById) - addEvent('status.all', self.all) - addEvent('app.initialize', self.fill) - addEvent('app.load', self.all) # Cache all statuses - - addApiView('status.list', self.list, docs = { - 'desc': 'Check for available update', - 'return': {'type': 'object', 'example': """{ - 'success': True, - 'list': array, statuses -}"""} - }) - - def list(self, **kwargs): - - return { - 'success': True, - 'list': self.all() - } - - def getById(self, id): - db = get_session() - status = db.query(Status).filter_by(id = id).first() - status_dict = status.to_dict() - db.close() - - return status_dict - - def all(self): - - db = get_session() - - statuses = db.query(Status).all() - - temp = [] - for status in statuses: - s = status.to_dict() - temp.append(s) - - # Update cache - self.status_cached[status.identifier] = s - - db.close() - return temp - - def get(self, identifiers): - - if not isinstance(identifiers, list): - identifiers = [identifiers] - - try: - db = get_session() - return_list = [] - - for identifier in identifiers: - - if self.status_cached.get(identifier): - return_list.append(self.status_cached.get(identifier)) - continue - - s = db.query(Status).filter_by(identifier = identifier).first() - if not s: - s = Status( - identifier = identifier, - label = toUnicode(identifier.capitalize()) - ) - db.add(s) - db.commit() - - status_dict = s.to_dict() - - self.status_cached[identifier] = status_dict - return_list.append(status_dict) - - return return_list if len(identifiers) > 1 else return_list[0] - except: - log.error('Failed: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() - - def fill(self): - - try: - db = get_session() - - for identifier, label in self.statuses.items(): - s = db.query(Status).filter_by(identifier = identifier).first() - if not s: - log.info('Creating status: %s', label) - s = Status( - identifier = identifier, - label = toUnicode(label) - ) - db.add(s) - - s.label = toUnicode(label) - db.commit() - except: - log.error('Failed: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() - diff --git a/couchpotato/core/plugins/status/static/status.js b/couchpotato/core/plugins/status/static/status.js deleted file mode 100644 index 2b8d30f3..00000000 --- a/couchpotato/core/plugins/status/static/status.js +++ /dev/null @@ -1,17 +0,0 @@ -var StatusBase = new Class({ - - setup: function(statuses){ - var self = this; - - self.statuses = statuses; - - }, - - get: function(id){ - return this.statuses.filter(function(status){ - return status.id == id - }).pick() - }, - -}); -window.Status = new StatusBase(); diff --git a/couchpotato/core/plugins/subtitle/main.py b/couchpotato/core/plugins/subtitle/main.py index e51df35e..b8d9a06b 100644 --- a/couchpotato/core/plugins/subtitle/main.py +++ b/couchpotato/core/plugins/subtitle/main.py @@ -41,7 +41,7 @@ class Subtitle(Plugin): # get subtitles for those files subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services) - db.close() + pass #db.close() def searchSingle(self, group): if self.isDisabled(): return diff --git a/couchpotato/core/providers/info/_modifier/main.py b/couchpotato/core/providers/info/_modifier/main.py index 598a01d7..cefecb74 100644 --- a/couchpotato/core/providers/info/_modifier/main.py +++ b/couchpotato/core/providers/info/_modifier/main.py @@ -104,7 +104,7 @@ class MovieResultModifier(Plugin): except: log.error('Tried getting more info on searched movies: %s', traceback.format_exc()) - db.close() + pass #db.close() return temp def checkLibrary(self, result): diff --git a/couchpotato/core/providers/metadata/base.py b/couchpotato/core/providers/metadata/base.py index 72d07609..3f336be4 100644 --- a/couchpotato/core/providers/metadata/base.py +++ b/couchpotato/core/providers/metadata/base.py @@ -90,6 +90,7 @@ class MetaDataBase(Plugin): file_type = {} for ft in file_types: + # TODO: change type to "image_"+wanted_file_type if ft.get('identifier') == wanted_file_type: file_type = ft break diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py index e6565d07..bbbc9381 100644 --- a/couchpotato/core/settings/__init__.py +++ b/couchpotato/core/settings/__init__.py @@ -4,6 +4,7 @@ from couchpotato.api import addApiView from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.variable import mergeDicts, tryInt, tryFloat +from couchpotato.core.settings.index import PropertyIndex from couchpotato.core.settings.model import Properties import ConfigParser @@ -42,6 +43,7 @@ class Settings(object): } }"""} }) + addApiView('settings.save', self.saveView, docs = { 'desc': 'Save setting to config file (settings.conf)', 'params': { @@ -51,6 +53,8 @@ class Settings(object): } }) + addEvent('database.setup', self.databaseSetup) + def setFile(self, config_file): self.file = config_file @@ -62,6 +66,17 @@ class Settings(object): self.connectEvents() + def databaseSetup(self): + from couchpotato import get_db + + db = get_db() + + try: + db.add_index(PropertyIndex(db.path, 'property')) + except: + self.log.debug('Index already exists') + db.edit_index(PropertyIndex(db.path, 'property')) + def parser(self): return self.p @@ -206,36 +221,33 @@ class Settings(object): } def getProperty(self, identifier): - from couchpotato import get_session + from couchpotato import get_db - db = get_session() + db = get_db() prop = None try: - propert = db.query(Properties).filter_by(identifier = identifier).first() - prop = propert.value + propert = db.get('property', identifier, with_doc = True) + prop = propert['doc']['value'] except: - pass + self.log.debug('Property doesn\'t exist: %s', traceback.format_exc(0)) - db.close() return prop def setProperty(self, identifier, value = ''): - from couchpotato import get_session + from couchpotato import get_db + + db = get_db() try: - db = get_session() - - p = db.query(Properties).filter_by(identifier = identifier).first() - if not p: - p = Properties() - db.add(p) - - p.identifier = identifier - p.value = toUnicode(value) - - db.commit() + p = db.get('property', identifier, with_doc = True) + p['doc'].update({ + 'identifier': identifier, + 'value': toUnicode(value), + }) + db.update(p['doc']) except: - self.log.error('Failed: %s', traceback.format_exc()) - db.rollback() - finally: - db.close() + db.insert({ + 'type': 'property', + 'identifier': identifier, + 'value': toUnicode(value), + }) diff --git a/couchpotato/core/settings/index.py b/couchpotato/core/settings/index.py new file mode 100644 index 00000000..45d366eb --- /dev/null +++ b/couchpotato/core/settings/index.py @@ -0,0 +1,16 @@ +from CodernityDB.hash_index import UniqueHashIndex, HashIndex +from hashlib import md5 + + +class PropertyIndex(HashIndex): + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = '16s' + super(PropertyIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return md5(key).digest() + + def make_key_value(self, data): + if data.get('type') == 'property': + return md5(data['identifier']).digest(), None diff --git a/couchpotato/environment.py b/couchpotato/environment.py index 42f8fded..5eb1272c 100644 --- a/couchpotato/environment.py +++ b/couchpotato/environment.py @@ -31,7 +31,7 @@ class Env(object): _app_dir = "" _data_dir = "" _cache_dir = "" - _db_path = "" + _db = "" _log_path = "" @staticmethod @@ -54,28 +54,6 @@ class Env(object): def set(attr, value): return setattr(Env, '_' + attr, value) - @staticmethod - def getSession(): - existing_session = Env.get('session') - if existing_session: - return existing_session() - - session = scoped_session(sessionmaker(bind = Env.getEngine())) - Env.set('session', session) - - return session() - - @staticmethod - def getEngine(): - existing_engine = Env.get('engine') - if existing_engine: - return existing_engine - - engine = create_engine(Env.get('db_path'), echo = False) - Env.set('engine', engine) - - return engine - @staticmethod def setting(attr, section = 'core', value = None, default = '', type = None): diff --git a/couchpotato/runner.py b/couchpotato/runner.py index bd033983..744d425b 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -1,3 +1,4 @@ +from CodernityDB.database_thread_safe import ThreadSafeDatabase from argparse import ArgumentParser from cache import FileSystemCache from couchpotato import KeyHandler, LoginHandler, LogoutHandler @@ -82,52 +83,25 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En Env.set('encoding', encoding) # Do db stuff - db_path = toUnicode(os.path.join(data_dir, 'couchpotato.db')) + db_path = toUnicode(os.path.join(data_dir, 'database')) - # Backup before start and cleanup old databases - new_backup = toUnicode(os.path.join(data_dir, 'db_backup', str(int(time.time())))) - if not os.path.isdir(new_backup): os.makedirs(new_backup) + # Check if database exists + print db_path - # Remove older backups, keep backups 3 days or at least 3 - backups = [] - for directory in os.listdir(os.path.dirname(new_backup)): - backup = toUnicode(os.path.join(os.path.dirname(new_backup), directory)) - if os.path.isdir(backup): - backups.append(backup) + db = ThreadSafeDatabase(db_path) + db_exists = db.exists() + if db_exists: + db.open() + else: + db.create() - latest_backup = tryInt(os.path.basename(sorted(backups)[-1])) if len(backups) > 0 else 0 - if latest_backup < time.time() - 3600: - # Create path and copy - src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal'] - for src_file in src_files: - if os.path.isfile(src_file): - dst_file = toUnicode(os.path.join(new_backup, os.path.basename(src_file))) - shutil.copyfile(src_file, dst_file) - - # Try and copy stats seperately - try: shutil.copystat(src_file, dst_file) - except: pass - - total_backups = len(backups) - for backup in backups: - if total_backups > 3: - if tryInt(os.path.basename(backup)) < time.time() - 259200: - for the_file in os.listdir(backup): - file_path = os.path.join(backup, the_file) - try: - if os.path.isfile(file_path): - os.remove(file_path) - except: - raise - - os.rmdir(backup) - total_backups -= 1 + # TODO:Backup before start and cleanup old databases # Register environment settings Env.set('app_dir', toUnicode(base_path)) Env.set('data_dir', toUnicode(data_dir)) Env.set('log_path', toUnicode(os.path.join(log_dir, 'CouchPotato.log'))) - Env.set('db_path', toUnicode('sqlite:///' + db_path)) + Env.set('db', db) Env.set('cache_dir', toUnicode(os.path.join(data_dir, 'cache'))) Env.set('cache', FileSystemCache(toUnicode(os.path.join(Env.get('cache_dir'), 'python')))) Env.set('console_log', options.console_log) @@ -183,34 +157,6 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En log.warning('%s %s %s line:%s', (category, message, filename, lineno)) warnings.showwarning = customwarn - # Check if database exists - db = Env.get('db_path') - db_exists = os.path.isfile(toUnicode(db_path)) - - # Load migrations - if False and db_exists: - - from migrate.versioning.api import version_control, db_version, version, upgrade - repo = os.path.join(base_path, 'couchpotato', 'core', 'migration') - - latest_db_version = version(repo) - try: - current_db_version = db_version(db, repo) - except: - version_control(db, repo, version = latest_db_version) - current_db_version = db_version(db, repo) - - if current_db_version < latest_db_version: - if development: - log.error('There is a database migration ready, but you are running development mode, so it won\'t be used. If you see this, you are stupid. Please disable development mode.') - else: - log.info('Doing database upgrade. From %d to %d', (current_db_version, latest_db_version)) - upgrade(db, repo) - - # Configure Database - from couchpotato.core.settings.model import setup - setup() - # Create app from couchpotato import WebHandler web_base = ('/' + Env.setting('url_base').lstrip('/') + '/') if Env.setting('url_base') else '/' @@ -279,6 +225,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # Fill database with needed stuff if not db_exists: fireEvent('app.initialize', in_order = True) + fireEvent('database.setup') # Go go go! from tornado.ioloop import IOLoop @@ -287,7 +234,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # Some logging and fire load event try: log.info('Starting server on port %(port)s', config) except: pass - fireEventAsync('app.load') + fireEventAsync('app.load2') if config['ssl_cert'] and config['ssl_key']: server = HTTPServer(application, no_keep_alive = True, ssl_options = { diff --git a/couchpotato/templates/index.html b/couchpotato/templates/index.html index 52a4491b..af257454 100644 --- a/couchpotato/templates/index.html +++ b/couchpotato/templates/index.html @@ -71,8 +71,6 @@ Status.setup({{ json_encode(fireEvent('status.all', single = True)) }}); - File.Type.setup({{ json_encode(fireEvent('file.types', single = True)) }}); - CategoryList.setup({{ json_encode(fireEvent('category.all', single = True)) }}); App.setup({ @@ -95,4 +93,4 @@