Merge branch 'refs/heads/develop'
This commit is contained in:
@@ -15,6 +15,7 @@ from sqlalchemy.orm import scoped_session
|
|||||||
from sqlalchemy.orm.session import sessionmaker
|
from sqlalchemy.orm.session import sessionmaker
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -73,5 +74,10 @@ def getApiKey():
|
|||||||
def page_not_found(error):
|
def page_not_found(error):
|
||||||
index_url = url_for('web.index')
|
index_url = url_for('web.index')
|
||||||
url = getattr(request, 'path')[len(index_url):]
|
url = getattr(request, 'path')[len(index_url):]
|
||||||
return redirect(index_url + '#' + url)
|
|
||||||
|
if url[:3] != 'api':
|
||||||
|
return redirect(index_url + '#' + url)
|
||||||
|
else:
|
||||||
|
time.sleep(0.1)
|
||||||
|
return 'Wrong API key used', 404
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from flask.blueprints import Blueprint
|
from flask.blueprints import Blueprint
|
||||||
from flask.helpers import url_for
|
from flask.helpers import url_for
|
||||||
from flask.templating import render_template
|
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect
|
||||||
|
|
||||||
api = Blueprint('api', __name__)
|
api = Blueprint('api', __name__)
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class Core(Plugin):
|
|||||||
return '%s:%d%s' % (cleanHost(host).rstrip('/'), int(port), '/' + Env.setting('url_base').lstrip('/') if Env.setting('url_base') else '')
|
return '%s:%d%s' % (cleanHost(host).rstrip('/'), int(port), '/' + Env.setting('url_base').lstrip('/') if Env.setting('url_base') else '')
|
||||||
|
|
||||||
def createApiUrl(self):
|
def createApiUrl(self):
|
||||||
return '%s/%s' % (self.createBaseUrl(), Env.setting('api_key'))
|
return '%s/api/%s' % (self.createBaseUrl(), Env.setting('api_key'))
|
||||||
|
|
||||||
def version(self):
|
def version(self):
|
||||||
ver = fireEvent('updater.info', single = True)
|
ver = fireEvent('updater.info', single = True)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def getParam(attr, default = None):
|
|||||||
try:
|
try:
|
||||||
return toUnicode(unquote_plus(getattr(flask.request, 'args').get(attr, default))).encode('utf-8')
|
return toUnicode(unquote_plus(getattr(flask.request, 'args').get(attr, default))).encode('utf-8')
|
||||||
except:
|
except:
|
||||||
return None
|
return default
|
||||||
|
|
||||||
def padded_jsonify(callback, *args, **kwargs):
|
def padded_jsonify(callback, *args, **kwargs):
|
||||||
content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')'
|
content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')'
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class Loader(object):
|
|||||||
pass
|
pass
|
||||||
# todo:: this needs to be more descriptive.
|
# todo:: this needs to be more descriptive.
|
||||||
log.error('Import error, remove the empty folder: %s' % plugin.get('module'))
|
log.error('Import error, remove the empty folder: %s' % plugin.get('module'))
|
||||||
|
log.debug('Can\'t import %s: %s' % (module_name, traceback.format_exc()))
|
||||||
except:
|
except:
|
||||||
log.error('Can\'t import %s: %s' % (module_name, traceback.format_exc()))
|
log.error('Can\'t import %s: %s' % (module_name, traceback.format_exc()))
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class CoreNotifier(Notification):
|
|||||||
addApiView('notification.markread', self.markAsRead, docs = {
|
addApiView('notification.markread', self.markAsRead, docs = {
|
||||||
'desc': 'Mark notifications as read',
|
'desc': 'Mark notifications as read',
|
||||||
'params': {
|
'params': {
|
||||||
'id': {'desc': 'Notification id you want to mark as read.', 'type': 'int (comma separated)'},
|
'ids': {'desc': 'Notification id you want to mark as read.', 'type': 'int (comma separated)'},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,21 @@ config = [{
|
|||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Also send message when movie is snatched.',
|
'description': 'Also send message when movie is snatched.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'hostname',
|
||||||
|
'description': 'Notify growl over network. Needs restart.',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'port',
|
||||||
|
'type': 'int',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'password',
|
||||||
|
'type': 'password',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -23,11 +23,19 @@ class Growl(Notification):
|
|||||||
def register(self):
|
def register(self):
|
||||||
if self.registered: return
|
if self.registered: return
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
hostname = self.conf('hostname')
|
||||||
|
password = self.conf('password')
|
||||||
|
port = self.conf('port')
|
||||||
|
|
||||||
self.growl = notifier.GrowlNotifier(
|
self.growl = notifier.GrowlNotifier(
|
||||||
applicationName = 'CouchPotato',
|
applicationName = 'CouchPotato',
|
||||||
notifications = ["Updates"],
|
notifications = ["Updates"],
|
||||||
defaultNotifications = ["Updates"],
|
defaultNotifications = ["Updates"],
|
||||||
applicationIcon = '%s/static/images/couch.png' % fireEvent('app.api_url', single = True),
|
applicationIcon = '%s/static/images/couch.png' % fireEvent('app.api_url', single = True),
|
||||||
|
hostname = hostname if hostname else 'localhost',
|
||||||
|
password = password if password else None,
|
||||||
|
port = port if port else 23053
|
||||||
)
|
)
|
||||||
self.growl.register()
|
self.growl.register()
|
||||||
self.registered = True
|
self.registered = True
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class Plugin(object):
|
|||||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__class__.__name__)
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__class__.__name__)
|
||||||
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||||
|
|
||||||
path = '%s/static/%s/' % (Env.setting('api_key'), class_name)
|
path = 'api/%s/static/%s/' % (Env.setting('api_key'), class_name)
|
||||||
addView(path + '<path:filename>', self.showStatic, static = True)
|
addView(path + '<path:filename>', self.showStatic, static = True)
|
||||||
|
|
||||||
if add_to_head:
|
if add_to_head:
|
||||||
|
|||||||
@@ -21,13 +21,24 @@ class Logging(Plugin):
|
|||||||
'success': True,
|
'success': True,
|
||||||
'log': string, //Log file
|
'log': string, //Log file
|
||||||
'total': int, //Total log files available
|
'total': int, //Total log files available
|
||||||
|
}"""}
|
||||||
|
})
|
||||||
|
addApiView('logging.partial', self.partial, docs = {
|
||||||
|
'desc': 'Get a partial log',
|
||||||
|
'params': {
|
||||||
|
'type': {'desc': 'Type of log', 'type': 'string: all(default), error, info, debug'},
|
||||||
|
'lines': {'desc': 'Number of lines. Last to first. Default 30'},
|
||||||
|
},
|
||||||
|
'return': {'type': 'object', 'example': """{
|
||||||
|
'success': True,
|
||||||
|
'log': string, //Log file
|
||||||
}"""}
|
}"""}
|
||||||
})
|
})
|
||||||
addApiView('logging.clear', self.clear, docs = {
|
addApiView('logging.clear', self.clear, docs = {
|
||||||
'desc': 'Remove all the log files'
|
'desc': 'Remove all the log files'
|
||||||
})
|
})
|
||||||
addApiView('logging.log', self.log, docs = {
|
addApiView('logging.log', self.log, docs = {
|
||||||
'desc': 'Get the full log file by number',
|
'desc': 'Log errors',
|
||||||
'params': {
|
'params': {
|
||||||
'type': {'desc': 'Type of logging, default "error"'},
|
'type': {'desc': 'Type of logging, default "error"'},
|
||||||
'**kwargs': {'type':'object', 'desc': 'All other params will be printed in the log string.'},
|
'**kwargs': {'type':'object', 'desc': 'All other params will be printed in the log string.'},
|
||||||
@@ -64,6 +75,46 @@ class Logging(Plugin):
|
|||||||
'total': total,
|
'total': total,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def partial(self):
|
||||||
|
|
||||||
|
log_type = getParam('type', 'all')
|
||||||
|
total_lines = getParam('lines', 30)
|
||||||
|
|
||||||
|
log_lines = []
|
||||||
|
|
||||||
|
for x in range(0, 50):
|
||||||
|
|
||||||
|
path = '%s%s' % (Env.get('log_path'), '.%s' % x if x > 0 else '')
|
||||||
|
|
||||||
|
# Check see if the log exists
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
break
|
||||||
|
|
||||||
|
reversed_lines = []
|
||||||
|
f = open(path, 'r')
|
||||||
|
reversed_lines = f.read().split('[0m\n')
|
||||||
|
reversed_lines.reverse()
|
||||||
|
|
||||||
|
brk = False
|
||||||
|
for line in reversed_lines:
|
||||||
|
|
||||||
|
#print '%s ' % log_type in line.lower()
|
||||||
|
if log_type == 'all' or '%s ' % log_type.upper() in line:
|
||||||
|
log_lines.append(line)
|
||||||
|
|
||||||
|
if len(log_lines) >= total_lines:
|
||||||
|
brk = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if brk:
|
||||||
|
break
|
||||||
|
|
||||||
|
log_lines.reverse()
|
||||||
|
return jsonified({
|
||||||
|
'success': True,
|
||||||
|
'log': '[0m\n'.join(log_lines),
|
||||||
|
})
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
|
||||||
for x in range(0, 50):
|
for x in range(0, 50):
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \
|
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||||
simplifyString
|
|
||||||
from couchpotato.core.helpers.request import getParams, jsonified, getParam
|
from couchpotato.core.helpers.request import getParams, jsonified, getParam
|
||||||
from couchpotato.core.helpers.variable import getImdb
|
from couchpotato.core.helpers.variable import getImdb
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -79,6 +78,7 @@ class MoviePlugin(Plugin):
|
|||||||
'desc': 'Delete a movie from the wanted list',
|
'desc': 'Delete a movie from the wanted list',
|
||||||
'params': {
|
'params': {
|
||||||
'id': {'desc': 'Movie ID(s) you want to delete.', 'type': 'int (comma separated)'},
|
'id': {'desc': 'Movie ID(s) you want to delete.', 'type': 'int (comma separated)'},
|
||||||
|
'delete_from': {'desc': 'Delete movie from this page', 'type': 'string: all (default), wanted, manage'},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -358,20 +358,45 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
ids = [x.strip() for x in params.get('id').split(',')]
|
ids = [x.strip() for x in params.get('id').split(',')]
|
||||||
for movie_id in ids:
|
for movie_id in ids:
|
||||||
self.delete(movie_id)
|
self.delete(movie_id, delete_from = params.get('delete_from', 'all'))
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
})
|
||||||
|
|
||||||
def delete(self, movie_id):
|
def delete(self, movie_id, delete_from = None):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
movie = db.query(Movie).filter_by(id = movie_id).first()
|
movie = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
if movie:
|
if movie:
|
||||||
db.delete(movie)
|
if delete_from == 'all':
|
||||||
db.commit()
|
db.delete(movie)
|
||||||
|
db.commit()
|
||||||
|
else:
|
||||||
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
|
||||||
|
total_releases = len(movie.releases)
|
||||||
|
total_deleted = 0
|
||||||
|
new_movie_status = None
|
||||||
|
for release in movie.releases:
|
||||||
|
if delete_from == 'wanted' and release.status_id != done_status.get('id'):
|
||||||
|
db.delete(release)
|
||||||
|
total_deleted += 1
|
||||||
|
new_movie_status = 'done'
|
||||||
|
elif delete_from == 'manage' and release.status_id == done_status.get('id'):
|
||||||
|
db.delete(release)
|
||||||
|
total_deleted += 1
|
||||||
|
new_movie_status = 'active'
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
if total_releases == total_deleted:
|
||||||
|
db.delete(movie)
|
||||||
|
db.commit()
|
||||||
|
elif new_movie_status:
|
||||||
|
new_status = fireEvent('status.get', new_movie_status, single = True)
|
||||||
|
movie.status_id = new_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -253,7 +253,8 @@ var MovieList = new Class({
|
|||||||
(e).preventDefault();
|
(e).preventDefault();
|
||||||
Api.request('movie.delete', {
|
Api.request('movie.delete', {
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(',')
|
'id': ids.join(','),
|
||||||
|
'delete_from': self.options.identifier
|
||||||
},
|
},
|
||||||
'onSuccess': function(){
|
'onSuccess': function(){
|
||||||
qObj.close();
|
qObj.close();
|
||||||
@@ -381,6 +382,8 @@ var MovieList = new Class({
|
|||||||
update: function(){
|
update: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
self.reset();
|
||||||
|
self.movie_list.empty();
|
||||||
self.getMovies();
|
self.getMovies();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ var Movie = new Class({
|
|||||||
|
|
||||||
action: {},
|
action: {},
|
||||||
|
|
||||||
initialize: function(self, options, data){
|
initialize: function(list, options, data){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.data = data;
|
self.data = data;
|
||||||
self.view = options.view || 'thumbs';
|
self.view = options.view || 'thumbs';
|
||||||
|
self.list = list;
|
||||||
|
|
||||||
self.profile = Quality.getProfile(data.profile_id) || {};
|
self.profile = Quality.getProfile(data.profile_id) || {};
|
||||||
self.parent(self, options);
|
self.parent(self, options);
|
||||||
@@ -35,10 +36,10 @@ var Movie = new Class({
|
|||||||
}).adopt(
|
}).adopt(
|
||||||
self.info_container = new Element('div.info').adopt(
|
self.info_container = new Element('div.info').adopt(
|
||||||
self.title = new Element('div.title', {
|
self.title = new Element('div.title', {
|
||||||
'text': self.getTitle()
|
'text': self.getTitle() || 'n/a'
|
||||||
}),
|
}),
|
||||||
self.year = new Element('div.year', {
|
self.year = new Element('div.year', {
|
||||||
'text': self.data.library.year || 'Unknown'
|
'text': self.data.library.year || 'n/a'
|
||||||
}),
|
}),
|
||||||
self.rating = new Element('div.rating.icon', {
|
self.rating = new Element('div.rating.icon', {
|
||||||
'text': self.data.library.rating
|
'text': self.data.library.rating
|
||||||
@@ -294,7 +295,7 @@ var ReleaseAction = new Class({
|
|||||||
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
||||||
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
||||||
new Element('span.quality', {'text': quality.get('label')}),
|
new Element('span.quality', {'text': quality.get('label')}),
|
||||||
new Element('span.size', {'text': (self.get(release, 'size') || 'unknown')}),
|
new Element('span.size', {'text': (self.get(release, 'size'))}),
|
||||||
new Element('span.age', {'text': self.get(release, 'age')}),
|
new Element('span.age', {'text': self.get(release, 'age')}),
|
||||||
new Element('span.score', {'text': self.get(release, 'score')}),
|
new Element('span.score', {'text': self.get(release, 'score')}),
|
||||||
new Element('span.provider', {'text': self.get(release, 'provider')}),
|
new Element('span.provider', {'text': self.get(release, 'provider')}),
|
||||||
@@ -332,7 +333,7 @@ var ReleaseAction = new Class({
|
|||||||
|
|
||||||
return (release.info.filter(function(info){
|
return (release.info.filter(function(info){
|
||||||
return type == info.identifier
|
return type == info.identifier
|
||||||
}).pick() || {}).value
|
}).pick() || {}).value || 'n/a'
|
||||||
},
|
},
|
||||||
|
|
||||||
download: function(release){
|
download: function(release){
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ Block.Search = new Class({
|
|||||||
'keyup': self.keyup.bind(self),
|
'keyup': self.keyup.bind(self),
|
||||||
'focus': function(){
|
'focus': function(){
|
||||||
self.el.addClass('focused')
|
self.el.addClass('focused')
|
||||||
|
if(this.get('value'))
|
||||||
|
self.hideResults(false)
|
||||||
},
|
},
|
||||||
'blur': function(){
|
'blur': function(){
|
||||||
self.el.removeClass('focused')
|
self.el.removeClass('focused')
|
||||||
@@ -55,6 +57,7 @@ Block.Search = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
(e).preventDefault();
|
(e).preventDefault();
|
||||||
|
|
||||||
|
self.last_q = '';
|
||||||
self.input.set('value', '');
|
self.input.set('value', '');
|
||||||
self.input.focus()
|
self.input.focus()
|
||||||
|
|
||||||
@@ -319,6 +322,13 @@ Block.Search.Item = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!self.options.hasClass('set')){
|
if(!self.options.hasClass('set')){
|
||||||
|
|
||||||
|
if(self.info.in_library){
|
||||||
|
var in_library = [];
|
||||||
|
self.info.in_library.releases.each(function(release){
|
||||||
|
in_library.include(release.quality.label)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self.options.adopt(
|
self.options.adopt(
|
||||||
new Element('div').adopt(
|
new Element('div').adopt(
|
||||||
@@ -326,9 +336,9 @@ Block.Search.Item = new Class({
|
|||||||
'src': self.info.images.poster[0]
|
'src': self.info.images.poster[0]
|
||||||
}) : null,
|
}) : null,
|
||||||
self.info.in_wanted ? new Element('span.in_wanted', {
|
self.info.in_wanted ? new Element('span.in_wanted', {
|
||||||
'text': 'Already in wanted list: ' + self.info.in_wanted.label
|
'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
|
||||||
}) : (self.info.in_library ? new Element('span.in_library', {
|
}) : (in_library ? new Element('span.in_library', {
|
||||||
'text': 'Already in library: ' + self.info.in_library.label
|
'text': 'Already in library: ' + in_library.join(', ')
|
||||||
}) : null),
|
}) : null),
|
||||||
self.title_select = new Element('select', {
|
self.title_select = new Element('select', {
|
||||||
'name': 'title'
|
'name': 'title'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, toSafeString
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams
|
from couchpotato.core.helpers.request import jsonified, getParams
|
||||||
from couchpotato.core.helpers.variable import mergeDicts, md5, getExt
|
from couchpotato.core.helpers.variable import mergeDicts, md5, getExt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -184,16 +184,17 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
# Check on unreliable stuff
|
# Check on unreliable stuff
|
||||||
if loose:
|
if loose:
|
||||||
# Check extension + filesize
|
|
||||||
if list(set(quality.get('ext', [])) & set(words)) and size >= quality['size_min'] and size <= quality['size_max']:
|
|
||||||
log.debug('Found %s via ext %s in %s' % (quality['identifier'], quality.get('ext'), words))
|
|
||||||
return self.setCache(hash, quality)
|
|
||||||
|
|
||||||
# Last check on resolution only
|
# Last check on resolution only
|
||||||
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
||||||
log.debug('Found %s via resolution_width: %s == %s' % (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
log.debug('Found %s via resolution_width: %s == %s' % (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
|
# Check extension + filesize
|
||||||
|
if list(set(quality.get('ext', [])) & set(words)) and size >= quality['size_min'] and size <= quality['size_max']:
|
||||||
|
log.debug('Found %s via ext and filesize %s in %s' % (quality['identifier'], quality.get('ext'), words))
|
||||||
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
|
|
||||||
# Try again with loose testing
|
# Try again with loose testing
|
||||||
if not loose:
|
if not loose:
|
||||||
@@ -202,4 +203,4 @@ class QualityPlugin(Plugin):
|
|||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
log.debug('Could not identify quality for: %s' % files)
|
log.debug('Could not identify quality for: %s' % files)
|
||||||
return self.setCache(hash, self.single('dvdrip'))
|
return None
|
||||||
|
|||||||
@@ -180,15 +180,18 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
# Normal identifier
|
# Normal identifier
|
||||||
identifier = self.createStringIdentifier(file_path, folder, exclude_filename = is_dvd_file)
|
identifier = self.createStringIdentifier(file_path, folder, exclude_filename = is_dvd_file)
|
||||||
|
identifiers = [identifier]
|
||||||
|
|
||||||
# Identifier with quality
|
# Identifier with quality
|
||||||
quality = fireEvent('quality.guess', [file_path], single = True) if not is_dvd_file else {'identifier':'dvdr'}
|
quality = fireEvent('quality.guess', [file_path], single = True) if not is_dvd_file else {'identifier':'dvdr'}
|
||||||
identifier_with_quality = '%s %s' % (identifier, quality.get('identifier', ''))
|
if quality:
|
||||||
|
identifier_with_quality = '%s %s' % (identifier, quality.get('identifier', ''))
|
||||||
|
identifiers = [identifier_with_quality, identifier]
|
||||||
|
|
||||||
if not movie_files.get(identifier):
|
if not movie_files.get(identifier):
|
||||||
movie_files[identifier] = {
|
movie_files[identifier] = {
|
||||||
'unsorted_files': [],
|
'unsorted_files': [],
|
||||||
'identifiers': [identifier_with_quality, identifier],
|
'identifiers': identifiers,
|
||||||
'is_dvd': is_dvd_file,
|
'is_dvd': is_dvd_file,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,7 +498,7 @@ class Scanner(Plugin):
|
|||||||
'identifier': imdb_id
|
'identifier': imdb_id
|
||||||
}, update_after = False, single = True)
|
}, update_after = False, single = True)
|
||||||
|
|
||||||
log.error('No imdb_id found for %s.' % group['identifiers'])
|
log.error('No imdb_id found for %s. Add a NFO file with IMDB id or add the year to the filename.' % group['identifiers'])
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def getCPImdb(self, string):
|
def getCPImdb(self, string):
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
def single(self, movie):
|
def single(self, movie):
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
|
||||||
pre_releases = fireEvent('quality.pre_releases', single = True)
|
pre_releases = fireEvent('quality.pre_releases', single = True)
|
||||||
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
||||||
available_status = fireEvent('status.get', 'available', single = True)
|
available_status = fireEvent('status.get', 'available', single = True)
|
||||||
@@ -94,7 +96,6 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
# Add them to this movie releases list
|
# Add them to this movie releases list
|
||||||
for nzb in sorted_results:
|
for nzb in sorted_results:
|
||||||
db = get_session()
|
|
||||||
|
|
||||||
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
|
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
|
||||||
if not rls:
|
if not rls:
|
||||||
@@ -106,20 +107,23 @@ class Searcher(Plugin):
|
|||||||
)
|
)
|
||||||
db.add(rls)
|
db.add(rls)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
else:
|
||||||
|
[db.delete(info) for info in rls.info]
|
||||||
|
db.commit()
|
||||||
|
|
||||||
for info in nzb:
|
for info in nzb:
|
||||||
try:
|
try:
|
||||||
if not isinstance(nzb[info], (str, unicode, int, long)):
|
if not isinstance(nzb[info], (str, unicode, int, long)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rls_info = ReleaseInfo(
|
rls_info = ReleaseInfo(
|
||||||
identifier = info,
|
identifier = info,
|
||||||
value = toUnicode(nzb[info])
|
value = toUnicode(nzb[info])
|
||||||
)
|
)
|
||||||
rls.info.append(rls_info)
|
rls.info.append(rls_info)
|
||||||
db.commit()
|
db.commit()
|
||||||
except InterfaceError:
|
except InterfaceError:
|
||||||
log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc()))
|
log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc()))
|
||||||
|
|
||||||
|
|
||||||
for nzb in sorted_results:
|
for nzb in sorted_results:
|
||||||
@@ -137,6 +141,7 @@ class Searcher(Plugin):
|
|||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
|
db.remove()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def download(self, data, movie, manual = False):
|
def download(self, data, movie, manual = False):
|
||||||
@@ -356,15 +361,15 @@ class Searcher(Plugin):
|
|||||||
else:
|
else:
|
||||||
if wanted_quality in pre_releases:
|
if wanted_quality in pre_releases:
|
||||||
# Prerelease 1 week before theaters
|
# Prerelease 1 week before theaters
|
||||||
if dates.get('theater') >= now - 604800 and wanted_quality in pre_releases:
|
if dates.get('theater') - 604800 < now:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# 6 weeks after theater release
|
# 6 weeks after theater release
|
||||||
if dates.get('theater') < now - 3628800:
|
if dates.get('theater') + 3628800 < now:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 6 weeks before dvd release
|
# 6 weeks before dvd release
|
||||||
if dates.get('dvd') > now - 3628800:
|
if dates.get('dvd') - 3628800 < now:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Dvd should be released
|
# Dvd should be released
|
||||||
|
|||||||
@@ -28,14 +28,16 @@ class MetaDataBase(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error('Failed to update movie, before creating metadata: %s' % traceback.format_exc())
|
log.error('Failed to update movie, before creating metadata: %s' % traceback.format_exc())
|
||||||
|
|
||||||
root = self.getRootName(release)
|
root_name = self.getRootName(release)
|
||||||
|
meta_name = os.path.basename(root_name)
|
||||||
|
root = os.path.dirname(root_name)
|
||||||
|
|
||||||
movie_info = release['library'].get('info')
|
movie_info = release['library'].get('info')
|
||||||
|
|
||||||
for file_type in ['nfo', 'thumbnail', 'fanart']:
|
for file_type in ['nfo', 'thumbnail', 'fanart']:
|
||||||
try:
|
try:
|
||||||
# Get file path
|
# Get file path
|
||||||
name = getattr(self, 'get' + file_type.capitalize() + 'Name')(root)
|
name = getattr(self, 'get' + file_type.capitalize() + 'Name')(meta_name, root)
|
||||||
|
|
||||||
if name and self.conf('meta_' + file_type):
|
if name and self.conf('meta_' + file_type):
|
||||||
|
|
||||||
@@ -54,13 +56,13 @@ class MetaDataBase(Plugin):
|
|||||||
def getRootName(self, data):
|
def getRootName(self, data):
|
||||||
return
|
return
|
||||||
|
|
||||||
def getFanartName(self, root):
|
def getFanartName(self, name, root):
|
||||||
return
|
return
|
||||||
|
|
||||||
def getThumbnailName(self, root):
|
def getThumbnailName(self, name, root):
|
||||||
return
|
return
|
||||||
|
|
||||||
def getNfoName(self, root):
|
def getNfoName(self, name, root):
|
||||||
return
|
return
|
||||||
|
|
||||||
def getNfo(self, movie_info = {}, data = {}):
|
def getNfo(self, movie_info = {}, data = {}):
|
||||||
|
|||||||
@@ -14,14 +14,17 @@ class XBMC(MetaDataBase):
|
|||||||
def getRootName(self, data = {}):
|
def getRootName(self, data = {}):
|
||||||
return os.path.join(data['destination_dir'], data['filename'])
|
return os.path.join(data['destination_dir'], data['filename'])
|
||||||
|
|
||||||
def getFanartName(self, root):
|
def getFanartName(self, name, root):
|
||||||
return self.conf('meta_fanart_name') % root
|
return self.createMetaName(self.conf('meta_fanart_name'), name, root)
|
||||||
|
|
||||||
def getThumbnailName(self, root):
|
def getThumbnailName(self, name, root):
|
||||||
return self.conf('meta_thumbnail_name') % root
|
return self.createMetaName(self.conf('meta_thumbnail_name'), name, root)
|
||||||
|
|
||||||
def getNfoName(self, root):
|
def getNfoName(self, name, root):
|
||||||
return self.conf('meta_nfo_name') % root
|
return self.createMetaName(self.conf('meta_nfo_name'), name, root)
|
||||||
|
|
||||||
|
def createMetaName(self, basename, name, root):
|
||||||
|
return os.path.join(root, basename.replace('%s', name))
|
||||||
|
|
||||||
def getNfo(self, movie_info = {}, data = {}):
|
def getNfo(self, movie_info = {}, data = {}):
|
||||||
nfoxml = Element('movie')
|
nfoxml = Element('movie')
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ class MovieResultModifier(Plugin):
|
|||||||
|
|
||||||
for movie in l.movies:
|
for movie in l.movies:
|
||||||
if movie.status_id == active_status['id']:
|
if movie.status_id == active_status['id']:
|
||||||
temp['in_wanted'] = movie.profile.to_dict()
|
temp['in_wanted'] = fireEvent('movie.get', movie.id, single = True)
|
||||||
|
|
||||||
for release in movie.releases:
|
for release in movie.releases:
|
||||||
if release.status_id == done_status['id']:
|
if release.status_id == done_status['id']:
|
||||||
temp['in_library'] = release.quality.to_dict()
|
temp['in_library'] = fireEvent('movie.get', movie.id, single = True)
|
||||||
except:
|
except:
|
||||||
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
|
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from couchpotato.core.event import addEvent
|
|
||||||
from couchpotato.core.providers.base import YarrProvider
|
from couchpotato.core.providers.base import YarrProvider
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class Newznab(NZBProvider, RSS):
|
|||||||
'size': int(size) / 1024 / 1024,
|
'size': int(size) / 1024 / 1024,
|
||||||
'url': (self.getUrl(host['host'], self.urls['download']) % id) + self.getApiExt(host),
|
'url': (self.getUrl(host['host'], self.urls['download']) % id) + self.getApiExt(host),
|
||||||
'download': self.download,
|
'download': self.download,
|
||||||
'detail_url': (self.getUrl(host['host'], self.urls['detail']) % id) + self.getApiExt(host),
|
'detail_url': '%sdetails/%s' % (cleanHost(host['host']), id),
|
||||||
'content': self.getTextElement(nzb, "description"),
|
'content': self.getTextElement(nzb, "description"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ from elixir.entity import Entity
|
|||||||
from elixir.fields import Field
|
from elixir.fields import Field
|
||||||
from elixir.options import options_defaults, using_options
|
from elixir.options import options_defaults, using_options
|
||||||
from elixir.relationships import ManyToMany, OneToMany, ManyToOne
|
from elixir.relationships import ManyToMany, OneToMany, ManyToOne
|
||||||
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, Float, \
|
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \
|
||||||
String, TypeDecorator
|
TypeDecorator
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class Movie(Entity):
|
|||||||
|
|
||||||
last_edit = Field(Integer, default = lambda: int(time.time()))
|
last_edit = Field(Integer, default = lambda: int(time.time()))
|
||||||
|
|
||||||
library = ManyToOne('Library')
|
library = ManyToOne('Library', cascade = 'delete, delete-orphan', single_parent = True)
|
||||||
status = ManyToOne('Status')
|
status = ManyToOne('Status')
|
||||||
profile = ManyToOne('Profile')
|
profile = ManyToOne('Profile')
|
||||||
releases = OneToMany('Release', cascade = 'all, delete-orphan')
|
releases = OneToMany('Release', cascade = 'all, delete-orphan')
|
||||||
|
|||||||
@@ -172,13 +172,13 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
|
|
||||||
# Static path
|
# Static path
|
||||||
app.static_folder = os.path.join(base_path, 'couchpotato', 'static')
|
app.static_folder = os.path.join(base_path, 'couchpotato', 'static')
|
||||||
web.add_url_rule('%s/static/<path:filename>' % api_key,
|
web.add_url_rule('api/%s/static/<path:filename>' % api_key,
|
||||||
endpoint = 'static',
|
endpoint = 'static',
|
||||||
view_func = app.send_static_file)
|
view_func = app.send_static_file)
|
||||||
|
|
||||||
# Register modules
|
# Register modules
|
||||||
app.register_blueprint(web, url_prefix = '%s/' % url_base)
|
app.register_blueprint(web, url_prefix = '%s/' % url_base)
|
||||||
app.register_blueprint(api, url_prefix = '%s/%s/' % (url_base, api_key))
|
app.register_blueprint(api, url_prefix = '%s/api/%s/' % (url_base, api_key))
|
||||||
|
|
||||||
# Some logging and fire load event
|
# Some logging and fire load event
|
||||||
try: log.info('Starting server on port %(port)s' % config)
|
try: log.info('Starting server on port %(port)s' % config)
|
||||||
|
|||||||
@@ -117,15 +117,15 @@ var CouchPotato = new Class({
|
|||||||
openPage: function(url) {
|
openPage: function(url) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var current_url = url.replace(/^\/+|\/+$/g, '');
|
|
||||||
if(current_url == self.current_url)
|
|
||||||
return;
|
|
||||||
|
|
||||||
self.route.parse();
|
self.route.parse();
|
||||||
var page_name = self.route.getPage().capitalize();
|
var page_name = self.route.getPage().capitalize();
|
||||||
var action = self.route.getAction();
|
var action = self.route.getAction();
|
||||||
var params = self.route.getParams();
|
var params = self.route.getParams();
|
||||||
|
|
||||||
|
var current_url = self.route.getCurrentUrl();
|
||||||
|
if(current_url == self.current_url)
|
||||||
|
return;
|
||||||
|
|
||||||
if(self.current_page)
|
if(self.current_page)
|
||||||
self.current_page.hide()
|
self.current_page.hide()
|
||||||
|
|
||||||
@@ -287,8 +287,8 @@ var Route = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var path = History.getPath().replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/')
|
var path = History.getPath().replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/')
|
||||||
var current = path.replace(/^\/+|\/+$/g, '')
|
self.current = path.replace(/^\/+|\/+$/g, '')
|
||||||
var url = current.split('/')
|
var url = self.current.split('/')
|
||||||
|
|
||||||
self.page = (url.length > 0) ? url.shift() : self.defaults.page
|
self.page = (url.length > 0) ? url.shift() : self.defaults.page
|
||||||
self.action = (url.length > 0) ? url.shift() : self.defaults.action
|
self.action = (url.length > 0) ? url.shift() : self.defaults.action
|
||||||
@@ -324,6 +324,10 @@ var Route = new Class({
|
|||||||
return this.params
|
return this.params
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getCurrentUrl: function(){
|
||||||
|
return this.current
|
||||||
|
},
|
||||||
|
|
||||||
get: function(param){
|
get: function(param){
|
||||||
return this.params[param]
|
return this.params[param]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1114,7 +1114,7 @@ Option.Combined = new Class({
|
|||||||
self.values[nr][name] = value.trim();
|
self.values[nr][name] = value.trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.inputs[name].getParent('.ctrlHolder').hide();
|
self.inputs[name].getParent('.ctrlHolder').setStyle('display', 'none');
|
||||||
self.inputs[name].addEvent('change', self.addEmpty.bind(self))
|
self.inputs[name].addEvent('change', self.addEmpty.bind(self))
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -206,7 +206,8 @@ window.addEvent('domready', function(){
|
|||||||
function(){
|
function(){
|
||||||
Api.request('movie.delete', {
|
Api.request('movie.delete', {
|
||||||
'data': {
|
'data': {
|
||||||
'id': self.movie.get('id')
|
'id': self.movie.get('id'),
|
||||||
|
'delete_from': self.movie.list.options.identifier
|
||||||
},
|
},
|
||||||
'onComplete': function(){
|
'onComplete': function(){
|
||||||
movie.set('tween', {
|
movie.set('tween', {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Get the API key:
|
Get the API key:
|
||||||
<pre><a href="/getkey/?p=md5(password)&u=md5(username)">/getkey/?p=md5(password)&u=md5(username)</a></pre>
|
<pre><a href="{{ url_for('web.index') }}getkey/?p=md5(password)&u=md5(username)">{{ url_for('web.index') }}getkey/?p=md5(password)&u=md5(username)</a></pre>
|
||||||
Will return {"api_key": "XXXXXXXXXX", "success": true}. When username or password is empty you don't need to md5 it.
|
Will return {"api_key": "XXXXXXXXXX", "success": true}. When username or password is empty you don't need to md5 it.
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
import ntpath
|
|
||||||
import os.path
|
import os.path
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@ def split_path(path):
|
|||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
while True:
|
while True:
|
||||||
head, tail = ntpath.split(path)
|
head, tail = os.path.split(path)
|
||||||
|
|
||||||
# on Unix systems, the root folder is '/'
|
# on Unix systems, the root folder is '/'
|
||||||
if head == '/' and tail == '':
|
if head == '/' and tail == '':
|
||||||
|
|||||||
Reference in New Issue
Block a user