summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-07-20 21:19:14 +0200
committerGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-07-20 21:19:14 +0200
commit37e135d40931617e9e135e15cb0e6dad0667b0cb (patch)
tree62aebc280d4137ab8846b6a3b6982c563f8727c0
parentadded missing init file (diff)
downloadpyload-37e135d40931617e9e135e15cb0e6dad0667b0cb.tar.xz
tried to fix hoster tester, removed unneeded files
-rw-r--r--docs/module_overview.rst2
-rw-r--r--pyload/remote/ttypes.py534
-rw-r--r--setup.cfg2
-rw-r--r--tests/HosterPluginTester.py71
-rw-r--r--tests/helper/PluginTester.py5
-rw-r--r--tests/helper/Stubs.py11
-rw-r--r--tests/helper/parser.py22
7 files changed, 68 insertions, 579 deletions
diff --git a/docs/module_overview.rst b/docs/module_overview.rst
index 2876e465e..770265198 100644
--- a/docs/module_overview.rst
+++ b/docs/module_overview.rst
@@ -20,4 +20,4 @@ A little selection of most important modules in pyLoad.
pyload.interaction.EventManager.EventManager
pyload.interaction.InteractionManager.InteractionManager
pyload.interaction.InteractionTask.InteractionTask
- pyload.remote.ttypes
+ pyload.remote.apitypes
diff --git a/pyload/remote/ttypes.py b/pyload/remote/ttypes.py
deleted file mode 100644
index 1f91403d5..000000000
--- a/pyload/remote/ttypes.py
+++ /dev/null
@@ -1,534 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Autogenerated by pyload
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-
-class BaseObject(object):
- __slots__ = []
-
- def __str__(self):
- return "<%s %s>" % (self.__class__.__name__, ", ".join("%s=%s" % (k,getattr(self,k)) for k in self.__slots__))
-
-class ExceptionObject(Exception):
- __slots__ = []
-
-class DownloadState:
- All = 0
- Finished = 1
- Unfinished = 2
- Failed = 3
- Unmanaged = 4
-
-class DownloadStatus:
- NA = 0
- Offline = 1
- Online = 2
- Queued = 3
- Paused = 4
- Finished = 5
- Skipped = 6
- Failed = 7
- Starting = 8
- Waiting = 9
- Downloading = 10
- TempOffline = 11
- Aborted = 12
- Decrypting = 13
- Processing = 14
- Custom = 15
- Unknown = 16
-
-class FileStatus:
- Ok = 0
- Missing = 1
- Remote = 2
-
-class Input:
- NA = 0
- Text = 1
- Int = 2
- File = 3
- Folder = 4
- Textbox = 5
- Password = 6
- Bool = 7
- Click = 8
- Select = 9
- Multiple = 10
- List = 11
- Table = 12
-
-class MediaType:
- All = 0
- Other = 1
- Audio = 2
- Image = 4
- Video = 8
- Document = 16
- Archive = 32
-
-class Output:
- All = 0
- Notification = 1
- Captcha = 2
- Query = 4
-
-class PackageStatus:
- Ok = 0
- Paused = 1
- Folder = 2
- Remote = 3
-
-class Permission:
- All = 0
- Add = 1
- Delete = 2
- Modify = 4
- Download = 8
- Accounts = 16
- Interaction = 32
- Plugins = 64
-
-class Role:
- Admin = 0
- User = 1
-
-class AccountInfo(BaseObject):
- __slots__ = ['plugin', 'loginname', 'owner', 'valid', 'validuntil', 'trafficleft', 'maxtraffic', 'premium', 'activated', 'shared', 'options']
-
- def __init__(self, plugin=None, loginname=None, owner=None, valid=None, validuntil=None, trafficleft=None, maxtraffic=None, premium=None, activated=None, shared=None, options=None):
- self.plugin = plugin
- self.loginname = loginname
- self.owner = owner
- self.valid = valid
- self.validuntil = validuntil
- self.trafficleft = trafficleft
- self.maxtraffic = maxtraffic
- self.premium = premium
- self.activated = activated
- self.shared = shared
- self.options = options
-
-class AddonInfo(BaseObject):
- __slots__ = ['func_name', 'description', 'value']
-
- def __init__(self, func_name=None, description=None, value=None):
- self.func_name = func_name
- self.description = description
- self.value = value
-
-class AddonService(BaseObject):
- __slots__ = ['func_name', 'description', 'arguments', 'media']
-
- def __init__(self, func_name=None, description=None, arguments=None, media=None):
- self.func_name = func_name
- self.description = description
- self.arguments = arguments
- self.media = media
-
-class ConfigHolder(BaseObject):
- __slots__ = ['name', 'label', 'description', 'long_description', 'items', 'info', 'handler']
-
- def __init__(self, name=None, label=None, description=None, long_description=None, items=None, info=None, handler=None):
- self.name = name
- self.label = label
- self.description = description
- self.long_description = long_description
- self.items = items
- self.info = info
- self.handler = handler
-
-class ConfigInfo(BaseObject):
- __slots__ = ['name', 'label', 'description', 'category', 'user_context', 'activated']
-
- def __init__(self, name=None, label=None, description=None, category=None, user_context=None, activated=None):
- self.name = name
- self.label = label
- self.description = description
- self.category = category
- self.user_context = user_context
- self.activated = activated
-
-class ConfigItem(BaseObject):
- __slots__ = ['name', 'label', 'description', 'type', 'default_value', 'value']
-
- def __init__(self, name=None, label=None, description=None, type=None, default_value=None, value=None):
- self.name = name
- self.label = label
- self.description = description
- self.type = type
- self.default_value = default_value
- self.value = value
-
-class DownloadInfo(BaseObject):
- __slots__ = ['url', 'plugin', 'hash', 'status', 'statusmsg', 'error']
-
- def __init__(self, url=None, plugin=None, hash=None, status=None, statusmsg=None, error=None):
- self.url = url
- self.plugin = plugin
- self.hash = hash
- self.status = status
- self.statusmsg = statusmsg
- self.error = error
-
-class DownloadProgress(BaseObject):
- __slots__ = ['fid', 'pid', 'speed', 'status']
-
- def __init__(self, fid=None, pid=None, speed=None, status=None):
- self.fid = fid
- self.pid = pid
- self.speed = speed
- self.status = status
-
-class EventInfo(BaseObject):
- __slots__ = ['eventname', 'event_args']
-
- def __init__(self, eventname=None, event_args=None):
- self.eventname = eventname
- self.event_args = event_args
-
-class FileDoesNotExists(ExceptionObject):
- __slots__ = ['fid']
-
- def __init__(self, fid=None):
- self.fid = fid
-
-class FileInfo(BaseObject):
- __slots__ = ['fid', 'name', 'package', 'owner', 'size', 'status', 'media', 'added', 'fileorder', 'download']
-
- def __init__(self, fid=None, name=None, package=None, owner=None, size=None, status=None, media=None, added=None, fileorder=None, download=None):
- self.fid = fid
- self.name = name
- self.package = package
- self.owner = owner
- self.size = size
- self.status = status
- self.media = media
- self.added = added
- self.fileorder = fileorder
- self.download = download
-
-class Forbidden(ExceptionObject):
- pass
-
-class InteractionTask(BaseObject):
- __slots__ = ['iid', 'input', 'data', 'output', 'default_value', 'title', 'description', 'plugin']
-
- def __init__(self, iid=None, input=None, data=None, output=None, default_value=None, title=None, description=None, plugin=None):
- self.iid = iid
- self.input = input
- self.data = data
- self.output = output
- self.default_value = default_value
- self.title = title
- self.description = description
- self.plugin = plugin
-
-class InvalidConfigSection(ExceptionObject):
- __slots__ = ['section']
-
- def __init__(self, section=None):
- self.section = section
-
-class LinkStatus(BaseObject):
- __slots__ = ['url', 'name', 'plugin', 'size', 'status', 'packagename']
-
- def __init__(self, url=None, name=None, plugin=None, size=None, status=None, packagename=None):
- self.url = url
- self.name = name
- self.plugin = plugin
- self.size = size
- self.status = status
- self.packagename = packagename
-
-class OnlineCheck(BaseObject):
- __slots__ = ['rid', 'data']
-
- def __init__(self, rid=None, data=None):
- self.rid = rid
- self.data = data
-
-class PackageDoesNotExists(ExceptionObject):
- __slots__ = ['pid']
-
- def __init__(self, pid=None):
- self.pid = pid
-
-class PackageInfo(BaseObject):
- __slots__ = ['pid', 'name', 'folder', 'root', 'owner', 'site', 'comment', 'password', 'added', 'tags', 'status', 'shared', 'packageorder', 'stats', 'fids', 'pids']
-
- def __init__(self, pid=None, name=None, folder=None, root=None, owner=None, site=None, comment=None, password=None, added=None, tags=None, status=None, shared=None, packageorder=None, stats=None, fids=None, pids=None):
- self.pid = pid
- self.name = name
- self.folder = folder
- self.root = root
- self.owner = owner
- self.site = site
- self.comment = comment
- self.password = password
- self.added = added
- self.tags = tags
- self.status = status
- self.shared = shared
- self.packageorder = packageorder
- self.stats = stats
- self.fids = fids
- self.pids = pids
-
-class PackageStats(BaseObject):
- __slots__ = ['linkstotal', 'linksdone', 'sizetotal', 'sizedone']
-
- def __init__(self, linkstotal=None, linksdone=None, sizetotal=None, sizedone=None):
- self.linkstotal = linkstotal
- self.linksdone = linksdone
- self.sizetotal = sizetotal
- self.sizedone = sizedone
-
-class ProgressInfo(BaseObject):
- __slots__ = ['plugin', 'name', 'statusmsg', 'eta', 'done', 'total', 'download']
-
- def __init__(self, plugin=None, name=None, statusmsg=None, eta=None, done=None, total=None, download=None):
- self.plugin = plugin
- self.name = name
- self.statusmsg = statusmsg
- self.eta = eta
- self.done = done
- self.total = total
- self.download = download
-
-class ServerStatus(BaseObject):
- __slots__ = ['queuedDownloads', 'totalDownloads', 'speed', 'pause', 'download', 'reconnect']
-
- def __init__(self, queuedDownloads=None, totalDownloads=None, speed=None, pause=None, download=None, reconnect=None):
- self.queuedDownloads = queuedDownloads
- self.totalDownloads = totalDownloads
- self.speed = speed
- self.pause = pause
- self.download = download
- self.reconnect = reconnect
-
-class ServiceDoesNotExists(ExceptionObject):
- __slots__ = ['plugin', 'func']
-
- def __init__(self, plugin=None, func=None):
- self.plugin = plugin
- self.func = func
-
-class ServiceException(ExceptionObject):
- __slots__ = ['msg']
-
- def __init__(self, msg=None):
- self.msg = msg
-
-class TreeCollection(BaseObject):
- __slots__ = ['root', 'files', 'packages']
-
- def __init__(self, root=None, files=None, packages=None):
- self.root = root
- self.files = files
- self.packages = packages
-
-class Unauthorized(ExceptionObject):
- pass
-
-class UserData(BaseObject):
- __slots__ = ['uid', 'name', 'email', 'role', 'permission', 'folder', 'traffic', 'dllimit', 'dlquota', 'hddquota', 'user', 'templateName']
-
- def __init__(self, uid=None, name=None, email=None, role=None, permission=None, folder=None, traffic=None, dllimit=None, dlquota=None, hddquota=None, user=None, templateName=None):
- self.uid = uid
- self.name = name
- self.email = email
- self.role = role
- self.permission = permission
- self.folder = folder
- self.traffic = traffic
- self.dllimit = dllimit
- self.dlquota = dlquota
- self.hddquota = hddquota
- self.user = user
- self.templateName = templateName
-
-class UserDoesNotExists(ExceptionObject):
- __slots__ = ['user']
-
- def __init__(self, user=None):
- self.user = user
-
-class Iface(object):
- def addFromCollector(self, name, paused):
- pass
- def addLinks(self, pid, links):
- pass
- def addLocalFile(self, pid, name, path):
- pass
- def addPackage(self, name, links, password):
- pass
- def addPackageChild(self, name, links, password, root, paused):
- pass
- def addPackageP(self, name, links, password, paused):
- pass
- def addToCollector(self, links):
- pass
- def addUser(self, username, password):
- pass
- def callAddon(self, plugin, func, arguments):
- pass
- def callAddonHandler(self, plugin, func, pid_or_fid):
- pass
- def checkOnlineStatus(self, urls):
- pass
- def checkOnlineStatusContainer(self, urls, filename, data):
- pass
- def checkURLs(self, urls):
- pass
- def configurePlugin(self, plugin):
- pass
- def createPackage(self, name, folder, root, password, site, comment, paused):
- pass
- def deleteCollLink(self, url):
- pass
- def deleteCollPack(self, name):
- pass
- def deleteConfig(self, plugin):
- pass
- def deleteFiles(self, fids):
- pass
- def deletePackages(self, pids):
- pass
- def findFiles(self, pattern):
- pass
- def findPackages(self, tags):
- pass
- def freeSpace(self):
- pass
- def generateAndAddPackages(self, links, paused):
- pass
- def generateDownloadLink(self, fid, timeout):
- pass
- def generatePackages(self, links):
- pass
- def getAccountTypes(self):
- pass
- def getAccounts(self, refresh):
- pass
- def getAddonHandler(self):
- pass
- def getAllFiles(self):
- pass
- def getAllUserData(self):
- pass
- def getAutocompletion(self, pattern):
- pass
- def getAvailablePlugins(self):
- pass
- def getCollector(self):
- pass
- def getConfig(self):
- pass
- def getConfigValue(self, section, option):
- pass
- def getCoreConfig(self):
- pass
- def getEvents(self, uuid):
- pass
- def getFileInfo(self, fid):
- pass
- def getFileTree(self, pid, full):
- pass
- def getFilteredFileTree(self, pid, full, state):
- pass
- def getFilteredFiles(self, state):
- pass
- def getInteractionTask(self, mode):
- pass
- def getLog(self, offset):
- pass
- def getNotifications(self):
- pass
- def getPackageContent(self, pid):
- pass
- def getPackageInfo(self, pid):
- pass
- def getPluginConfig(self):
- pass
- def getProgressInfo(self):
- pass
- def getServerStatus(self):
- pass
- def getServerVersion(self):
- pass
- def getUserData(self):
- pass
- def getWSAddress(self):
- pass
- def hasAddonHandler(self, plugin, func):
- pass
- def isInteractionWaiting(self, mode):
- pass
- def login(self, username, password):
- pass
- def moveFiles(self, fids, pid):
- pass
- def movePackage(self, pid, root):
- pass
- def orderFiles(self, fids, pid, position):
- pass
- def orderPackage(self, pids, position):
- pass
- def parseURLs(self, html, url):
- pass
- def pauseServer(self):
- pass
- def pollResults(self, rid):
- pass
- def quit(self):
- pass
- def recheckPackage(self, pid):
- pass
- def removeAccount(self, plugin, account):
- pass
- def removeUser(self, uid):
- pass
- def renameCollPack(self, name, new_name):
- pass
- def restart(self):
- pass
- def restartFailed(self):
- pass
- def restartFile(self, fid):
- pass
- def restartPackage(self, pid):
- pass
- def saveConfig(self, config):
- pass
- def setConfigHandler(self, plugin, iid, value):
- pass
- def setConfigValue(self, section, option, value):
- pass
- def setInteractionResult(self, iid, result):
- pass
- def setPackageFolder(self, pid, path):
- pass
- def setPassword(self, username, old_password, new_password):
- pass
- def stopAllDownloads(self):
- pass
- def stopDownloads(self, fids):
- pass
- def togglePause(self):
- pass
- def toggleReconnect(self):
- pass
- def unpauseServer(self):
- pass
- def updateAccount(self, plugin, account, password):
- pass
- def updateAccountInfo(self, account):
- pass
- def updatePackage(self, pack):
- pass
- def updateUserData(self, data):
- pass
- def uploadContainer(self, filename, data):
- pass
-
diff --git a/setup.cfg b/setup.cfg
index 79c983f89..923d8361e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,6 +8,6 @@ upload-dir = doc/_build/html
[pep8]
format = default
-exclude = lib
+exclude = .git,lib,apitypes.py,apitypes_debug.py,thriftgen
ignore = W292,E261,E262,E302,E701
max-line-length = 139 \ No newline at end of file
diff --git a/tests/HosterPluginTester.py b/tests/HosterPluginTester.py
index 0639adab7..3a6eca84b 100644
--- a/tests/HosterPluginTester.py
+++ b/tests/HosterPluginTester.py
@@ -5,20 +5,21 @@ from logging import log, DEBUG
from hashlib import md5
from time import time
from shutil import move
-import codecs
from nose.tools import nottest
from helper.Stubs import Core
+from helper.parser import parse_config
from helper.PluginTester import PluginTester
-from pyload.datatypes.PyFile import PyFile
+from pyload.datatypes.PyFile import PyFile, statusMap
from pyload.plugins.Base import Fail
from pyload.utils import accumulate
from pyload.utils.fs import save_join, join, exists, listdir, remove, stat
DL_DIR = join("Downloads", "tmp")
+
class HosterPluginTester(PluginTester):
files = {}
@@ -35,13 +36,13 @@ class HosterPluginTester(PluginTester):
@nottest
- def test_plugin(self, name, url, flag):
+ def test_plugin(self, name, url, status):
# Print to stdout to see whats going on
- print "%s: %s, %s" % (name, url.encode("utf8"), flag)
- log(DEBUG, "%s: %s, %s", name, url.encode("utf8"), flag)
+ print "%s: %s, %s" % (name, url.encode("utf8"), status)
+ log(DEBUG, "%s: %s, %s", name, url.encode("utf8"), status)
# url and plugin should be only important thing
- pyfile = PyFile(self.core, -1, url, url, 0, 0, "", name, 0, 0)
+ pyfile = PyFile(self.core, -1, url, url, 0, 0, 0, 0, url, name, "", 0, 0, 0, 0)
pyfile.initPlugin()
self.thread.pyfile = pyfile
@@ -54,7 +55,7 @@ class HosterPluginTester(PluginTester):
log(DEBUG, "downloading took %ds" % (time() - a))
log(DEBUG, "size %d kb" % (pyfile.size / 1024))
- if flag == "offline":
+ if status == "offline":
raise Exception("No offline Exception raised.")
if pyfile.name not in self.files:
@@ -73,7 +74,7 @@ class HosterPluginTester(PluginTester):
if hash.hexdigest() != self.files[pyfile.name]:
log(DEBUG, "Hash is %s" % hash.hexdigest())
-
+
size = stat(f.name).st_size
if size < 1024 * 1024 * 10: # 10MB
# Copy for debug report
@@ -82,38 +83,36 @@ class HosterPluginTester(PluginTester):
raise Exception("Hash does not match.")
-
-
except Exception, e:
- if isinstance(e, Fail) and flag == "fail":
+ if isinstance(e, Fail) and status == "failed":
pass
- elif isinstance(e, Fail) and flag == "offline" and e.message == "offline":
+ elif isinstance(e, Fail) and status == "offline" and e.message == "offline":
pass
else:
raise
-
# setup methods
-
c = Core()
-# decode everything as unicode
-f = codecs.open(join(dirname(__file__), "hosterlinks.txt"), "r", "utf_8")
-links = [x.strip() for x in f.readlines()]
-urls = []
-flags = {}
+sections = parse_config(join(dirname(__file__), "hosterlinks.txt"))
-for l in links:
- if not l or l.startswith("#"): continue
- if l.startswith("http"):
- if "||" in l:
- l, flag = l.split("||")
- flags[l] = str(flag.strip())
- urls.append(l)
+for f in sections["files"]:
+ name, hash = f.rsplit(" ", 1)
+ HosterPluginTester.files[name] = str(hash)
- elif len(l.rsplit(" ", 1)) == 2:
- name, hash = l.rsplit(" ", 1)
- HosterPluginTester.files[name] = str(hash)
+del sections["files"]
+
+print HosterPluginTester.files
+
+urls = []
+status = {}
+
+for k, v in sections.iteritems():
+ if k not in statusMap:
+ print "Unknown status %s" % k
+ for url in v:
+ urls.append(url)
+ status[url] = k
hoster, c = c.pluginManager.parseUrls(urls)
@@ -123,28 +122,28 @@ for plugin, urls in plugins.iteritems():
def meta_class(plugin):
class _testerClass(HosterPluginTester):
pass
+
_testerClass.__name__ = plugin
return _testerClass
_testerClass = meta_class(plugin)
for i, url in enumerate(urls):
- def meta(__plugin, url, flag, sig):
+ def meta(__plugin, url, status, sig):
def _test(self):
- self.test_plugin(__plugin, url, flag)
+ self.test_plugin(__plugin, url, status)
_test.func_name = sig
return _test
- tmp_flag = flags.get(url, None)
- if flags.get(url, None):
- sig = "test_LINK%d_%s" % (i, tmp_flag)
+ tmp_status = status.get(url)
+ if tmp_status != "online":
+ sig = "test_LINK%d_%s" % (i, tmp_status)
else:
sig = "test_LINK%d" % i
# set test method
- setattr(_testerClass, sig, meta(plugin, url, tmp_flag, sig))
-
+ setattr(_testerClass, sig, meta(plugin, url, tmp_status, sig))
#register class
locals()[plugin] = _testerClass
diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py
index 9312eb7bf..2bfb16af7 100644
--- a/tests/helper/PluginTester.py
+++ b/tests/helper/PluginTester.py
@@ -16,7 +16,8 @@ from json import loads
from Stubs import Thread, Core, noop
from pyload.network.RequestFactory import getRequest, getURL
-from pyload.plugins.Hoster import Hoster, Abort, Fail
+from pyload.plugins.Base import Abort, Fail
+from pyload.plugins.Hoster import Hoster
def _wait(self):
""" waits the time previously set """
@@ -106,8 +107,6 @@ def respond(ticket, value):
finally:
f.close()
-
-
def invalidCaptcha(self):
log(DEBUG, "Captcha invalid")
if self.cTask:
diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py
index 2c356ba3e..551778828 100644
--- a/tests/helper/Stubs.py
+++ b/tests/helper/Stubs.py
@@ -27,8 +27,10 @@ from logging import log, DEBUG, INFO, WARN, ERROR
def noop(*args, **kwargs):
pass
+
ConfigParser.save = noop
+
class LogStub:
def debug(self, *args):
log(DEBUG, *args)
@@ -72,7 +74,7 @@ class Core:
self.accountManager = AccountManager()
self.addonManager = AddonManager()
self.eventManager = self.evm = NoopClass()
- self.interActionManager = self.im = NoopClass()
+ self.interactionManager = self.im = NoopClass()
self.js = JsEngine()
self.cache = {}
self.packageCache = {}
@@ -87,7 +89,7 @@ class Core:
def path(self, path):
return path
- def updateLink(self, *args):
+ def updateFile(self, *args):
pass
def updatePackage(self, *args):
@@ -96,8 +98,8 @@ class Core:
def processingIds(self, *args):
return []
- def getPackage(self, id):
- return PyPackage(self, 0, "tmp", "tmp", "", "", 0, 0)
+ def getPackage(self, *args):
+ return PyPackage(self, 0, "tmp", "tmp", -1, 0, "", "", "", 0, "", 0, 0, 0)
def print_exc(self):
log(ERROR, format_exc())
@@ -132,6 +134,7 @@ class Thread(BaseThread):
return dump
+
__builtin__._ = lambda x: x
__builtin__.pypath = abspath(join(dirname(__file__), "..", ".."))
__builtin__.addonManager = AddonManager()
diff --git a/tests/helper/parser.py b/tests/helper/parser.py
new file mode 100644
index 000000000..5031ca7c3
--- /dev/null
+++ b/tests/helper/parser.py
@@ -0,0 +1,22 @@
+
+import codecs
+
+def parse_config(path):
+ f = codecs.open(path, "rb", "utf_8")
+ result = {}
+
+ current_section = None
+ for line in f.readlines():
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+
+ if line.startswith("["):
+ current_section = line.replace("[", "").replace("]", "")
+ result[current_section] = []
+ else:
+ if not current_section:
+ raise Exception("Line without section: %s" % line)
+ result[current_section].append(line)
+
+ return result