new dl gui
parent
bfb3c289dd
commit
951b936d12
|
@ -0,0 +1,5 @@
|
||||||
|
#-*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter v2 plugin for XBMC/Kodi
|
||||||
|
Copyright (C) 2015 DiMartino
|
||||||
|
'''
|
36
default.py
36
default.py
|
@ -1,40 +1,22 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import xbmcplugin, xbmcgui, os
|
from python_libtorrent import get_libtorrent, get_platform, log
|
||||||
from python_libtorrent.platform_pulsar import get_platform
|
import xbmcgui
|
||||||
from ctypes import *
|
|
||||||
|
|
||||||
sucsess=False
|
sucsess=False
|
||||||
dialog = xbmcgui.Dialog()
|
version=''
|
||||||
p=get_platform()
|
p=get_platform()
|
||||||
ROOT_PATH=os.path.dirname(__file__)
|
dialog = xbmcgui.Dialog()
|
||||||
dirname=os.path.join(ROOT_PATH, 'python_libtorrent', p['system'])
|
|
||||||
#dirname = os.path.join(xbmc.translatePath('special://home'), 'addons', 'script.module.libtorrent',
|
|
||||||
# 'python_libtorrent', platform['system'])
|
|
||||||
#sys.path.insert(0, dirname)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import python_libtorrent as libtorrent
|
libtorrent=get_libtorrent()
|
||||||
|
|
||||||
print '[script.module.libtorrent]: Imported libtorrent v' + libtorrent.version + ' from python_libtorrent'
|
log('Imported libtorrent v' + libtorrent.version + ' from get_libtorrent()')
|
||||||
|
version=str(libtorrent.version)
|
||||||
sucsess=True
|
sucsess=True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print '[script.module.libtorrent]: Error importing from system. Exception: ' + str(e)
|
log('Error importing from get_libtorrent(). Exception: ' + str(e))
|
||||||
|
|
||||||
try:
|
|
||||||
cdll.LoadLibrary(dirname + '/libpython2.6.so')
|
|
||||||
except Exception, e:
|
|
||||||
print '[script.module.libtorrent]: Error importing from '+str(dirname)+'. Exception: ' + str(e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
cdll.LoadLibrary(dirname + '/libpython2.6.so')
|
|
||||||
cdll.LoadLibrary(dirname + '/libtorrent.so')
|
|
||||||
|
|
||||||
print '[script.module.libtorrent]: Imported libtorrent v' + libtorrent.version + ' from cdll'
|
|
||||||
sucsess=True
|
|
||||||
except Exception, e:
|
|
||||||
print '[script.module.libtorrent]: Error importing from '+str(dirname)+'. Exception: ' + str(e)
|
|
||||||
|
|
||||||
|
|
||||||
line2='WE DID IT! IMPORTED' if sucsess else 'Failed!'
|
line2='Python-libtorrent %s IMPORTED successfully' % version if sucsess else 'Failed to import python-libtorrent!'
|
||||||
dialog.ok('Libtorrent','OS:'+p['os']+' arch:'+p['arch'], line2)
|
dialog.ok('Libtorrent','OS:'+p['os']+' arch:'+p['arch'], line2)
|
|
@ -20,7 +20,7 @@ dirname = os.path.join(xbmc.translatePath('special://temp'), 'xbmcup', 'script.m
|
||||||
#dirname = os.path.join(xbmc.translatePath('special://home'), 'addons', 'script.module.libtorrent',
|
#dirname = os.path.join(xbmc.translatePath('special://home'), 'addons', 'script.module.libtorrent',
|
||||||
# 'python_libtorrent')
|
# 'python_libtorrent')
|
||||||
dest_path = os.path.join(dirname, platform['system'])
|
dest_path = os.path.join(dirname, platform['system'])
|
||||||
sys.path.insert(0, dirname)
|
sys.path.insert(0, dest_path)
|
||||||
|
|
||||||
lm=LibraryManager(dest_path)
|
lm=LibraryManager(dest_path)
|
||||||
if not lm.check_exist():
|
if not lm.check_exist():
|
||||||
|
@ -33,14 +33,8 @@ if __settings__.getSetting('plugin_name')!=__plugin__:
|
||||||
|
|
||||||
log('platform ' + str(platform))
|
log('platform ' + str(platform))
|
||||||
try:
|
try:
|
||||||
if platform['system'] == 'darwin':
|
if platform['system'] in ['darwin', 'linux_x86', 'linux_x86_64', 'windows']:
|
||||||
from darwin import libtorrent
|
import libtorrent
|
||||||
elif platform['system'] == 'linux_x86':
|
|
||||||
from linux_x86 import libtorrent
|
|
||||||
elif platform['system'] == 'linux_x86_64':
|
|
||||||
from linux_x86_64 import libtorrent
|
|
||||||
elif platform['system'] == 'windows':
|
|
||||||
from windows import libtorrent
|
|
||||||
elif platform['system'] == 'android' and platform['arch'] == 'arm':
|
elif platform['system'] == 'android' and platform['arch'] == 'arm':
|
||||||
import imp
|
import imp
|
||||||
from ctypes import *
|
from ctypes import *
|
||||||
|
@ -48,20 +42,19 @@ try:
|
||||||
dll_path=os.path.join(dest_path, 'liblibtorrent.so')
|
dll_path=os.path.join(dest_path, 'liblibtorrent.so')
|
||||||
print "CDLL path = " + dll_path
|
print "CDLL path = " + dll_path
|
||||||
liblibtorrent=CDLL(dll_path)
|
liblibtorrent=CDLL(dll_path)
|
||||||
print 'CDLL = ' + str(liblibtorrent)
|
log('CDLL = ' + str(liblibtorrent))
|
||||||
|
|
||||||
path_list = [dest_path]
|
path_list = [dest_path]
|
||||||
print 'path_list = ' + str(path_list)
|
log('path_list = ' + str(path_list))
|
||||||
fp, pathname, description = imp.find_module('libtorrent', path_list)
|
fp, pathname, description = imp.find_module('libtorrent', path_list)
|
||||||
print 'fp = ' + str(fp)
|
log('fp = ' + str(fp))
|
||||||
print 'pathname = ' + str(pathname)
|
log('pathname = ' + str(pathname))
|
||||||
libtorrent = imp.load_module('libtorrent', fp, pathname, description)
|
libtorrent = imp.load_module('libtorrent', fp, pathname, description)
|
||||||
|
|
||||||
print '[script.module.libtorrent]: Imported libtorrent v' + libtorrent.version + ' from python_libtorrent.' + platform[
|
log('Imported libtorrent v' + libtorrent.version + ' from ' + dest_path)
|
||||||
'system']
|
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print '[script.module.libtorrent]: Error importing python_libtorrent.' + platform['system'] + '. Exception: ' + str(e)
|
log('Error importing libtorrent from' + dest_path + '. Exception: ' + str(e))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_libtorrent():
|
def get_libtorrent():
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import xbmc, xbmcgui, xbmcvfs
|
import xbmc, xbmcgui, xbmcvfs
|
||||||
import urllib
|
from net import HTTP
|
||||||
|
|
||||||
|
|
||||||
__libbaseurl__ = "https://github.com/DiMartinoXBMC/script.module.libtorrent/raw/master/python_libtorrent/"
|
__libbaseurl__ = "https://github.com/DiMartinoXBMC/script.module.libtorrent/raw/master/python_libtorrent/"
|
||||||
__scriptname__ = "script.module.libtorrent"
|
__scriptname__ = "script.module.libtorrent"
|
||||||
|
@ -15,30 +14,14 @@ class DownloaderClass():
|
||||||
self.platform = get_platform()
|
self.platform = get_platform()
|
||||||
tempdir(self.platform)
|
tempdir(self.platform)
|
||||||
|
|
||||||
def download(self, url, dest):
|
|
||||||
self.dp = xbmcgui.DialogProgress()
|
|
||||||
self.dp.create("script.module.libtorrent","Downloading File", url)
|
|
||||||
urllib.urlretrieve(url, dest, lambda nb, bs, fs, url= url: self._pbhook(nb,bs,fs))
|
|
||||||
|
|
||||||
def _pbhook(self, numblocks, blocksize, filesize):
|
|
||||||
try:
|
|
||||||
percent = min((numblocks*blocksize*100)/filesize, 100)
|
|
||||||
self.dp.update(percent)
|
|
||||||
except:
|
|
||||||
percent = 100
|
|
||||||
self.dp.update(percent)
|
|
||||||
log("libtorrent: DOWNLOAD FAILED")
|
|
||||||
if self.dp.iscanceled():
|
|
||||||
log("libtorrent: DOWNLOAD CANCELLED")
|
|
||||||
self.dp.close()
|
|
||||||
|
|
||||||
def tools_download(self):
|
def tools_download(self):
|
||||||
log("libtorrent: try to fetch libtorrent.so")
|
|
||||||
for libname in get_libname(self.platform):
|
for libname in get_libname(self.platform):
|
||||||
url = "%s/%s/%s.zip" % (__libbaseurl__, self.platform, libname)
|
log("try to fetch %s" % libname)
|
||||||
|
url = "%s/%s/%s.zip" % (__libbaseurl__, self.platform['system'], libname)
|
||||||
dest = os.path.join(self.dest_path, libname)
|
dest = os.path.join(self.dest_path, libname)
|
||||||
try:
|
try:
|
||||||
self.download(url, dest + ".zip")
|
self.http = HTTP()
|
||||||
|
self.http.fetch(url, download=dest + ".zip", progress=True)
|
||||||
log("%s -> %s" % (url, dest))
|
log("%s -> %s" % (url, dest))
|
||||||
xbmc.executebuiltin('XBMC.Extract("%s.zip","%s")' % (dest, self.dest_path), True)
|
xbmc.executebuiltin('XBMC.Extract("%s.zip","%s")' % (dest, self.dest_path), True)
|
||||||
xbmcvfs.delete(dest + ".zip")
|
xbmcvfs.delete(dest + ".zip")
|
||||||
|
@ -46,7 +29,6 @@ class DownloaderClass():
|
||||||
text = 'Failed!'
|
text = 'Failed!'
|
||||||
xbmc.executebuiltin("XBMC.Notification(%s,%s,%s,%s)" % (__scriptname__,text,750,__icon__))
|
xbmc.executebuiltin("XBMC.Notification(%s,%s,%s,%s)" % (__scriptname__,text,750,__icon__))
|
||||||
|
|
||||||
|
|
||||||
def log(msg):
|
def log(msg):
|
||||||
xbmc.log("### [%s]: %s" % (__scriptname__,msg,), level=xbmc.LOGINFO )
|
xbmc.log("### [%s]: %s" % (__scriptname__,msg,), level=xbmc.LOGINFO )
|
||||||
#print "### [%s]: %s" % (__scriptname__,msg,)
|
#print "### [%s]: %s" % (__scriptname__,msg,)
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,297 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
import cookielib
|
||||||
|
import base64
|
||||||
|
import mimetools
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcvfs
|
||||||
|
|
||||||
|
RE = {
|
||||||
|
'content-disposition': re.compile('attachment;\sfilename="*([^"\s]+)"|\s')
|
||||||
|
}
|
||||||
|
|
||||||
|
# ################################
|
||||||
|
#
|
||||||
|
# HTTP
|
||||||
|
#
|
||||||
|
# ################################
|
||||||
|
|
||||||
|
class HTTP:
|
||||||
|
def __init__(self):
|
||||||
|
self._dirname = xbmc.translatePath('special://temp')
|
||||||
|
for subdir in ('xbmcup', 'script.module.libtorrent'):
|
||||||
|
self._dirname = os.path.join(self._dirname, subdir)
|
||||||
|
if not xbmcvfs.exists(self._dirname):
|
||||||
|
xbmcvfs.mkdir(self._dirname)
|
||||||
|
|
||||||
|
def fetch(self, request, **kwargs):
|
||||||
|
self.con, self.fd, self.progress, self.cookies, self.request = None, None, None, None, request
|
||||||
|
|
||||||
|
if not isinstance(self.request, HTTPRequest):
|
||||||
|
self.request = HTTPRequest(url=self.request, **kwargs)
|
||||||
|
|
||||||
|
self.response = HTTPResponse(self.request)
|
||||||
|
|
||||||
|
xbmc.log('XBMCup: HTTP: request: ' + str(self.request), xbmc.LOGDEBUG)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._opener()
|
||||||
|
self._fetch()
|
||||||
|
except Exception, e:
|
||||||
|
xbmc.log('XBMCup: HTTP: ' + str(e), xbmc.LOGERROR)
|
||||||
|
if isinstance(e, urllib2.HTTPError):
|
||||||
|
self.response.code = e.code
|
||||||
|
self.response.error = e
|
||||||
|
else:
|
||||||
|
self.response.code = 200
|
||||||
|
|
||||||
|
if self.fd:
|
||||||
|
self.fd.close()
|
||||||
|
self.fd = None
|
||||||
|
|
||||||
|
if self.con:
|
||||||
|
self.con.close()
|
||||||
|
self.con = None
|
||||||
|
|
||||||
|
if self.progress:
|
||||||
|
self.progress.close()
|
||||||
|
self.progress = None
|
||||||
|
|
||||||
|
self.response.time = time.time() - self.response.time
|
||||||
|
|
||||||
|
xbmc.log('XBMCup: HTTP: response: ' + str(self.response), xbmc.LOGDEBUG)
|
||||||
|
|
||||||
|
return self.response
|
||||||
|
|
||||||
|
def _opener(self):
|
||||||
|
|
||||||
|
build = [urllib2.HTTPHandler()]
|
||||||
|
|
||||||
|
if self.request.redirect:
|
||||||
|
build.append(urllib2.HTTPRedirectHandler())
|
||||||
|
|
||||||
|
if self.request.proxy_host and self.request.proxy_port:
|
||||||
|
build.append(urllib2.ProxyHandler(
|
||||||
|
{self.request.proxy_protocol: self.request.proxy_host + ':' + str(self.request.proxy_port)}))
|
||||||
|
|
||||||
|
if self.request.proxy_username:
|
||||||
|
proxy_auth_handler = urllib2.ProxyBasicAuthHandler()
|
||||||
|
proxy_auth_handler.add_password('realm', 'uri', self.request.proxy_username,
|
||||||
|
self.request.proxy_password)
|
||||||
|
build.append(proxy_auth_handler)
|
||||||
|
|
||||||
|
if self.request.cookies:
|
||||||
|
self.request.cookies = os.path.join(self._dirname, self.request.cookies)
|
||||||
|
self.cookies = cookielib.MozillaCookieJar()
|
||||||
|
if os.path.isfile(self.request.cookies):
|
||||||
|
self.cookies.load(self.request.cookies)
|
||||||
|
build.append(urllib2.HTTPCookieProcessor(self.cookies))
|
||||||
|
|
||||||
|
urllib2.install_opener(urllib2.build_opener(*build))
|
||||||
|
|
||||||
|
def _fetch(self):
|
||||||
|
params = {} if self.request.params is None else self.request.params
|
||||||
|
|
||||||
|
if self.request.upload:
|
||||||
|
boundary, upload = self._upload(self.request.upload, params)
|
||||||
|
req = urllib2.Request(self.request.url)
|
||||||
|
req.add_data(upload)
|
||||||
|
else:
|
||||||
|
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if isinstance(params, dict) or isinstance(params, list):
|
||||||
|
params = urllib.urlencode(params)
|
||||||
|
req = urllib2.Request(self.request.url, params)
|
||||||
|
else:
|
||||||
|
req = urllib2.Request(self.request.url)
|
||||||
|
|
||||||
|
for key, value in self.request.headers.iteritems():
|
||||||
|
req.add_header(key, value)
|
||||||
|
|
||||||
|
if self.request.upload:
|
||||||
|
req.add_header('Content-type', 'multipart/form-data; boundary=%s' % boundary)
|
||||||
|
req.add_header('Content-length', len(upload))
|
||||||
|
|
||||||
|
if self.request.auth_username and self.request.auth_password:
|
||||||
|
req.add_header('Authorization', 'Basic %s' % base64.encodestring(
|
||||||
|
':'.join([self.request.auth_username, self.request.auth_password])).strip())
|
||||||
|
|
||||||
|
self.con = urllib2.urlopen(req, timeout=self.request.timeout)
|
||||||
|
# self.con = urllib2.urlopen(req)
|
||||||
|
self.response.headers = self._headers(self.con.info())
|
||||||
|
|
||||||
|
if self.request.download:
|
||||||
|
self._download()
|
||||||
|
else:
|
||||||
|
self.response.body = self.con.read()
|
||||||
|
|
||||||
|
if self.request.cookies:
|
||||||
|
self.cookies.save(self.request.cookies)
|
||||||
|
|
||||||
|
def _download(self):
|
||||||
|
fd = open(self.request.download, 'wb')
|
||||||
|
if self.request.progress:
|
||||||
|
self.progress = xbmcgui.DialogProgress()
|
||||||
|
self.progress.create(u'Download')
|
||||||
|
|
||||||
|
bs = 1024 * 8
|
||||||
|
size = -1
|
||||||
|
read = 0
|
||||||
|
name = None
|
||||||
|
|
||||||
|
if self.request.progress:
|
||||||
|
if 'content-length' in self.response.headers:
|
||||||
|
size = int(self.response.headers['content-length'])
|
||||||
|
if 'content-disposition' in self.response.headers:
|
||||||
|
r = RE['content-disposition'].search(self.response.headers['content-disposition'])
|
||||||
|
if r:
|
||||||
|
name = urllib.unquote(r.group(1))
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
buf = self.con.read(bs)
|
||||||
|
if buf == '':
|
||||||
|
break
|
||||||
|
read += len(buf)
|
||||||
|
fd.write(buf)
|
||||||
|
|
||||||
|
if self.request.progress:
|
||||||
|
self.progress.update(*self._progress(read, size, name))
|
||||||
|
|
||||||
|
self.response.filename = self.request.download
|
||||||
|
|
||||||
|
def _upload(self, upload, params):
|
||||||
|
res = []
|
||||||
|
boundary = mimetools.choose_boundary()
|
||||||
|
part_boundary = '--' + boundary
|
||||||
|
|
||||||
|
if params:
|
||||||
|
for name, value in params.iteritems():
|
||||||
|
res.append([part_boundary, 'Content-Disposition: form-data; name="%s"' % name, '', value])
|
||||||
|
|
||||||
|
if isinstance(upload, dict):
|
||||||
|
upload = [upload]
|
||||||
|
|
||||||
|
for obj in upload:
|
||||||
|
name = obj.get('name')
|
||||||
|
filename = obj.get('filename', 'default')
|
||||||
|
content_type = obj.get('content-type')
|
||||||
|
try:
|
||||||
|
body = obj['body'].read()
|
||||||
|
except AttributeError:
|
||||||
|
body = obj['body']
|
||||||
|
|
||||||
|
if content_type:
|
||||||
|
res.append([part_boundary,
|
||||||
|
'Content-Disposition: file; name="%s"; filename="%s"' % (name, urllib.quote(filename)),
|
||||||
|
'Content-Type: %s' % content_type, '', body])
|
||||||
|
else:
|
||||||
|
res.append([part_boundary,
|
||||||
|
'Content-Disposition: file; name="%s"; filename="%s"' % (name, urllib.quote(filename)), '',
|
||||||
|
body])
|
||||||
|
|
||||||
|
result = list(itertools.chain(*res))
|
||||||
|
result.append('--' + boundary + '--')
|
||||||
|
result.append('')
|
||||||
|
return boundary, '\r\n'.join(result)
|
||||||
|
|
||||||
|
def _headers(self, raw):
|
||||||
|
headers = {}
|
||||||
|
for line in raw.headers:
|
||||||
|
pair = line.split(':', 1)
|
||||||
|
if len(pair) == 2:
|
||||||
|
tag = pair[0].lower().strip()
|
||||||
|
value = pair[1].strip()
|
||||||
|
if tag and value:
|
||||||
|
headers[tag] = value
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def _progress(self, read, size, name):
|
||||||
|
res = []
|
||||||
|
if size < 0:
|
||||||
|
res.append(1)
|
||||||
|
else:
|
||||||
|
res.append(int(float(read) / (float(size) / 100.0)))
|
||||||
|
if name:
|
||||||
|
res.append(u'File: ' + name)
|
||||||
|
if size != -1:
|
||||||
|
res.append(u'Size: ' + self._human(size))
|
||||||
|
res.append(u'Load: ' + self._human(read))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _human(self, size):
|
||||||
|
human = None
|
||||||
|
for h, f in (('KB', 1024), ('MB', 1024 * 1024), ('GB', 1024 * 1024 * 1024), ('TB', 1024 * 1024 * 1024 * 1024)):
|
||||||
|
if size / f > 0:
|
||||||
|
human = h
|
||||||
|
factor = f
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if human is None:
|
||||||
|
return (u'%10.1f %s' % (size, u'byte')).replace(u'.0', u'')
|
||||||
|
else:
|
||||||
|
return u'%10.2f %s' % (float(size) / float(factor), human)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRequest:
|
||||||
|
def __init__(self, url, method='GET', headers=None, cookies=None, params=None, upload=None, download=None,
|
||||||
|
progress=False, auth_username=None, auth_password=None, proxy_protocol='http', proxy_host=None,
|
||||||
|
proxy_port=None, proxy_username=None, proxy_password='', timeout=20.0, redirect=True, gzip=False):
|
||||||
|
if headers is None:
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
self.url = url
|
||||||
|
self.method = method
|
||||||
|
self.headers = headers
|
||||||
|
|
||||||
|
self.cookies = cookies
|
||||||
|
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
self.upload = upload
|
||||||
|
self.download = download
|
||||||
|
self.progress = progress
|
||||||
|
|
||||||
|
self.auth_username = auth_username
|
||||||
|
self.auth_password = auth_password
|
||||||
|
|
||||||
|
self.proxy_protocol = proxy_protocol
|
||||||
|
self.proxy_host = proxy_host
|
||||||
|
self.proxy_port = proxy_port
|
||||||
|
self.proxy_username = proxy_username
|
||||||
|
self.proxy_password = proxy_password
|
||||||
|
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
self.redirect = redirect
|
||||||
|
|
||||||
|
self.gzip = gzip
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '%s(%s)' % (self.__class__.__name__, ','.join('%s=%r' % i for i in self.__dict__.iteritems()))
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPResponse:
|
||||||
|
def __init__(self, request):
|
||||||
|
self.request = request
|
||||||
|
self.code = None
|
||||||
|
self.headers = {}
|
||||||
|
self.error = None
|
||||||
|
self.body = None
|
||||||
|
self.filename = None
|
||||||
|
self.time = time.time()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
args = ','.join('%s=%r' % i for i in self.__dict__.iteritems() if i[0] != 'body')
|
||||||
|
if self.body:
|
||||||
|
args += ',body=<data>'
|
||||||
|
else:
|
||||||
|
args += ',body=None'
|
||||||
|
return '%s(%s)' % (self.__class__.__name__, args)
|
Loading…
Reference in New Issue