This commit is contained in:
Ruud
2014-02-07 15:11:06 +01:00
parent 99252074be
commit a609b401c4
18 changed files with 316 additions and 395 deletions
+3 -3
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.
@@ -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 :
+90 -12
View File
@@ -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()
+110 -157
View File
@@ -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()
+27 -23
View File
@@ -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')
@@ -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;
}
});
});
@@ -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(){
@@ -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,
@@ -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 = []
@@ -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);
})
+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.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
@@ -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')
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:
+1 -2
View File
@@ -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())
@@ -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
}
},
@@ -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();
},
+5 -5
View File
@@ -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')}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)