diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index 8386ed9..57d7d33 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -15,6 +15,7 @@ from sqlalchemy.orm import scoped_session from sqlalchemy.orm.session import sessionmaker from werkzeug.utils import redirect import os +import time log = CPLog(__name__) @@ -73,5 +74,10 @@ def getApiKey(): def page_not_found(error): index_url = url_for('web.index') 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 diff --git a/couchpotato/api.py b/couchpotato/api.py index 71cf9b8..934fa94 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -1,6 +1,5 @@ from flask.blueprints import Blueprint from flask.helpers import url_for -from flask.templating import render_template from werkzeug.utils import redirect api = Blueprint('api', __name__) diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 81ed944..81ccac2 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/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 '') 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): ver = fireEvent('updater.info', single = True) diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 72c9b9e..e00880b 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -59,7 +59,7 @@ def getParam(attr, default = None): try: return toUnicode(unquote_plus(getattr(flask.request, 'args').get(attr, default))).encode('utf-8') except: - return None + return default def padded_jsonify(callback, *args, **kwargs): content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')' diff --git a/couchpotato/core/loader.py b/couchpotato/core/loader.py index ee7afcb..957a699 100644 --- a/couchpotato/core/loader.py +++ b/couchpotato/core/loader.py @@ -58,6 +58,7 @@ class Loader(object): pass # todo:: this needs to be more descriptive. 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: log.error('Can\'t import %s: %s' % (module_name, traceback.format_exc())) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 4c20094..7081740 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -30,7 +30,7 @@ class CoreNotifier(Notification): addApiView('notification.markread', self.markAsRead, docs = { 'desc': 'Mark notifications as read', '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)'}, }, }) diff --git a/couchpotato/core/notifications/growl/__init__.py b/couchpotato/core/notifications/growl/__init__.py index 6db390f..3e882b4 100644 --- a/couchpotato/core/notifications/growl/__init__.py +++ b/couchpotato/core/notifications/growl/__init__.py @@ -22,6 +22,21 @@ config = [{ 'advanced': True, '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, + }, ], } ], diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py index 29ade28..06accc8 100644 --- a/couchpotato/core/notifications/growl/main.py +++ b/couchpotato/core/notifications/growl/main.py @@ -23,11 +23,19 @@ class Growl(Notification): def register(self): if self.registered: return try: + + hostname = self.conf('hostname') + password = self.conf('password') + port = self.conf('port') + self.growl = notifier.GrowlNotifier( applicationName = 'CouchPotato', notifications = ["Updates"], defaultNotifications = ["Updates"], 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.registered = True diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 035ccab..d91fc6c 100644 --- a/couchpotato/core/plugins/base.py +++ b/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__) 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 + '', self.showStatic, static = True) if add_to_head: diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py index 2a2c022..a29f1e1 100644 --- a/couchpotato/core/plugins/log/main.py +++ b/couchpotato/core/plugins/log/main.py @@ -23,11 +23,22 @@ class Logging(Plugin): '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 = { 'desc': 'Remove all the log files' }) addApiView('logging.log', self.log, docs = { - 'desc': 'Get the full log file by number', + 'desc': 'Log errors', 'params': { 'type': {'desc': 'Type of logging, default "error"'}, '**kwargs': {'type':'object', 'desc': 'All other params will be printed in the log string.'}, @@ -64,6 +75,46 @@ class Logging(Plugin): '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): for x in range(0, 50): diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index e18d000..63518bb 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -1,8 +1,7 @@ from couchpotato import get_session from couchpotato.api import addApiView from couchpotato.core.event import fireEvent, fireEventAsync, addEvent -from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \ - simplifyString +from couchpotato.core.helpers.encoding import toUnicode, simplifyString from couchpotato.core.helpers.request import getParams, jsonified, getParam from couchpotato.core.helpers.variable import getImdb from couchpotato.core.logger import CPLog @@ -79,6 +78,7 @@ class MoviePlugin(Plugin): 'desc': 'Delete a movie from the wanted list', 'params': { '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(',')] for movie_id in ids: - self.delete(movie_id) + self.delete(movie_id, delete_from = params.get('delete_from', 'all')) return jsonified({ 'success': True, }) - def delete(self, movie_id): + def delete(self, movie_id, delete_from = None): db = get_session() movie = db.query(Movie).filter_by(id = movie_id).first() if movie: - db.delete(movie) - db.commit() + if delete_from == 'all': + 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 diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index d70db1f..becdc14 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -253,7 +253,8 @@ var MovieList = new Class({ (e).preventDefault(); Api.request('movie.delete', { 'data': { - 'id': ids.join(',') + 'id': ids.join(','), + 'delete_from': self.options.identifier }, 'onSuccess': function(){ qObj.close(); @@ -381,6 +382,8 @@ var MovieList = new Class({ update: function(){ var self = this; + self.reset(); + self.movie_list.empty(); self.getMovies(); }, diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index 5544ed4..4ce2eab 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -4,11 +4,12 @@ var Movie = new Class({ action: {}, - initialize: function(self, options, data){ + initialize: function(list, options, data){ var self = this; self.data = data; self.view = options.view || 'thumbs'; + self.list = list; self.profile = Quality.getProfile(data.profile_id) || {}; self.parent(self, options); @@ -35,10 +36,10 @@ var Movie = new Class({ }).adopt( self.info_container = new Element('div.info').adopt( self.title = new Element('div.title', { - 'text': self.getTitle() + 'text': self.getTitle() || 'n/a' }), 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', { '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.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}), 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.score', {'text': self.get(release, 'score')}), new Element('span.provider', {'text': self.get(release, 'provider')}), @@ -332,7 +333,7 @@ var ReleaseAction = new Class({ return (release.info.filter(function(info){ return type == info.identifier - }).pick() || {}).value + }).pick() || {}).value || 'n/a' }, download: function(release){ diff --git a/couchpotato/core/plugins/movie/static/search.js b/couchpotato/core/plugins/movie/static/search.js index 10381b5..438ba9a 100644 --- a/couchpotato/core/plugins/movie/static/search.js +++ b/couchpotato/core/plugins/movie/static/search.js @@ -15,6 +15,8 @@ Block.Search = new Class({ 'keyup': self.keyup.bind(self), 'focus': function(){ self.el.addClass('focused') + if(this.get('value')) + self.hideResults(false) }, 'blur': function(){ self.el.removeClass('focused') @@ -55,6 +57,7 @@ Block.Search = new Class({ var self = this; (e).preventDefault(); + self.last_q = ''; self.input.set('value', ''); self.input.focus() @@ -319,6 +322,13 @@ Block.Search.Item = new Class({ var self = this; 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( new Element('div').adopt( @@ -326,9 +336,9 @@ Block.Search.Item = new Class({ 'src': self.info.images.poster[0] }) : null, self.info.in_wanted ? new Element('span.in_wanted', { - 'text': 'Already in wanted list: ' + self.info.in_wanted.label - }) : (self.info.in_library ? new Element('span.in_library', { - 'text': 'Already in library: ' + self.info.in_library.label + 'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label + }) : (in_library ? new Element('span.in_library', { + 'text': 'Already in library: ' + in_library.join(', ') }) : null), self.title_select = new Element('select', { 'name': 'title' diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index e38b818..b962dcd 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -1,7 +1,7 @@ from couchpotato import get_session from couchpotato.api import addApiView 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.variable import mergeDicts, md5, getExt from couchpotato.core.logger import CPLog @@ -184,16 +184,17 @@ class QualityPlugin(Plugin): # Check on unreliable stuff 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 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))) 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 if not loose: @@ -202,4 +203,4 @@ class QualityPlugin(Plugin): return self.setCache(hash, quality) log.debug('Could not identify quality for: %s' % files) - return self.setCache(hash, self.single('dvdrip')) + return None diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index e71d5c2..358d992 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -180,15 +180,18 @@ class Scanner(Plugin): # Normal identifier identifier = self.createStringIdentifier(file_path, folder, exclude_filename = is_dvd_file) + identifiers = [identifier] # Identifier with quality 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): movie_files[identifier] = { 'unsorted_files': [], - 'identifiers': [identifier_with_quality, identifier], + 'identifiers': identifiers, 'is_dvd': is_dvd_file, } @@ -495,7 +498,7 @@ class Scanner(Plugin): 'identifier': imdb_id }, 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 {} def getCPImdb(self, string): diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 530c4ce..663b3b6 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -65,6 +65,8 @@ class Searcher(Plugin): def single(self, movie): + db = get_session() + pre_releases = fireEvent('quality.pre_releases', single = True) release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True) available_status = fireEvent('status.get', 'available', single = True) @@ -94,7 +96,6 @@ class Searcher(Plugin): # Add them to this movie releases list for nzb in sorted_results: - db = get_session() rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first() if not rls: @@ -106,20 +107,23 @@ class Searcher(Plugin): ) db.add(rls) db.commit() + else: + [db.delete(info) for info in rls.info] + db.commit() - for info in nzb: - try: - if not isinstance(nzb[info], (str, unicode, int, long)): - continue + for info in nzb: + try: + if not isinstance(nzb[info], (str, unicode, int, long)): + continue - rls_info = ReleaseInfo( - identifier = info, - value = toUnicode(nzb[info]) - ) - rls.info.append(rls_info) - db.commit() - except InterfaceError: - log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc())) + rls_info = ReleaseInfo( + identifier = info, + value = toUnicode(nzb[info]) + ) + rls.info.append(rls_info) + db.commit() + except InterfaceError: + log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc())) for nzb in sorted_results: @@ -137,6 +141,7 @@ class Searcher(Plugin): if self.shuttingDown(): break + db.remove() return False def download(self, data, movie, manual = False): @@ -356,15 +361,15 @@ class Searcher(Plugin): else: if wanted_quality in pre_releases: # 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 else: # 6 weeks after theater release - if dates.get('theater') < now - 3628800: + if dates.get('theater') + 3628800 < now: return True # 6 weeks before dvd release - if dates.get('dvd') > now - 3628800: + if dates.get('dvd') - 3628800 < now: return True # Dvd should be released diff --git a/couchpotato/core/providers/metadata/base.py b/couchpotato/core/providers/metadata/base.py index d18ecbf..0f59c9f 100644 --- a/couchpotato/core/providers/metadata/base.py +++ b/couchpotato/core/providers/metadata/base.py @@ -28,14 +28,16 @@ class MetaDataBase(Plugin): except: 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') for file_type in ['nfo', 'thumbnail', 'fanart']: try: # 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): @@ -54,13 +56,13 @@ class MetaDataBase(Plugin): def getRootName(self, data): return - def getFanartName(self, root): + def getFanartName(self, name, root): return - def getThumbnailName(self, root): + def getThumbnailName(self, name, root): return - def getNfoName(self, root): + def getNfoName(self, name, root): return def getNfo(self, movie_info = {}, data = {}): diff --git a/couchpotato/core/providers/metadata/xbmc/main.py b/couchpotato/core/providers/metadata/xbmc/main.py index 7797cce..9a09edd 100644 --- a/couchpotato/core/providers/metadata/xbmc/main.py +++ b/couchpotato/core/providers/metadata/xbmc/main.py @@ -14,14 +14,17 @@ class XBMC(MetaDataBase): def getRootName(self, data = {}): return os.path.join(data['destination_dir'], data['filename']) - def getFanartName(self, root): - return self.conf('meta_fanart_name') % root + def getFanartName(self, name, root): + return self.createMetaName(self.conf('meta_fanart_name'), name, root) - def getThumbnailName(self, root): - return self.conf('meta_thumbnail_name') % root + def getThumbnailName(self, name, root): + return self.createMetaName(self.conf('meta_thumbnail_name'), name, root) - def getNfoName(self, root): - return self.conf('meta_nfo_name') % root + def getNfoName(self, 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 = {}): nfoxml = Element('movie') diff --git a/couchpotato/core/providers/movie/_modifier/main.py b/couchpotato/core/providers/movie/_modifier/main.py index f131adf..527c7b9 100644 --- a/couchpotato/core/providers/movie/_modifier/main.py +++ b/couchpotato/core/providers/movie/_modifier/main.py @@ -55,11 +55,11 @@ class MovieResultModifier(Plugin): for movie in l.movies: 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: 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: log.error('Tried getting more info on searched movies: %s' % traceback.format_exc()) diff --git a/couchpotato/core/providers/nzb/base.py b/couchpotato/core/providers/nzb/base.py index 06dd2c8..f11382b 100644 --- a/couchpotato/core/providers/nzb/base.py +++ b/couchpotato/core/providers/nzb/base.py @@ -1,4 +1,3 @@ -from couchpotato.core.event import addEvent from couchpotato.core.providers.base import YarrProvider import time diff --git a/couchpotato/core/providers/nzb/newznab/main.py b/couchpotato/core/providers/nzb/newznab/main.py index 99fb771..57691b7 100644 --- a/couchpotato/core/providers/nzb/newznab/main.py +++ b/couchpotato/core/providers/nzb/newznab/main.py @@ -133,7 +133,7 @@ class Newznab(NZBProvider, RSS): 'size': int(size) / 1024 / 1024, 'url': (self.getUrl(host['host'], self.urls['download']) % id) + self.getApiExt(host), '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"), } diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py index bcf5adb..9a909be 100644 --- a/couchpotato/core/settings/model.py +++ b/couchpotato/core/settings/model.py @@ -3,8 +3,8 @@ from elixir.entity import Entity from elixir.fields import Field from elixir.options import options_defaults, using_options from elixir.relationships import ManyToMany, OneToMany, ManyToOne -from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, Float, \ - String, TypeDecorator +from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \ + TypeDecorator import json import time @@ -41,7 +41,7 @@ class Movie(Entity): 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') profile = ManyToOne('Profile') releases = OneToMany('Release', cascade = 'all, delete-orphan') diff --git a/couchpotato/runner.py b/couchpotato/runner.py index 66d9d7d..0be3bdb 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -172,13 +172,13 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # Static path app.static_folder = os.path.join(base_path, 'couchpotato', 'static') - web.add_url_rule('%s/static/' % api_key, + web.add_url_rule('api/%s/static/' % api_key, endpoint = 'static', view_func = app.send_static_file) # Register modules 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 try: log.info('Starting server on port %(port)s' % config) diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 8f412ee..451e434 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -117,15 +117,15 @@ var CouchPotato = new Class({ openPage: function(url) { var self = this; - var current_url = url.replace(/^\/+|\/+$/g, ''); - if(current_url == self.current_url) - return; - self.route.parse(); var page_name = self.route.getPage().capitalize(); var action = self.route.getAction(); var params = self.route.getParams(); + var current_url = self.route.getCurrentUrl(); + if(current_url == self.current_url) + return; + if(self.current_page) self.current_page.hide() @@ -287,8 +287,8 @@ var Route = new Class({ var self = this; var path = History.getPath().replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/') - var current = path.replace(/^\/+|\/+$/g, '') - var url = current.split('/') + self.current = path.replace(/^\/+|\/+$/g, '') + var url = self.current.split('/') self.page = (url.length > 0) ? url.shift() : self.defaults.page self.action = (url.length > 0) ? url.shift() : self.defaults.action @@ -324,6 +324,10 @@ var Route = new Class({ return this.params }, + getCurrentUrl: function(){ + return this.current + }, + get: function(param){ return this.params[param] } diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 92cdf36..a33d774 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -1114,7 +1114,7 @@ Option.Combined = new Class({ 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)) }); diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index af6d774..ef7ddc8 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -206,7 +206,8 @@ window.addEvent('domready', function(){ function(){ Api.request('movie.delete', { 'data': { - 'id': self.movie.get('id') + 'id': self.movie.get('id'), + 'delete_from': self.movie.list.options.identifier }, 'onComplete': function(){ movie.set('tween', { diff --git a/couchpotato/templates/api.html b/couchpotato/templates/api.html index f0e76e3..45d20f1 100644 --- a/couchpotato/templates/api.html +++ b/couchpotato/templates/api.html @@ -21,7 +21,7 @@

Get the API key: -
/getkey/?p=md5(password)&u=md5(username)
+
{{ url_for('web.index') }}getkey/?p=md5(password)&u=md5(username)
Will return {"api_key": "XXXXXXXXXX", "success": true}. When username or password is empty you don't need to md5 it.
diff --git a/libs/guessit/fileutils.py b/libs/guessit/fileutils.py index 13d12c5..e146390 100644 --- a/libs/guessit/fileutils.py +++ b/libs/guessit/fileutils.py @@ -18,7 +18,6 @@ # along with this program. If not, see . # -import ntpath import os.path import zipfile @@ -46,7 +45,7 @@ def split_path(path): """ result = [] while True: - head, tail = ntpath.split(path) + head, tail = os.path.split(path) # on Unix systems, the root folder is '/' if head == '/' and tail == '':