Browse Source

Merge branch 'refs/heads/develop'

pull/228/head
Ruud 13 years ago
parent
commit
829b3cfb3b
  1. 8
      couchpotato/__init__.py
  2. 1
      couchpotato/api.py
  3. 2
      couchpotato/core/_base/_core/main.py
  4. 2
      couchpotato/core/helpers/request.py
  5. 1
      couchpotato/core/loader.py
  6. 2
      couchpotato/core/notifications/core/main.py
  7. 15
      couchpotato/core/notifications/growl/__init__.py
  8. 8
      couchpotato/core/notifications/growl/main.py
  9. 2
      couchpotato/core/plugins/base.py
  10. 53
      couchpotato/core/plugins/log/main.py
  11. 37
      couchpotato/core/plugins/movie/main.py
  12. 5
      couchpotato/core/plugins/movie/static/list.js
  13. 11
      couchpotato/core/plugins/movie/static/movie.js
  14. 16
      couchpotato/core/plugins/movie/static/search.js
  15. 13
      couchpotato/core/plugins/quality/main.py
  16. 9
      couchpotato/core/plugins/scanner/main.py
  17. 37
      couchpotato/core/plugins/searcher/main.py
  18. 12
      couchpotato/core/providers/metadata/base.py
  19. 15
      couchpotato/core/providers/metadata/xbmc/main.py
  20. 4
      couchpotato/core/providers/movie/_modifier/main.py
  21. 1
      couchpotato/core/providers/nzb/base.py
  22. 2
      couchpotato/core/providers/nzb/newznab/main.py
  23. 6
      couchpotato/core/settings/model.py
  24. 4
      couchpotato/runner.py
  25. 16
      couchpotato/static/scripts/couchpotato.js
  26. 2
      couchpotato/static/scripts/page/settings.js
  27. 3
      couchpotato/static/scripts/page/wanted.js
  28. 2
      couchpotato/templates/api.html
  29. 3
      libs/guessit/fileutils.py

8
couchpotato/__init__.py

@ -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
couchpotato/api.py

@ -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__)

2
couchpotato/core/_base/_core/main.py

@ -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)

2
couchpotato/core/helpers/request.py

@ -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)) + ')'

1
couchpotato/core/loader.py

@ -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()))

2
couchpotato/core/notifications/core/main.py

@ -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)'},
}, },
}) })

15
couchpotato/core/notifications/growl/__init__.py

@ -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,
},
], ],
} }
], ],

8
couchpotato/core/notifications/growl/main.py

@ -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

2
couchpotato/core/plugins/base.py

@ -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:

53
couchpotato/core/plugins/log/main.py

@ -23,11 +23,22 @@ class Logging(Plugin):
'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):

37
couchpotato/core/plugins/movie/main.py

@ -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

5
couchpotato/core/plugins/movie/static/list.js

@ -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();
}, },

11
couchpotato/core/plugins/movie/static/movie.js

@ -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){

16
couchpotato/core/plugins/movie/static/search.js

@ -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'

13
couchpotato/core/plugins/quality/main.py

@ -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

9
couchpotato/core/plugins/scanner/main.py

@ -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):

37
couchpotato/core/plugins/searcher/main.py

@ -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

12
couchpotato/core/providers/metadata/base.py

@ -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 = {}):

15
couchpotato/core/providers/metadata/xbmc/main.py

@ -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')

4
couchpotato/core/providers/movie/_modifier/main.py

@ -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
couchpotato/core/providers/nzb/base.py

@ -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

2
couchpotato/core/providers/nzb/newznab/main.py

@ -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"),
} }

6
couchpotato/core/settings/model.py

@ -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')

4
couchpotato/runner.py

@ -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)

16
couchpotato/static/scripts/couchpotato.js

@ -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]
} }

2
couchpotato/static/scripts/page/settings.js

@ -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))
}); });

3
couchpotato/static/scripts/page/wanted.js

@ -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', {

2
couchpotato/templates/api.html

@ -21,7 +21,7 @@
<br /> <br />
<br /> <br />
Get the API key: Get the API key:
<pre><a href="/getkey/?p=md5(password)&amp;u=md5(username)">/getkey/?p=md5(password)&amp;u=md5(username)</a></pre> <pre><a href="{{ url_for('web.index') }}getkey/?p=md5(password)&amp;u=md5(username)">{{ url_for('web.index') }}getkey/?p=md5(password)&amp;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>

3
libs/guessit/fileutils.py

@ -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 == '':

Loading…
Cancel
Save