This commit is contained in:
Ruud committed 2014-02-07 15:11:06 +01:00
1 parent 99252074be
commit a609b401c4
17 files changed
+313 -392

No files matched your search

+2 -2
View File
@@ -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. 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 ## 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: Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for more details:
+90 -12
View File
@@ -1,7 +1,9 @@
from itertools import izip from itertools import izip
from string import ascii_letters
from CodernityDB.hash_index import HashIndex from CodernityDB.hash_index import HashIndex
from CodernityDB.tree_index import MultiTreeBasedIndex, TreeBasedIndex from CodernityDB.tree_index import MultiTreeBasedIndex, TreeBasedIndex
from hashlib import md5 from hashlib import md5
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
class MediaIMDBIndex(HashIndex): class MediaIMDBIndex(HashIndex):
@@ -22,13 +24,13 @@ class MediaIMDBIndex(HashIndex):
return db.get('id', media_id) 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]) status = list(status if isinstance(status, (list, tuple)) else [status])
for s in status: for s in status:
for ms in db.get_many('media_status', s, with_doc = True): for ms in db.get_many('media_status', s, with_doc = with_doc):
yield ms['doc'] yield ms['doc'] if with_doc else ms
class MediaStatusIndex(TreeBasedIndex): class MediaStatusIndex(TreeBasedIndex):
@@ -45,31 +47,46 @@ class MediaStatusIndex(TreeBasedIndex):
return md5(data.get('status')).hexdigest(), None return md5(data.get('status')).hexdigest(), None
class TitleIndex(MultiTreeBasedIndex): class MediaTypeIndex(TreeBasedIndex):
custom_header = """from CodernityDB.tree_index import MultiTreeBasedIndex
from itertools import izip"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['key_format'] = '32s' 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) self.__l = kwargs.get('w_len', 2)
def make_key_value(self, data): 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() out = set()
title = data.get('title').lower() title = str(simplifyString(data.get('title').lower()))
l = self.__l l = self.__l
max_l = len(title) max_l = len(title)
for x in xrange(l - 1, max_l): for x in xrange(l - 1, max_l):
m = (title, ) m = (title, )
for y in xrange(0, x): for y in xrange(0, x):
m += (title[y + 1:],) 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): def make_key(self, key):
return key.rjust(32, '_').lower() return key.rjust(32, '_').lower()
@@ -87,3 +104,64 @@ class YearIndex(TreeBasedIndex):
def make_key_value(self, data): def make_key_value(self, data):
if data.get('_t') == 'media' and data.get('year') is not None: if data.get('_t') == 'media' and data.get('year') is not None:
return data['year'], 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()
+110 -157
View File
@@ -1,14 +1,13 @@
import traceback import traceback
import time
from couchpotato import get_session, tryInt, get_db from couchpotato import get_session, tryInt, get_db
from couchpotato.api import addApiView from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent 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.logger import CPLog
from couchpotato.core.media import MediaBase from couchpotato.core.media import MediaBase
from .index import MediaIMDBIndex, TitleIndex, MediaStatusIndex, YearIndex from .index import MediaIMDBIndex, MediaStatusIndex, YearIndex, MediaTypeIndex, TitleSearchIndex, TitleIndex, StartsWithIndex
from couchpotato.core.settings.model import Library, LibraryTitle, Release, \ from couchpotato.core.settings.model import Media
Media
from string import ascii_lowercase from string import ascii_lowercase
log = CPLog(__name__) log = CPLog(__name__)
@@ -61,10 +60,10 @@ class MediaPlugin(MediaBase):
addEvent('database.setup', self.databaseSetup) addEvent('database.setup', self.databaseSetup)
addEvent('app.load', self.addSingleRefreshView) addEvent('app.load', self.addSingleRefreshView, priority = 100)
addEvent('app.load', self.addSingleListView) addEvent('app.load', self.addSingleListView, priority = 100)
addEvent('app.load', self.addSingleCharView) addEvent('app.load', self.addSingleCharView, priority = 100)
addEvent('app.load', self.addSingleDeleteView) addEvent('app.load', self.addSingleDeleteView, priority = 100)
addEvent('media.get', self.get) addEvent('media.get', self.get)
addEvent('media.list', self.list) addEvent('media.list', self.list)
@@ -84,10 +83,10 @@ class MediaPlugin(MediaBase):
# Title index # Title index
try: try:
db.add_index(TitleIndex(db.path, 'media_title')) db.add_index(TitleSearchIndex(db.path, 'media_search_title'))
except: except:
log.debug('Index already exists') log.debug('Index already exists')
db.edit_index(TitleIndex(db.path, 'media_title')) db.edit_index(TitleSearchIndex(db.path, 'media_search_title'))
# Status index # Status index
try: try:
@@ -96,11 +95,31 @@ class MediaPlugin(MediaBase):
log.debug('Index already exists') log.debug('Index already exists')
db.edit_index(MediaStatusIndex(db.path, 'media_status')) db.edit_index(MediaStatusIndex(db.path, 'media_status'))
# Year index # Type index
try: db.add_index(YearIndex(db.path, 'year')) try:
db.add_index(MediaTypeIndex(db.path, 'media_by_type'))
except: except:
log.debug('Index already exists') 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): def refresh(self, id = '', **kwargs):
handlers = [] handlers = []
@@ -112,7 +131,7 @@ class MediaPlugin(MediaBase):
if refresh_handler: if refresh_handler:
handlers.append(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) fireEventAsync('schedule.queue', handlers = handlers)
return { return {
@@ -124,8 +143,9 @@ class MediaPlugin(MediaBase):
try: try:
media = get_db().get('id', media_id) media = get_db().get('id', media_id)
default_title = getTitle(media) default_title = getTitle(media)
event = 'library.update.%s' % media.get('type') event = '%s.update_info' % media.get('type')
def handler(): def handler():
fireEvent(event, identifier = media.get('identifier'), default_title = default_title, on_complete = self.createOnComplete(media_id)) 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() db = get_db()
start = time.time()
# Make a list from string # Make a list from string
if status and not isinstance(status, (list, tuple)): if status and not isinstance(status, (list, tuple)):
status = [status] status = [status]
@@ -183,125 +201,73 @@ class MediaPlugin(MediaBase):
types = [types] types = [types]
# query media ids # 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 media_ids = all_media_ids
filter_by = {} filter_by = {}
# Filter on movie status # Filter on movie status
if status and len(status) > 0: if status and len(status) > 0:
filter_by['media_status'] = set() 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_by['media_status'].add(media_status.get('_id'))
# Filter on release status # Filter on release status
if release_status and len(release_status) > 0: if release_status and len(release_status) > 0:
filter_by['release_status'] = set() 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')) 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 # Filter by combining ids
for x in filter_by: for x in filter_by:
media_ids = media_ids & filter_by[x] media_ids = [n for n in media_ids if n in 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))
total_count = len(media_ids) total_count = len(media_ids)
if total_count == 0: if total_count == 0:
return 0, [] return 0, []
# if order == 'release_order': if limit_offset:
# q = q.order_by(desc(Release.last_edit)) splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
# else: limit = tryInt(splt[0])
# q = q.order_by(asc(LibraryTitle.simple_title)) 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: # List movies based on title order
# 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
medias = [] 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, { media = db.run('media', 'to_dict', media_id)
'library': {'titles': {}, 'files': {}},
'files': {},
})
media['releases'] = [] media['releases'] = []
for r in db.get_many('release', media_id, with_doc = True): 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 # Merge releases with movie dict
medias.append(media) 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 return total_count, medias
@@ -341,63 +307,52 @@ class MediaPlugin(MediaBase):
def availableChars(self, types = None, status = None, release_status = None): def availableChars(self, types = None, status = None, release_status = None):
types = types or [] db = get_db()
status = status or []
release_status = release_status or []
db = get_session()
# Make a list from string # Make a list from string
if not isinstance(status, (list, tuple)): if status and not isinstance(status, (list, tuple)):
status = [status] status = [status]
if release_status and not isinstance(release_status, (list, tuple)): if release_status and not isinstance(release_status, (list, tuple)):
release_status = [release_status] release_status = [release_status]
if types and not isinstance(types, (list, tuple)): if types and not isinstance(types, (list, tuple)):
types = [types] 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 # Filter on movie status
if status and len(status) > 0: if status and len(status) > 0:
statuses = fireEvent('status.get', status, single = len(release_status) > 1) filter_by['media_status'] = set()
statuses = [s.get('id') for s in statuses] for media_status in db.run('media', 'with_status', status, with_doc = False):
filter_by['media_status'].add(media_status.get('_id'))
q = q.filter(Media.status_id.in_(statuses))
# Filter on release status # Filter on release status
if release_status and len(release_status) > 0: 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) # Filter by combining ids
statuses = [s.get('id') for s in statuses] for x in filter_by:
media_ids = [n for n in media_ids if n in filter_by[x]]
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()
chars = set() chars = set()
for title in titles: for x in db.all('media_startswith'):
try: if x['_id'] in media_ids:
char = title[0][0] chars.add(x['key'])
char = char if char in ascii_lowercase else '#'
chars.add(str(char))
except:
log.error('Failed getting title for %s', title.libraries_id)
if len(chars) == 25: if len(chars) == 25:
break break
pass #db.close() return list(chars)
return ''.join(sorted(chars))
def charView(self, **kwargs): def charView(self, **kwargs):
@@ -487,32 +442,30 @@ class MediaPlugin(MediaBase):
def restatus(self, media_id): def restatus(self, media_id):
try: try:
db = get_session() db = get_db()
m = db.query(Media).filter_by(id = media_id).first() m = db.get('id', media_id)
if not m or len(m.library.titles) == 0:
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
return False
log.debug('Changing status for %s', m.library.titles[0].title) log.debug('Changing status for %s', getTitle(m))
if not m['profile_id']: if not m['profile_id']:
m['status'] = 'done' m['status'] = 'done'
else: else:
move_to_wanted = True move_to_wanted = True
for t in m.profile.types: profile = db.get('id', m['profile_id'])
for release in m.releases: media_releases = db.get_many('release', m['_id'])
if t.quality.identifier is release.quality.identifier and (release.get('status') == 'done' and t.finish):
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 move_to_wanted = False
m['status'] = 'active' if move_to_wanted else 'done' m['status'] = 'active' if move_to_wanted else 'done'
db.commit() db.update(m)
return True return True
except: except:
log.error('Failed restatus: %s', traceback.format_exc()) log.error('Failed restatus: %s', traceback.format_exc())
db.rollback()
finally:
pass #db.close()
+27 -23
View File
@@ -117,12 +117,11 @@ class MovieBase(MovieTypeBase):
added = True added = True
do_search = False do_search = False
search_after = search_after and self.conf('search_on_add', section = 'moviesearcher') search_after = search_after and self.conf('search_on_add', section = 'moviesearcher')
onComplete = None
if new: if new:
onComplete = None
if search_after: if search_after:
onComplete = self.createOnComplete(m['_id']) onComplete = self.createOnComplete(m['_id'])
fireEventAsync('movie.update_info', m['_id'], default_title = params.get('title', ''), on_complete = onComplete)
search_after = False search_after = False
elif force_readd: elif force_readd:
@@ -148,6 +147,9 @@ class MovieBase(MovieTypeBase):
if added: if added:
db.update(m) 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 # Remove releases
for rel in db.run('release', 'for_media', m['_id']): for rel in db.run('release', 'for_media', m['_id']):
if rel['status'] is 'available': if rel['status'] is 'available':
@@ -205,8 +207,9 @@ class MovieBase(MovieTypeBase):
# Default title # Default title
if kwargs.get('default_title'): if kwargs.get('default_title'):
for title in m['titles']: m['title'] = kwargs.get('default_title')
title.default = toUnicode(kwargs.get('default_title', '')).lower() == toUnicode(title.title).lower()
print m
db.update(m) db.update(m)
@@ -266,35 +269,36 @@ class MovieBase(MovieTypeBase):
titles = info.get('titles', []) titles = info.get('titles', [])
log.debug('Adding titles: %s', titles) log.debug('Adding titles: %s', titles)
counter = 0
# Define default title
def_title = None def_title = None
for title in titles: if default_title:
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): counter = 0
def_title = toUnicode(title) for title in titles:
break if title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == six.u('') and toUnicode(titles[0]) == title):
counter += 1 def_title = toUnicode(title)
break
counter += 1
if not def_title: if not def_title:
def_title = toUnicode(titles[0]) def_title = toUnicode(titles[0])
media = { media['title'] = def_title
'title': def_title,
}
# Files # Files
images = info.get('images', []) images = info.get('images', [])
media['files'] = media.get('filed', []) media['files'] = media.get('files', {})
for image_type in ['poster']: for image_type in ['poster']:
for image in images.get(image_type, []): for image in images.get(image_type, []):
if not isinstance(image, (str, unicode)): if not isinstance(image, (str, unicode)):
continue continue
file_path = fireEvent('file.download', url = image, single = True) file_type = 'image_%s' % image_type
media['files'].append({ if file_type not in media['files']:
'type': 'image_%s' % image_type, file_path = fireEvent('file.download', url = image, single = True)
'path': file_path media['files']['image_%s' % image_type] = [file_path];
})
break
db.update(media) db.update(media)
@@ -306,7 +310,7 @@ class MovieBase(MovieTypeBase):
def updateReleaseDate(self, media_id): def updateReleaseDate(self, media_id):
""" """
Update releasedate (eta) info only Update release_date (eta) info only
@param media_id: document id @param media_id: document id
@return: dict, with dates dvd, theater, bluray, expires @return: dict, with dates dvd, theater, bluray, expires
@@ -318,8 +322,8 @@ class MovieBase(MovieTypeBase):
media = db.get('id', media_id) media = db.get('id', media_id)
if not media.get('info'): if not media.get('info'):
info_dict = self.updateInfo(media_id) media = self.updateInfo(media_id)
dates = info_dict.get('info', {}).get('release_date') dates = media.get('info', {}).get('release_date')
else: else:
dates = media.get('info').get('release_date') dates = media.get('info').get('release_date')
@@ -59,9 +59,9 @@ var MovieList = new Class({
movieDeleted: function(notification){ movieDeleted: function(notification){
var self = this; var self = this;
if(self.movies_added[notification.data.id]){ if(self.movies_added[notification.data._id]){
self.movies.each(function(movie){ self.movies.each(function(movie){
if(movie.get('id') == notification.data.id){ if(movie.get('_id') == notification.data._id){
movie.destroy(); movie.destroy();
delete self.movies_added[notification.data.id]; delete self.movies_added[notification.data.id];
self.setCounter(self.counter_count-1); self.setCounter(self.counter_count-1);
@@ -288,7 +288,7 @@ var MovieList = new Class({
'onSuccess': function(json){ 'onSuccess': function(json){
available_chars = json.chars available_chars = json.chars
json.chars.split('').each(function(c){ available_chars.each(function(c){
self.letters[c.capitalize()].addClass('available') self.letters[c.capitalize()].addClass('available')
}) })
@@ -438,7 +438,7 @@ var MovieList = new Class({
var ids = [] var ids = []
self.movies.each(function(movie){ self.movies.each(function(movie){
if (movie.isSelected()) if (movie.isSelected())
ids.include(movie.get('id')) ids.include(movie.get('_id'))
}); });
return ids return ids
@@ -467,7 +467,7 @@ var MovieList = new Class({
self.offset = 0; self.offset = 0;
if(self.scrollspy){ if(self.scrollspy){
self.load_more.show(); //self.load_more.show();
self.scrollspy.start(); self.scrollspy.start();
} }
}, },
@@ -427,7 +427,7 @@ MA.Release = new Class({
Api.request('media.delete', { Api.request('media.delete', {
'data': { 'data': {
'id': self.movie.get('id'), 'id': self.movie.get('_id'),
'delete_from': 'wanted' 'delete_from': 'wanted'
}, },
'onComplete': function(){ 'onComplete': function(){
@@ -449,7 +449,7 @@ MA.Release = new Class({
Api.request('movie.searcher.try_next', { Api.request('movie.searcher.try_next', {
'data': { 'data': {
'id': self.movie.get('id') 'id': self.movie.get('_id')
} }
}); });
@@ -604,13 +604,13 @@ MA.Edit = new Class({
) )
).inject(self.movie, 'top'); ).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', { new Element('option', {
'text': alt.title 'text': title
}).inject(self.title_select); }).inject(self.title_select);
if(alt['default']) if(title == self.movie.data.title)
self.title_select.set('value', alt.title); self.title_select.set('value', title);
}); });
@@ -643,7 +643,7 @@ MA.Edit = new Class({
profiles.each(function(profile){ profiles.each(function(profile){
var profile_id = profile.id ? profile.id : profile.data.id; var profile_id = profile.get('_id');
new Element('option', { new Element('option', {
'value': profile_id, 'value': profile_id,
@@ -666,7 +666,7 @@ MA.Edit = new Class({
Api.request('movie.edit', { Api.request('movie.edit', {
'data': { 'data': {
'id': self.movie.get('id'), 'id': self.movie.get('_id'),
'default_title': self.title_select.get('value'), 'default_title': self.title_select.get('value'),
'profile_id': self.profile_select.get('value'), 'profile_id': self.profile_select.get('value'),
'category_id': self.category_select.get('value') 'category_id': self.category_select.get('value')
@@ -706,7 +706,7 @@ MA.Refresh = new Class({
Api.request('media.refresh', { Api.request('media.refresh', {
'data': { 'data': {
'id': self.movie.get('id') 'id': self.movie.get('_id')
} }
}); });
} }
@@ -817,7 +817,7 @@ MA.Delete = new Class({
function(){ function(){
Api.request('media.delete', { Api.request('media.delete', {
'data': { 'data': {
'id': self.movie.get('id'), 'id': self.movie.get('_id'),
'delete_from': self.movie.list.options.identifier 'delete_from': self.movie.list.options.identifier
}, },
'onComplete': function(){ 'onComplete': function(){
@@ -59,7 +59,7 @@ var Movie = new Class({
if(!self.data.releases) if(!self.data.releases)
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(); 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.data_container = new Element('div.data.inlay.light').adopt(
self.info_container = new Element('div.info').adopt( self.info_container = new Element('div.info').adopt(
new Element('div.title').adopt( new Element('div.title').adopt(
@@ -194,7 +197,7 @@ var Movie = new Class({
if(self.profile.data) if(self.profile.data)
self.profile.getTypes().each(function(type){ 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')){ if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
q.addClass('finish'); q.addClass('finish');
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.') 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; status = release.status;
if(!q && (status == 'snatched' || status == 'seeding' || status == 'done')) 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)){ if (q && !q.hasClass(status)){
q.addClass(status); q.addClass(status);
@@ -233,10 +236,10 @@ var Movie = new Class({
}); });
}, },
addQuality: function(quality_id){ addQuality: function(quality_identifier){
var self = this; var self = this;
var q = Quality.getQuality(quality_id); var q = Quality.getQuality(quality_identifier);
return new Element('span', { return new Element('span', {
'text': q.label, 'text': q.label,
'class': 'q_'+q.identifier + ' q_id' + q.id, 'class': 'q_'+q.identifier + ' q_id' + q.id,
@@ -122,7 +122,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
return return
pre_releases = fireEvent('quality.pre_releases', single = True) 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 = [] found_releases = []
too_early_to_search = [] too_early_to_search = []
@@ -147,7 +147,7 @@ var NotificationBase = new Class({
// Process data // Process data
if(json){ if(json){
Array.each(json.result, function(result){ Array.each(json.result, function(result){
App.trigger(result.type, [result]); App.trigger(result._t, [result]);
if(result.message && result.read === undefined) if(result.message && result.read === undefined)
self.showMessage(result.message); self.showMessage(result.message);
}) })
+25 -67
View File
@@ -1,13 +1,9 @@
from couchpotato import get_session from couchpotato import get_db
from couchpotato.api import addApiView from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import splitString, tryInt from couchpotato.core.helpers.variable import splitString, tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin 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 random as rndm
import time import time
@@ -21,26 +17,21 @@ class Dashboard(Plugin):
def getSoonView(self, limit_offset = None, random = False, late = False, **kwargs): def getSoonView(self, limit_offset = None, random = False, late = False, **kwargs):
db = get_session() db = get_db()
now = time.time() now = time.time()
# Get profiles first, determine pre or post theater # Get profiles first, determine pre or post theater
profiles = fireEvent('profile.all', single = True) profiles = fireEvent('profile.all', single = True)
qualities = fireEvent('quality.all', single = True)
pre_releases = fireEvent('quality.pre_releases', 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 # See what the profile contain and cache it
profile_pre = {} profile_pre = {}
for profile in profiles: for profile in profiles:
contains = {} contains = {}
for profile_type in profile.get('types', []): for q_identifier in profile.get('qualities', []):
contains['theater' if id_pre.get(profile_type.get('quality_id')) else 'dvd'] = True 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 # Add limit
limit = 12 limit = 12
@@ -48,43 +39,32 @@ class Dashboard(Plugin):
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
limit = tryInt(splt[0]) limit = tryInt(splt[0])
# Get all active movies # Get all active medias
q = db.query(Media) \ active_ids = [x['_id'] for x in db.get_many('media_status', 'active')]
.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')))
if not random: medias = []
q = q.join(LibraryTitle) \ if len(active_ids) > 0:
.filter(LibraryTitle.default == True) \
.order_by(asc(LibraryTitle.simple_title))
active = q.all() # Order by title or randomize
movies = [] 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 pp = profile_pre.get(media['profile_id'])
if random:
rndm.shuffle(active)
movie_ids = []
for movie in active:
movie_id, profile_id, info, year = movie
pp = profile_pre.get(profile_id)
if not pp: continue if not pp: continue
eta = info.get('release_date', {}) or {} eta = media['info'].get('release_date', {}) or {}
coming_soon = False coming_soon = False
# Theater quality # 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 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 coming_soon = True
if coming_soon: if coming_soon:
@@ -92,37 +72,15 @@ class Dashboard(Plugin):
# Don't list older movies # 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 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))): (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 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 { return {
'success': True, 'success': True,
'empty': len(movies) == 0, 'empty': len(medias) == 0,
'movies': movies, 'movies': medias,
} }
getLateView = getSoonView getLateView = getSoonView
@@ -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();
+1 -1
View File
@@ -124,7 +124,7 @@ class Manage(Plugin):
fireEvent('media.delete', media_id = done_movie['id'], delete_from = 'all') fireEvent('media.delete', media_id = done_movie['id'], delete_from = 'all')
else: 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: for release in releases:
if len(release.get('files', [])) > 0: if len(release.get('files', [])) > 0:
+1 -2
View File
@@ -56,8 +56,7 @@ class ProfilePlugin(Plugin):
for media in medias: for media in medias:
if media.get('profile_id') not in profile_ids: if media.get('profile_id') not in profile_ids:
default_profile = self.default() media['profile_id'] = profile_ids[0].get('_id')
media['profile_id'] = default_profile.get('id')
db.update(media) db.update(media)
except: except:
log.error('Failed: %s', traceback.format_exc()) log.error('Failed: %s', traceback.format_exc())
@@ -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) if(data.types)
Object.each(data.types, self.addType.bind(self)) data.types.each(self.addType.bind(self));
else else
self.delete_button.hide(); self.delete_button.hide();
@@ -111,8 +121,9 @@ var Profile = new Class({
Array.each(self.type_container.getElements('.type'), function(type){ Array.each(self.type_container.getElements('.type'), function(type){
if(!type.hasClass('deleted') && type.getElement('select').get('value') > 0) if(!type.hasClass('deleted') && type.getElement('select').get('value') > 0)
data.types.include({ data.types.include({
'quality_id': type.getElement('select').get('value'), 'quality_identifier': type.getElement('select').get('value'),
'finish': +type.getElement('input[type=checkbox]').checked 'finish': +type.getElement('input[type=checkbox]').checked,
'wait_for': 0
}); });
}) })
@@ -145,7 +156,7 @@ var Profile = new Class({
var self = this; var self = this;
return self.types.filter(function(type){ 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') 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); self.finish_class = new Form.Check(self.finish);
@@ -287,11 +298,11 @@ Profile.Type = new Class({
Object.each(Quality.qualities, function(q){ Object.each(Quality.qualities, function(q){
new Element('option', { new Element('option', {
'text': q.label, 'text': q.label,
'value': q.id 'value': q.identifier
}).inject(self.qualities) }).inject(self.qualities)
}); });
self.qualities.set('value', self.data.quality_id); self.qualities.set('value', self.data.quality_identifier);
return self.qualities; return self.qualities;
@@ -301,8 +312,9 @@ Profile.Type = new Class({
var self = this; var self = this;
return { return {
'quality_id': self.qualities.get('value'), 'quality_identifier': self.qualities.get('value'),
'finish': +self.finish.checked 'finish': +self.finish.checked,
'wait_for': 0
} }
}, },
@@ -17,7 +17,7 @@ var QualityBase = new Class({
getProfile: function(id){ getProfile: function(id){
return this.profiles.filter(function(profile){ return this.profiles.filter(function(profile){
return profile.data.id == id return profile.data._id == id
}).pick() }).pick()
}, },
@@ -28,9 +28,9 @@ var QualityBase = new Class({
}); });
}, },
getQuality: function(id){ getQuality: function(identifier){
return this.qualities.filter(function(q){ return this.qualities.filter(function(q){
return q.id == id; return q.identifier == identifier;
}).pick(); }).pick();
}, },
+5 -5
View File
@@ -19,13 +19,13 @@ class ReleaseIndex(TreeBasedIndex):
for release in db.get_many('release', media_id, with_doc = True): for release in db.get_many('release', media_id, with_doc = True):
yield release['doc'] 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]) status = list(status if isinstance(status, (list, tuple)) else [status])
for s in status: for s in status:
for ms in db.get_many('release_status', s, with_doc = True): for ms in db.get_many('release_status', s, with_doc = with_doc):
yield ms['doc'] yield ms['doc'] if with_doc else ms
class ReleaseStatusIndex(TreeBasedIndex): class ReleaseStatusIndex(TreeBasedIndex):
@@ -39,7 +39,7 @@ class ReleaseStatusIndex(TreeBasedIndex):
def make_key_value(self, data): def make_key_value(self, data):
if data.get('_t') == 'release' and data.get('status'): 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): class ReleaseIDIndex(TreeBasedIndex):
@@ -53,4 +53,4 @@ class ReleaseIDIndex(TreeBasedIndex):
def make_key_value(self, data): def make_key_value(self, data):
if data.get('_t') == 'release' and data.get('identifier'): 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')}
+1 -1
View File
@@ -461,7 +461,7 @@ class Renamer(Plugin):
self.tagRelease(group = group, tag = 'exists') self.tagRelease(group = group, tag = 'exists')
# Notify on rename fail # 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) fireEvent('movie.renaming.canceled', message = download_message, data = group)
remove_leftovers = False remove_leftovers = False