40 changed files with 4416 additions and 990 deletions
@ -0,0 +1,109 @@ |
|||||
|
from couchpotato import get_db |
||||
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.helpers.variable import tryInt |
||||
|
from couchpotato.core.media import MediaBase |
||||
|
|
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'Episode' |
||||
|
|
||||
|
|
||||
|
class Episode(MediaBase): |
||||
|
|
||||
|
def __init__(self): |
||||
|
addEvent('show.episode.add', self.add) |
||||
|
addEvent('show.episode.update', self.update) |
||||
|
addEvent('show.episode.update_extras', self.updateExtras) |
||||
|
|
||||
|
def add(self, parent_id, info = None, update_after = True, status = None): |
||||
|
if not info: info = {} |
||||
|
|
||||
|
identifiers = info.pop('identifiers', None) |
||||
|
|
||||
|
if not identifiers: |
||||
|
log.warning('Unable to add episode, missing identifiers (info provider mismatch?)') |
||||
|
return |
||||
|
|
||||
|
# Add Season |
||||
|
episode_info = { |
||||
|
'_t': 'media', |
||||
|
'type': 'show.episode', |
||||
|
'identifiers': identifiers, |
||||
|
'status': status if status else 'active', |
||||
|
'parent_id': parent_id, |
||||
|
'info': info, # Returned dict by providers |
||||
|
} |
||||
|
|
||||
|
# Check if season already exists |
||||
|
existing_episode = fireEvent('media.with_identifiers', identifiers, with_doc = True, single = True) |
||||
|
|
||||
|
db = get_db() |
||||
|
|
||||
|
if existing_episode: |
||||
|
s = existing_episode['doc'] |
||||
|
s.update(episode_info) |
||||
|
|
||||
|
episode = db.update(s) |
||||
|
else: |
||||
|
episode = db.insert(episode_info) |
||||
|
|
||||
|
# Update library info |
||||
|
if update_after is not False: |
||||
|
handle = fireEventAsync if update_after is 'async' else fireEvent |
||||
|
handle('show.episode.update_extras', episode, info, store = True, single = True) |
||||
|
|
||||
|
return episode |
||||
|
|
||||
|
def update(self, media_id = None, identifiers = None, info = None): |
||||
|
if not info: info = {} |
||||
|
|
||||
|
if self.shuttingDown(): |
||||
|
return |
||||
|
|
||||
|
db = get_db() |
||||
|
|
||||
|
episode = db.get('id', media_id) |
||||
|
|
||||
|
# Get new info |
||||
|
if not info: |
||||
|
season = db.get('id', episode['parent_id']) |
||||
|
show = db.get('id', season['parent_id']) |
||||
|
|
||||
|
info = fireEvent( |
||||
|
'episode.info', show.get('identifiers'), { |
||||
|
'season_identifiers': season.get('identifiers'), |
||||
|
'season_number': season.get('info', {}).get('number'), |
||||
|
|
||||
|
'episode_identifiers': episode.get('identifiers'), |
||||
|
'episode_number': episode.get('info', {}).get('number'), |
||||
|
|
||||
|
'absolute_number': episode.get('info', {}).get('absolute_number') |
||||
|
}, |
||||
|
merge = True |
||||
|
) |
||||
|
|
||||
|
info['season_number'] = season.get('info', {}).get('number') |
||||
|
|
||||
|
identifiers = info.pop('identifiers', None) or identifiers |
||||
|
|
||||
|
# Update/create media |
||||
|
episode['identifiers'].update(identifiers) |
||||
|
episode.update({'info': info}) |
||||
|
|
||||
|
self.updateExtras(episode, info) |
||||
|
|
||||
|
db.update(episode) |
||||
|
return episode |
||||
|
|
||||
|
def updateExtras(self, episode, info, store=False): |
||||
|
db = get_db() |
||||
|
|
||||
|
# Get images |
||||
|
image_urls = info.get('images', []) |
||||
|
existing_files = episode.get('files', {}) |
||||
|
self.getPoster(image_urls, existing_files) |
||||
|
|
||||
|
if store: |
||||
|
db.update(episode) |
@ -0,0 +1,94 @@ |
|||||
|
from couchpotato import get_db |
||||
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.helpers.variable import tryInt |
||||
|
from couchpotato.core.media import MediaBase |
||||
|
|
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'Season' |
||||
|
|
||||
|
|
||||
|
class Season(MediaBase): |
||||
|
|
||||
|
def __init__(self): |
||||
|
addEvent('show.season.add', self.add) |
||||
|
addEvent('show.season.update', self.update) |
||||
|
addEvent('show.season.update_extras', self.updateExtras) |
||||
|
|
||||
|
def add(self, parent_id, info = None, update_after = True, status = None): |
||||
|
if not info: info = {} |
||||
|
|
||||
|
identifiers = info.pop('identifiers', None) |
||||
|
info.pop('episodes', None) |
||||
|
|
||||
|
# Add Season |
||||
|
season_info = { |
||||
|
'_t': 'media', |
||||
|
'type': 'show.season', |
||||
|
'identifiers': identifiers, |
||||
|
'status': status if status else 'active', |
||||
|
'parent_id': parent_id, |
||||
|
'info': info, # Returned dict by providers |
||||
|
} |
||||
|
|
||||
|
# Check if season already exists |
||||
|
existing_season = fireEvent('media.with_identifiers', identifiers, with_doc = True, single = True) |
||||
|
|
||||
|
db = get_db() |
||||
|
|
||||
|
if existing_season: |
||||
|
s = existing_season['doc'] |
||||
|
s.update(season_info) |
||||
|
|
||||
|
season = db.update(s) |
||||
|
else: |
||||
|
season = db.insert(season_info) |
||||
|
|
||||
|
# Update library info |
||||
|
if update_after is not False: |
||||
|
handle = fireEventAsync if update_after is 'async' else fireEvent |
||||
|
handle('show.season.update_extras', season, info, store = True, single = True) |
||||
|
|
||||
|
return season |
||||
|
|
||||
|
def update(self, media_id = None, identifiers = None, info = None): |
||||
|
if not info: info = {} |
||||
|
|
||||
|
if self.shuttingDown(): |
||||
|
return |
||||
|
|
||||
|
db = get_db() |
||||
|
|
||||
|
season = db.get('id', media_id) |
||||
|
show = db.get('id', season['parent_id']) |
||||
|
|
||||
|
# Get new info |
||||
|
if not info: |
||||
|
info = fireEvent('season.info', show.get('identifiers'), { |
||||
|
'season_number': season.get('info', {}).get('number', 0) |
||||
|
}, merge = True) |
||||
|
|
||||
|
identifiers = info.pop('identifiers', None) or identifiers |
||||
|
info.pop('episodes', None) |
||||
|
|
||||
|
# Update/create media |
||||
|
season['identifiers'].update(identifiers) |
||||
|
season.update({'info': info}) |
||||
|
|
||||
|
self.updateExtras(season, info) |
||||
|
|
||||
|
db.update(season) |
||||
|
return season |
||||
|
|
||||
|
def updateExtras(self, season, info, store=False): |
||||
|
db = get_db() |
||||
|
|
||||
|
# Get images |
||||
|
image_urls = info.get('images', []) |
||||
|
existing_files = season.get('files', {}) |
||||
|
self.getPoster(image_urls, existing_files) |
||||
|
|
||||
|
if store: |
||||
|
db.update(season) |
@ -0,0 +1,474 @@ |
|||||
|
var EpisodeAction = new Class({ |
||||
|
|
||||
|
Implements: [Options], |
||||
|
|
||||
|
class_name: 'item-action icon2', |
||||
|
|
||||
|
initialize: function(episode, options){ |
||||
|
var self = this; |
||||
|
self.setOptions(options); |
||||
|
|
||||
|
self.show = episode.show; |
||||
|
self.episode = episode; |
||||
|
|
||||
|
self.create(); |
||||
|
if(self.el) |
||||
|
self.el.addClass(self.class_name) |
||||
|
}, |
||||
|
|
||||
|
create: function(){}, |
||||
|
|
||||
|
disable: function(){ |
||||
|
if(this.el) |
||||
|
this.el.addClass('disable') |
||||
|
}, |
||||
|
|
||||
|
enable: function(){ |
||||
|
if(this.el) |
||||
|
this.el.removeClass('disable') |
||||
|
}, |
||||
|
|
||||
|
getTitle: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
try { |
||||
|
return self.show.getTitle(); |
||||
|
} |
||||
|
catch(e){ |
||||
|
try { |
||||
|
return self.show.original_title ? self.show.original_title : self.show.titles[0]; |
||||
|
} |
||||
|
catch(e){ |
||||
|
return 'Unknown'; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
get: function(key){ |
||||
|
var self = this; |
||||
|
try { |
||||
|
return self.show.get(key) |
||||
|
} |
||||
|
catch(e){ |
||||
|
return self.show[key] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
createMask: function(){ |
||||
|
var self = this; |
||||
|
self.mask = new Element('div.mask', { |
||||
|
'styles': { |
||||
|
'z-index': '1' |
||||
|
} |
||||
|
}).inject(self.show, 'top').fade('hide'); |
||||
|
}, |
||||
|
|
||||
|
toElement: function(){ |
||||
|
return this.el || null |
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
var EA = {}; |
||||
|
|
||||
|
EA.IMDB = new Class({ |
||||
|
|
||||
|
Extends: EpisodeAction, |
||||
|
id: null, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.id = self.show.getIdentifier ? self.show.getIdentifier() : self.get('imdb'); |
||||
|
|
||||
|
self.el = new Element('a.imdb', { |
||||
|
'title': 'Go to the IMDB page of ' + self.getTitle(), |
||||
|
'href': 'http://www.imdb.com/title/'+self.id+'/', |
||||
|
'target': '_blank' |
||||
|
}); |
||||
|
|
||||
|
if(!self.id) self.disable(); |
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
EA.Release = new Class({ |
||||
|
|
||||
|
Extends: EpisodeAction, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.el = new Element('a.releases.download', { |
||||
|
'title': 'Show the releases that are available for ' + self.getTitle(), |
||||
|
'events': { |
||||
|
'click': self.toggle.bind(self) |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
self.options = new Element('div.episode-options').inject(self.episode.el); |
||||
|
|
||||
|
if(!self.episode.data.releases || self.episode.data.releases.length == 0) |
||||
|
self.el.hide(); |
||||
|
else |
||||
|
self.showHelper(); |
||||
|
|
||||
|
App.on('show.searcher.ended', function(notification){ |
||||
|
if(self.show.data._id != notification.data._id) return; |
||||
|
|
||||
|
self.releases = null; |
||||
|
if(self.options_container){ |
||||
|
self.options_container.destroy(); |
||||
|
self.options_container = null; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
toggle: function(e){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(self.options && self.options.hasClass('expanded')) { |
||||
|
self.close(); |
||||
|
} else { |
||||
|
self.open(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
open: function(e){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(e) |
||||
|
(e).preventDefault(); |
||||
|
|
||||
|
self.createReleases(); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
close: function(e) { |
||||
|
var self = this; |
||||
|
|
||||
|
if(e) |
||||
|
(e).preventDefault(); |
||||
|
|
||||
|
self.options.setStyle('height', 0) |
||||
|
.removeClass('expanded'); |
||||
|
}, |
||||
|
|
||||
|
createReleases: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(!self.releases_table){ |
||||
|
self.options.adopt( |
||||
|
self.releases_table = new Element('div.releases.table') |
||||
|
); |
||||
|
|
||||
|
// Header
|
||||
|
new Element('div.item.head').adopt( |
||||
|
new Element('span.name', {'text': 'Release name'}), |
||||
|
new Element('span.status', {'text': 'Status'}), |
||||
|
new Element('span.quality', {'text': 'Quality'}), |
||||
|
new Element('span.size', {'text': 'Size'}), |
||||
|
new Element('span.age', {'text': 'Age'}), |
||||
|
new Element('span.score', {'text': 'Score'}), |
||||
|
new Element('span.provider', {'text': 'Provider'}) |
||||
|
).inject(self.releases_table); |
||||
|
|
||||
|
if(self.episode.data.releases) |
||||
|
self.episode.data.releases.each(function(release){ |
||||
|
|
||||
|
var quality = Quality.getQuality(release.quality) || {}, |
||||
|
info = release.info || {}, |
||||
|
provider = self.get(release, 'provider') + (info['provider_extra'] ? self.get(release, 'provider_extra') : ''); |
||||
|
|
||||
|
var release_name = self.get(release, 'name'); |
||||
|
if(release.files && release.files.length > 0){ |
||||
|
try { |
||||
|
var movie_file = release.files.filter(function(file){ |
||||
|
var type = File.Type.get(file.type_id); |
||||
|
return type && type.identifier == 'movie' |
||||
|
}).pick(); |
||||
|
release_name = movie_file.path.split(Api.getOption('path_sep')).getLast(); |
||||
|
} |
||||
|
catch(e){} |
||||
|
} |
||||
|
|
||||
|
// Create release
|
||||
|
release['el'] = new Element('div', { |
||||
|
'class': 'item '+release.status, |
||||
|
'id': 'release_'+release._id |
||||
|
}).adopt( |
||||
|
new Element('span.name', {'text': release_name, 'title': release_name}), |
||||
|
new Element('span.status', {'text': release.status, 'class': 'status '+release.status}), |
||||
|
new Element('span.quality', {'text': quality.label + (release.is_3d ? ' 3D' : '') || 'n/a'}), |
||||
|
new Element('span.size', {'text': info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}), |
||||
|
new Element('span.age', {'text': self.get(release, 'age')}), |
||||
|
new Element('span.score', {'text': self.get(release, 'score')}), |
||||
|
new Element('span.provider', { 'text': provider, 'title': provider }), |
||||
|
info['detail_url'] ? new Element('a.info.icon2', { |
||||
|
'href': info['detail_url'], |
||||
|
'target': '_blank' |
||||
|
}) : new Element('a'), |
||||
|
new Element('a.download.icon2', { |
||||
|
'events': { |
||||
|
'click': function(e){ |
||||
|
(e).preventDefault(); |
||||
|
if(!this.hasClass('completed')) |
||||
|
self.download(release); |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
new Element('a.delete.icon2', { |
||||
|
'events': { |
||||
|
'click': function(e){ |
||||
|
(e).preventDefault(); |
||||
|
self.ignore(release); |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
).inject(self.releases_table); |
||||
|
|
||||
|
if(release.status == 'ignored' || release.status == 'failed' || release.status == 'snatched'){ |
||||
|
if(!self.last_release || (self.last_release && self.last_release.status != 'snatched' && release.status == 'snatched')) |
||||
|
self.last_release = release; |
||||
|
} |
||||
|
else if(!self.next_release && release.status == 'available'){ |
||||
|
self.next_release = release; |
||||
|
} |
||||
|
|
||||
|
var update_handle = function(notification) { |
||||
|
if(notification.data._id != release._id) return; |
||||
|
|
||||
|
var q = self.show.quality.getElement('.q_' + release.quality), |
||||
|
new_status = notification.data.status; |
||||
|
|
||||
|
release.el.set('class', 'item ' + new_status); |
||||
|
|
||||
|
var status_el = release.el.getElement('.release_status'); |
||||
|
status_el.set('class', 'release_status ' + new_status); |
||||
|
status_el.set('text', new_status); |
||||
|
|
||||
|
if(!q && (new_status == 'snatched' || new_status == 'seeding' || new_status == 'done')) |
||||
|
q = self.addQuality(release.quality_id); |
||||
|
|
||||
|
if(q && !q.hasClass(new_status)) { |
||||
|
q.removeClass(release.status).addClass(new_status); |
||||
|
q.set('title', q.get('title').replace(release.status, new_status)); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
App.on('release.update_status', update_handle); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
if(self.last_release) |
||||
|
self.releases_table.getElements('#release_'+self.last_release._id).addClass('last_release'); |
||||
|
|
||||
|
if(self.next_release) |
||||
|
self.releases_table.getElements('#release_'+self.next_release._id).addClass('next_release'); |
||||
|
|
||||
|
if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status) === false)){ |
||||
|
|
||||
|
self.trynext_container = new Element('div.buttons.try_container').inject(self.releases_table, 'top'); |
||||
|
|
||||
|
var nr = self.next_release, |
||||
|
lr = self.last_release; |
||||
|
|
||||
|
self.trynext_container.adopt( |
||||
|
new Element('span.or', { |
||||
|
'text': 'If anything went wrong, download' |
||||
|
}), |
||||
|
lr ? new Element('a.button.orange', { |
||||
|
'text': 'the same release again', |
||||
|
'events': { |
||||
|
'click': function(){ |
||||
|
self.download(lr); |
||||
|
} |
||||
|
} |
||||
|
}) : null, |
||||
|
nr && lr ? new Element('span.or', { |
||||
|
'text': ',' |
||||
|
}) : null, |
||||
|
nr ? [new Element('a.button.green', { |
||||
|
'text': lr ? 'another release' : 'the best release', |
||||
|
'events': { |
||||
|
'click': function(){ |
||||
|
self.download(nr); |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
new Element('span.or', { |
||||
|
'text': 'or pick one below' |
||||
|
})] : null |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
self.last_release = null; |
||||
|
self.next_release = null; |
||||
|
|
||||
|
self.episode.el.addEvent('outerClick', function(){ |
||||
|
self.close(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
self.options.setStyle('height', self.releases_table.getSize().y) |
||||
|
.addClass('expanded'); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
showHelper: function(e){ |
||||
|
var self = this; |
||||
|
if(e) |
||||
|
(e).preventDefault(); |
||||
|
|
||||
|
var has_available = false, |
||||
|
has_snatched = false; |
||||
|
|
||||
|
if(self.episode.data.releases) |
||||
|
self.episode.data.releases.each(function(release){ |
||||
|
if(has_available && has_snatched) return; |
||||
|
|
||||
|
if(['snatched', 'downloaded', 'seeding'].contains(release.status)) |
||||
|
has_snatched = true; |
||||
|
|
||||
|
if(['available'].contains(release.status)) |
||||
|
has_available = true; |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
if(has_available || has_snatched){ |
||||
|
|
||||
|
self.trynext_container = new Element('div.buttons.trynext').inject(self.show.info_container); |
||||
|
|
||||
|
self.trynext_container.adopt( |
||||
|
has_available ? [new Element('a.icon2.readd', { |
||||
|
'text': has_snatched ? 'Download another release' : 'Download the best release', |
||||
|
'events': { |
||||
|
'click': self.tryNextRelease.bind(self) |
||||
|
} |
||||
|
}), |
||||
|
new Element('a.icon2.download', { |
||||
|
'text': 'pick one yourself', |
||||
|
'events': { |
||||
|
'click': function(){ |
||||
|
self.show.quality.fireEvent('click'); |
||||
|
} |
||||
|
} |
||||
|
})] : null, |
||||
|
new Element('a.icon2.completed', { |
||||
|
'text': 'mark this movie done', |
||||
|
'events': { |
||||
|
'click': self.markMovieDone.bind(self) |
||||
|
} |
||||
|
}) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
get: function(release, type){ |
||||
|
return (release.info && release.info[type] !== undefined) ? release.info[type] : 'n/a' |
||||
|
}, |
||||
|
|
||||
|
download: function(release){ |
||||
|
var self = this; |
||||
|
|
||||
|
var release_el = self.releases_table.getElement('#release_'+release._id), |
||||
|
icon = release_el.getElement('.download.icon2'); |
||||
|
|
||||
|
if(icon) |
||||
|
icon.addClass('icon spinner').removeClass('download'); |
||||
|
|
||||
|
Api.request('release.manual_download', { |
||||
|
'data': { |
||||
|
'id': release._id |
||||
|
}, |
||||
|
'onComplete': function(json){ |
||||
|
if(icon) |
||||
|
icon.removeClass('icon spinner'); |
||||
|
|
||||
|
if(json.success){ |
||||
|
if(icon) |
||||
|
icon.addClass('completed'); |
||||
|
release_el.getElement('.release_status').set('text', 'snatched'); |
||||
|
} |
||||
|
else |
||||
|
if(icon) |
||||
|
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
ignore: function(release){ |
||||
|
|
||||
|
Api.request('release.ignore', { |
||||
|
'data': { |
||||
|
'id': release._id |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
markMovieDone: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
Api.request('media.delete', { |
||||
|
'data': { |
||||
|
'id': self.show.get('_id'), |
||||
|
'delete_from': 'wanted' |
||||
|
}, |
||||
|
'onComplete': function(){ |
||||
|
var movie = $(self.show); |
||||
|
movie.set('tween', { |
||||
|
'duration': 300, |
||||
|
'onComplete': function(){ |
||||
|
self.show.destroy() |
||||
|
} |
||||
|
}); |
||||
|
movie.tween('height', 0); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
tryNextRelease: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
Api.request('movie.searcher.try_next', { |
||||
|
'data': { |
||||
|
'media_id': self.show.get('_id') |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
EA.Refresh = new Class({ |
||||
|
|
||||
|
Extends: EpisodeAction, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.el = new Element('a.refresh', { |
||||
|
'title': 'Refresh the movie info and do a forced search', |
||||
|
'events': { |
||||
|
'click': self.doRefresh.bind(self) |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
doRefresh: function(e){ |
||||
|
var self = this; |
||||
|
(e).preventDefault(); |
||||
|
|
||||
|
Api.request('media.refresh', { |
||||
|
'data': { |
||||
|
'id': self.episode.get('_id') |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
}); |
@ -0,0 +1,128 @@ |
|||||
|
var Episode = new Class({ |
||||
|
|
||||
|
Extends: BlockBase, |
||||
|
|
||||
|
action: {}, |
||||
|
|
||||
|
initialize: function(show, options, data){ |
||||
|
var self = this; |
||||
|
self.setOptions(options); |
||||
|
|
||||
|
self.show = show; |
||||
|
self.options = options; |
||||
|
self.data = data; |
||||
|
|
||||
|
self.profile = self.show.profile; |
||||
|
|
||||
|
self.el = new Element('div.item.episode').adopt( |
||||
|
self.detail = new Element('div.item.data') |
||||
|
); |
||||
|
|
||||
|
self.create(); |
||||
|
}, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.detail.set('id', 'episode_'+self.data._id); |
||||
|
|
||||
|
self.detail.adopt( |
||||
|
new Element('span.episode', {'text': (self.data.info.number || 0)}), |
||||
|
new Element('span.name', {'text': self.getTitle()}), |
||||
|
new Element('span.firstaired', {'text': self.data.info.firstaired}), |
||||
|
|
||||
|
self.quality = new Element('span.quality', { |
||||
|
'events': { |
||||
|
'click': function(e){ |
||||
|
var releases = self.detail.getElement('.item-actions .releases'); |
||||
|
|
||||
|
if(releases.isVisible()) |
||||
|
releases.fireEvent('click', [e]) |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
self.actions = new Element('div.item-actions') |
||||
|
); |
||||
|
|
||||
|
// Add profile
|
||||
|
if(self.profile.data) { |
||||
|
self.profile.getTypes().each(function(type){ |
||||
|
var q = self.addQuality(type.get('quality'), type.get('3d')); |
||||
|
|
||||
|
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.') |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Add releases
|
||||
|
self.updateReleases(); |
||||
|
|
||||
|
Object.each(self.options.actions, function(action, key){ |
||||
|
self.action[key.toLowerCase()] = action = new self.options.actions[key](self); |
||||
|
if(action.el) |
||||
|
self.actions.adopt(action) |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
updateReleases: function(){ |
||||
|
var self = this; |
||||
|
if(!self.data.releases || self.data.releases.length == 0) return; |
||||
|
|
||||
|
self.data.releases.each(function(release){ |
||||
|
|
||||
|
var q = self.quality.getElement('.q_'+ release.quality+(release.is_3d ? '.is_3d' : ':not(.is_3d)')), |
||||
|
status = release.status; |
||||
|
|
||||
|
if(!q && (status == 'snatched' || status == 'seeding' || status == 'done')) |
||||
|
q = self.addQuality(release.quality, release.is_3d || false); |
||||
|
|
||||
|
if (q && !q.hasClass(status)){ |
||||
|
q.addClass(status); |
||||
|
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status) |
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
addQuality: function(quality, is_3d){ |
||||
|
var self = this, |
||||
|
q = Quality.getQuality(quality); |
||||
|
|
||||
|
return new Element('span', { |
||||
|
'text': q.label + (is_3d ? ' 3D' : ''), |
||||
|
'class': 'q_'+q.identifier + (is_3d ? ' is_3d' : ''), |
||||
|
'title': '' |
||||
|
}).inject(self.quality); |
||||
|
}, |
||||
|
|
||||
|
getTitle: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
var title = ''; |
||||
|
|
||||
|
if(self.data.info.titles && self.data.info.titles.length > 0) { |
||||
|
title = self.data.info.titles[0]; |
||||
|
} else { |
||||
|
title = 'Episode ' + self.data.info.number; |
||||
|
} |
||||
|
|
||||
|
return title; |
||||
|
}, |
||||
|
|
||||
|
getIdentifier: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
try { |
||||
|
return self.get('identifiers').imdb; |
||||
|
} |
||||
|
catch (e){ } |
||||
|
|
||||
|
return self.get('imdb'); |
||||
|
}, |
||||
|
|
||||
|
get: function(attr){ |
||||
|
return this.data[attr] || this.data.info[attr] |
||||
|
} |
||||
|
}); |
@ -0,0 +1,636 @@ |
|||||
|
var ShowList = new Class({ |
||||
|
|
||||
|
Implements: [Events, Options], |
||||
|
|
||||
|
options: { |
||||
|
navigation: true, |
||||
|
limit: 50, |
||||
|
load_more: true, |
||||
|
loader: true, |
||||
|
menu: [], |
||||
|
add_new: false, |
||||
|
force_view: false |
||||
|
}, |
||||
|
|
||||
|
movies: [], |
||||
|
movies_added: {}, |
||||
|
total_movies: 0, |
||||
|
letters: {}, |
||||
|
filter: null, |
||||
|
|
||||
|
initialize: function(options){ |
||||
|
var self = this; |
||||
|
self.setOptions(options); |
||||
|
|
||||
|
self.offset = 0; |
||||
|
self.filter = self.options.filter || { |
||||
|
'starts_with': null, |
||||
|
'search': null |
||||
|
}; |
||||
|
|
||||
|
self.el = new Element('div.shows').adopt( |
||||
|
self.title = self.options.title ? new Element('h2', { |
||||
|
'text': self.options.title, |
||||
|
'styles': {'display': 'none'} |
||||
|
}) : null, |
||||
|
self.description = self.options.description ? new Element('div.description', { |
||||
|
'html': self.options.description, |
||||
|
'styles': {'display': 'none'} |
||||
|
}) : null, |
||||
|
self.movie_list = new Element('div.list'), |
||||
|
self.load_more = self.options.load_more ? new Element('a.load_more', { |
||||
|
'events': { |
||||
|
'click': self.loadMore.bind(self) |
||||
|
} |
||||
|
}) : null |
||||
|
); |
||||
|
|
||||
|
if($(window).getSize().x <= 480 && !self.options.force_view) |
||||
|
self.changeView('list'); |
||||
|
else |
||||
|
self.changeView(self.getSavedView() || self.options.view || 'details'); |
||||
|
|
||||
|
self.getMovies(); |
||||
|
|
||||
|
App.on('movie.added', self.movieAdded.bind(self)); |
||||
|
App.on('movie.deleted', self.movieDeleted.bind(self)) |
||||
|
}, |
||||
|
|
||||
|
movieDeleted: function(notification){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(self.movies_added[notification.data._id]){ |
||||
|
self.movies.each(function(movie){ |
||||
|
if(movie.get('_id') == notification.data._id){ |
||||
|
movie.destroy(); |
||||
|
delete self.movies_added[notification.data._id]; |
||||
|
self.setCounter(self.counter_count-1); |
||||
|
self.total_movies--; |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
self.checkIfEmpty(); |
||||
|
}, |
||||
|
|
||||
|
movieAdded: function(notification){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.fireEvent('movieAdded', notification); |
||||
|
if(self.options.add_new && !self.movies_added[notification.data._id] && notification.data.status == self.options.status){ |
||||
|
window.scroll(0,0); |
||||
|
self.createShow(notification.data, 'top'); |
||||
|
self.setCounter(self.counter_count+1); |
||||
|
|
||||
|
self.checkIfEmpty(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
// Create the alphabet nav
|
||||
|
if(self.options.navigation) |
||||
|
self.createNavigation(); |
||||
|
|
||||
|
if(self.options.load_more) |
||||
|
self.scrollspy = new ScrollSpy({ |
||||
|
min: function(){ |
||||
|
var c = self.load_more.getCoordinates(); |
||||
|
return c.top - window.document.getSize().y - 300 |
||||
|
}, |
||||
|
onEnter: self.loadMore.bind(self) |
||||
|
}); |
||||
|
|
||||
|
self.created = true; |
||||
|
}, |
||||
|
|
||||
|
addMovies: function(movies, total){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(!self.created) self.create(); |
||||
|
|
||||
|
// do scrollspy
|
||||
|
if(movies.length < self.options.limit && self.scrollspy){ |
||||
|
self.load_more.hide(); |
||||
|
self.scrollspy.stop(); |
||||
|
} |
||||
|
|
||||
|
Object.each(movies, function(movie){ |
||||
|
self.createShow(movie); |
||||
|
}); |
||||
|
|
||||
|
self.total_movies += total; |
||||
|
self.setCounter(total); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
setCounter: function(count){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(!self.navigation_counter) return; |
||||
|
|
||||
|
self.counter_count = count; |
||||
|
self.navigation_counter.set('text', (count || 0) + ' shows'); |
||||
|
|
||||
|
if (self.empty_message) { |
||||
|
self.empty_message.destroy(); |
||||
|
self.empty_message = null; |
||||
|
} |
||||
|
|
||||
|
if(self.total_movies && count == 0 && !self.empty_message){ |
||||
|
var message = (self.filter.search ? 'for "'+self.filter.search+'"' : '') + |
||||
|
(self.filter.starts_with ? ' in <strong>'+self.filter.starts_with+'</strong>' : ''); |
||||
|
|
||||
|
self.empty_message = new Element('.message', { |
||||
|
'html': 'No shows found ' + message + '.<br/>' |
||||
|
}).grab( |
||||
|
new Element('a', { |
||||
|
'text': 'Reset filter', |
||||
|
'events': { |
||||
|
'click': function(){ |
||||
|
self.filter = { |
||||
|
'starts_with': null, |
||||
|
'search': null |
||||
|
}; |
||||
|
self.navigation_search_input.set('value', ''); |
||||
|
self.reset(); |
||||
|
self.activateLetter(); |
||||
|
self.getMovies(true); |
||||
|
self.last_search_value = ''; |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
).inject(self.movie_list); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
createShow: function(show, inject_at){ |
||||
|
var self = this; |
||||
|
var m = new Show(self, { |
||||
|
'actions': self.options.actions, |
||||
|
'view': self.current_view, |
||||
|
'onSelect': self.calculateSelected.bind(self) |
||||
|
}, show); |
||||
|
|
||||
|
$(m).inject(self.movie_list, inject_at || 'bottom'); |
||||
|
|
||||
|
m.fireEvent('injected'); |
||||
|
|
||||
|
self.movies.include(m); |
||||
|
self.movies_added[show._id] = true; |
||||
|
}, |
||||
|
|
||||
|
createNavigation: function(){ |
||||
|
var self = this; |
||||
|
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
||||
|
|
||||
|
self.el.addClass('with_navigation'); |
||||
|
|
||||
|
self.navigation = new Element('div.alph_nav').adopt( |
||||
|
self.mass_edit_form = new Element('div.mass_edit_form').adopt( |
||||
|
new Element('span.select').adopt( |
||||
|
self.mass_edit_select = new Element('input[type=checkbox].inlay', { |
||||
|
'events': { |
||||
|
'change': self.massEditToggleAll.bind(self) |
||||
|
} |
||||
|
}), |
||||
|
self.mass_edit_selected = new Element('span.count', {'text': 0}), |
||||
|
self.mass_edit_selected_label = new Element('span', {'text': 'selected'}) |
||||
|
), |
||||
|
new Element('div.quality').adopt( |
||||
|
self.mass_edit_quality = new Element('select'), |
||||
|
new Element('a.button.orange', { |
||||
|
'text': 'Change quality', |
||||
|
'events': { |
||||
|
'click': self.changeQualitySelected.bind(self) |
||||
|
} |
||||
|
}) |
||||
|
), |
||||
|
new Element('div.delete').adopt( |
||||
|
new Element('span[text=or]'), |
||||
|
new Element('a.button.red', { |
||||
|
'text': 'Delete', |
||||
|
'events': { |
||||
|
'click': self.deleteSelected.bind(self) |
||||
|
} |
||||
|
}) |
||||
|
), |
||||
|
new Element('div.refresh').adopt( |
||||
|
new Element('span[text=or]'), |
||||
|
new Element('a.button.green', { |
||||
|
'text': 'Refresh', |
||||
|
'events': { |
||||
|
'click': self.refreshSelected.bind(self) |
||||
|
} |
||||
|
}) |
||||
|
) |
||||
|
), |
||||
|
new Element('div.menus').adopt( |
||||
|
self.navigation_counter = new Element('span.counter[title=Total]'), |
||||
|
self.filter_menu = new Block.Menu(self, { |
||||
|
'class': 'filter' |
||||
|
}), |
||||
|
self.navigation_actions = new Element('ul.actions', { |
||||
|
'events': { |
||||
|
'click:relay(li)': function(e, el){ |
||||
|
var a = 'active'; |
||||
|
self.navigation_actions.getElements('.'+a).removeClass(a); |
||||
|
self.changeView(el.get('data-view')); |
||||
|
this.addClass(a); |
||||
|
|
||||
|
el.inject(el.getParent(), 'top'); |
||||
|
el.getSiblings().hide(); |
||||
|
setTimeout(function(){ |
||||
|
el.getSiblings().setStyle('display', null); |
||||
|
}, 100) |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
self.navigation_menu = new Block.Menu(self, { |
||||
|
'class': 'extra' |
||||
|
}) |
||||
|
) |
||||
|
).inject(self.el, 'top'); |
||||
|
|
||||
|
// Mass edit
|
||||
|
self.mass_edit_select_class = new Form.Check(self.mass_edit_select); |
||||
|
Quality.getActiveProfiles().each(function(profile){ |
||||
|
new Element('option', { |
||||
|
'value': profile.get('_id'), |
||||
|
'text': profile.get('label') |
||||
|
}).inject(self.mass_edit_quality) |
||||
|
}); |
||||
|
|
||||
|
self.filter_menu.addLink( |
||||
|
self.navigation_search_input = new Element('input', { |
||||
|
'title': 'Search through ' + self.options.identifier, |
||||
|
'placeholder': 'Search through ' + self.options.identifier, |
||||
|
'events': { |
||||
|
'keyup': self.search.bind(self), |
||||
|
'change': self.search.bind(self) |
||||
|
} |
||||
|
}) |
||||
|
).addClass('search'); |
||||
|
|
||||
|
var available_chars; |
||||
|
self.filter_menu.addEvent('open', function(){ |
||||
|
self.navigation_search_input.focus(); |
||||
|
|
||||
|
// Get available chars and highlight
|
||||
|
if(!available_chars && (self.navigation.isDisplayed() || self.navigation.isVisible())) |
||||
|
Api.request('media.available_chars', { |
||||
|
'data': Object.merge({ |
||||
|
'type': 'show', |
||||
|
'status': self.options.status |
||||
|
}, self.filter), |
||||
|
'onSuccess': function(json){ |
||||
|
available_chars = json.chars; |
||||
|
|
||||
|
available_chars.each(function(c){ |
||||
|
self.letters[c.capitalize()].addClass('available') |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
self.filter_menu.addLink( |
||||
|
self.navigation_alpha = new Element('ul.numbers', { |
||||
|
'events': { |
||||
|
'click:relay(li.available)': function(e, el){ |
||||
|
self.activateLetter(el.get('data-letter')); |
||||
|
self.getMovies(true) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
); |
||||
|
|
||||
|
// Actions
|
||||
|
['mass_edit', 'details', 'list'].each(function(view){ |
||||
|
var current = self.current_view == view; |
||||
|
new Element('li', { |
||||
|
'class': 'icon2 ' + view + (current ? ' active ' : ''), |
||||
|
'data-view': view |
||||
|
}).inject(self.navigation_actions, current ? 'top' : 'bottom'); |
||||
|
}); |
||||
|
|
||||
|
// All
|
||||
|
self.letters['all'] = new Element('li.letter_all.available.active', { |
||||
|
'text': 'ALL' |
||||
|
}).inject(self.navigation_alpha); |
||||
|
|
||||
|
// Chars
|
||||
|
chars.split('').each(function(c){ |
||||
|
self.letters[c] = new Element('li', { |
||||
|
'text': c, |
||||
|
'class': 'letter_'+c, |
||||
|
'data-letter': c |
||||
|
}).inject(self.navigation_alpha); |
||||
|
}); |
||||
|
|
||||
|
// Add menu or hide
|
||||
|
if (self.options.menu.length > 0) |
||||
|
self.options.menu.each(function(menu_item){ |
||||
|
self.navigation_menu.addLink(menu_item); |
||||
|
}); |
||||
|
else |
||||
|
self.navigation_menu.hide(); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
calculateSelected: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
var selected = 0, |
||||
|
movies = self.movies.length; |
||||
|
self.movies.each(function(movie){ |
||||
|
selected += movie.isSelected() ? 1 : 0 |
||||
|
}); |
||||
|
|
||||
|
var indeterminate = selected > 0 && selected < movies, |
||||
|
checked = selected == movies && selected > 0; |
||||
|
|
||||
|
self.mass_edit_select.set('indeterminate', indeterminate); |
||||
|
|
||||
|
self.mass_edit_select_class[checked ? 'check' : 'uncheck'](); |
||||
|
self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate'); |
||||
|
|
||||
|
self.mass_edit_selected.set('text', selected); |
||||
|
}, |
||||
|
|
||||
|
deleteSelected: function(){ |
||||
|
var self = this, |
||||
|
ids = self.getSelectedMovies(), |
||||
|
help_msg = self.identifier == 'wanted' ? 'If you do, you won\'t be able to watch them, as they won\'t get downloaded!' : 'Your files will be safe, this will only delete the reference from the CouchPotato manage list'; |
||||
|
|
||||
|
var qObj = new Question('Are you sure you want to delete '+ids.length+' movie'+ (ids.length != 1 ? 's' : '') +'?', help_msg, [{ |
||||
|
'text': 'Yes, delete '+(ids.length != 1 ? 'them' : 'it'), |
||||
|
'class': 'delete', |
||||
|
'events': { |
||||
|
'click': function(e){ |
||||
|
(e).preventDefault(); |
||||
|
this.set('text', 'Deleting..'); |
||||
|
Api.request('media.delete', { |
||||
|
'method': 'post', |
||||
|
'data': { |
||||
|
'id': ids.join(','), |
||||
|
'delete_from': self.options.identifier |
||||
|
}, |
||||
|
'onSuccess': function(){ |
||||
|
qObj.close(); |
||||
|
|
||||
|
var erase_movies = []; |
||||
|
self.movies.each(function(movie){ |
||||
|
if (movie.isSelected()){ |
||||
|
$(movie).destroy(); |
||||
|
erase_movies.include(movie); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
erase_movies.each(function(movie){ |
||||
|
self.movies.erase(movie); |
||||
|
movie.destroy(); |
||||
|
self.setCounter(self.counter_count-1); |
||||
|
self.total_movies--; |
||||
|
}); |
||||
|
|
||||
|
self.calculateSelected(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
}, { |
||||
|
'text': 'Cancel', |
||||
|
'cancel': true |
||||
|
}]); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
changeQualitySelected: function(){ |
||||
|
var self = this; |
||||
|
var ids = self.getSelectedMovies(); |
||||
|
|
||||
|
Api.request('movie.edit', { |
||||
|
'method': 'post', |
||||
|
'data': { |
||||
|
'id': ids.join(','), |
||||
|
'profile_id': self.mass_edit_quality.get('value') |
||||
|
}, |
||||
|
'onSuccess': self.search.bind(self) |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
refreshSelected: function(){ |
||||
|
var self = this; |
||||
|
var ids = self.getSelectedMovies(); |
||||
|
|
||||
|
Api.request('media.refresh', { |
||||
|
'method': 'post', |
||||
|
'data': { |
||||
|
'id': ids.join(',') |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
getSelectedMovies: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
var ids = []; |
||||
|
self.movies.each(function(movie){ |
||||
|
if (movie.isSelected()) |
||||
|
ids.include(movie.get('_id')) |
||||
|
}); |
||||
|
|
||||
|
return ids |
||||
|
}, |
||||
|
|
||||
|
massEditToggleAll: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
var select = self.mass_edit_select.get('checked'); |
||||
|
|
||||
|
self.movies.each(function(movie){ |
||||
|
movie.select(select) |
||||
|
}); |
||||
|
|
||||
|
self.calculateSelected() |
||||
|
}, |
||||
|
|
||||
|
reset: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.movies = []; |
||||
|
if(self.mass_edit_select) |
||||
|
self.calculateSelected(); |
||||
|
if(self.navigation_alpha) |
||||
|
self.navigation_alpha.getElements('.active').removeClass('active'); |
||||
|
|
||||
|
self.offset = 0; |
||||
|
if(self.scrollspy){ |
||||
|
//self.load_more.show();
|
||||
|
self.scrollspy.start(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
activateLetter: function(letter){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.reset(); |
||||
|
|
||||
|
self.letters[letter || 'all'].addClass('active'); |
||||
|
self.filter.starts_with = letter; |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
changeView: function(new_view){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.el |
||||
|
.removeClass(self.current_view+'_list') |
||||
|
.addClass(new_view+'_list'); |
||||
|
|
||||
|
self.current_view = new_view; |
||||
|
Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000}); |
||||
|
}, |
||||
|
|
||||
|
getSavedView: function(){ |
||||
|
var self = this; |
||||
|
return Cookie.read(self.options.identifier+'_view2'); |
||||
|
}, |
||||
|
|
||||
|
search: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(self.search_timer) clearTimeout(self.search_timer); |
||||
|
self.search_timer = (function(){ |
||||
|
var search_value = self.navigation_search_input.get('value'); |
||||
|
if (search_value == self.last_search_value) return; |
||||
|
|
||||
|
self.reset(); |
||||
|
|
||||
|
self.activateLetter(); |
||||
|
self.filter.search = search_value; |
||||
|
|
||||
|
self.getMovies(true); |
||||
|
|
||||
|
self.last_search_value = search_value; |
||||
|
|
||||
|
}).delay(250); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
update: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.reset(); |
||||
|
self.getMovies(true); |
||||
|
}, |
||||
|
|
||||
|
getMovies: function(reset){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(self.scrollspy){ |
||||
|
self.scrollspy.stop(); |
||||
|
self.load_more.set('text', 'loading...'); |
||||
|
} |
||||
|
|
||||
|
if(self.movies.length == 0 && self.options.loader){ |
||||
|
|
||||
|
self.loader_first = new Element('div.loading').adopt( |
||||
|
new Element('div.message', {'text': self.options.title ? 'Loading \'' + self.options.title + '\'' : 'Loading...'}) |
||||
|
).inject(self.el, 'top'); |
||||
|
|
||||
|
createSpinner(self.loader_first, { |
||||
|
radius: 4, |
||||
|
length: 4, |
||||
|
width: 1 |
||||
|
}); |
||||
|
|
||||
|
self.el.setStyle('min-height', 93); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
Api.request(self.options.api_call || 'media.list', { |
||||
|
'data': Object.merge({ |
||||
|
'type': self.options.type || 'movie', |
||||
|
'status': self.options.status, |
||||
|
'limit_offset': self.options.limit ? self.options.limit + ',' + self.offset : null |
||||
|
}, self.filter), |
||||
|
'onSuccess': function(json){ |
||||
|
|
||||
|
if(reset) |
||||
|
self.movie_list.empty(); |
||||
|
|
||||
|
if(self.loader_first){ |
||||
|
var lf = self.loader_first; |
||||
|
self.loader_first.addClass('hide'); |
||||
|
self.loader_first = null; |
||||
|
setTimeout(function(){ |
||||
|
lf.destroy(); |
||||
|
}, 20000); |
||||
|
self.el.setStyle('min-height', null); |
||||
|
} |
||||
|
|
||||
|
self.store(json.shows); |
||||
|
self.addMovies(json.shows, json.total || json.shows.length); |
||||
|
if(self.scrollspy) { |
||||
|
self.load_more.set('text', 'load more movies'); |
||||
|
self.scrollspy.start(); |
||||
|
} |
||||
|
|
||||
|
self.checkIfEmpty(); |
||||
|
self.fireEvent('loaded'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
loadMore: function(){ |
||||
|
var self = this; |
||||
|
if(self.offset >= self.options.limit) |
||||
|
self.getMovies() |
||||
|
}, |
||||
|
|
||||
|
store: function(movies){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.offset += movies.length; |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
checkIfEmpty: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined); |
||||
|
|
||||
|
if(self.title) |
||||
|
self.title[is_empty ? 'hide' : 'show'](); |
||||
|
|
||||
|
if(self.description) |
||||
|
self.description.setStyle('display', [is_empty ? 'none' : '']); |
||||
|
|
||||
|
if(is_empty && self.options.on_empty_element){ |
||||
|
self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, 'after'); |
||||
|
|
||||
|
if(self.navigation) |
||||
|
self.navigation.hide(); |
||||
|
|
||||
|
self.empty_element = self.options.on_empty_element; |
||||
|
} |
||||
|
else if(self.empty_element){ |
||||
|
self.empty_element.destroy(); |
||||
|
|
||||
|
if(self.navigation) |
||||
|
self.navigation.show(); |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
toElement: function(){ |
||||
|
return this.el; |
||||
|
} |
||||
|
|
||||
|
}); |
@ -0,0 +1,127 @@ |
|||||
|
var Season = new Class({ |
||||
|
|
||||
|
Extends: BlockBase, |
||||
|
|
||||
|
action: {}, |
||||
|
|
||||
|
initialize: function(show, options, data){ |
||||
|
var self = this; |
||||
|
self.setOptions(options); |
||||
|
|
||||
|
self.show = show; |
||||
|
self.options = options; |
||||
|
self.data = data; |
||||
|
|
||||
|
self.profile = self.show.profile; |
||||
|
|
||||
|
self.el = new Element('div.item.season').adopt( |
||||
|
self.detail = new Element('div.item.data') |
||||
|
); |
||||
|
|
||||
|
self.create(); |
||||
|
}, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.detail.set('id', 'season_'+self.data._id); |
||||
|
|
||||
|
self.detail.adopt( |
||||
|
new Element('span.name', {'text': self.getTitle()}), |
||||
|
|
||||
|
self.quality = new Element('span.quality', { |
||||
|
'events': { |
||||
|
'click': function(e){ |
||||
|
var releases = self.detail.getElement('.item-actions .releases'); |
||||
|
|
||||
|
if(releases.isVisible()) |
||||
|
releases.fireEvent('click', [e]) |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
self.actions = new Element('div.item-actions') |
||||
|
); |
||||
|
|
||||
|
// Add profile
|
||||
|
if(self.profile.data) { |
||||
|
self.profile.getTypes().each(function(type){ |
||||
|
var q = self.addQuality(type.get('quality'), type.get('3d')); |
||||
|
|
||||
|
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.') |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Add releases
|
||||
|
self.updateReleases(); |
||||
|
|
||||
|
Object.each(self.options.actions, function(action, key){ |
||||
|
self.action[key.toLowerCase()] = action = new self.options.actions[key](self); |
||||
|
if(action.el) |
||||
|
self.actions.adopt(action) |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
updateReleases: function(){ |
||||
|
var self = this; |
||||
|
if(!self.data.releases || self.data.releases.length == 0) return; |
||||
|
|
||||
|
self.data.releases.each(function(release){ |
||||
|
|
||||
|
var q = self.quality.getElement('.q_'+ release.quality+(release.is_3d ? '.is_3d' : ':not(.is_3d)')), |
||||
|
status = release.status; |
||||
|
|
||||
|
if(!q && (status == 'snatched' || status == 'seeding' || status == 'done')) |
||||
|
q = self.addQuality(release.quality, release.is_3d || false); |
||||
|
|
||||
|
if (q && !q.hasClass(status)){ |
||||
|
q.addClass(status); |
||||
|
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status) |
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
addQuality: function(quality, is_3d){ |
||||
|
var self = this, |
||||
|
q = Quality.getQuality(quality); |
||||
|
|
||||
|
return new Element('span', { |
||||
|
'text': q.label + (is_3d ? ' 3D' : ''), |
||||
|
'class': 'q_'+q.identifier + (is_3d ? ' is_3d' : ''), |
||||
|
'title': '' |
||||
|
}).inject(self.quality); |
||||
|
}, |
||||
|
|
||||
|
getTitle: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
var title = ''; |
||||
|
|
||||
|
if(self.data.info.number) { |
||||
|
title = 'Season ' + self.data.info.number; |
||||
|
} else { |
||||
|
// Season 0 / Specials
|
||||
|
title = 'Specials'; |
||||
|
} |
||||
|
|
||||
|
return title; |
||||
|
}, |
||||
|
|
||||
|
getIdentifier: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
try { |
||||
|
return self.get('identifiers').imdb; |
||||
|
} |
||||
|
catch (e){ } |
||||
|
|
||||
|
return self.get('imdb'); |
||||
|
}, |
||||
|
|
||||
|
get: function(attr){ |
||||
|
return this.data[attr] || this.data.info[attr] |
||||
|
} |
||||
|
}); |
File diff suppressed because it is too large
@ -0,0 +1,92 @@ |
|||||
|
var Episodes = new Class({ |
||||
|
initialize: function(show, options) { |
||||
|
var self = this; |
||||
|
|
||||
|
self.show = show; |
||||
|
self.options = options; |
||||
|
}, |
||||
|
|
||||
|
open: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(!self.container){ |
||||
|
self.container = new Element('div.options').grab( |
||||
|
self.episodes_container = new Element('div.episodes.table') |
||||
|
); |
||||
|
|
||||
|
self.container.inject(self.show, 'top'); |
||||
|
|
||||
|
Api.request('library.tree', { |
||||
|
'data': { |
||||
|
'media_id': self.show.data._id |
||||
|
}, |
||||
|
'onComplete': function(json){ |
||||
|
self.data = json.result; |
||||
|
|
||||
|
self.createEpisodes(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
self.show.slide('in', self.container, true); |
||||
|
}, |
||||
|
|
||||
|
createEpisodes: function() { |
||||
|
var self = this; |
||||
|
|
||||
|
self.data.seasons.sort(self.sortSeasons); |
||||
|
self.data.seasons.each(function(season) { |
||||
|
self.createSeason(season); |
||||
|
|
||||
|
season.episodes.sort(self.sortEpisodes); |
||||
|
season.episodes.each(function(episode) { |
||||
|
self.createEpisode(episode); |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
createSeason: function(season) { |
||||
|
var self = this, |
||||
|
s = new Season(self.show, self.options, season); |
||||
|
|
||||
|
$(s).inject(self.episodes_container); |
||||
|
}, |
||||
|
|
||||
|
createEpisode: function(episode){ |
||||
|
var self = this, |
||||
|
e = new Episode(self.show, self.options, episode); |
||||
|
|
||||
|
$(e).inject(self.episodes_container); |
||||
|
}, |
||||
|
|
||||
|
sortSeasons: function(a, b) { |
||||
|
// Move "Specials" to the bottom of the list
|
||||
|
if(!a.info.number) { |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
if(!b.info.number) { |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
// Order seasons descending
|
||||
|
if(a.info.number < b.info.number) |
||||
|
return -1; |
||||
|
|
||||
|
if(a.info.number > b.info.number) |
||||
|
return 1; |
||||
|
|
||||
|
return 0; |
||||
|
}, |
||||
|
|
||||
|
sortEpisodes: function(a, b) { |
||||
|
// Order episodes descending
|
||||
|
if(a.info.number < b.info.number) |
||||
|
return -1; |
||||
|
|
||||
|
if(a.info.number > b.info.number) |
||||
|
return 1; |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
}); |
@ -0,0 +1,370 @@ |
|||||
|
var Show = new Class({ |
||||
|
|
||||
|
Extends: BlockBase, |
||||
|
|
||||
|
action: {}, |
||||
|
|
||||
|
initialize: function(list, options, data){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.data = data; |
||||
|
self.view = options.view || 'details'; |
||||
|
self.list = list; |
||||
|
|
||||
|
self.el = new Element('div.show'); |
||||
|
|
||||
|
self.episodes = new Episodes(self, { |
||||
|
'actions': [EA.IMDB, EA.Release, EA.Refresh] |
||||
|
}); |
||||
|
|
||||
|
self.profile = Quality.getProfile(data.profile_id) || {}; |
||||
|
self.category = CategoryList.getCategory(data.category_id) || {}; |
||||
|
self.parent(self, options); |
||||
|
|
||||
|
self.addEvents(); |
||||
|
}, |
||||
|
|
||||
|
addEvents: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.global_events = {}; |
||||
|
|
||||
|
// Do refresh with new data
|
||||
|
self.global_events['movie.update'] = function(notification){ |
||||
|
if(self.data._id != notification.data._id) return; |
||||
|
|
||||
|
self.busy(false); |
||||
|
self.removeView(); |
||||
|
self.update.delay(2000, self, notification); |
||||
|
}; |
||||
|
App.on('movie.update', self.global_events['movie.update']); |
||||
|
|
||||
|
// Add spinner on load / search
|
||||
|
['media.busy', 'movie.searcher.started'].each(function(listener){ |
||||
|
self.global_events[listener] = function(notification){ |
||||
|
if(notification.data && (self.data._id == notification.data._id || (typeOf(notification.data._id) == 'array' && notification.data._id.indexOf(self.data._id) > -1))) |
||||
|
self.busy(true); |
||||
|
}; |
||||
|
App.on(listener, self.global_events[listener]); |
||||
|
}); |
||||
|
|
||||
|
// Remove spinner
|
||||
|
self.global_events['movie.searcher.ended'] = function(notification){ |
||||
|
if(notification.data && self.data._id == notification.data._id) |
||||
|
self.busy(false) |
||||
|
}; |
||||
|
App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']); |
||||
|
|
||||
|
// Reload when releases have updated
|
||||
|
self.global_events['release.update_status'] = function(notification){ |
||||
|
var data = notification.data; |
||||
|
if(data && self.data._id == data.movie_id){ |
||||
|
|
||||
|
if(!self.data.releases) |
||||
|
self.data.releases = []; |
||||
|
|
||||
|
self.data.releases.push({'quality': data.quality, 'status': data.status}); |
||||
|
self.updateReleases(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
App.on('release.update_status', self.global_events['release.update_status']); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
destroy: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.el.destroy(); |
||||
|
delete self.list.movies_added[self.get('id')]; |
||||
|
self.list.movies.erase(self); |
||||
|
|
||||
|
self.list.checkIfEmpty(); |
||||
|
|
||||
|
// Remove events
|
||||
|
Object.each(self.global_events, function(handle, listener){ |
||||
|
App.off(listener, handle); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
busy: function(set_busy, timeout){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(!set_busy){ |
||||
|
setTimeout(function(){ |
||||
|
if(self.spinner){ |
||||
|
self.mask.fade('out'); |
||||
|
setTimeout(function(){ |
||||
|
if(self.mask) |
||||
|
self.mask.destroy(); |
||||
|
if(self.spinner) |
||||
|
self.spinner.el.destroy(); |
||||
|
self.spinner = null; |
||||
|
self.mask = null; |
||||
|
}, timeout || 400); |
||||
|
} |
||||
|
}, timeout || 1000) |
||||
|
} |
||||
|
else if(!self.spinner) { |
||||
|
self.createMask(); |
||||
|
self.spinner = createSpinner(self.mask); |
||||
|
self.mask.fade('in'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
createMask: function(){ |
||||
|
var self = this; |
||||
|
self.mask = new Element('div.mask', { |
||||
|
'styles': { |
||||
|
'z-index': 4 |
||||
|
} |
||||
|
}).inject(self.el, 'top').fade('hide'); |
||||
|
}, |
||||
|
|
||||
|
update: function(notification){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.data = notification.data; |
||||
|
self.el.empty(); |
||||
|
self.removeView(); |
||||
|
|
||||
|
self.profile = Quality.getProfile(self.data.profile_id) || {}; |
||||
|
self.category = CategoryList.getCategory(self.data.category_id) || {}; |
||||
|
self.create(); |
||||
|
|
||||
|
self.busy(false); |
||||
|
}, |
||||
|
|
||||
|
create: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.el.addClass('status_'+self.get('status')); |
||||
|
|
||||
|
var eta = null, |
||||
|
eta_date = null, |
||||
|
now = Math.round(+new Date()/1000); |
||||
|
|
||||
|
if(self.data.info.release_date) |
||||
|
[self.data.info.release_date.dvd, self.data.info.release_date.theater].each(function(timestamp){ |
||||
|
if (timestamp > 0 && (eta == null || Math.abs(timestamp - now) < Math.abs(eta - now))) |
||||
|
eta = timestamp; |
||||
|
}); |
||||
|
|
||||
|
if(eta){ |
||||
|
eta_date = new Date(eta * 1000); |
||||
|
eta_date = eta_date.toLocaleString('en-us', { month: "long" }) + ' ' + eta_date.getFullYear(); |
||||
|
} |
||||
|
|
||||
|
self.el.adopt( |
||||
|
self.select_checkbox = new Element('input[type=checkbox].inlay', { |
||||
|
'events': { |
||||
|
'change': function(){ |
||||
|
self.fireEvent('select') |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
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].split(Api.getOption('path_sep')).pop() |
||||
|
}): null, |
||||
|
self.data_container = new Element('div.data.inlay.light').adopt( |
||||
|
self.info_container = new Element('div.info').adopt( |
||||
|
new Element('div.title').adopt( |
||||
|
self.title = new Element('a', { |
||||
|
'events': { |
||||
|
'click': function(e){ |
||||
|
self.episodes.open(); |
||||
|
} |
||||
|
}, |
||||
|
'text': self.getTitle() || 'n/a' |
||||
|
}), |
||||
|
self.year = new Element('div.year', { |
||||
|
'text': self.data.info.year || 'n/a' |
||||
|
}) |
||||
|
), |
||||
|
self.description = new Element('div.description.tiny_scroll', { |
||||
|
'text': self.data.info.plot |
||||
|
}), |
||||
|
self.eta = eta_date && (now+8035200 > eta) ? new Element('div.eta', { |
||||
|
'text': eta_date, |
||||
|
'title': 'ETA' |
||||
|
}) : null, |
||||
|
self.quality = new Element('div.quality', { |
||||
|
'events': { |
||||
|
'click': function(e){ |
||||
|
var releases = self.el.getElement('.actions .releases'); |
||||
|
if(releases.isVisible()) |
||||
|
releases.fireEvent('click', [e]) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
), |
||||
|
self.actions = new Element('div.actions') |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
if(!self.thumbnail) |
||||
|
self.el.addClass('no_thumbnail'); |
||||
|
|
||||
|
//self.changeView(self.view);
|
||||
|
self.select_checkbox_class = new Form.Check(self.select_checkbox); |
||||
|
|
||||
|
// Add profile
|
||||
|
if(self.profile.data) |
||||
|
self.profile.getTypes().each(function(type){ |
||||
|
|
||||
|
var q = self.addQuality(type.get('quality'), type.get('3d')); |
||||
|
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.') |
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
// Add releases
|
||||
|
self.updateReleases(); |
||||
|
|
||||
|
Object.each(self.options.actions, function(action, key){ |
||||
|
self.action[key.toLowerCase()] = action = new self.options.actions[key](self); |
||||
|
if(action.el) |
||||
|
self.actions.adopt(action) |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
updateReleases: function(){ |
||||
|
var self = this; |
||||
|
if(!self.data.releases || self.data.releases.length == 0) return; |
||||
|
|
||||
|
self.data.releases.each(function(release){ |
||||
|
|
||||
|
var q = self.quality.getElement('.q_'+ release.quality+(release.is_3d ? '.is_3d' : ':not(.is_3d)')), |
||||
|
status = release.status; |
||||
|
|
||||
|
if(!q && (status == 'snatched' || status == 'seeding' || status == 'done')) |
||||
|
q = self.addQuality(release.quality, release.is_3d || false); |
||||
|
|
||||
|
if (q && !q.hasClass(status)){ |
||||
|
q.addClass(status); |
||||
|
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status) |
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
addQuality: function(quality, is_3d){ |
||||
|
var self = this; |
||||
|
|
||||
|
var q = Quality.getQuality(quality); |
||||
|
return new Element('span', { |
||||
|
'text': q.label + (is_3d ? ' 3D' : ''), |
||||
|
'class': 'q_'+q.identifier + (is_3d ? ' is_3d' : ''), |
||||
|
'title': '' |
||||
|
}).inject(self.quality); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
getTitle: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(self.data.title) |
||||
|
return self.getUnprefixedTitle(self.data.title); |
||||
|
else if(self.data.info.titles.length > 0) |
||||
|
return self.getUnprefixedTitle(self.data.info.titles[0]); |
||||
|
|
||||
|
return 'Unknown movie' |
||||
|
}, |
||||
|
|
||||
|
getUnprefixedTitle: function(t){ |
||||
|
if(t.substr(0, 4).toLowerCase() == 'the ') |
||||
|
t = t.substr(4) + ', The'; |
||||
|
else if(t.substr(0, 3).toLowerCase() == 'an ') |
||||
|
t = t.substr(3) + ', An'; |
||||
|
else if(t.substr(0, 2).toLowerCase() == 'a ') |
||||
|
t = t.substr(2) + ', A'; |
||||
|
return t; |
||||
|
}, |
||||
|
|
||||
|
slide: function(direction, el, expand){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(direction == 'in'){ |
||||
|
self.temp_view = self.view; |
||||
|
self.changeView('details'); |
||||
|
|
||||
|
self.el.addEvent('outerClick', function(){ |
||||
|
self.removeView(); |
||||
|
self.slide('out') |
||||
|
}); |
||||
|
el.show(); |
||||
|
|
||||
|
|
||||
|
if(expand === true) { |
||||
|
self.el.addClass('expanded'); |
||||
|
self.el.getElements('.table').addClass('expanded'); |
||||
|
} |
||||
|
|
||||
|
self.data_container.addClass('hide_right'); |
||||
|
} |
||||
|
else { |
||||
|
self.el.removeEvents('outerClick'); |
||||
|
|
||||
|
setTimeout(function(){ |
||||
|
if(self.el) |
||||
|
{ |
||||
|
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide(); |
||||
|
self.el.getElements('.table').removeClass('expanded'); |
||||
|
} |
||||
|
}, 600); |
||||
|
|
||||
|
self.el.removeClass('expanded'); |
||||
|
self.data_container.removeClass('hide_right'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
changeView: function(new_view){ |
||||
|
var self = this; |
||||
|
|
||||
|
if(self.el) |
||||
|
self.el |
||||
|
.removeClass(self.view+'_view') |
||||
|
.addClass(new_view+'_view'); |
||||
|
|
||||
|
self.view = new_view; |
||||
|
}, |
||||
|
|
||||
|
removeView: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
self.el.removeClass(self.view+'_view') |
||||
|
}, |
||||
|
|
||||
|
getIdentifier: function(){ |
||||
|
var self = this; |
||||
|
|
||||
|
try { |
||||
|
return self.get('identifiers').imdb; |
||||
|
} |
||||
|
catch (e){ } |
||||
|
|
||||
|
return self.get('imdb'); |
||||
|
}, |
||||
|
|
||||
|
get: function(attr){ |
||||
|
return this.data[attr] || this.data.info[attr] |
||||
|
}, |
||||
|
|
||||
|
select: function(bool){ |
||||
|
var self = this; |
||||
|
self.select_checkbox_class[bool ? 'check' : 'uncheck']() |
||||
|
}, |
||||
|
|
||||
|
isSelected: function(){ |
||||
|
return this.select_checkbox.get('checked'); |
||||
|
}, |
||||
|
|
||||
|
toElement: function(){ |
||||
|
return this.el; |
||||
|
} |
||||
|
|
||||
|
}); |
@ -1,147 +0,0 @@ |
|||||
from couchpotato import get_db |
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync |
|
||||
from couchpotato.core.logger import CPLog |
|
||||
from couchpotato.core.helpers.variable import tryInt |
|
||||
from couchpotato.core.media import MediaBase |
|
||||
|
|
||||
|
|
||||
log = CPLog(__name__) |
|
||||
|
|
||||
autoload = 'Episode' |
|
||||
|
|
||||
|
|
||||
class Episode(MediaBase): |
|
||||
|
|
||||
def __init__(self): |
|
||||
addEvent('media.search_query', self.query) |
|
||||
addEvent('media.identifier', self.identifier) |
|
||||
|
|
||||
addEvent('show.episode.add', self.add) |
|
||||
addEvent('show.episode.update_info', self.updateInfo) |
|
||||
|
|
||||
def add(self, parent_id, info = None, update_after = True): |
|
||||
if not info: info = {} |
|
||||
|
|
||||
identifiers = info.get('identifiers') |
|
||||
try: del info['identifiers'] |
|
||||
except: pass |
|
||||
|
|
||||
# Add Season |
|
||||
episode_info = { |
|
||||
'_t': 'media', |
|
||||
'type': 'episode', |
|
||||
'identifiers': identifiers, |
|
||||
'parent_id': parent_id, |
|
||||
'info': info, # Returned dict by providers |
|
||||
} |
|
||||
|
|
||||
# Check if season already exists |
|
||||
existing_episode = fireEvent('media.with_identifiers', identifiers, with_doc = True, single = True) |
|
||||
|
|
||||
db = get_db() |
|
||||
|
|
||||
if existing_episode: |
|
||||
s = existing_episode['doc'] |
|
||||
s.update(episode_info) |
|
||||
episode = db.update(s) |
|
||||
else: |
|
||||
episode = db.insert(episode_info) |
|
||||
|
|
||||
# Update library info |
|
||||
if update_after is not False: |
|
||||
handle = fireEventAsync if update_after is 'async' else fireEvent |
|
||||
handle('show.season.update_info', episode.get('_id'), info = info, single = True) |
|
||||
|
|
||||
return episode |
|
||||
|
|
||||
def updateInfo(self, media_id = None, info = None, force = False): |
|
||||
if not info: info = {} |
|
||||
|
|
||||
if self.shuttingDown(): |
|
||||
return |
|
||||
|
|
||||
db = get_db() |
|
||||
|
|
||||
episode = db.get('id', media_id) |
|
||||
|
|
||||
# Get new info |
|
||||
if not info: |
|
||||
info = fireEvent('episode.info', episode.get('identifiers'), merge = True) |
|
||||
|
|
||||
# Update/create media |
|
||||
if force: |
|
||||
|
|
||||
episode['identifiers'].update(info['identifiers']) |
|
||||
if 'identifiers' in info: |
|
||||
del info['identifiers'] |
|
||||
|
|
||||
episode.update({'info': info}) |
|
||||
e = db.update(episode) |
|
||||
episode.update(e) |
|
||||
|
|
||||
# Get images |
|
||||
image_urls = info.get('images', []) |
|
||||
existing_files = episode.get('files', {}) |
|
||||
self.getPoster(image_urls, existing_files) |
|
||||
|
|
||||
return episode |
|
||||
|
|
||||
def query(self, library, first = True, condense = True, include_identifier = True, **kwargs): |
|
||||
if library is list or library.get('type') != 'episode': |
|
||||
return |
|
||||
|
|
||||
# Get the titles of the season |
|
||||
if not library.get('related_libraries', {}).get('season', []): |
|
||||
log.warning('Invalid library, unable to determine title.') |
|
||||
return |
|
||||
|
|
||||
titles = fireEvent( |
|
||||
'media.search_query', |
|
||||
library['related_libraries']['season'][0], |
|
||||
first=False, |
|
||||
include_identifier=include_identifier, |
|
||||
condense=condense, |
|
||||
|
|
||||
single=True |
|
||||
) |
|
||||
|
|
||||
identifier = fireEvent('media.identifier', library, single = True) |
|
||||
|
|
||||
# Add episode identifier to titles |
|
||||
if include_identifier and identifier.get('episode'): |
|
||||
titles = [title + ('E%02d' % identifier['episode']) for title in titles] |
|
||||
|
|
||||
|
|
||||
if first: |
|
||||
return titles[0] if titles else None |
|
||||
|
|
||||
return titles |
|
||||
|
|
||||
|
|
||||
def identifier(self, media): |
|
||||
if media.get('type') != 'episode': |
|
||||
return |
|
||||
|
|
||||
identifier = { |
|
||||
'season': None, |
|
||||
'episode': None |
|
||||
} |
|
||||
|
|
||||
scene_map = media['info'].get('map_episode', {}).get('scene') |
|
||||
|
|
||||
if scene_map: |
|
||||
# Use scene mappings if they are available |
|
||||
identifier['season'] = scene_map.get('season_nr') |
|
||||
identifier['episode'] = scene_map.get('episode_nr') |
|
||||
else: |
|
||||
# Fallback to normal season/episode numbers |
|
||||
identifier['season'] = media['info'].get('season_number') |
|
||||
identifier['episode'] = media['info'].get('number') |
|
||||
|
|
||||
|
|
||||
# Cast identifiers to integers |
|
||||
# TODO this will need changing to support identifiers with trailing 'a', 'b' characters |
|
||||
identifier['season'] = tryInt(identifier['season'], None) |
|
||||
identifier['episode'] = tryInt(identifier['episode'], None) |
|
||||
|
|
||||
return identifier |
|
@ -0,0 +1,71 @@ |
|||||
|
from couchpotato.core.event import addEvent, fireEvent |
||||
|
from couchpotato.core.helpers.variable import tryInt |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.media._base.library.base import LibraryBase |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'EpisodeLibraryPlugin' |
||||
|
|
||||
|
|
||||
|
class EpisodeLibraryPlugin(LibraryBase): |
||||
|
def __init__(self): |
||||
|
addEvent('library.query', self.query) |
||||
|
addEvent('library.identifier', self.identifier) |
||||
|
|
||||
|
def query(self, media, first = True, condense = True, include_identifier = True, **kwargs): |
||||
|
if media.get('type') != 'show.episode': |
||||
|
return |
||||
|
|
||||
|
related = fireEvent('library.related', media, single = True) |
||||
|
|
||||
|
# Get season titles |
||||
|
titles = fireEvent( |
||||
|
'library.query', related['season'], |
||||
|
|
||||
|
first = False, |
||||
|
include_identifier = include_identifier, |
||||
|
condense = condense, |
||||
|
|
||||
|
single = True |
||||
|
) |
||||
|
|
||||
|
# Add episode identifier to titles |
||||
|
if include_identifier: |
||||
|
identifier = fireEvent('library.identifier', media, single = True) |
||||
|
|
||||
|
if identifier and identifier.get('episode'): |
||||
|
titles = [title + ('E%02d' % identifier['episode']) for title in titles] |
||||
|
|
||||
|
if first: |
||||
|
return titles[0] if titles else None |
||||
|
|
||||
|
return titles |
||||
|
|
||||
|
def identifier(self, media): |
||||
|
if media.get('type') != 'show.episode': |
||||
|
return |
||||
|
|
||||
|
identifier = { |
||||
|
'season': None, |
||||
|
'episode': None |
||||
|
} |
||||
|
|
||||
|
# TODO identifier mapping |
||||
|
# scene_map = media['info'].get('map_episode', {}).get('scene') |
||||
|
|
||||
|
# if scene_map: |
||||
|
# # Use scene mappings if they are available |
||||
|
# identifier['season'] = scene_map.get('season_nr') |
||||
|
# identifier['episode'] = scene_map.get('episode_nr') |
||||
|
# else: |
||||
|
# Fallback to normal season/episode numbers |
||||
|
identifier['season'] = media['info'].get('season_number') |
||||
|
identifier['episode'] = media['info'].get('number') |
||||
|
|
||||
|
# Cast identifiers to integers |
||||
|
# TODO this will need changing to support identifiers with trailing 'a', 'b' characters |
||||
|
identifier['season'] = tryInt(identifier['season'], None) |
||||
|
identifier['episode'] = tryInt(identifier['episode'], None) |
||||
|
|
||||
|
return identifier |
@ -0,0 +1,52 @@ |
|||||
|
from couchpotato.core.event import addEvent, fireEvent |
||||
|
from couchpotato.core.helpers.variable import tryInt |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.media._base.library.base import LibraryBase |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'SeasonLibraryPlugin' |
||||
|
|
||||
|
|
||||
|
class SeasonLibraryPlugin(LibraryBase): |
||||
|
def __init__(self): |
||||
|
addEvent('library.query', self.query) |
||||
|
addEvent('library.identifier', self.identifier) |
||||
|
|
||||
|
def query(self, media, first = True, condense = True, include_identifier = True, **kwargs): |
||||
|
if media.get('type') != 'show.season': |
||||
|
return |
||||
|
|
||||
|
related = fireEvent('library.related', media, single = True) |
||||
|
|
||||
|
# Get show titles |
||||
|
titles = fireEvent( |
||||
|
'library.query', related['show'], |
||||
|
|
||||
|
first = False, |
||||
|
condense = condense, |
||||
|
|
||||
|
single = True |
||||
|
) |
||||
|
|
||||
|
# TODO map_names |
||||
|
|
||||
|
# Add season identifier to titles |
||||
|
if include_identifier: |
||||
|
identifier = fireEvent('library.identifier', media, single = True) |
||||
|
|
||||
|
if identifier and identifier.get('season') is not None: |
||||
|
titles = [title + (' S%02d' % identifier['season']) for title in titles] |
||||
|
|
||||
|
if first: |
||||
|
return titles[0] if titles else None |
||||
|
|
||||
|
return titles |
||||
|
|
||||
|
def identifier(self, media): |
||||
|
if media.get('type') != 'show.season': |
||||
|
return |
||||
|
|
||||
|
return { |
||||
|
'season': tryInt(media['info']['number'], None) |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
from couchpotato.core.event import addEvent |
||||
|
from couchpotato.core.helpers.encoding import simplifyString |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.media._base.library.base import LibraryBase |
||||
|
from qcond import QueryCondenser |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'ShowLibraryPlugin' |
||||
|
|
||||
|
|
||||
|
class ShowLibraryPlugin(LibraryBase): |
||||
|
query_condenser = QueryCondenser() |
||||
|
|
||||
|
def __init__(self): |
||||
|
addEvent('library.query', self.query) |
||||
|
|
||||
|
def query(self, media, first = True, condense = True, include_identifier = True, **kwargs): |
||||
|
if media.get('type') != 'show': |
||||
|
return |
||||
|
|
||||
|
titles = media['info']['titles'] |
||||
|
|
||||
|
if condense: |
||||
|
# Use QueryCondenser to build a list of optimal search titles |
||||
|
condensed_titles = self.query_condenser.distinct(titles) |
||||
|
|
||||
|
if condensed_titles: |
||||
|
# Use condensed titles if we got a valid result |
||||
|
titles = condensed_titles |
||||
|
else: |
||||
|
# Fallback to simplifying titles |
||||
|
titles = [simplifyString(title) for title in titles] |
||||
|
|
||||
|
if first: |
||||
|
return titles[0] if titles else None |
||||
|
|
||||
|
return titles |
@ -0,0 +1,7 @@ |
|||||
|
from .main import ShowMatcher |
||||
|
|
||||
|
|
||||
|
def autoload(): |
||||
|
return ShowMatcher() |
||||
|
|
||||
|
config = [] |
@ -0,0 +1,30 @@ |
|||||
|
from couchpotato import fireEvent, CPLog |
||||
|
from couchpotato.core.media.show.matcher.base import Base |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
|
||||
|
class Episode(Base): |
||||
|
type = 'show.episode' |
||||
|
|
||||
|
def correctIdentifier(self, chain, media): |
||||
|
identifier = self.getChainIdentifier(chain) |
||||
|
if not identifier: |
||||
|
log.info2('Wrong: release identifier is not valid (unsupported or missing identifier)') |
||||
|
return False |
||||
|
|
||||
|
# TODO - Parse episode ranges from identifier to determine if they are multi-part episodes |
||||
|
if any([x in identifier for x in ['episode_from', 'episode_to']]): |
||||
|
log.info2('Wrong: releases with identifier ranges are not supported yet') |
||||
|
return False |
||||
|
|
||||
|
required = fireEvent('library.identifier', media, single = True) |
||||
|
|
||||
|
# TODO - Support air by date episodes |
||||
|
# TODO - Support episode parts |
||||
|
|
||||
|
if identifier != required: |
||||
|
log.info2('Wrong: required identifier (%s) does not match release identifier (%s)', (required, identifier)) |
||||
|
return False |
||||
|
|
||||
|
return True |
@ -0,0 +1,9 @@ |
|||||
|
from couchpotato.core.media._base.providers.base import MultiProvider |
||||
|
from couchpotato.core.media.show.matcher.episode import Episode |
||||
|
from couchpotato.core.media.show.matcher.season import Season |
||||
|
|
||||
|
|
||||
|
class ShowMatcher(MultiProvider): |
||||
|
|
||||
|
def getTypes(self): |
||||
|
return [Season, Episode] |
@ -0,0 +1,27 @@ |
|||||
|
from couchpotato import fireEvent, CPLog |
||||
|
from couchpotato.core.media.show.matcher.base import Base |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
|
||||
|
class Season(Base): |
||||
|
type = 'show.season' |
||||
|
|
||||
|
def correctIdentifier(self, chain, media): |
||||
|
identifier = self.getChainIdentifier(chain) |
||||
|
if not identifier: |
||||
|
log.info2('Wrong: release identifier is not valid (unsupported or missing identifier)') |
||||
|
return False |
||||
|
|
||||
|
# TODO - Parse episode ranges from identifier to determine if they are season packs |
||||
|
if any([x in identifier for x in ['episode_from', 'episode_to']]): |
||||
|
log.info2('Wrong: releases with identifier ranges are not supported yet') |
||||
|
return False |
||||
|
|
||||
|
required = fireEvent('library.identifier', media, single = True) |
||||
|
|
||||
|
if identifier != required: |
||||
|
log.info2('Wrong: required identifier (%s) does not match release identifier (%s)', (required, identifier)) |
||||
|
return False |
||||
|
|
||||
|
return True |
@ -0,0 +1,86 @@ |
|||||
|
import urllib |
||||
|
|
||||
|
from couchpotato.core.event import addEvent |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.media.show.providers.base import ShowProvider |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'Trakt' |
||||
|
|
||||
|
|
||||
|
class Trakt(ShowProvider): |
||||
|
api_key = 'c043de5ada9d180028c10229d2a3ea5b' |
||||
|
base_url = 'http://api.trakt.tv/%%s.json/%s' % api_key |
||||
|
|
||||
|
def __init__(self): |
||||
|
addEvent('info.search', self.search, priority = 1) |
||||
|
addEvent('show.search', self.search, priority = 1) |
||||
|
|
||||
|
def search(self, q, limit = 12): |
||||
|
if self.isDisabled(): |
||||
|
return False |
||||
|
|
||||
|
# Check for cached result |
||||
|
cache_key = 'trakt.cache.search.%s.%s' % (q, limit) |
||||
|
results = self.getCache(cache_key) or [] |
||||
|
|
||||
|
if results: |
||||
|
return results |
||||
|
|
||||
|
# Search |
||||
|
log.debug('Searching for show: "%s"', q) |
||||
|
response = self._request('search/shows', query=q, limit=limit) |
||||
|
|
||||
|
if not response: |
||||
|
return [] |
||||
|
|
||||
|
# Parse search results |
||||
|
for show in response: |
||||
|
results.append(self._parseShow(show)) |
||||
|
|
||||
|
log.info('Found: %s', [result['titles'][0] + ' (' + str(result.get('year', 0)) + ')' for result in results]) |
||||
|
|
||||
|
self.setCache(cache_key, results) |
||||
|
return results |
||||
|
|
||||
|
def _request(self, action, **kwargs): |
||||
|
url = self.base_url % action |
||||
|
|
||||
|
if kwargs: |
||||
|
url += '?' + urllib.urlencode(kwargs) |
||||
|
|
||||
|
return self.getJsonData(url) |
||||
|
|
||||
|
def _parseShow(self, show): |
||||
|
# Images |
||||
|
images = show.get('images', {}) |
||||
|
|
||||
|
poster = images.get('poster') |
||||
|
backdrop = images.get('backdrop') |
||||
|
|
||||
|
# Rating |
||||
|
rating = show.get('ratings', {}).get('percentage') |
||||
|
|
||||
|
# Build show dict |
||||
|
show_data = { |
||||
|
'identifiers': { |
||||
|
'thetvdb': show.get('tvdb_id'), |
||||
|
'imdb': show.get('imdb_id'), |
||||
|
'tvrage': show.get('tvrage_id'), |
||||
|
}, |
||||
|
'type': 'show', |
||||
|
'titles': [show.get('title')], |
||||
|
'images': { |
||||
|
'poster': [poster] if poster else [], |
||||
|
'backdrop': [backdrop] if backdrop else [], |
||||
|
'poster_original': [], |
||||
|
'backdrop_original': [], |
||||
|
}, |
||||
|
'year': show.get('year'), |
||||
|
'rating': { |
||||
|
'trakt': float(rating) / 10 |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
return dict((k, v) for k, v in show_data.iteritems() if v) |
@ -1,232 +0,0 @@ |
|||||
import time |
|
||||
from couchpotato import Env, get_db |
|
||||
from couchpotato.core.event import addEvent, fireEvent |
|
||||
from couchpotato.core.helpers.variable import getTitle, toIterable |
|
||||
from couchpotato.core.logger import CPLog |
|
||||
from couchpotato.core.media._base.searcher.base import SearcherBase |
|
||||
from couchpotato.core.media._base.searcher.main import SearchSetupError |
|
||||
from couchpotato.core.media.show import ShowTypeBase |
|
||||
from qcond import QueryCondenser |
|
||||
|
|
||||
log = CPLog(__name__) |
|
||||
|
|
||||
autoload = 'ShowSearcher' |
|
||||
|
|
||||
|
|
||||
class ShowSearcher(SearcherBase, ShowTypeBase): |
|
||||
|
|
||||
type = ['show', 'season', 'episode'] |
|
||||
|
|
||||
in_progress = False |
|
||||
|
|
||||
def __init__(self): |
|
||||
super(ShowSearcher, self).__init__() |
|
||||
|
|
||||
self.query_condenser = QueryCondenser() |
|
||||
|
|
||||
addEvent('season.searcher.single', self.singleSeason) |
|
||||
addEvent('episode.searcher.single', self.singleEpisode) |
|
||||
|
|
||||
addEvent('searcher.correct_release', self.correctRelease) |
|
||||
addEvent('searcher.get_search_title', self.getSearchTitle) |
|
||||
|
|
||||
|
|
||||
|
|
||||
def single(self, media, search_protocols = None, manual = False): |
|
||||
|
|
||||
# Find out search type |
|
||||
try: |
|
||||
if not search_protocols: |
|
||||
search_protocols = fireEvent('searcher.protocols', single = True) |
|
||||
except SearchSetupError: |
|
||||
return |
|
||||
|
|
||||
if not media['profile_id'] or media['status'] == 'done': |
|
||||
log.debug('Show doesn\'t have a profile or already done, assuming in manage tab.') |
|
||||
return |
|
||||
|
|
||||
show_title = fireEvent('media.search_query', media, condense = False, single = True) |
|
||||
|
|
||||
fireEvent('notify.frontend', type = 'show.searcher.started.%s' % media['_id'], data = True, message = 'Searching for "%s"' % show_title) |
|
||||
|
|
||||
media = self.extendShow(media) |
|
||||
|
|
||||
db = get_db() |
|
||||
|
|
||||
profile = db.get('id', media['profile_id']) |
|
||||
quality_order = fireEvent('quality.order', single = True) |
|
||||
|
|
||||
seasons = media.get('seasons', {}) |
|
||||
for sx in seasons: |
|
||||
|
|
||||
# Skip specials for now TODO: set status for specials to skipped by default |
|
||||
if sx == 0: continue |
|
||||
|
|
||||
season = seasons.get(sx) |
|
||||
|
|
||||
# Check if full season can be downloaded TODO: add |
|
||||
season_success = self.singleSeason(season, media, profile) |
|
||||
|
|
||||
# Do each episode seperately |
|
||||
if not season_success: |
|
||||
episodes = season.get('episodes', {}) |
|
||||
for ex in episodes: |
|
||||
episode = episodes.get(ex) |
|
||||
|
|
||||
self.singleEpisode(episode, season, media, profile, quality_order, search_protocols) |
|
||||
|
|
||||
# TODO |
|
||||
return |
|
||||
|
|
||||
# TODO |
|
||||
return |
|
||||
|
|
||||
fireEvent('notify.frontend', type = 'show.searcher.ended.%s' % media['_id'], data = True) |
|
||||
|
|
||||
def singleSeason(self, media, show, profile): |
|
||||
|
|
||||
# Check if any episode is already snatched |
|
||||
active = 0 |
|
||||
episodes = media.get('episodes', {}) |
|
||||
for ex in episodes: |
|
||||
episode = episodes.get(ex) |
|
||||
|
|
||||
if episode.get('status') in ['active']: |
|
||||
active += 1 |
|
||||
|
|
||||
if active != len(episodes): |
|
||||
return False |
|
||||
|
|
||||
# Try and search for full season |
|
||||
# TODO: |
|
||||
|
|
||||
return False |
|
||||
|
|
||||
def singleEpisode(self, media, season, show, profile, quality_order, search_protocols = None, manual = False): |
|
||||
|
|
||||
|
|
||||
# TODO: check episode status |
|
||||
|
|
||||
|
|
||||
# TODO: check air date |
|
||||
#if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): |
|
||||
# too_early_to_search.append(quality_type['quality']['identifier']) |
|
||||
# return |
|
||||
|
|
||||
ret = False |
|
||||
has_better_quality = None |
|
||||
found_releases = [] |
|
||||
too_early_to_search = [] |
|
||||
|
|
||||
releases = fireEvent('release.for_media', media['_id'], single = True) |
|
||||
show_title = getTitle(show) |
|
||||
episode_identifier = '%s S%02d%s' % (show_title, season['info'].get('number', 0), "E%02d" % media['info'].get('number')) |
|
||||
|
|
||||
# Add parents |
|
||||
media['show'] = show |
|
||||
media['season'] = season |
|
||||
|
|
||||
index = 0 |
|
||||
for q_identifier in profile.get('qualities'): |
|
||||
quality_custom = { |
|
||||
'quality': q_identifier, |
|
||||
'finish': profile['finish'][index], |
|
||||
'wait_for': profile['wait_for'][index], |
|
||||
'3d': profile['3d'][index] if profile.get('3d') else False |
|
||||
} |
|
||||
|
|
||||
has_better_quality = 0 |
|
||||
|
|
||||
# See if better quality is available |
|
||||
for release in releases: |
|
||||
if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']: |
|
||||
has_better_quality += 1 |
|
||||
|
|
||||
# Don't search for quality lower then already available. |
|
||||
if has_better_quality is 0: |
|
||||
|
|
||||
log.info('Searching for %s in %s', (episode_identifier, q_identifier)) |
|
||||
quality = fireEvent('quality.single', identifier = q_identifier, single = True) |
|
||||
quality['custom'] = quality_custom |
|
||||
|
|
||||
results = fireEvent('searcher.search', search_protocols, media, quality, single = True) |
|
||||
if len(results) == 0: |
|
||||
log.debug('Nothing found for %s in %s', (episode_identifier, q_identifier)) |
|
||||
|
|
||||
# Add them to this movie releases list |
|
||||
found_releases += fireEvent('release.create_from_search', results, media, quality, single = True) |
|
||||
|
|
||||
# Try find a valid result and download it |
|
||||
if fireEvent('release.try_download_result', results, media, quality, manual, single = True): |
|
||||
ret = True |
|
||||
|
|
||||
# Remove releases that aren't found anymore |
|
||||
for release in releases: |
|
||||
if release.get('status') == 'available' and release.get('identifier') not in found_releases: |
|
||||
fireEvent('release.delete', release.get('id'), single = True) |
|
||||
else: |
|
||||
log.info('Better quality (%s) already available or snatched for %s', (q_identifier, episode_identifier)) |
|
||||
fireEvent('media.restatus', media['_id']) |
|
||||
break |
|
||||
|
|
||||
# Break if CP wants to shut down |
|
||||
if self.shuttingDown() or ret: |
|
||||
break |
|
||||
|
|
||||
if len(too_early_to_search) > 0: |
|
||||
log.info2('Too early to search for %s, %s', (too_early_to_search, episode_identifier)) |
|
||||
|
|
||||
def correctRelease(self, release = None, media = None, quality = None, **kwargs): |
|
||||
|
|
||||
if media.get('type') not in ['season', 'episode']: return |
|
||||
|
|
||||
retention = Env.setting('retention', section = 'nzb') |
|
||||
|
|
||||
if release.get('seeders') is None and 0 < retention < release.get('age', 0): |
|
||||
log.info2('Wrong: Outside retention, age is %s, needs %s or lower: %s', (release['age'], retention, release['name'])) |
|
||||
return False |
|
||||
|
|
||||
# Check for required and ignored words |
|
||||
if not fireEvent('searcher.correct_words', release['name'], media, single = True): |
|
||||
return False |
|
||||
|
|
||||
# TODO Matching is quite costly, maybe we should be caching release matches somehow? (also look at caper optimizations) |
|
||||
match = fireEvent('matcher.match', release, media, quality, single = True) |
|
||||
if match: |
|
||||
return match.weight |
|
||||
|
|
||||
return False |
|
||||
|
|
||||
def extendShow(self, media): |
|
||||
|
|
||||
db = get_db() |
|
||||
|
|
||||
seasons = db.get_many('media_children', media['_id'], with_doc = True) |
|
||||
|
|
||||
media['seasons'] = {} |
|
||||
|
|
||||
for sx in seasons: |
|
||||
season = sx['doc'] |
|
||||
|
|
||||
# Add episode info |
|
||||
season['episodes'] = {} |
|
||||
episodes = db.get_many('media_children', sx['_id'], with_doc = True) |
|
||||
|
|
||||
for se in episodes: |
|
||||
episode = se['doc'] |
|
||||
season['episodes'][episode['info'].get('number')] = episode |
|
||||
|
|
||||
# Add season to show |
|
||||
media['seasons'][season['info'].get('number', 0)] = season |
|
||||
|
|
||||
return media |
|
||||
|
|
||||
def searchAll(self): |
|
||||
pass |
|
||||
|
|
||||
def getSearchTitle(self, media): |
|
||||
# TODO: this should be done for season and episode |
|
||||
if media['type'] == 'season': |
|
||||
return getTitle(media) |
|
||||
elif media['type'] == 'episode': |
|
||||
return getTitle(media) |
|
@ -0,0 +1,152 @@ |
|||||
|
from couchpotato import fireEvent, get_db, Env |
||||
|
from couchpotato.api import addApiView |
||||
|
from couchpotato.core.event import addEvent, fireEventAsync |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.media._base.searcher.base import SearcherBase |
||||
|
from couchpotato.core.media._base.searcher.main import SearchSetupError |
||||
|
from couchpotato.core.media.show import ShowTypeBase |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'EpisodeSearcher' |
||||
|
|
||||
|
|
||||
|
class EpisodeSearcher(SearcherBase, ShowTypeBase): |
||||
|
type = 'episode' |
||||
|
|
||||
|
in_progress = False |
||||
|
|
||||
|
def __init__(self): |
||||
|
super(EpisodeSearcher, self).__init__() |
||||
|
|
||||
|
addEvent('%s.searcher.all' % self.getType(), self.searchAll) |
||||
|
addEvent('%s.searcher.single' % self.getType(), self.single) |
||||
|
addEvent('searcher.correct_release', self.correctRelease) |
||||
|
|
||||
|
addApiView('%s.searcher.full_search' % self.getType(), self.searchAllView, docs = { |
||||
|
'desc': 'Starts a full search for all wanted shows', |
||||
|
}) |
||||
|
|
||||
|
addApiView('%s.searcher.single' % self.getType(), self.singleView) |
||||
|
|
||||
|
def searchAllView(self, **kwargs): |
||||
|
fireEventAsync('%s.searcher.all' % self.getType(), manual = True) |
||||
|
|
||||
|
return { |
||||
|
'success': not self.in_progress |
||||
|
} |
||||
|
|
||||
|
def searchAll(self, manual = False): |
||||
|
pass |
||||
|
|
||||
|
def singleView(self, media_id, **kwargs): |
||||
|
db = get_db() |
||||
|
media = db.get('id', media_id) |
||||
|
|
||||
|
return { |
||||
|
'result': fireEvent('%s.searcher.single' % self.getType(), media, single = True) |
||||
|
} |
||||
|
|
||||
|
def single(self, media, profile = None, quality_order = None, search_protocols = None, manual = False): |
||||
|
db = get_db() |
||||
|
|
||||
|
related = fireEvent('library.related', media, single = True) |
||||
|
|
||||
|
# TODO search_protocols, profile, quality_order can be moved to a base method |
||||
|
# Find out search type |
||||
|
try: |
||||
|
if not search_protocols: |
||||
|
search_protocols = fireEvent('searcher.protocols', single = True) |
||||
|
except SearchSetupError: |
||||
|
return |
||||
|
|
||||
|
if not profile and related['show']['profile_id']: |
||||
|
profile = db.get('id', related['show']['profile_id']) |
||||
|
|
||||
|
if not quality_order: |
||||
|
quality_order = fireEvent('quality.order', single = True) |
||||
|
|
||||
|
# TODO: check episode status |
||||
|
# TODO: check air date |
||||
|
#if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): |
||||
|
# too_early_to_search.append(quality_type['quality']['identifier']) |
||||
|
# return |
||||
|
|
||||
|
ret = False |
||||
|
has_better_quality = None |
||||
|
found_releases = [] |
||||
|
too_early_to_search = [] |
||||
|
|
||||
|
releases = fireEvent('release.for_media', media['_id'], single = True) |
||||
|
query = fireEvent('library.query', media, condense = False, single = True) |
||||
|
|
||||
|
index = 0 |
||||
|
for q_identifier in profile.get('qualities'): |
||||
|
quality_custom = { |
||||
|
'quality': q_identifier, |
||||
|
'finish': profile['finish'][index], |
||||
|
'wait_for': profile['wait_for'][index], |
||||
|
'3d': profile['3d'][index] if profile.get('3d') else False |
||||
|
} |
||||
|
|
||||
|
has_better_quality = 0 |
||||
|
|
||||
|
# See if better quality is available |
||||
|
for release in releases: |
||||
|
if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']: |
||||
|
has_better_quality += 1 |
||||
|
|
||||
|
# Don't search for quality lower then already available. |
||||
|
if has_better_quality is 0: |
||||
|
|
||||
|
log.info('Searching for %s in %s', (query, q_identifier)) |
||||
|
quality = fireEvent('quality.single', identifier = q_identifier, single = True) |
||||
|
quality['custom'] = quality_custom |
||||
|
|
||||
|
results = fireEvent('searcher.search', search_protocols, media, quality, single = True) |
||||
|
if len(results) == 0: |
||||
|
log.debug('Nothing found for %s in %s', (query, q_identifier)) |
||||
|
|
||||
|
# Add them to this movie releases list |
||||
|
found_releases += fireEvent('release.create_from_search', results, media, quality, single = True) |
||||
|
|
||||
|
# Try find a valid result and download it |
||||
|
if fireEvent('release.try_download_result', results, media, quality, single = True): |
||||
|
ret = True |
||||
|
|
||||
|
# Remove releases that aren't found anymore |
||||
|
for release in releases: |
||||
|
if release.get('status') == 'available' and release.get('identifier') not in found_releases: |
||||
|
fireEvent('release.delete', release.get('_id'), single = True) |
||||
|
else: |
||||
|
log.info('Better quality (%s) already available or snatched for %s', (q_identifier, query)) |
||||
|
fireEvent('media.restatus', media['_id']) |
||||
|
break |
||||
|
|
||||
|
# Break if CP wants to shut down |
||||
|
if self.shuttingDown() or ret: |
||||
|
break |
||||
|
|
||||
|
if len(too_early_to_search) > 0: |
||||
|
log.info2('Too early to search for %s, %s', (too_early_to_search, query)) |
||||
|
|
||||
|
def correctRelease(self, release = None, media = None, quality = None, **kwargs): |
||||
|
if media.get('type') != 'show.episode': |
||||
|
return |
||||
|
|
||||
|
retention = Env.setting('retention', section = 'nzb') |
||||
|
|
||||
|
if release.get('seeders') is None and 0 < retention < release.get('age', 0): |
||||
|
log.info2('Wrong: Outside retention, age is %s, needs %s or lower: %s', (release['age'], retention, release['name'])) |
||||
|
return False |
||||
|
|
||||
|
# Check for required and ignored words |
||||
|
if not fireEvent('searcher.correct_words', release['name'], media, single = True): |
||||
|
return False |
||||
|
|
||||
|
# TODO Matching is quite costly, maybe we should be caching release matches somehow? (also look at caper optimizations) |
||||
|
match = fireEvent('matcher.match', release, media, quality, single = True) |
||||
|
if match: |
||||
|
return match.weight |
||||
|
|
||||
|
return False |
@ -0,0 +1,172 @@ |
|||||
|
from couchpotato import get_db, Env |
||||
|
from couchpotato.api import addApiView |
||||
|
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.media._base.searcher.base import SearcherBase |
||||
|
from couchpotato.core.media.movie.searcher import SearchSetupError |
||||
|
from couchpotato.core.media.show import ShowTypeBase |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'SeasonSearcher' |
||||
|
|
||||
|
|
||||
|
class SeasonSearcher(SearcherBase, ShowTypeBase): |
||||
|
type = 'season' |
||||
|
|
||||
|
in_progress = False |
||||
|
|
||||
|
def __init__(self): |
||||
|
super(SeasonSearcher, self).__init__() |
||||
|
|
||||
|
addEvent('%s.searcher.all' % self.getType(), self.searchAll) |
||||
|
addEvent('%s.searcher.single' % self.getType(), self.single) |
||||
|
addEvent('searcher.correct_release', self.correctRelease) |
||||
|
|
||||
|
addApiView('%s.searcher.full_search' % self.getType(), self.searchAllView, docs = { |
||||
|
'desc': 'Starts a full search for all wanted seasons', |
||||
|
}) |
||||
|
|
||||
|
def searchAllView(self, **kwargs): |
||||
|
fireEventAsync('%s.searcher.all' % self.getType(), manual = True) |
||||
|
|
||||
|
return { |
||||
|
'success': not self.in_progress |
||||
|
} |
||||
|
|
||||
|
def searchAll(self, manual = False): |
||||
|
pass |
||||
|
|
||||
|
def single(self, media, profile = None, quality_order = None, search_protocols = None, manual = False): |
||||
|
db = get_db() |
||||
|
|
||||
|
related = fireEvent('library.related', media, single = True) |
||||
|
|
||||
|
# TODO search_protocols, profile, quality_order can be moved to a base method |
||||
|
# Find out search type |
||||
|
try: |
||||
|
if not search_protocols: |
||||
|
search_protocols = fireEvent('searcher.protocols', single = True) |
||||
|
except SearchSetupError: |
||||
|
return |
||||
|
|
||||
|
if not profile and related['show']['profile_id']: |
||||
|
profile = db.get('id', related['show']['profile_id']) |
||||
|
|
||||
|
if not quality_order: |
||||
|
quality_order = fireEvent('quality.order', single = True) |
||||
|
|
||||
|
# Find 'active' episodes |
||||
|
episodes = related['episodes'] |
||||
|
episodes_active = [] |
||||
|
|
||||
|
for episode in episodes: |
||||
|
if episode.get('status') != 'active': |
||||
|
continue |
||||
|
|
||||
|
episodes_active.append(episode) |
||||
|
|
||||
|
if len(episodes_active) == len(episodes): |
||||
|
# All episodes are 'active', try and search for full season |
||||
|
if self.search(media, profile, quality_order, search_protocols): |
||||
|
# Success, end season search |
||||
|
return True |
||||
|
else: |
||||
|
log.info('Unable to find season pack, searching for individual episodes...') |
||||
|
|
||||
|
# Search for each episode individually |
||||
|
for episode in episodes_active: |
||||
|
fireEvent('show.episode.searcher.single', episode, profile, quality_order, search_protocols, manual) |
||||
|
|
||||
|
# TODO (testing) only grab one episode |
||||
|
return True |
||||
|
|
||||
|
return True |
||||
|
|
||||
|
def search(self, media, profile, quality_order, search_protocols): |
||||
|
# TODO: check episode status |
||||
|
# TODO: check air date |
||||
|
#if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): |
||||
|
# too_early_to_search.append(quality_type['quality']['identifier']) |
||||
|
# return |
||||
|
|
||||
|
ret = False |
||||
|
has_better_quality = None |
||||
|
found_releases = [] |
||||
|
too_early_to_search = [] |
||||
|
|
||||
|
releases = fireEvent('release.for_media', media['_id'], single = True) |
||||
|
query = fireEvent('library.query', media, condense = False, single = True) |
||||
|
|
||||
|
index = 0 |
||||
|
for q_identifier in profile.get('qualities'): |
||||
|
quality_custom = { |
||||
|
'quality': q_identifier, |
||||
|
'finish': profile['finish'][index], |
||||
|
'wait_for': profile['wait_for'][index], |
||||
|
'3d': profile['3d'][index] if profile.get('3d') else False |
||||
|
} |
||||
|
|
||||
|
has_better_quality = 0 |
||||
|
|
||||
|
# See if better quality is available |
||||
|
for release in releases: |
||||
|
if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']: |
||||
|
has_better_quality += 1 |
||||
|
|
||||
|
# Don't search for quality lower then already available. |
||||
|
if has_better_quality is 0: |
||||
|
|
||||
|
log.info('Searching for %s in %s', (query, q_identifier)) |
||||
|
quality = fireEvent('quality.single', identifier = q_identifier, single = True) |
||||
|
quality['custom'] = quality_custom |
||||
|
|
||||
|
results = fireEvent('searcher.search', search_protocols, media, quality, single = True) |
||||
|
if len(results) == 0: |
||||
|
log.debug('Nothing found for %s in %s', (query, q_identifier)) |
||||
|
|
||||
|
# Add them to this movie releases list |
||||
|
found_releases += fireEvent('release.create_from_search', results, media, quality, single = True) |
||||
|
|
||||
|
# Try find a valid result and download it |
||||
|
if fireEvent('release.try_download_result', results, media, quality, single = True): |
||||
|
ret = True |
||||
|
|
||||
|
# Remove releases that aren't found anymore |
||||
|
for release in releases: |
||||
|
if release.get('status') == 'available' and release.get('identifier') not in found_releases: |
||||
|
fireEvent('release.delete', release.get('_id'), single = True) |
||||
|
else: |
||||
|
log.info('Better quality (%s) already available or snatched for %s', (q_identifier, query)) |
||||
|
fireEvent('media.restatus', media['_id']) |
||||
|
break |
||||
|
|
||||
|
# Break if CP wants to shut down |
||||
|
if self.shuttingDown() or ret: |
||||
|
break |
||||
|
|
||||
|
if len(too_early_to_search) > 0: |
||||
|
log.info2('Too early to search for %s, %s', (too_early_to_search, query)) |
||||
|
|
||||
|
return len(found_releases) > 0 |
||||
|
|
||||
|
def correctRelease(self, release = None, media = None, quality = None, **kwargs): |
||||
|
if media.get('type') != 'show.season': |
||||
|
return |
||||
|
|
||||
|
retention = Env.setting('retention', section = 'nzb') |
||||
|
|
||||
|
if release.get('seeders') is None and 0 < retention < release.get('age', 0): |
||||
|
log.info2('Wrong: Outside retention, age is %s, needs %s or lower: %s', (release['age'], retention, release['name'])) |
||||
|
return False |
||||
|
|
||||
|
# Check for required and ignored words |
||||
|
if not fireEvent('searcher.correct_words', release['name'], media, single = True): |
||||
|
return False |
||||
|
|
||||
|
# TODO Matching is quite costly, maybe we should be caching release matches somehow? (also look at caper optimizations) |
||||
|
match = fireEvent('matcher.match', release, media, quality, single = True) |
||||
|
if match: |
||||
|
return match.weight |
||||
|
|
||||
|
return False |
@ -0,0 +1,88 @@ |
|||||
|
from couchpotato import get_db |
||||
|
from couchpotato.api import addApiView |
||||
|
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync |
||||
|
from couchpotato.core.helpers.variable import getTitle |
||||
|
from couchpotato.core.logger import CPLog |
||||
|
from couchpotato.core.media._base.searcher.base import SearcherBase |
||||
|
from couchpotato.core.media._base.searcher.main import SearchSetupError |
||||
|
from couchpotato.core.media.show import ShowTypeBase |
||||
|
|
||||
|
log = CPLog(__name__) |
||||
|
|
||||
|
autoload = 'ShowSearcher' |
||||
|
|
||||
|
|
||||
|
class ShowSearcher(SearcherBase, ShowTypeBase): |
||||
|
type = 'show' |
||||
|
|
||||
|
in_progress = False |
||||
|
|
||||
|
def __init__(self): |
||||
|
super(ShowSearcher, self).__init__() |
||||
|
|
||||
|
addEvent('%s.searcher.all' % self.getType(), self.searchAll) |
||||
|
addEvent('%s.searcher.single' % self.getType(), self.single) |
||||
|
addEvent('searcher.get_search_title', self.getSearchTitle) |
||||
|
|
||||
|
addApiView('%s.searcher.full_search' % self.getType(), self.searchAllView, docs = { |
||||
|
'desc': 'Starts a full search for all wanted episodes', |
||||
|
}) |
||||
|
|
||||
|
def searchAllView(self, **kwargs): |
||||
|
fireEventAsync('%s.searcher.all' % self.getType(), manual = True) |
||||
|
|
||||
|
return { |
||||
|
'success': not self.in_progress |
||||
|
} |
||||
|
|
||||
|
def searchAll(self, manual = False): |
||||
|
pass |
||||
|
|
||||
|
def single(self, media, search_protocols = None, manual = False): |
||||
|
# Find out search type |
||||
|
try: |
||||
|
if not search_protocols: |
||||
|
search_protocols = fireEvent('searcher.protocols', single = True) |
||||
|
except SearchSetupError: |
||||
|
return |
||||
|
|
||||
|
if not media['profile_id'] or media['status'] == 'done': |
||||
|
log.debug('Show doesn\'t have a profile or already done, assuming in manage tab.') |
||||
|
return |
||||
|
|
||||
|
show_title = fireEvent('media.search_query', media, condense = False, single = True) |
||||
|
|
||||
|
fireEvent('notify.frontend', type = 'show.searcher.started.%s' % media['_id'], data = True, message = 'Searching for "%s"' % show_title) |
||||
|
|
||||
|
show_tree = fireEvent('library.tree', media, single = True) |
||||
|
|
||||
|
db = get_db() |
||||
|
|
||||
|
profile = db.get('id', media['profile_id']) |
||||
|
quality_order = fireEvent('quality.order', single = True) |
||||
|
|
||||
|
for season in show_tree.get('seasons', []): |
||||
|
if not season.get('info'): |
||||
|
continue |
||||
|
|
||||
|
# Skip specials (and seasons missing 'number') for now |
||||
|
# TODO: set status for specials to skipped by default |
||||
|
if not season['info'].get('number'): |
||||
|
continue |
||||
|
|
||||
|
# Check if full season can be downloaded |
||||
|
fireEvent('show.season.searcher.single', season, profile, quality_order, search_protocols, manual) |
||||
|
|
||||
|
# TODO (testing) only snatch one season |
||||
|
return |
||||
|
|
||||
|
fireEvent('notify.frontend', type = 'show.searcher.ended.%s' % media['_id'], data = True) |
||||
|
|
||||
|
def getSearchTitle(self, media): |
||||
|
if media.get('type') != 'show': |
||||
|
related = fireEvent('library.related', media, single = True) |
||||
|
show = related['show'] |
||||
|
else: |
||||
|
show = media |
||||
|
|
||||
|
return getTitle(show) |
@ -1,137 +0,0 @@ |
|||||
from couchpotato import get_db |
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync |
|
||||
from couchpotato.core.logger import CPLog |
|
||||
from couchpotato.core.helpers.variable import tryInt |
|
||||
from couchpotato.core.media import MediaBase |
|
||||
|
|
||||
|
|
||||
log = CPLog(__name__) |
|
||||
|
|
||||
autoload = 'Season' |
|
||||
|
|
||||
|
|
||||
class Season(MediaBase): |
|
||||
|
|
||||
def __init__(self): |
|
||||
addEvent('media.search_query', self.query) |
|
||||
addEvent('media.identifier', self.identifier) |
|
||||
|
|
||||
addEvent('show.season.add', self.add) |
|
||||
addEvent('show.season.update_info', self.updateInfo) |
|
||||
|
|
||||
def add(self, parent_id, info = None, update_after = True): |
|
||||
if not info: info = {} |
|
||||
|
|
||||
identifiers = info.get('identifiers') |
|
||||
try: del info['identifiers'] |
|
||||
except: pass |
|
||||
try: del info['episodes'] |
|
||||
except: pass |
|
||||
|
|
||||
# Add Season |
|
||||
season_info = { |
|
||||
'_t': 'media', |
|
||||
'type': 'season', |
|
||||
'identifiers': identifiers, |
|
||||
'parent_id': parent_id, |
|
||||
'info': info, # Returned dict by providers |
|
||||
} |
|
||||
|
|
||||
# Check if season already exists |
|
||||
existing_season = fireEvent('media.with_identifiers', identifiers, with_doc = True, single = True) |
|
||||
|
|
||||
db = get_db() |
|
||||
|
|
||||
if existing_season: |
|
||||
s = existing_season['doc'] |
|
||||
s.update(season_info) |
|
||||
season = db.update(s) |
|
||||
else: |
|
||||
season = db.insert(season_info) |
|
||||
|
|
||||
# Update library info |
|
||||
if update_after is not False: |
|
||||
handle = fireEventAsync if update_after is 'async' else fireEvent |
|
||||
handle('show.season.update_info', season.get('_id'), info = info, single = True) |
|
||||
|
|
||||
return season |
|
||||
|
|
||||
def updateInfo(self, media_id = None, info = None, force = False): |
|
||||
if not info: info = {} |
|
||||
|
|
||||
if self.shuttingDown(): |
|
||||
return |
|
||||
|
|
||||
db = get_db() |
|
||||
|
|
||||
season = db.get('id', media_id) |
|
||||
|
|
||||
# Get new info |
|
||||
if not info: |
|
||||
info = fireEvent('season.info', season.get('identifiers'), merge = True) |
|
||||
|
|
||||
# Update/create media |
|
||||
if force: |
|
||||
|
|
||||
season['identifiers'].update(info['identifiers']) |
|
||||
if 'identifiers' in info: |
|
||||
del info['identifiers'] |
|
||||
|
|
||||
season.update({'info': info}) |
|
||||
s = db.update(season) |
|
||||
season.update(s) |
|
||||
|
|
||||
# Get images |
|
||||
image_urls = info.get('images', []) |
|
||||
existing_files = season.get('files', {}) |
|
||||
self.getPoster(image_urls, existing_files) |
|
||||
|
|
||||
return season |
|
||||
|
|
||||
def query(self, library, first = True, condense = True, include_identifier = True, **kwargs): |
|
||||
if library is list or library.get('type') != 'season': |
|
||||
return |
|
||||
|
|
||||
# Get the titles of the show |
|
||||
if not library.get('related_libraries', {}).get('show', []): |
|
||||
log.warning('Invalid library, unable to determine title.') |
|
||||
return |
|
||||
|
|
||||
titles = fireEvent( |
|
||||
'media._search_query', |
|
||||
library['related_libraries']['show'][0], |
|
||||
first=False, |
|
||||
condense=condense, |
|
||||
|
|
||||
single=True |
|
||||
) |
|
||||
|
|
||||
# Add season map_names if they exist |
|
||||
if 'map_names' in library['info']: |
|
||||
season_names = library['info']['map_names'].get(str(library['season_number']), {}) |
|
||||
|
|
||||
# Add titles from all locations |
|
||||
# TODO only add name maps from a specific location |
|
||||
for location, names in season_names.items(): |
|
||||
titles += [name for name in names if name and name not in titles] |
|
||||
|
|
||||
|
|
||||
identifier = fireEvent('media.identifier', library, single = True) |
|
||||
|
|
||||
# Add season identifier to titles |
|
||||
if include_identifier and identifier.get('season') is not None: |
|
||||
titles = [title + (' S%02d' % identifier['season']) for title in titles] |
|
||||
|
|
||||
|
|
||||
if first: |
|
||||
return titles[0] if titles else None |
|
||||
|
|
||||
return titles |
|
||||
|
|
||||
def identifier(self, library): |
|
||||
if library.get('type') != 'season': |
|
||||
return |
|
||||
|
|
||||
return { |
|
||||
'season': tryInt(library['season_number'], None) |
|
||||
} |
|
Loading…
Reference in new issue