diff --git a/README.md b/README.md index e38ea0e8..19616889 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -CouchPotato Server +CouchPotato ===== CouchPotato (CP) is an automatic NZB and torrent downloader. You can keep a "movies I want"-list and it will search for NZBs/torrents of these movies every X hours. @@ -7,7 +7,7 @@ Once a movie is found, it will send it to SABnzbd or download the torrent to a s ## Running from Source -CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed also. +CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed. Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for more details: @@ -40,7 +40,7 @@ Linux (ubuntu / debian): * Make it executable. `sudo chmod +x /etc/init.d/couchpotato` * Add it to defaults. `sudo update-rc.d couchpotato defaults` * Open your browser and go to: `http://localhost:5050/` - + FreeBSD : diff --git a/couchpotato/core/media/_base/media/index.py b/couchpotato/core/media/_base/media/index.py index 97b16066..d83c347c 100644 --- a/couchpotato/core/media/_base/media/index.py +++ b/couchpotato/core/media/_base/media/index.py @@ -1,7 +1,9 @@ from itertools import izip +from string import ascii_letters from CodernityDB.hash_index import HashIndex from CodernityDB.tree_index import MultiTreeBasedIndex, TreeBasedIndex from hashlib import md5 +from couchpotato.core.helpers.encoding import toUnicode, simplifyString class MediaIMDBIndex(HashIndex): @@ -22,13 +24,13 @@ class MediaIMDBIndex(HashIndex): return db.get('id', media_id) - def run_with_status(self, db, status = []): + def run_with_status(self, db, status = [], with_doc = True): status = list(status if isinstance(status, (list, tuple)) else [status]) for s in status: - for ms in db.get_many('media_status', s, with_doc = True): - yield ms['doc'] + for ms in db.get_many('media_status', s, with_doc = with_doc): + yield ms['doc'] if with_doc else ms class MediaStatusIndex(TreeBasedIndex): @@ -45,31 +47,46 @@ class MediaStatusIndex(TreeBasedIndex): return md5(data.get('status')).hexdigest(), None -class TitleIndex(MultiTreeBasedIndex): - - custom_header = """from CodernityDB.tree_index import MultiTreeBasedIndex -from itertools import izip""" +class MediaTypeIndex(TreeBasedIndex): def __init__(self, *args, **kwargs): kwargs['key_format'] = '32s' - super(TitleIndex, self).__init__(*args, **kwargs) + super(MediaTypeIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return md5(key).hexdigest() + + def make_key_value(self, data): + if data.get('_t') == 'media' and data.get('type'): + return md5(data.get('type')).hexdigest(), None + + +class TitleSearchIndex(MultiTreeBasedIndex): + + custom_header = """from CodernityDB.tree_index import MultiTreeBasedIndex +from itertools import izip +from couchpotato.core.helpers.encoding import simplifyString""" + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = '32s' + super(TitleSearchIndex, self).__init__(*args, **kwargs) self.__l = kwargs.get('w_len', 2) def make_key_value(self, data): - if data.get('_t') == 'title' and len(data.get('title', '')) > 0: + if data.get('_t') == 'media' and len(data.get('title', '')) > 0: out = set() - title = data.get('title').lower() + title = str(simplifyString(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 + out.update(set(''.join(x).rjust(32, '_').lower() for x in izip(*m))) - return out, {'media_id': data.get('media_id')} + return out, None def make_key(self, key): return key.rjust(32, '_').lower() @@ -87,3 +104,64 @@ class YearIndex(TreeBasedIndex): def make_key_value(self, data): if data.get('_t') == 'media' and data.get('year') is not None: return data['year'], None + + +class TitleIndex(TreeBasedIndex): + + custom_header = """from CodernityDB.tree_index import TreeBasedIndex +from string import ascii_letters +from couchpotato.core.helpers.encoding import toUnicode, simplifyString""" + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = '32s' + super(TitleIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return self.simplify(key) + + def make_key_value(self, data): + if data.get('_t') == 'media' and data.get('title') is not None: + return self.simplify(data['title']), None + + def simplify(self, title): + + title = toUnicode(title) + + nr_prefix = '' if title[0] in ascii_letters else '#' + title = simplifyString(title) + + for prefix in ['the ']: + if prefix == title[:len(prefix)]: + title = title[len(prefix):] + break + + return str(nr_prefix + title).ljust(32, '_')[:32] + + +class StartsWithIndex(TreeBasedIndex): + + custom_header = """from CodernityDB.tree_index import TreeBasedIndex +from string import ascii_letters +from couchpotato.core.helpers.encoding import toUnicode, simplifyString""" + + def __init__(self, *args, **kwargs): + kwargs['key_format'] = '1s' + super(StartsWithIndex, self).__init__(*args, **kwargs) + + def make_key(self, key): + return self.first(key) + + def make_key_value(self, data): + if data.get('_t') == 'media' and data.get('title') is not None: + return self.first(data['title']), None + + def first(self, title): + title = toUnicode(title) + title = simplifyString(title) + + for prefix in ['the ']: + if prefix == title[:len(prefix)]: + title = title[len(prefix):] + break + + return str(title[0] if title[0] in ascii_letters else '#').lower() diff --git a/couchpotato/core/media/_base/media/main.py b/couchpotato/core/media/_base/media/main.py index b4b0cc18..17e443b3 100644 --- a/couchpotato/core/media/_base/media/main.py +++ b/couchpotato/core/media/_base/media/main.py @@ -1,14 +1,13 @@ import traceback -import time 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.variable import mergeDicts, splitString, getImdb, getTitle +from couchpotato.core.helpers.encoding import toUnicode +from couchpotato.core.helpers.variable import splitString, getImdb, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.media import MediaBase -from .index import MediaIMDBIndex, TitleIndex, MediaStatusIndex, YearIndex -from couchpotato.core.settings.model import Library, LibraryTitle, Release, \ - Media +from .index import MediaIMDBIndex, MediaStatusIndex, YearIndex, MediaTypeIndex, TitleSearchIndex, TitleIndex, StartsWithIndex +from couchpotato.core.settings.model import Media from string import ascii_lowercase log = CPLog(__name__) @@ -61,10 +60,10 @@ class MediaPlugin(MediaBase): addEvent('database.setup', self.databaseSetup) - addEvent('app.load', self.addSingleRefreshView) - addEvent('app.load', self.addSingleListView) - addEvent('app.load', self.addSingleCharView) - addEvent('app.load', self.addSingleDeleteView) + addEvent('app.load', self.addSingleRefreshView, priority = 100) + addEvent('app.load', self.addSingleListView, priority = 100) + addEvent('app.load', self.addSingleCharView, priority = 100) + addEvent('app.load', self.addSingleDeleteView, priority = 100) addEvent('media.get', self.get) addEvent('media.list', self.list) @@ -84,10 +83,10 @@ class MediaPlugin(MediaBase): # Title index try: - db.add_index(TitleIndex(db.path, 'media_title')) + db.add_index(TitleSearchIndex(db.path, 'media_search_title')) except: log.debug('Index already exists') - db.edit_index(TitleIndex(db.path, 'media_title')) + db.edit_index(TitleSearchIndex(db.path, 'media_search_title')) # Status index try: @@ -96,11 +95,31 @@ class MediaPlugin(MediaBase): log.debug('Index already exists') db.edit_index(MediaStatusIndex(db.path, 'media_status')) - # Year index - try: db.add_index(YearIndex(db.path, 'year')) + # Type index + try: + db.add_index(MediaTypeIndex(db.path, 'media_by_type')) except: log.debug('Index already exists') - db.edit_index(YearIndex(db.path, 'year')) + db.edit_index(MediaTypeIndex(db.path, 'media_by_type')) + + # Year index + try: db.add_index(YearIndex(db.path, 'media_year')) + except: + log.debug('Index already exists') + db.edit_index(YearIndex(db.path, 'media_year')) + + # Title index + try: db.add_index(TitleIndex(db.path, 'media_title')) + except: + log.debug('Index already exists') + db.edit_index(TitleIndex(db.path, 'media_title')) + + # Startswith index + try: db.add_index(StartsWithIndex(db.path, 'media_startswith')) + except: + log.debug('Index already exists') + db.edit_index(StartsWithIndex(db.path, 'media_startswith')) + def refresh(self, id = '', **kwargs): handlers = [] @@ -112,7 +131,7 @@ class MediaPlugin(MediaBase): if refresh_handler: handlers.append(refresh_handler) - fireEvent('notify.frontend', type = 'media.busy', data = {'id': [tryInt(x) for x in ids]}) + fireEvent('notify.frontend', type = 'media.busy', data = {'id': ids}) fireEventAsync('schedule.queue', handlers = handlers) return { @@ -124,8 +143,9 @@ class MediaPlugin(MediaBase): try: media = get_db().get('id', media_id) + default_title = getTitle(media) - event = 'library.update.%s' % media.get('type') + event = '%s.update_info' % media.get('type') def handler(): fireEvent(event, identifier = media.get('identifier'), default_title = default_title, on_complete = self.createOnComplete(media_id)) @@ -172,8 +192,6 @@ class MediaPlugin(MediaBase): db = get_db() - start = time.time() - # Make a list from string if status and not isinstance(status, (list, tuple)): status = [status] @@ -183,125 +201,73 @@ class MediaPlugin(MediaBase): types = [types] # query media ids - all_media_ids = set([x['_id'] for x in db.all('media')]) + if types: + all_media_ids = set() + for media_type in types: + all_media_ids = all_media_ids.union(set([x['_id'] for x in db.get_many('media_by_type', media_type)])) + else: + all_media_ids = set([x['_id'] for x in db.all('media')]) + media_ids = all_media_ids filter_by = {} # Filter on movie status if status and len(status) > 0: filter_by['media_status'] = set() - for media_status in db.run('media', 'with_status', status): + for media_status in db.run('media', 'with_status', status, with_doc = False): filter_by['media_status'].add(media_status.get('_id')) # Filter on release status if release_status and len(release_status) > 0: filter_by['release_status'] = set() - for release_status in db.run('release', 'with_status', release_status): + for release_status in db.run('release', 'with_status', release_status, with_doc = False): filter_by['release_status'].add(release_status.get('media_id')) + # Add search filters + if starts_with: + filter_by['starts_with'] = set() + starts_with = toUnicode(starts_with.lower())[0] + starts_with = starts_with if starts_with in ascii_lowercase else '#' + filter_by['starts_with'] = [x['_id'] for x in db.get_many('media_startswith', starts_with)] + + # Filter with search query + if search: + filter_by['search'] = [x['_id'] for x in db.get_many('media_search_title', search)] + # Filter by combining ids for x in filter_by: - media_ids = media_ids & filter_by[x] - - # Filter on type - # if types and len(types) > 0: - # try: q = q.filter(Media.type.in_(types)) - # except: pass - - # Only join when searching / ordering - # if starts_with or search or order != 'release_order': - # q = q.join(Media.library, Library.titles) \ - # .filter(LibraryTitle.default == True) - - # Add search filters - # filter_or = [] - # if starts_with: - # starts_with = toUnicode(starts_with.lower()) - # if starts_with in ascii_lowercase: - # filter_or.append(LibraryTitle.simple_title.startswith(starts_with)) - # else: - # ignore = [] - # for letter in ascii_lowercase: - # ignore.append(LibraryTitle.simple_title.startswith(toUnicode(letter))) - # filter_or.append(not_(or_(*ignore))) - - if search: - search_ids = db.get_many('media_title', search) - for search_id in search_ids: - print search_id - - #if len(filter_or) > 0: - # pass #q = q.filter(or_(*filter_or)) + media_ids = [n for n in media_ids if n in filter_by[x]] total_count = len(media_ids) if total_count == 0: return 0, [] - # if order == 'release_order': - # q = q.order_by(desc(Release.last_edit)) - # else: - # q = q.order_by(asc(LibraryTitle.simple_title)) + if limit_offset: + splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset + limit = tryInt(splt[0]) + offset = tryInt(0 if len(splt) is 1 else splt[1]) + start = offset * limit + end = start + limit + media_ids = media_ids[start:end] - # if limit_offset: - # splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset - # limit = splt[0] - # offset = 0 if len(splt) is 1 else splt[1] - # q = q.limit(limit).offset(offset) - - # Get all media_ids in sorted order - # media_ids = [m.id for m in q.all()] - - # List release statuses - # releases = db.query(Release) \ - # .filter(Release.movie_id.in_(media_ids)) \ - # .all() - - # release_statuses = dict((m, set()) for m in media_ids) - # releases_count = dict((m, 0) for m in media_ids) - # for release in releases: - # release_statuses[release.movie_id].add('%d,%d' % (release.status_id, release.quality_id)) - # releases_count[release.movie_id] += 1 - - # Get main movie data - # q2 = db.query(Media) \ - # .options(joinedload_all('library.titles')) \ - # .options(joinedload_all('library.files')) \ - # .options(joinedload_all('status')) \ - # .options(joinedload_all('files')) - - # q2 = q2.filter(Media.id.in_(media_ids)) - - # results = q2.all() - - # Create dict by movie id - # medias = {} - # for media in media_ids: - # movie_dict[movie.id] = movie - - # List movies based on media_ids order + # List movies based on title order medias = [] - for media_id in media_ids: + for m in db.all('media_title'): + media_id = m['_id'] + if media_id not in media_ids: continue - media = db.run('media', 'to_dict', media_id, { - 'library': {'titles': {}, 'files': {}}, - 'files': {}, - }) + media = db.run('media', 'to_dict', media_id) media['releases'] = [] for r in db.get_many('release', media_id, with_doc = True): - media['releases'].append(r) + media['releases'].append(r['doc']) # Merge releases with movie dict medias.append(media) - # medias.append(mergeDicts(movie_dict[media_id].to_dict({ - # 'library': {'titles': {}, 'files': {}}, - # 'files': {}, - # }), { - # 'releases': releases, - # 'releases_count': releases_count.get(media_id), - # })) - print time.time() - start + # remove from media ids + media_ids.remove(media_id) + if len(media_ids) == 0: break return total_count, medias @@ -341,63 +307,52 @@ class MediaPlugin(MediaBase): def availableChars(self, types = None, status = None, release_status = None): - types = types or [] - status = status or [] - release_status = release_status or [] - - db = get_session() + db = get_db() # Make a list from string - if not isinstance(status, (list, tuple)): + if status and not isinstance(status, (list, tuple)): status = [status] if release_status and not isinstance(release_status, (list, tuple)): release_status = [release_status] if types and not isinstance(types, (list, tuple)): types = [types] - q = db.query(Media) + # query media ids + if types: + all_media_ids = set() + for media_type in types: + all_media_ids = all_media_ids.union(set([x['_id'] for x in db.get_many('media_by_type', media_type)])) + else: + all_media_ids = set([x['_id'] for x in db.all('media')]) + + media_ids = all_media_ids + filter_by = {} # Filter on movie status if status and len(status) > 0: - statuses = fireEvent('status.get', status, single = len(release_status) > 1) - statuses = [s.get('id') for s in statuses] - - q = q.filter(Media.status_id.in_(statuses)) + filter_by['media_status'] = set() + for media_status in db.run('media', 'with_status', status, with_doc = False): + filter_by['media_status'].add(media_status.get('_id')) # Filter on release status if release_status and len(release_status) > 0: + filter_by['release_status'] = set() + for release_status in db.run('release', 'with_status', release_status, with_doc = False): + filter_by['release_status'].add(release_status.get('media_id')) - statuses = fireEvent('status.get', release_status, single = len(release_status) > 1) - statuses = [s.get('id') for s in statuses] - - q = q.join(Media.releases) \ - .filter(Release.status_id.in_(statuses)) - - # Filter on type - if types and len(types) > 0: - try: q = q.filter(Media.type.in_(types)) - except: pass - - q = q.join(Library, LibraryTitle) \ - .with_entities(LibraryTitle.simple_title) \ - .filter(LibraryTitle.default == True) - - titles = q.all() + # Filter by combining ids + for x in filter_by: + media_ids = [n for n in media_ids if n in filter_by[x]] chars = set() - for title in titles: - try: - char = title[0][0] - char = char if char in ascii_lowercase else '#' - chars.add(str(char)) - except: - log.error('Failed getting title for %s', title.libraries_id) + for x in db.all('media_startswith'): + if x['_id'] in media_ids: + chars.add(x['key']) if len(chars) == 25: break - pass #db.close() - return ''.join(sorted(chars)) + return list(chars) def charView(self, **kwargs): @@ -487,32 +442,30 @@ class MediaPlugin(MediaBase): def restatus(self, media_id): try: - db = get_session() + db = get_db() - 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.get('id', media_id) - log.debug('Changing status for %s', m.library.titles[0].title) + log.debug('Changing status for %s', getTitle(m)) if not m['profile_id']: m['status'] = 'done' 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.get('status') == 'done' and t.finish): + profile = db.get('id', m['profile_id']) + media_releases = db.get_many('release', m['_id']) + + for q_identifier in profile['qualities']: + index = profile['qualities'].index(q_identifier) + + for release in media_releases: + if q_identifier is release['quality'] and (release.get('status') == 'done' and profile['finish'][index]): move_to_wanted = False m['status'] = 'active' if move_to_wanted else 'done' - db.commit() + db.update(m) return True except: log.error('Failed restatus: %s', traceback.format_exc()) - db.rollback() - finally: - pass #db.close() - diff --git a/couchpotato/core/media/movie/_base/main.py b/couchpotato/core/media/movie/_base/main.py index f35d4a8b..bb04b392 100644 --- a/couchpotato/core/media/movie/_base/main.py +++ b/couchpotato/core/media/movie/_base/main.py @@ -117,12 +117,11 @@ class MovieBase(MovieTypeBase): added = True do_search = False search_after = search_after and self.conf('search_on_add', section = 'moviesearcher') + onComplete = None + if new: - onComplete = None if search_after: onComplete = self.createOnComplete(m['_id']) - - fireEventAsync('movie.update_info', m['_id'], default_title = params.get('title', ''), on_complete = onComplete) search_after = False elif force_readd: @@ -148,6 +147,9 @@ class MovieBase(MovieTypeBase): if added: db.update(m) + # Do full update to get images etc + fireEventAsync('movie.update_info', m['_id'], default_title = params.get('title'), on_complete = onComplete) + # Remove releases for rel in db.run('release', 'for_media', m['_id']): if rel['status'] is 'available': @@ -205,8 +207,9 @@ class MovieBase(MovieTypeBase): # Default title if kwargs.get('default_title'): - for title in m['titles']: - title.default = toUnicode(kwargs.get('default_title', '')).lower() == toUnicode(title.title).lower() + m['title'] = kwargs.get('default_title') + + print m db.update(m) @@ -266,35 +269,36 @@ class MovieBase(MovieTypeBase): titles = info.get('titles', []) log.debug('Adding titles: %s', titles) - counter = 0 + # Define default title 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 default_title: + counter = 0 + for title in titles: + if 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]) - media = { - 'title': def_title, - } + media['title'] = def_title # Files images = info.get('images', []) - media['files'] = media.get('filed', []) + media['files'] = media.get('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) - media['files'].append({ - 'type': 'image_%s' % image_type, - 'path': file_path - }) + file_type = 'image_%s' % image_type + if file_type not in media['files']: + file_path = fireEvent('file.download', url = image, single = True) + media['files']['image_%s' % image_type] = [file_path]; + + break db.update(media) @@ -306,7 +310,7 @@ class MovieBase(MovieTypeBase): def updateReleaseDate(self, media_id): """ - Update releasedate (eta) info only + Update release_date (eta) info only @param media_id: document id @return: dict, with dates dvd, theater, bluray, expires @@ -318,8 +322,8 @@ class MovieBase(MovieTypeBase): media = db.get('id', media_id) if not media.get('info'): - info_dict = self.updateInfo(media_id) - dates = info_dict.get('info', {}).get('release_date') + media = self.updateInfo(media_id) + dates = media.get('info', {}).get('release_date') else: dates = media.get('info').get('release_date') diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index 85dee2e5..a193daf5 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -59,9 +59,9 @@ var MovieList = new Class({ movieDeleted: function(notification){ var self = this; - if(self.movies_added[notification.data.id]){ + if(self.movies_added[notification.data._id]){ self.movies.each(function(movie){ - if(movie.get('id') == notification.data.id){ + if(movie.get('_id') == notification.data._id){ movie.destroy(); delete self.movies_added[notification.data.id]; self.setCounter(self.counter_count-1); @@ -288,7 +288,7 @@ var MovieList = new Class({ 'onSuccess': function(json){ available_chars = json.chars - json.chars.split('').each(function(c){ + available_chars.each(function(c){ self.letters[c.capitalize()].addClass('available') }) @@ -438,7 +438,7 @@ var MovieList = new Class({ var ids = [] self.movies.each(function(movie){ if (movie.isSelected()) - ids.include(movie.get('id')) + ids.include(movie.get('_id')) }); return ids @@ -467,7 +467,7 @@ var MovieList = new Class({ self.offset = 0; if(self.scrollspy){ - self.load_more.show(); + //self.load_more.show(); self.scrollspy.start(); } }, @@ -629,4 +629,4 @@ var MovieList = new Class({ return this.el; } -}); \ No newline at end of file +}); diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index c5186935..b52a5923 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -427,7 +427,7 @@ MA.Release = new Class({ Api.request('media.delete', { 'data': { - 'id': self.movie.get('id'), + 'id': self.movie.get('_id'), 'delete_from': 'wanted' }, 'onComplete': function(){ @@ -449,7 +449,7 @@ MA.Release = new Class({ Api.request('movie.searcher.try_next', { 'data': { - 'id': self.movie.get('id') + 'id': self.movie.get('_id') } }); @@ -604,13 +604,13 @@ MA.Edit = new Class({ ) ).inject(self.movie, 'top'); - Array.each(self.movie.data.library.titles, function(alt){ + Array.each(self.movie.data.info.titles, function(title){ new Element('option', { - 'text': alt.title + 'text': title }).inject(self.title_select); - if(alt['default']) - self.title_select.set('value', alt.title); + if(title == self.movie.data.title) + self.title_select.set('value', title); }); @@ -643,7 +643,7 @@ MA.Edit = new Class({ profiles.each(function(profile){ - var profile_id = profile.id ? profile.id : profile.data.id; + var profile_id = profile.get('_id'); new Element('option', { 'value': profile_id, @@ -666,7 +666,7 @@ MA.Edit = new Class({ Api.request('movie.edit', { 'data': { - 'id': self.movie.get('id'), + 'id': self.movie.get('_id'), 'default_title': self.title_select.get('value'), 'profile_id': self.profile_select.get('value'), 'category_id': self.category_select.get('value') @@ -706,7 +706,7 @@ MA.Refresh = new Class({ Api.request('media.refresh', { 'data': { - 'id': self.movie.get('id') + 'id': self.movie.get('_id') } }); } @@ -817,7 +817,7 @@ MA.Delete = new Class({ function(){ Api.request('media.delete', { 'data': { - 'id': self.movie.get('id'), + 'id': self.movie.get('_id'), 'delete_from': self.movie.list.options.identifier }, 'onComplete': function(){ diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index bd290dcd..7e07cd4a 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -59,7 +59,7 @@ var Movie = new Class({ if(!self.data.releases) self.data.releases = []; - self.data.releases.push({'quality_id': data.quality_id, 'status': data.status}); + self.data.releases.push({'quality_identifier': data.quality_identifier, 'status': data.status}); self.updateReleases(); } } @@ -156,7 +156,10 @@ var Movie = new Class({ } } }), - self.thumbnail = File.Select.single('poster', self.data.files || []), + self.thumbnail = (self.data.files && self.data.files.image_poster) ? new Element('img', { + 'class': 'type_image poster', + 'src': Api.createUrl('file.cache') + (self.data.files || {})['image_poster'][0] + }): null, self.data_container = new Element('div.data.inlay.light').adopt( self.info_container = new Element('div.info').adopt( new Element('div.title').adopt( @@ -194,7 +197,7 @@ var Movie = new Class({ if(self.profile.data) self.profile.getTypes().each(function(type){ - var q = self.addQuality(type.quality_id || type.get('quality_id')); + var q = self.addQuality(type.quality_identifier || type.get('quality_identifier')); if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){ q.addClass('finish'); q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.') @@ -223,7 +226,7 @@ var Movie = new Class({ status = release.status; if(!q && (status == 'snatched' || status == 'seeding' || status == 'done')) - var q = self.addQuality(release.quality_id) + var q = self.addQuality(release.quality_identifier) if (q && !q.hasClass(status)){ q.addClass(status); @@ -233,10 +236,10 @@ var Movie = new Class({ }); }, - addQuality: function(quality_id){ + addQuality: function(quality_identifier){ var self = this; - var q = Quality.getQuality(quality_id); + var q = Quality.getQuality(quality_identifier); return new Element('span', { 'text': q.label, 'class': 'q_'+q.identifier + ' q_id' + q.id, diff --git a/couchpotato/core/media/movie/searcher/main.py b/couchpotato/core/media/movie/searcher/main.py index fdff2fd9..69615a16 100644 --- a/couchpotato/core/media/movie/searcher/main.py +++ b/couchpotato/core/media/movie/searcher/main.py @@ -122,7 +122,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase): return pre_releases = fireEvent('quality.pre_releases', single = True) - release_dates = fireEvent('movie.update_release_date', media_id = movie['_id'], merge = True) + release_dates = fireEvent('movie.update_release_dates', movie['_id'], merge = True) found_releases = [] too_early_to_search = [] diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 18d09e76..f8d46645 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._t, [result]); if(result.message && result.read === undefined) self.showMessage(result.message); }) diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 10c1c61b..e2e4c792 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -1,13 +1,9 @@ -from couchpotato import get_session +from couchpotato import get_db from couchpotato.api import addApiView from couchpotato.core.event import fireEvent from couchpotato.core.helpers.variable import splitString, tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Media, Library, LibraryTitle, \ - Release -from sqlalchemy.orm import joinedload_all -from sqlalchemy.sql.expression import asc, or_ import random as rndm import time @@ -21,26 +17,21 @@ class Dashboard(Plugin): def getSoonView(self, limit_offset = None, random = False, late = False, **kwargs): - db = get_session() + db = get_db() now = time.time() # Get profiles first, determine pre or post theater profiles = fireEvent('profile.all', single = True) - qualities = fireEvent('quality.all', single = True) pre_releases = fireEvent('quality.pre_releases', single = True) - id_pre = {} - for quality in qualities: - id_pre[quality.get('id')] = quality.get('identifier') in pre_releases - # See what the profile contain and cache it profile_pre = {} for profile in profiles: contains = {} - for profile_type in profile.get('types', []): - contains['theater' if id_pre.get(profile_type.get('quality_id')) else 'dvd'] = True + for q_identifier in profile.get('qualities', []): + contains['theater' if q_identifier in pre_releases else 'dvd'] = True - profile_pre[profile.get('id')] = contains + profile_pre[profile.get('_id')] = contains # Add limit limit = 12 @@ -48,43 +39,32 @@ class Dashboard(Plugin): splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset limit = tryInt(splt[0]) - # Get all active movies - q = db.query(Media) \ - .join(Library) \ - .outerjoin(Media.releases) \ - .filter(Media.status_id == active_status.get('id')) \ - .with_entities(Media.id, Media.profile_id, Library.info, Library.year) \ - .group_by(Media.id) \ - .filter(or_(Release.id == None, Release.status_id == ignored_status.get('id'))) + # Get all active medias + active_ids = [x['_id'] for x in db.get_many('media_status', 'active')] - if not random: - q = q.join(LibraryTitle) \ - .filter(LibraryTitle.default == True) \ - .order_by(asc(LibraryTitle.simple_title)) + medias = [] + if len(active_ids) > 0: - active = q.all() - movies = [] + # Order by title or randomize + if not random: + orders_ids = db.all('media_title') + active_ids = [x['_id'] for x in orders_ids if x['_id'] in active_ids] + else: + rndm.shuffle(active_ids) - if len(active) > 0: + for media_id in active_ids: + media = db.get('id', media_id) - # Do the shuffle - if random: - rndm.shuffle(active) - - movie_ids = [] - for movie in active: - movie_id, profile_id, info, year = movie - - pp = profile_pre.get(profile_id) + pp = profile_pre.get(media['profile_id']) if not pp: continue - eta = info.get('release_date', {}) or {} + eta = media['info'].get('release_date', {}) or {} coming_soon = False # Theater quality - if pp.get('theater') and fireEvent('movie.searcher.could_be_released', True, eta, year, single = True): + if pp.get('theater') and fireEvent('movie.searcher.could_be_released', True, eta, media['info']['year'], single = True): coming_soon = True - elif pp.get('dvd') and fireEvent('movie.searcher.could_be_released', False, eta, year, single = True): + elif pp.get('dvd') and fireEvent('movie.searcher.could_be_released', False, eta, media['info']['year'], single = True): coming_soon = True if coming_soon: @@ -92,37 +72,15 @@ class Dashboard(Plugin): # Don't list older movies if ((not late and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or (late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))): - movie_ids.append(movie_id) + medias.append(media) - if len(movie_ids) >= limit: + if len(medias) >= limit: break - if len(movie_ids) > 0: - - # Get all movie information - movies_raw = db.query(Media) \ - .options(joinedload_all('library.titles')) \ - .options(joinedload_all('library.files')) \ - .options(joinedload_all('files')) \ - .filter(Media.id.in_(movie_ids)) \ - .all() - - # Create dict by movie id - movie_dict = {} - for movie in movies_raw: - movie_dict[movie.id] = movie - - for movie_id in movie_ids: - movies.append(movie_dict[movie_id].to_dict({ - 'library': {'titles': {}, 'files': {}}, - 'files': {}, - })) - - pass #db.close() return { 'success': True, - 'empty': len(movies) == 0, - 'movies': movies, + 'empty': len(medias) == 0, + 'movies': medias, } getLateView = getSoonView diff --git a/couchpotato/core/plugins/file/static/file.js b/couchpotato/core/plugins/file/static/file.js deleted file mode 100644 index 6659c882..00000000 --- a/couchpotato/core/plugins/file/static/file.js +++ /dev/null @@ -1,86 +0,0 @@ -var File = new Class({ - - initialize: function(type, file){ - var self = this; - - if(!file){ - self.empty = true; - self.el = new Element('div.empty_file.'+type); - return - } - - self.data = file; - self.type = File.Type.get(file.type_id); - - self['create'+(self.type.type).capitalize()]() - - }, - - createImage: function(){ - var self = this; - - var file_name = self.data.path.replace(/^.*[\\\/]/, ''); - - self.el = new Element('div', { - 'class': 'type_image ' + self.type.identifier, - 'styles': { - 'background-image': 'url('+Api.createUrl('file.cache') + file_name+')' - } - }).adopt( - new Element('img', { - 'src': Api.createUrl('file.cache') + file_name - }) - ) - }, - - toElement: function(){ - return this.el; - } - -}); - -var FileSelect = new Class({ - - multiple: function(type, files, single){ - - var results = files.filter(function(file){ - return file.type_id == File.Type.get(type).id; - }); - - if(single) - return new File(type, results.pop()); - - return results; - - }, - - single: function(type, files){ - return this.multiple(type, files, true); - } - -}); -window.File.Select = new FileSelect(); - -var FileTypeBase = new Class({ - - setup: function(types){ - var self = this; - - self.typesById = {}; - self.typesByKey = {}; - Object.each(types, function(type){ - self.typesByKey[type.identifier] = type; - self.typesById[type.id] = type; - }); - - }, - - get: function(identifier){ - if(typeOf(identifier) == 'number') - return this.typesById[identifier] - else - return this.typesByKey[identifier] - } - -}); -window.File.Type = new FileTypeBase(); diff --git a/couchpotato/core/plugins/manage/main.py b/couchpotato/core/plugins/manage/main.py index 86d00557..1c734b13 100644 --- a/couchpotato/core/plugins/manage/main.py +++ b/couchpotato/core/plugins/manage/main.py @@ -124,7 +124,7 @@ class Manage(Plugin): fireEvent('media.delete', media_id = done_movie['id'], delete_from = 'all') else: - releases = fireEvent('release.for_movie', id = done_movie.get('id'), single = True) + releases = fireEvent('release.for_movie', id = done_movie.get('_id'), single = True) for release in releases: if len(release.get('files', [])) > 0: diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py index 8f04fe3b..90335953 100644 --- a/couchpotato/core/plugins/profile/main.py +++ b/couchpotato/core/plugins/profile/main.py @@ -56,8 +56,7 @@ class ProfilePlugin(Plugin): for media in medias: if media.get('profile_id') not in profile_ids: - default_profile = self.default() - media['profile_id'] = default_profile.get('id') + media['profile_id'] = profile_ids[0].get('_id') db.update(media) except: log.error('Failed: %s', traceback.format_exc()) diff --git a/couchpotato/core/plugins/profile/static/profile.js b/couchpotato/core/plugins/profile/static/profile.js index bb0504b2..2789e98f 100644 --- a/couchpotato/core/plugins/profile/static/profile.js +++ b/couchpotato/core/plugins/profile/static/profile.js @@ -54,10 +54,20 @@ var Profile = new Class({ ) ); - self.makeSortable() + self.makeSortable(); + + // Combine qualities and properties into types + data.types = []; + data.qualities.each(function(quality, nr){ + data.types.include({ + 'quality_identifier': quality, + 'finish': data.finish[nr] || false, + 'wait_for': data.wait_for[nr] || 0 + }) + }); if(data.types) - Object.each(data.types, self.addType.bind(self)) + data.types.each(self.addType.bind(self)); else self.delete_button.hide(); @@ -111,8 +121,9 @@ var Profile = new Class({ Array.each(self.type_container.getElements('.type'), function(type){ if(!type.hasClass('deleted') && type.getElement('select').get('value') > 0) data.types.include({ - 'quality_id': type.getElement('select').get('value'), - 'finish': +type.getElement('input[type=checkbox]').checked + 'quality_identifier': type.getElement('select').get('value'), + 'finish': +type.getElement('input[type=checkbox]').checked, + 'wait_for': 0 }); }) @@ -145,7 +156,7 @@ var Profile = new Class({ var self = this; return self.types.filter(function(type){ - return type.get('quality_id') + return type.get('quality_identifier') }); }, @@ -264,7 +275,7 @@ Profile.Type = new Class({ new Element('span.handle') ); - self.el[self.data.quality_id > 0 ? 'removeClass' : 'addClass']('is_empty'); + self.el[self.data.quality_identifier ? 'removeClass' : 'addClass']('is_empty'); self.finish_class = new Form.Check(self.finish); @@ -287,11 +298,11 @@ Profile.Type = new Class({ Object.each(Quality.qualities, function(q){ new Element('option', { 'text': q.label, - 'value': q.id + 'value': q.identifier }).inject(self.qualities) }); - self.qualities.set('value', self.data.quality_id); + self.qualities.set('value', self.data.quality_identifier); return self.qualities; @@ -301,8 +312,9 @@ Profile.Type = new Class({ var self = this; return { - 'quality_id': self.qualities.get('value'), - 'finish': +self.finish.checked + 'quality_identifier': self.qualities.get('value'), + 'finish': +self.finish.checked, + 'wait_for': 0 } }, diff --git a/couchpotato/core/plugins/quality/static/quality.js b/couchpotato/core/plugins/quality/static/quality.js index ead9a904..af017755 100644 --- a/couchpotato/core/plugins/quality/static/quality.js +++ b/couchpotato/core/plugins/quality/static/quality.js @@ -17,7 +17,7 @@ var QualityBase = new Class({ getProfile: function(id){ return this.profiles.filter(function(profile){ - return profile.data.id == id + return profile.data._id == id }).pick() }, @@ -28,9 +28,9 @@ var QualityBase = new Class({ }); }, - getQuality: function(id){ + getQuality: function(identifier){ return this.qualities.filter(function(q){ - return q.id == id; + return q.identifier == identifier; }).pick(); }, diff --git a/couchpotato/core/plugins/release/index.py b/couchpotato/core/plugins/release/index.py index c68edf1c..11c12563 100644 --- a/couchpotato/core/plugins/release/index.py +++ b/couchpotato/core/plugins/release/index.py @@ -19,13 +19,13 @@ class ReleaseIndex(TreeBasedIndex): for release in db.get_many('release', media_id, with_doc = True): yield release['doc'] - def run_with_status(self, db, status = []): + def run_with_status(self, db, status = [], with_doc = True): status = list(status if isinstance(status, (list, tuple)) else [status]) for s in status: - for ms in db.get_many('release_status', s, with_doc = True): - yield ms['doc'] + for ms in db.get_many('release_status', s, with_doc = with_doc): + yield ms['doc'] if with_doc else ms class ReleaseStatusIndex(TreeBasedIndex): @@ -39,7 +39,7 @@ class ReleaseStatusIndex(TreeBasedIndex): def make_key_value(self, data): if data.get('_t') == 'release' and data.get('status'): - return md5(data.get('status')).hexdigest(), None + return md5(data.get('status')).hexdigest(), {'media_id': data.get('media_id')} class ReleaseIDIndex(TreeBasedIndex): @@ -53,4 +53,4 @@ class ReleaseIDIndex(TreeBasedIndex): def make_key_value(self, data): if data.get('_t') == 'release' and data.get('identifier'): - return md5(data.get('identifier')).hexdigest(), None + return md5(data.get('identifier')).hexdigest(), {'media_id': data.get('media_id')} diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 0e06a751..799b1774 100755 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -461,7 +461,7 @@ class Renamer(Plugin): self.tagRelease(group = group, tag = 'exists') # Notify on rename fail - download_message = 'Renaming of %s (%s) cancelled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label) + download_message = 'Renaming of %s (%s) cancelled, exists in %s already.' % (getTitle(movie), group['meta_data']['quality']['label'], release.quality.label) fireEvent('movie.renaming.canceled', message = download_message, data = group) remove_leftovers = False diff --git a/couchpotato/runner.py b/couchpotato/runner.py index b2ea606b..e3f07855 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -223,7 +223,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En loader.run() # Fill database with needed stuff - fireEvent('database.setup') + fireEvent('database.setup') if not db_exists: fireEvent('app.initialize', in_order = True)