Browse Source

Merge branch 'refs/heads/develop' into desktop

tags/build/2.2.1
Ruud 12 years ago
parent
commit
46783028b1
  1. 3
      couchpotato/api.py
  2. 10
      couchpotato/core/helpers/encoding.py
  3. 26
      couchpotato/core/media/movie/_base/static/movie.actions.js
  4. 18
      couchpotato/core/media/movie/_base/static/movie.js
  5. 7
      couchpotato/core/notifications/core/main.py
  6. 11
      couchpotato/core/plugins/dashboard/main.py
  7. 2
      couchpotato/core/plugins/suggestion/main.py
  8. 93
      couchpotato/core/plugins/suggestion/static/suggest.js
  9. 2
      couchpotato/core/plugins/userscript/static/userscript.js
  10. 12
      couchpotato/core/plugins/wizard/static/wizard.js
  11. 6
      couchpotato/core/providers/nzb/binsearch/main.py
  12. 40
      couchpotato/core/providers/nzb/ftdworld/__init__.py
  13. 83
      couchpotato/core/providers/nzb/ftdworld/main.py
  14. 1
      couchpotato/core/providers/nzb/newznab/__init__.py

3
couchpotato/api.py

@ -44,12 +44,13 @@ class NonBlockHandler(RequestHandler):
def onNewMessage(self, response):
if self.request.connection.stream.closed():
self.on_connection_close()
return
try:
self.finish(response)
except:
log.error('Failed doing nonblock request: %s', (traceback.format_exc()))
log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc()))
try: self.finish({'success': False, 'error': 'Failed returning results'})
except: pass

10
couchpotato/core/helpers/encoding.py

@ -38,8 +38,14 @@ def toUnicode(original, *args):
return toUnicode(ascii_text)
def ss(original, *args):
from couchpotato.environment import Env
return toUnicode(original, *args).encode(Env.get('encoding'))
u_original = toUnicode(original, *args)
try:
from couchpotato.environment import Env
return u_original.encode(Env.get('encoding'))
except Exception, e:
log.debug('Failed ss encoding char, force UTF8: %s', e)
return u_original.encode('UTF-8')
def ek(original, *args):
if isinstance(original, (str, unicode)):

26
couchpotato/core/media/movie/_base/static/movie.actions.js

@ -18,11 +18,13 @@ var MovieAction = new Class({
create: function(){},
disable: function(){
this.el.addClass('disable')
if(this.el)
this.el.addClass('disable')
},
enable: function(){
this.el.removeClass('disable')
if(this.el)
this.el.removeClass('disable')
},
getTitle: function(){
@ -252,10 +254,10 @@ MA.Release = new Class({
});
if(self.last_release)
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
self.release_container.getElements('#release_'+self.last_release.id).addClass('last_release');
if(self.next_release)
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
self.release_container.getElements('#release_'+self.next_release.id).addClass('next_release');
if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){
@ -365,21 +367,25 @@ MA.Release = new Class({
var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon2');
icon.addClass('icon spinner').removeClass('download');
if(icon)
icon.addClass('icon spinner').removeClass('download');
Api.request('release.download', {
'data': {
'id': release.id
},
'onComplete': function(json){
icon.removeClass('icon spinner');
if(icon)
icon.removeClass('icon spinner');
if(json.success){
icon.addClass('completed');
if(icon)
icon.addClass('completed');
release_el.getElement('.release_status').set('text', 'snatched');
}
else
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
if(icon)
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
}
});
},
@ -393,11 +399,11 @@ MA.Release = new Class({
},
'onComplete': function(){
var el = release.el;
if(el.hasClass('failed') || el.hasClass('ignored')){
if(el && (el.hasClass('failed') || el.hasClass('ignored'))){
el.removeClass('failed').removeClass('ignored');
el.getElement('.release_status').set('text', 'available');
}
else {
else if(el) {
el.addClass('ignored');
el.getElement('.release_status').set('text', 'ignored');
}

18
couchpotato/core/media/movie/_base/static/movie.js

@ -181,18 +181,18 @@ var Movie = new Class({
// Add releases
if(self.data.releases)
self.data.releases.each(function(release){
var q = self.quality.getElement('.q_id'+ release.quality_id),
status = Status.get(release.status_id);
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
var q = self.addQuality(release.quality_id)
if (status && q && !q.hasClass(status.identifier)){
q.addClass(status.identifier);
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
}
});
Object.each(self.options.actions, function(action, key){
@ -256,7 +256,8 @@ var Movie = new Class({
self.el.removeEvents('outerClick')
setTimeout(function(){
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
if(self.el)
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
}, 600);
self.data_container.removeClass('hide_right');
@ -266,9 +267,10 @@ var Movie = new Class({
changeView: function(new_view){
var self = this;
self.el
.removeClass(self.view+'_view')
.addClass(new_view+'_view')
if(self.el)
self.el
.removeClass(self.view+'_view')
.addClass(new_view+'_view')
self.view = new_view;
},

7
couchpotato/core/notifications/core/main.py

@ -198,13 +198,16 @@ class CoreNotifier(Notification):
def removeListener(self, callback):
self.m_lock.acquire()
new_listeners = []
for list_tuple in self.listeners:
try:
listener, last_id = list_tuple
if listener == callback:
self.listeners.remove(list_tuple)
if listener != callback:
new_listeners.append(list_tuple)
except:
log.debug('Failed removing listener: %s', traceback.format_exc())
self.listeners = new_listeners
self.m_lock.release()
def cleanMessages(self):

11
couchpotato/core/plugins/dashboard/main.py

@ -4,9 +4,10 @@ from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import splitString, tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie, Library, LibraryTitle
from couchpotato.core.settings.model import Movie, Library, LibraryTitle, \
Release
from sqlalchemy.orm import joinedload_all
from sqlalchemy.sql.expression import asc
from sqlalchemy.sql.expression import asc, or_
import random as rndm
import time
@ -48,12 +49,14 @@ class Dashboard(Plugin):
limit = tryInt(splt[0])
# Get all active movies
active_status = fireEvent('status.get', ['active'], single = True)
active_status, ignored_status = fireEvent('status.get', ['active', 'ignored'], single = True)
q = db.query(Movie) \
.join(Library) \
.outerjoin(Movie.releases) \
.filter(Movie.status_id == active_status.get('id')) \
.with_entities(Movie.id, Movie.profile_id, Library.info, Library.year) \
.group_by(Movie.id)
.group_by(Movie.id) \
.filter(or_(Release.id == None, Release.status_id == ignored_status.get('id')))
if not random:
q = q.join(LibraryTitle) \

2
couchpotato/core/plugins/suggestion/main.py

@ -102,6 +102,6 @@ class Suggestion(Plugin):
if suggestions:
new_suggestions.extend(suggestions)
self.setCache('suggestion_cached', new_suggestions, timeout = 6048000)
self.setCache('suggestion_cached', new_suggestions, timeout = 3024000)
return new_suggestions

93
couchpotato/core/plugins/suggestion/static/suggest.js

@ -58,54 +58,59 @@ var SuggestList = new Class({
var self = this;
if(!json) return;
if(!json || json.count == 0){
self.el.hide();
}
else {
Object.each(json.suggestions, function(movie){
var m = new Block.Search.Item(movie, {
'onAdded': function(){
self.afterAdded(m, movie)
}
});
m.data_container.grab(
new Element('div.actions').adopt(
new Element('a.add.icon2', {
'title': 'Add movie with your default quality',
'data-add': movie.imdb,
'events': {
'click': m.showOptions.bind(m)
}
}),
$(new MA.IMDB(m)),
$(new MA.Trailer(m, {
'height': 150
})),
new Element('a.delete.icon2', {
'title': 'Don\'t suggest this movie again',
'data-ignore': movie.imdb
}),
new Element('a.eye-open.icon2', {
'title': 'Seen it, like it, don\'t add',
'data-seen': movie.imdb
})
)
);
m.data_container.removeEvents('click');
// Add rating
m.info_container.adopt(
m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
'text': parseFloat(m.info.rating.imdb[0]),
'title': parseInt(m.info.rating.imdb[1]) + ' votes'
}) : null,
m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', {
'text': m.info.genres.slice(0, 3).join(', ')
}) : null
)
Object.each(json.suggestions, function(movie){
$(m).inject(self.el);
var m = new Block.Search.Item(movie, {
'onAdded': function(){
self.afterAdded(m, movie)
}
});
m.data_container.grab(
new Element('div.actions').adopt(
new Element('a.add.icon2', {
'title': 'Add movie with your default quality',
'data-add': movie.imdb,
'events': {
'click': m.showOptions.bind(m)
}
}),
$(new MA.IMDB(m)),
$(new MA.Trailer(m, {
'height': 150
})),
new Element('a.delete.icon2', {
'title': 'Don\'t suggest this movie again',
'data-ignore': movie.imdb
}),
new Element('a.eye-open.icon2', {
'title': 'Seen it, like it, don\'t add',
'data-seen': movie.imdb
})
)
);
m.data_container.removeEvents('click');
// Add rating
m.info_container.adopt(
m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
'text': parseFloat(m.info.rating.imdb[0]),
'title': parseInt(m.info.rating.imdb[1]) + ' votes'
}) : null,
m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', {
'text': m.info.genres.slice(0, 3).join(', ')
}) : null
)
$(m).inject(self.el);
});
}
self.fireEvent('loaded');

2
couchpotato/core/plugins/userscript/static/userscript.js

@ -96,7 +96,7 @@ var UserscriptSettingTab = new Class({
})
)
).setStyles({
'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')"
'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')"
});
});

12
couchpotato/core/plugins/wizard/static/wizard.js

@ -24,9 +24,10 @@ Page.Wizard = new Class({
'title': 'What download apps are you using?',
'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use the default Blackhole.'
},
'providers': {
'searcher': {
'label': 'Providers',
'title': 'Are you registered at any of these sites?',
'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.'
'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have more.'
},
'renamer': {
'title': 'Move & rename the movies after downloading?',
@ -38,7 +39,7 @@ Page.Wizard = new Class({
'<br />Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)',
'content': function(){
return App.createUserscriptButtons().setStyles({
'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')"
'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')"
})
}
},
@ -76,7 +77,7 @@ Page.Wizard = new Class({
)
}
},
groups: ['welcome', 'general', 'downloaders', 'searcher', 'providers', 'renamer', 'automation', 'finish'],
groups: ['welcome', 'general', 'downloaders', 'searcher', 'renamer', 'automation', 'finish'],
open: function(action, params){
var self = this;
@ -195,8 +196,7 @@ Page.Wizard = new Class({
self.el.getElement('.advanced_toggle').destroy();
// Hide retention
self.el.getElement('.tab_searcher').hide();
self.el.getElement('.t_searcher').hide();
self.el.getElement('.section_nzb').hide();
// Add pointer
new Element('.tab_wrapper').wraps(tabs);

6
couchpotato/core/providers/nzb/binsearch/main.py

@ -56,6 +56,10 @@ class BinSearch(NZBProvider):
info = row.find('span', attrs = {'class':'d'})
size_match = re.search('size:.(?P<size>[0-9\.]+.[GMB]+)', info.text)
age = 0
try: age = re.search('(?P<size>\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1]
except: pass
def extra_check(item):
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
total = tryInt(parts.group('total'))
@ -74,7 +78,7 @@ class BinSearch(NZBProvider):
results.append({
'id': nzb_id,
'name': title.text,
'age': tryInt(re.search('(?P<size>\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1]),
'age': tryInt(age),
'size': self.parseSize(size_match.group('size')),
'url': self.urls['download'] % nzb_id,
'detail_url': self.urls['detail'] % info.find('a')['href'],

40
couchpotato/core/providers/nzb/ftdworld/__init__.py

@ -1,40 +0,0 @@
from .main import FTDWorld
def start():
return FTDWorld()
config = [{
'name': 'ftdworld',
'groups': [
{
'tab': 'searcher',
'list': 'nzb_providers',
'name': 'FTDWorld',
'description': 'Free provider, less accurate. See <a href="http://ftdworld.net">FTDWorld</a>',
'wizard': True,
'options': [
{
'name': 'enabled',
'type': 'enabler',
},
{
'name': 'username',
'default': '',
},
{
'name': 'password',
'default': '',
'type': 'password',
},
{
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
'type': 'int',
'default': 0,
'description': 'Starting score for each release found via this provider.',
}
],
},
],
}]

83
couchpotato/core/providers/nzb/ftdworld/main.py

@ -1,83 +0,0 @@
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.nzb.base import NZBProvider
from couchpotato.environment import Env
import json
import traceback
log = CPLog(__name__)
class FTDWorld(NZBProvider):
urls = {
'search': 'http://ftdworld.net/api/index.php?%s',
'detail': 'http://ftdworld.net/spotinfo.php?id=%s',
'download': 'http://ftdworld.net/cgi-bin/nzbdown.pl?fileID=%s',
'login': 'http://ftdworld.net/api/login.php',
'login_check': 'http://ftdworld.net/api/login.php',
}
http_time_between_calls = 3 #seconds
cat_ids = [
([4, 11], ['dvdr']),
([1], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr', 'brrip']),
([7, 10, 13, 14], ['bd50', '720p', '1080p']),
]
cat_backup_id = 1
def _searchOnTitle(self, title, movie, quality, results):
q = '"%s" %s' % (title, movie['library']['year'])
params = tryUrlencode({
'ctitle': q,
'customQuery': 'usr',
'cage': Env.setting('retention', 'nzb'),
'csizemin': quality.get('size_min'),
'csizemax': quality.get('size_max'),
'ccategory': 14,
'ctype': ','.join([str(x) for x in self.getCatId(quality['identifier'])]),
})
data = self.getJsonData(self.urls['search'] % params, opener = self.login_opener)
if data:
try:
if data.get('numRes') == 0:
return
for item in data.get('data'):
nzb_id = tryInt(item.get('id'))
results.append({
'id': nzb_id,
'name': toUnicode(item.get('Title')),
'age': self.calculateAge(tryInt(item.get('Created'))),
'size': item.get('Size', 0),
'url': self.urls['download'] % nzb_id,
'detail_url': self.urls['detail'] % nzb_id,
'score': (tryInt(item.get('webPlus', 0)) - tryInt(item.get('webMin', 0))) * 3,
})
except:
log.error('Failed to parse HTML response from FTDWorld: %s', traceback.format_exc())
def getLoginParams(self):
return tryUrlencode({
'userlogin': self.conf('username'),
'passlogin': self.conf('password'),
'submit': 'Log In',
})
def loginSuccess(self, output):
try:
return json.loads(output).get('goodToGo', False)
except:
return False
loginCheckSuccess = loginSuccess

1
couchpotato/core/providers/nzb/newznab/__init__.py

@ -20,6 +20,7 @@ config = [{
{
'name': 'enabled',
'type': 'enabler',
'default': True,
},
{
'name': 'use',

Loading…
Cancel
Save