Compare commits

...
16 Commits
16 changed files with 111 additions and 203 deletions
+2 -1
View File
@@ -44,12 +44,13 @@ class NonBlockHandler(RequestHandler):
def onNewMessage(self, response): def onNewMessage(self, response):
if self.request.connection.stream.closed(): if self.request.connection.stream.closed():
self.on_connection_close()
return return
try: try:
self.finish(response) self.finish(response)
except: 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'}) try: self.finish({'success': False, 'error': 'Failed returning results'})
except: pass except: pass
+8 -2
View File
@@ -38,8 +38,14 @@ def toUnicode(original, *args):
return toUnicode(ascii_text) return toUnicode(ascii_text)
def ss(original, *args): 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): def ek(original, *args):
if isinstance(original, (str, unicode)): if isinstance(original, (str, unicode)):
@@ -18,11 +18,13 @@ var MovieAction = new Class({
create: function(){}, create: function(){},
disable: function(){ disable: function(){
this.el.addClass('disable') if(this.el)
this.el.addClass('disable')
}, },
enable: function(){ enable: function(){
this.el.removeClass('disable') if(this.el)
this.el.removeClass('disable')
}, },
getTitle: function(){ getTitle: function(){
@@ -252,10 +254,10 @@ MA.Release = new Class({
}); });
if(self.last_release) 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) 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)){ 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), var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon2'); icon = release_el.getElement('.download.icon2');
icon.addClass('icon spinner').removeClass('download'); if(icon)
icon.addClass('icon spinner').removeClass('download');
Api.request('release.download', { Api.request('release.download', {
'data': { 'data': {
'id': release.id 'id': release.id
}, },
'onComplete': function(json){ 'onComplete': function(json){
icon.removeClass('icon spinner'); if(icon)
icon.removeClass('icon spinner');
if(json.success){ if(json.success){
icon.addClass('completed'); if(icon)
icon.addClass('completed');
release_el.getElement('.release_status').set('text', 'snatched'); release_el.getElement('.release_status').set('text', 'snatched');
} }
else 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(){ 'onComplete': function(){
var el = release.el; 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.removeClass('failed').removeClass('ignored');
el.getElement('.release_status').set('text', 'available'); el.getElement('.release_status').set('text', 'available');
} }
else { else if(el) {
el.addClass('ignored'); el.addClass('ignored');
el.getElement('.release_status').set('text', 'ignored'); el.getElement('.release_status').set('text', 'ignored');
} }
@@ -256,7 +256,8 @@ var Movie = new Class({
self.el.removeEvents('outerClick') self.el.removeEvents('outerClick')
setTimeout(function(){ 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); }, 600);
self.data_container.removeClass('hide_right'); self.data_container.removeClass('hide_right');
@@ -266,9 +267,10 @@ var Movie = new Class({
changeView: function(new_view){ changeView: function(new_view){
var self = this; var self = this;
self.el if(self.el)
.removeClass(self.view+'_view') self.el
.addClass(new_view+'_view') .removeClass(self.view+'_view')
.addClass(new_view+'_view')
self.view = new_view; self.view = new_view;
}, },
+5 -2
View File
@@ -198,13 +198,16 @@ class CoreNotifier(Notification):
def removeListener(self, callback): def removeListener(self, callback):
self.m_lock.acquire() self.m_lock.acquire()
new_listeners = []
for list_tuple in self.listeners: for list_tuple in self.listeners:
try: try:
listener, last_id = list_tuple listener, last_id = list_tuple
if listener == callback: if listener != callback:
self.listeners.remove(list_tuple) new_listeners.append(list_tuple)
except: except:
log.debug('Failed removing listener: %s', traceback.format_exc()) log.debug('Failed removing listener: %s', traceback.format_exc())
self.listeners = new_listeners
self.m_lock.release() self.m_lock.release()
def cleanMessages(self): def cleanMessages(self):
+7 -4
View File
@@ -4,9 +4,10 @@ from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import splitString, tryInt from couchpotato.core.helpers.variable import splitString, tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie, Library, LibraryTitle from couchpotato.core.settings.model import Movie, Library, LibraryTitle, \
Release
from sqlalchemy.orm import joinedload_all from sqlalchemy.orm import joinedload_all
from sqlalchemy.sql.expression import asc from sqlalchemy.sql.expression import asc, or_
import random as rndm import random as rndm
import time import time
@@ -48,12 +49,14 @@ class Dashboard(Plugin):
limit = tryInt(splt[0]) limit = tryInt(splt[0])
# Get all active movies # 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) \ q = db.query(Movie) \
.join(Library) \ .join(Library) \
.outerjoin(Movie.releases) \
.filter(Movie.status_id == active_status.get('id')) \ .filter(Movie.status_id == active_status.get('id')) \
.with_entities(Movie.id, Movie.profile_id, Library.info, Library.year) \ .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: if not random:
q = q.join(LibraryTitle) \ q = q.join(LibraryTitle) \
+1 -1
View File
@@ -102,6 +102,6 @@ class Suggestion(Plugin):
if suggestions: if suggestions:
new_suggestions.extend(suggestions) new_suggestions.extend(suggestions)
self.setCache('suggestion_cached', new_suggestions, timeout = 6048000) self.setCache('suggestion_cached', new_suggestions, timeout = 3024000)
return new_suggestions return new_suggestions
@@ -58,54 +58,59 @@ var SuggestList = new Class({
var self = this; var self = this;
if(!json) return; if(!json || json.count == 0){
self.el.hide();
}
else {
Object.each(json.suggestions, function(movie){ Object.each(json.suggestions, function(movie){
var m = new Block.Search.Item(movie, { var m = new Block.Search.Item(movie, {
'onAdded': function(){ 'onAdded': function(){
self.afterAdded(m, movie) self.afterAdded(m, movie)
} }
}); });
m.data_container.grab( m.data_container.grab(
new Element('div.actions').adopt( new Element('div.actions').adopt(
new Element('a.add.icon2', { new Element('a.add.icon2', {
'title': 'Add movie with your default quality', 'title': 'Add movie with your default quality',
'data-add': movie.imdb, 'data-add': movie.imdb,
'events': { 'events': {
'click': m.showOptions.bind(m) 'click': m.showOptions.bind(m)
} }
}), }),
$(new MA.IMDB(m)), $(new MA.IMDB(m)),
$(new MA.Trailer(m, { $(new MA.Trailer(m, {
'height': 150 'height': 150
})), })),
new Element('a.delete.icon2', { new Element('a.delete.icon2', {
'title': 'Don\'t suggest this movie again', 'title': 'Don\'t suggest this movie again',
'data-ignore': movie.imdb 'data-ignore': movie.imdb
}), }),
new Element('a.eye-open.icon2', { new Element('a.eye-open.icon2', {
'title': 'Seen it, like it, don\'t add', 'title': 'Seen it, like it, don\'t add',
'data-seen': movie.imdb '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.data_container.removeEvents('click');
// Add rating $(m).inject(self.el);
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'); self.fireEvent('loaded');
@@ -96,7 +96,7 @@ var UserscriptSettingTab = new Class({
}) })
) )
).setStyles({ ).setStyles({
'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')" 'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')"
}); });
}); });
@@ -24,9 +24,10 @@ Page.Wizard = new Class({
'title': 'What download apps are you using?', '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.' '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?', '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': { 'renamer': {
'title': 'Move & rename the movies after downloading?', '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 ;)', '<br />Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)',
'content': function(){ 'content': function(){
return App.createUserscriptButtons().setStyles({ 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){ open: function(action, params){
var self = this; var self = this;
@@ -195,8 +196,7 @@ Page.Wizard = new Class({
self.el.getElement('.advanced_toggle').destroy(); self.el.getElement('.advanced_toggle').destroy();
// Hide retention // Hide retention
self.el.getElement('.tab_searcher').hide(); self.el.getElement('.section_nzb').hide();
self.el.getElement('.t_searcher').hide();
// Add pointer // Add pointer
new Element('.tab_wrapper').wraps(tabs); new Element('.tab_wrapper').wraps(tabs);
@@ -56,6 +56,10 @@ class BinSearch(NZBProvider):
info = row.find('span', attrs = {'class':'d'}) info = row.find('span', attrs = {'class':'d'})
size_match = re.search('size:.(?P<size>[0-9\.]+.[GMB]+)', info.text) 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): def extra_check(item):
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text) parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
total = tryInt(parts.group('total')) total = tryInt(parts.group('total'))
@@ -74,7 +78,7 @@ class BinSearch(NZBProvider):
results.append({ results.append({
'id': nzb_id, 'id': nzb_id,
'name': title.text, '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')), 'size': self.parseSize(size_match.group('size')),
'url': self.urls['download'] % nzb_id, 'url': self.urls['download'] % nzb_id,
'detail_url': self.urls['detail'] % info.find('a')['href'], 'detail_url': self.urls['detail'] % info.find('a')['href'],
@@ -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.',
}
],
},
],
}]
@@ -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
@@ -20,6 +20,7 @@ config = [{
{ {
'name': 'enabled', 'name': 'enabled',
'type': 'enabler', 'type': 'enabler',
'default': True,
}, },
{ {
'name': 'use', 'name': 'use',
+1 -1
View File
@@ -1,5 +1,5 @@
#define MyAppName "CouchPotato" #define MyAppName "CouchPotato"
#define MyAppVer "2.2.0" #define MyAppVer "2.2.1"
[Setup] [Setup]
AppName={#MyAppName} AppName={#MyAppName}
+1 -1
View File
@@ -1,2 +1,2 @@
VERSION = '2.2.0' VERSION = '2.2.1'
BRANCH = 'desktop' BRANCH = 'desktop'