Browse Source

Merge branch 'refs/heads/develop'

pull/228/head
Ruud 13 years ago
parent
commit
829b3cfb3b
  1. 6
      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. 33
      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. 7
      couchpotato/core/plugins/scanner/main.py
  17. 13
      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

6
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):]
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.helpers import url_for
from flask.templating import render_template
from werkzeug.utils import redirect
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 '')
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)

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

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

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

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

8
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

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__)
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)
if add_to_head:

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

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

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

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

16
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()
@ -320,15 +323,22 @@ Block.Search.Item = new Class({
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(
self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
'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'

13
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

7
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'}
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):

13
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,6 +107,9 @@ class Searcher(Plugin):
)
db.add(rls)
db.commit()
else:
[db.delete(info) for info in rls.info]
db.commit()
for info in nzb:
try:
@ -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

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

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

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

1
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

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

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

4
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/<path:filename>' % api_key,
web.add_url_rule('api/%s/static/<path:filename>' % 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)

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

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

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

2
couchpotato/templates/api.html

@ -21,7 +21,7 @@
<br />
<br />
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.
<br />
</div>

3
libs/guessit/fileutils.py

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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 == '':

Loading…
Cancel
Save